SecretScan detects credentials in project directories. Its rules are vendored from gitleaks, and scanning is implemented in Elixir so it can run as a library in the BEAM.
Add secret_scan as a dependency in the environment where CI runs it:
def deps do
[
{:secret_scan, "~> 0.1", only: [:dev, :test], runtime: false}
]
endRun the scanner from the project directory:
mix secret_scanWhen the current project defines a Hex package, the task scans the same regular
files selected for mix hex.build. It reads ignore globs from that project's
package metadata:
defp package do
[
files: ~w(lib priv mix.exs README.md),
secret_scan: [ignore: ["test/fixtures/**", "priv/certs/*.pem"]]
]
endA project that doesn't define a Hex package selects files from its own top-level project config:
def project do
[
app: :my_app,
version: "0.1.0",
secret_scan: [
files: ["lib", "config", "priv"],
rules: :hexpm,
occurrences: :all,
preview: :masked,
ignore: ["test/fixtures/**"]
]
]
endThe task returns a non-zero status when it finds a credential or can't complete the scan within its resource limits.
SecretScan.scan/2 recursively scans a directory:
{findings, incomplete?} =
SecretScan.scan(File.cwd!(),
rules: :hexpm,
ignore: ["test/fixtures/**"]
)Findings include a rule id, file location, masked or redacted preview, and HMAC fingerprint. The matching credential isn't returned.
Pass fingerprint_key: key when fingerprints must be stable across scans. The
default is a new random key for each call, which supports deduplication within
that result without producing a persistent candidate-checking hash.
The scanner runs every vendored Gitleaks rule by default. Pass rules: :hexpm
to use the subset Hex.pm uses for package-owner notifications, or select rule
ids directly:
SecretScan.scan(File.cwd!(),
rules: ["github-pat", "aws-access-token", "slack-bot-token"]
)Unknown ids raise instead of silently disabling a CI check. The same :rules
option works in the top-level :secret_scan project config used by
mix secret_scan. Use SecretScan.rule_ids/1 to list the IDs in the :all or
:hexpm ruleset.
When :max_findings is reached, priority_rules: :hexpm retains findings from
the high-precision Hex.pm ruleset first. This is the default. Pass :none,
:all, or a list of rule ids to change that priority without changing which
rules are scanned.
The operational limits are configurable in both APIs:
SecretScan.scan(File.cwd!(),
file_timeout: 10_000,
scan_timeout: 30_000,
max_concurrency: System.schedulers_online(),
priority_rules: :hexpm,
occurrences: :all,
preview: :masked,
max_findings: 100,
max_locations: 10,
max_path_length: :infinity
):occurrences can be :all or :first_per_file. The latter reports the first
location of a credential in each file while still reporting that credential in
other files. :preview can be :masked, which shows no credential bytes, or
:redacted, which retains a short prefix and suffix.
:max_locations limits how many locations are reported for the same
credential. The result's incomplete? flag is true when a file can't be read,
a timeout expires, or either finding limit drops results. Paths are sanitized
for display and aren't shortened unless :max_path_length is set.
rules/gitleaks.toml is vendored from Gitleaks'
config/gitleaks.toml. rules/SOURCE records the upstream commit and date.
The config and its upstream MIT license are included in the package.
When working in the SecretScan source checkout, run mix secret_scan.refresh
to fetch the latest ruleset. Pass --check-outdated to exit with status 100
when the vendored files differ. A nightly GitHub Actions workflow uses this to
open or update a pull request.
The task resolves the current upstream commit, downloads the config and license from that commit, and validates the supported schema and every regex before replacing the vendored files. It performs one mechanical rewrite: literal example credentials in upstream allowlists have their final byte wrapped in a character class. The regex remains equivalent, while repository secret scanners no longer see a contiguous credential.
The regexes target Go's RE2 syntax. They don't use lookaround or backreferences, so Erlang's PCRE engine can compile them. A keyword prefilter limits how many patterns run against each content window.