Troubleshoot¶
Find the symptom, then read its cause and fix. Two places hold most of what you need:
the console output of the scan, and ash.log in the output directory
(.ash/ash_output/ash.log by default), which has every warning, including the ones the
console summary leaves out.
Scanners that do not run¶
A scanner shows MISSING¶
Symptom: a row in the summary table has the result MISSING, and the scan ends with:
ERROR (1) Exiting because the scan was incomplete: 1 selected scanner(s) did not
evaluate everything they were given
grype: MISSING
its tool grype was not found on PATH or in /home/me/.ash/bin, /home/me/.local/share/uv/tools/automated-security-helper/bin, /usr/local/bin; install it with `ash dependencies install` or exclude grype with --exclude-scanners
Cause: ASH could not find the scanner's tool, or could not install it. The line under
each MISSING scanner gives the reason, and so does its missing_reason in
ash_aggregated_results.json. A warning in ash.log names what is missing too, for
example Grype executable not found in PATH. Please ensure grype is installed. The
console shows it when the progress display is off (--no-progress). For the scanners
ASH installs with uv, one or both of these warnings mean ASH found no uv, neither the
copy installed with ASH, beside the Python interpreter that runs it, nor one on your
PATH, so it cannot install them during the scan:
UV tool validation failed - UV is not available but required
UV tool execution is required but UV is not available for checkov
A scanner with nothing to scan in the project, such as cdk-nag with no CloudFormation
template, npm-audit with no lockfile beside a package.json, or actionlint with no
workflows, shows SKIPPED instead, whether or not its tool is installed. Scanners that
have no such check, such as grype and semgrep, show MISSING whenever their tool is
missing.
Fix: install the tool, usually with ash dependencies install --tool grype (using the
scanner's name). When a scanner shows MISSING
lists the exceptions: detect-secrets, cdk-nag, npm-audit, and cfn-nag. If you do not need the scanner,
leave it out with ash --exclude-scanners grype, which records it as SKIPPED.
A tool inside the scanned directory is not run¶
Symptom: a scanner shows MISSING although its tool is installed, and after the list of incomplete scanners the scan names each tool it found only inside the scanned directory:
Found only inside the scanned tree, so not run: grype (/home/me/repo/.venv/bin/grype). To use them, install it outside the scanned tree, or set ASH_ALLOW_IN_TREE_TOOLS=1 or pass --allow-in-tree-tools to allow tools from the tree.
ash.log has a warning for each one (the console shows it too when the progress display
is off):
Not running grype from inside the scanned tree /home/me/repo: /home/me/repo/.venv/bin/grype. To use it, install it outside the scanned tree, or set ASH_ALLOW_IN_TREE_TOOLS=1 or pass --allow-in-tree-tools to allow tools from the tree.
The scanner's missing_reason in ash_aggregated_results.json records the reason, as
"grype was found only inside the scanned tree, at ...".
Cause: during a scan, ASH does not run programs found inside the directory it scans.
The usual case is a scanner installed in the project's virtual environment or
node_modules, which uv run or npm run put first on PATH.
Fix: install the tool outside the repository, for example with
ash dependencies install or uv tool install. To use the in-tree copies for one
scan, pass --allow-in-tree-tools or set ASH_ALLOW_IN_TREE_TOOLS=1. ash mcp
ignores both. See
Tools inside the scanned directory.
A scanner is ERROR with an exit code it does not accept¶
Symptom: a scanner's row is ERROR, and its error names the tool's exit code:
Failed to execute semgrep scanner on source: SemgrepScanner scan failed: its tool exited with code 7, which this scanner does not accept as a completed run, so its results file is not graded (exit code 7: not an accepted exit code; success_exit_codes=[0, 1]). No stderr captured; checked .ash/ash_output/scanners/semgrep/source/SemgrepScanner.stderr.log, which the tool did not write. .ash/ash_output/scanners/semgrep/source/results_sarif.sarif reports: Invalid YAML file ...
Cause: the tool reported that it failed. Each scanner accepts the exit codes its tool uses
for a run that completed, with or without findings, and nothing else. A failed tool
can still leave a results file (semgrep exits 7 and writes SARIF holding only error
notifications when it cannot read a rule configuration; grype exits 1 and writes a
zero-byte report when it cannot load its database), so ASH does not read the file of
a run with any other code: an empty or error-only file would otherwise be graded
PASSED with no findings. A tool ended by a signal shows as 128 + N (129 for
SIGHUP, 130 for SIGINT, 137 for SIGKILL). 127 is a tool the OS could not start, or
a run ASH itself failed to carry out (the scanner's errors name ASH's error).
| Scanner | Accepted exit codes | What the tool uses other codes for |
|---|---|---|
| bandit | 0, 1 (issues found) | 2: any error (bandit/cli/main.py) |
| checkov | 0, 1 (failed checks) | 2: the run crashed (checkov/main.py) |
| grype | 0, 2 (a match at or above grype --fail-on) |
1: any error, such as a database it cannot load (grype's cmd/grype/cli package) |
| semgrep | 0, 1 (findings) | 2: fatal; 3 and up: specific failures, 7 for a rule configuration it cannot read (semgrep/error.py) |
| opengrep | 0, 1 | the same values as semgrep (src/osemgrep/core/Exit_code.ml) |
| trivy, trivy-repo | 0 | 1: any error (trivy's cmd/trivy package) |
| syft | 0 | 1: any error (syft's cmd/syft package) |
| ferret-scan | 0, 3 (coverage cut short under fail_on_incomplete: the findings are reported and the row is ERROR, as its target counts as failed) |
1: any error |
| snyk-code | 0, 1 (issues found) | 2: an error; 3: no supported project (src/cli/exit-codes.ts) |
| gitleaks | 0, 2 (leaks found: ASH runs gitleaks --exit-code=2) |
1: any error, such as an unreadable config; gitleaks exits 1 for leaks too unless told otherwise, which is why ASH moves the leaks code |
| zizmor | 0, 3 (nothing it could audit; findings never set the status because ASH runs zizmor --no-exit-codes) |
any other status |
| actionlint | 0, 1 (problems found) | 2: a bad command line; 3: a fatal error, such as an unreadable file |
| cfn-lint | 0 and the even values up to 14: 2 error, 4 warning and 8 informational, added together for a run with more than one level (cfnlint/runner/cli.py) |
1: a fatal error, so any odd status; anything above 15 |
| cfn-guard | 0, 19 (at least one rule failed) | any other status, such as 255 for a parse error or a missing rules path |
trivy exits 0 for a run with findings unless its exit-code setting names another
code. ASH runs trivy and trivy-repo with trivy --exit-code=0, which outranks an
exit-code in the operator's trivy config and TRIVY_EXIT_CODE (measured with trivy
0.75.0), and neither scanner reads a trivy.yaml from the scanned directory, so a
run with findings exits 0 and a nonzero exit only ever means trivy failed. ASH
decides the scan's exit code from the findings itself.
cfn-nag, npm audit, detect-secrets and cdk-nag are not in this table. cfn-nag exits with its count of failing violations and is judged per template; npm audit exits 1 both for advisories and for errors and is judged by whether it wrote a report; detect-secrets and cdk-nag run in ASH's own worker processes.
Fix: read what the message quotes, and fix what the tool reported: its stderr, or, when
it wrote none (semgrep and opengrep report a rule file they cannot read only in
their SARIF), the error-level notifications from its results file, which the
message names. The full text is in the <Scanner>.stderr.log, the
<Scanner>.stdout.log and the results file under scanners/<name>/<target>/ in the
output directory; the message names the ones the tool wrote.
For semgrep and opengrep under --offline, see
Seeding the semgrep and opengrep rule cache.
For grype, see What each scanner does offline.
The scan exits 1 for an ERROR scanner while fail_on_incomplete_scanners is on,
which is the default. With it off, the exit code comes from the findings alone, so a
scan whose only problem is the failed scanner exits 0; see
Incomplete scans.
semgrep and opengrep show SKIPPED on Windows¶
Cause: both are off by default on Windows, and semgrep publishes no Windows build, so it is SKIPPED even when you turn it on. A SKIPPED scanner does not fail the scan, so the scan passes without them, as long as another selected scanner runs.
Fix: to run them, scan from WSL or in a container. See Platform limits.
Exit codes¶
ASH exits 0 when nothing is actionable, 1 when the scan is incomplete or failed, 2 when it has actionable findings at or above the threshold, 3 for a configuration it cannot use or an input it refuses before scanning, such as a linked output directory, 4 for a workspace error, and 5 for a usage error. When a scan is both incomplete and has findings, it exits 1 (a workspace scan exits 2). See Exit codes.
The scan exits 1 with no findings¶
Cause: the scan is incomplete, or it stopped before running. The last lines say which:
Exiting because the scan was incomplete, followed by the scanners: a scanner is MISSING, or it ran and failed (ERROR), or it could not read some of its inputs. For ERROR, the scanner's own messages are inash.logand in its stderr log underscanners/in the output directory.Exiting because conversion was incomplete: see the next entry.Exiting because N content database(s) are past their declared age bound: see A content database is stale.Exiting because no scanner ran: every scanner was SKIPPED, so the scan shows nothing about the code. Each scanner'sskipped_reasoninash_aggregated_results.jsonsays why. Check--scanners,--exclude-scanners, and theenabledsettings in your configuration.- A message that no selected scanner had a file to scan: see No selected scanner had a file to scan.
Exiting because N rule(s) could not be evaluated: cdk-nag rules failed with an error on your templates, so the scan says nothing about them. Fix the template the rule fails on, or suppress that rule's findings for the template.
Fix: fix the cause named in the message. To accept an incomplete scan, pass
--no-fail-on-incomplete-scanners, as the scan's message says, or set
fail_on_incomplete_scanners: false at the top level of your configuration. Excluding
the scanner is usually better, because the reports then say it did not run.
No selected scanner had a file to scan¶
Symptom: the scan exits 1 with no findings, every selected scanner is SKIPPED, and the error names the scanners that had nothing to scan:
No selected scanner had a file to scan: bandit was SKIPPED with nothing to scan, so this run has shown the target to be neither clean nor dirty. To accept a run that scanned nothing, set fail_on_incomplete_scanners: false or pass --no-fail-on-incomplete-scanners.
When the config sets global_settings.ignore_paths, the error also shows its first three
patterns, with a count of any others, and suggests narrowing them:
No selected scanner had a file to scan: detect-secrets was SKIPPED with nothing to scan, so this run has shown the target to be neither clean nor dirty. The config's global_settings.ignore_paths ('**') leaves every path it matches out of what the scanners read, which can leave them nothing to scan; narrow it to have those files scanned. To accept a run that scanned nothing, set fail_on_incomplete_scanners: false or pass --no-fail-on-incomplete-scanners.
A run whose scanners were all SKIPPED for another reason, such as a --scanners name
that matches no scanner on this platform, or every selected scanner turned off in the
configuration, reports Scan ran no scanners: instead, with each scanner and its status;
see The scan exits 1 with no findings.
Cause: each selected scanner had nothing to read, so the scan shows nothing about the
code. A scanner that reads only some files, such as cfn-nag with no CloudFormation
template or bandit with no Python file, has nothing to read in a project without them,
and no scanner has anything to read when the scan set is empty. Two cases are ERROR
instead, and the error gives a count. semgrep and opengrep are ERROR when their own
exclusions, rather than ignore_paths, left out every file they found. bandit is ERROR
when it scanned no file although Python files that nothing excludes are there; when
ignore_paths or bandit's own excluded_paths leave out every Python file, bandit is
SKIPPED.
Fix: check that --source-dir names the project, that ignore_paths, and the
repository's .gitignore and .ignore files, leave the files you mean to scan, and that
the scanners you selected read them; Scanner coverage lists
what each scanner reads. To accept such a scan, pass --no-fail-on-incomplete-scanners
or set fail_on_incomplete_scanners: false; it then exits 0. ash merge gives the same
verdict over the shards.
The scan exits 1 because conversion was incomplete¶
Symptom:
ERROR (1) Exiting because conversion was incomplete: 1 converter(s) did not process the files they were given
Cause: ASH converts Jupyter notebooks to Python, and extracts .zip, .tar, and .gz
archives, before it scans them. A notebook that nbconvert could not convert, or an
archive that cannot be read, fails the scan so that its content is not silently left
unscanned. The lines after the message name each converter and up to five of the files
it could not convert, or, for a converter that crashed, its error as <Type>: <message>.
ash.log has a line for each file with the reason, Could not convert '<file>': <reason>
(the console shows it too when the progress display is off), and each converter's
failed_inputs in ash_aggregated_results.json records the same reason. A reason that
comes from an unexpected error is the error's type and message, such as
BadZipFile: Bad CRC-32 for file 'app.py'. For a notebook, the reason says that nbconvert
exited with a non-zero code, did not finish within 60 seconds, or wrote no output, or that
uv could not run nbconvert and no jupyter-nbconvert was found.
A notebook in an offline scan with no nbconvert installed gives
jupyter: dependencies unavailable, so it never ran, because ASH does not install
nbconvert offline.
Fix: for notebooks, install nbconvert ahead of time with
ash dependencies install --tool jupyter, or let ASH install it during an online scan.
To see nbconvert's own error output, run the scan again with --debug. For a corrupt
archive, fix or remove the file. To skip a file, add it to global_settings.ignore_paths
with a reason; see Suppress findings.
The scan exits 2¶
Cause: the scan found at least one finding at or above the severity threshold, which
is MEDIUM unless you change it. The scan ends with
ERROR (2) Exiting due to 2 actionable findings found in ASH scan.
Fix: fix or suppress the findings (Suppress findings). To
report findings without failing, pass --no-fail-on-findings, or set
fail_on_findings: false at the top level of your configuration, not under
global_settings. To fail only on more severe findings, set
global_settings.severity_threshold. See Set the gate.
The command exits 5¶
Symptom: the command stops before anything runs and writes no report. A mistyped option
prints an Error box:
Usage: ash scan [OPTIONS]
Try 'ash scan --help' for help.
╭─ Error ──────────────────────────────────────────────────────────────────────╮
│ No such option: --fail-on-finding (Possible options: --fail-on-findings, │
│ --no-fail-on-findings) │
╰──────────────────────────────────────────────────────────────────────────────╯
Cause: a usage error. Every ash command exits 5 when its command line is wrong: an
option or argument the command does not take, a value an option does not accept (such
as an invalid --output-formats, --mode, --strategy, or --min-severity), a
--scanners list that names no scanner, a --source-dir that does not exist or
cannot be entered, an --output-dir that ASH cannot create, enter, or write in,
--use-existing with no results to use, or an ash report --format that no reporter
answers to. The code is not 2, so a CI gate does not read a mistyped flag as a scan
with findings. Two selections exit 2 instead: an ash dependencies install --tool name
that it has no plugin for, and a --plugin-type other than converter, scanner, or
reporter (Exit codes).
ash report with a format no reporter answers to lists the ones it has, whether or not
there are results to report:
Error: Reporter plugin 'nosuch' not found.
Available reporter formats:
- csv
- cyclonedx
- github-ghas
...
Fix: correct the command line; ash <command> --help lists each command's options.
Scanner and reporter names use hyphens, as ash plugin list shows them. A single
misspelled name among valid ones in --scanners is not a usage error: ASH logs
No registered scanner matches ... and runs the others.
The scan exits 3¶
Symptom:
ERROR (3) Invalid configuration: Configuration validation failed for '/home/me/repo/.ashrc.yaml': Invalid value at 'global_settings.fail_on_findings': Extra inputs are not permitted. Run: ash config lint --config /home/me/repo/.ashrc.yaml
Cause: the configuration file has a key ASH does not know below the top level, or a value of the wrong type anywhere, top level included. A suppression, an ignore path, or a plugin's options with a key ASH does not know is refused too, and the message lists the keys it accepts:
cat > .ashrc.yaml <<'EOF'
global_settings:
suppressions:
- rule_id: B602
path: app.py
reason: Commands come from a fixed allowlist
expires: "2027-04-30"
EOF
ash config validate
❌ 1 error(s); a scan refuses the configuration because of these:
1. Invalid value at 'global_settings.suppressions[0]': Value error, Unknown key 'expires' (did you mean 'expiration'?) in a suppression. Accepted keys: expiration, line_end, line_start, package_name, package_path, package_version, path, reason, rule_id, symbol.
A common case is an unquoted expiration date in a suppression: YAML reads it as a
date, and ASH expects text. Quote it, as in "2027-04-30".
Fix: correct the key or value that the message names. ash config validate and
ash config lint check the file the way a scan does and report the same errors, with
exit code 3; lint marks each one [blocks the scan]. See
Configure ASH.
Your configuration has no effect¶
Symptom: the scan behaves as if your configuration file were not there: suppressions do not apply, or a setting is ignored. The scan does not exit 3.
Cause: one of these. A file that does not exist or does not parse is not one of them: the scan refuses it with exit 3.
- A top-level key is misspelled, such as
fail_on_finding. ASH ignores unknown keys at the top level. - ASH found a different file first.
ash config getprints the configuration a scan would use. - A suppression's or an ignore path's
expirationdate has passed. The entry then stops applying, and the scan starts with a warning that names it (see below). - The setting is one a repository's file may not set; see the next entry.
An ignore path that expired lets its files back into the scan, so the finding in app.py
counts again here:
cat > .ashrc.yaml <<'EOF'
global_settings:
ignore_paths:
- path: app.py
reason: Generated file, scanned upstream
expiration: "2026-01-31"
EOF
ash scan --scanners detect-secrets --no-progress
The following ignore paths and suppressions have expired and no longer apply:
- Ignore path 'app.py' expired on 2026-01-31. Reason: Generated file, scanned upstream
Remove them, or move their expiration dates, in your ASH configuration file.
Fix: run ash config validate, which checks the file the way a scan does and warns
about unknown top-level keys and a missing project_name, and ash config get, which
prints the configuration a scan would use. ash config lint also lists expired
suppressions and ignore paths.
A setting in the repository's config file is ignored¶
Symptom: a warning such as:
Ignoring sandbox.network_scanners from /home/me/repo/.ash/.ash.yaml: that config is inside a git checkout or the scanned tree, or was delivered by an MCP client. ...
Cause: some settings decide what ASH may run, read, or send, or whether a stale database
fails the scan: sandbox grants, plugin modules, reporter destinations and credentials,
options that name a scanner's own configuration or ignore file, tool_version, trivy-repo's
disable_telemetry: false, and a relaxed content_db_staleness. A config file inside the scanned directory, or inside any git
checkout, could have been written by someone other than you, so ASH ignores those settings
from it, even from a config you keep in an ops repository of its own. These settings count
as yours only from --config-overrides or a config file outside every git checkout and the
scanned directory. The warning names each
setting and the file, says why, and says how to keep the setting. For a plugin module
found inside a git checkout or the scanned directory, it also gives where Python found the
module and which checkout or directory that is. For other modules, it says that the module
is not importable, or what ASH's AWS or Snyk plugin package would send off the host.
Under ash mcp, a config file that an MCP client names as a scan's config_path, outside
the directories ASH_MCP_ALLOWED_CONFIG_ROOTS names, is held to the same limits. Under the
default policy, a config file an MCP client delivered is refused instead when its own sandbox
section gives a scanner a network or an extra_read_paths entry the server's settings do
not, or changes a sandbox mode other than off that the server sets, or when its own
ash_plugin_modules list adds a module this check would drop; see
What a delivered config cannot set.
Fix: pass the setting with --config-overrides, or move the file outside every git
checkout, such as under /etc/ash, and name it with --config. A file inside the scanned
directory has to move out of it as well. Name a plugin module with --ash-plugin-modules, or install it outside
every git checkout and the scanned directory. A config inside a checkout cannot add ASH's
AWS or Snyk plugin package at all, so name those with --ash-plugin-modules or one of the
two ways above.
For some scanners, the file such an option names must also be outside the scanned
tree; Options only the operator can set says
which. See Configuration trust model.
The scan exits 4¶
Cause: a workspace scan (--workspace) could not start, or a workspace option was used
without one. The message names the problem. The causes are:
- the workspace file is missing or is not valid JSON;
--workspace autofinds more than one workspace file;- a project path contains
.., or a project directory does not exist; --source-diris given with--workspace;--dry-run,--allow-missing-projects, or--workspace-configis given without--workspace.
Fix: correct the workspace file or the command line, or pass --allow-missing-projects
to skip projects that do not exist. See Scale to large repositories.
Output directory¶
The scan stops because the output directory is a symlink¶
Symptom: the scan exits 3 before writing anything, with one of:
ERROR (3) The output directory /home/me/out is a symlink. ASH does not write to, clear or mount its output directory through a link, whatever the sandbox mode. Pass --output-dir with a directory that is not a link, or replace the link with a directory.
ERROR (3) /home/me/repo/.ash is a symlink, on the way to the output directory /home/me/repo/.ash/ash_output. ...
ERROR (3) /home/me/out/reports is a symlink. ...
Cause: the output directory, a directory on the way to it inside the scanned
directory, or something ASH writes inside it, such as reports, is a symbolic link.
ASH clears and writes its output directory, so it does not do that through a link. This
check applies whether or not the sandbox is on. In an ash --workspace scan, a link at
projects/<project> fails only that project, and a link at projects or
.workspace-spool stops the whole run. Output files
lists every path ASH checks.
Fix: pass --output-dir with a real directory, which can be outside the project, or
replace the link with a directory. Turning the sandbox off does not change this.
The scan stops because a report file is a link¶
Symptom: the run exits 1 before any reporter writes, with this message (ash merge
prints the same text after Refusing to write reports:):
ERROR (1) /home/me/out/reports/ash.sarif is a symlink. ASH does not write a report through a link. Remove the link and run ASH again.
Cause: a file a reporter is about to write in reports/ is a symbolic link or a
directory junction. A scan clears reports/ first, so this usually comes from
ash scan --use-existing or ash merge writing into an output directory that already
holds the link.
Fix: remove the link and run the command again, or write to a new output directory.
Content databases¶
A content database is stale¶
Symptom:
ERROR (1) Exiting because 1 content database(s) are past their declared age bound
Content database semgrep-offline-rules (semgrep) is stale: downloaded 2026-08-31T22:43:12Z, 40d 0h old, past its 720h bound ...
Cause: grype, trivy, and offline semgrep and opengrep read databases that go out of date. ASH fails a scan whose database is past its age bound, or whose age it cannot read; Content databases lists the bounds. Online scans keep grype's and trivy's databases current, so this usually happens offline or with an old image.
Fix: refresh the database. For an offline image, rebuild it with
ash build-image --offline. For offline rules on your machine, download them again
(Seeding the semgrep and opengrep rule cache).
To scan with the old database anyway, pass --allow-stale-content-db or set
content_db_staleness: warn: the stale database then no longer fails the scan, and
every report names it. With warn, a scan passes even when a database is missing, so its
vulnerability results can be incomplete. See
Content databases and
Run ASH offline.
Slow or stuck scans¶
The first scan is slow¶
Cause: the first scan downloads grype's vulnerability database, which can take a minute or more, and installs any uv-based scanner that is not installed yet. Later scans reuse both.
Fix: run ash dependencies install ahead of time. In CI, cache the scanners' cache
directories, such as ~/.cache/grype and ~/.cache/trivy, between runs.
A scanner hangs¶
Symptom: the scan stops making progress. Every 60 seconds the log shows which scanners are still running:
Cause: each scanner has a time limit, scan_timeout, which defaults to 1800 seconds
(30 minutes). A scanner that reaches it is stopped and shows as ERROR. With
scan_timeout set to 65, for example:
(bandit @ source) timed out after 65.0s and was killed. Raise scanners.bandit.options.scan_timeout if this target legitimately needs longer.
There is no limit on the scan as a whole.
Fix: find the scanner in the "still running" lines or in ash.log, and look at its
stderr log under scanners/<scanner>/ in the output directory. Then do one of these:
- Exclude it for now:
ash --exclude-scanners bandit. - Lower or raise its limit:
ash --config-overrides 'scanners.bandit.options.scan_timeout=600'. - Run scanners one at a time to see where time goes:
ash --strategy sequential. - Get more detail with
ash --verboseorash --debug.
Installing and running ASH¶
The install fails while building rtoml¶
Symptom: uvx or uv tool install fails with
Failed to build rtoml and a message that the Python version (3.15) is newer than
PyO3's maximum supported version.
Cause: uv chose Python 3.15, which some of ASH's dependencies do not support yet.
Fix: pass a supported Python version, as in uv tool install --python 3.12. See
Install ASH.
The install fails because git is missing¶
Symptom: uv stops with an error whose causes end in:
cause: Git operation failed
cause: Git executable not found. Ensure that Git is installed and available.
Cause: uv and pip fetch ASH from its git repository, which needs git.
Fix: install git and run the command again.
ash is not found after installing¶
Cause: the directory where uv or pipx puts commands is not on your PATH in the
current shell.
Fix: open a new terminal. If that does not help, run uv tool update-shell (for uv)
or pipx ensurepath (for pipx), then open a new terminal.
ash runs a different program on Windows¶
Symptom: in Git Bash or an MSYS2 shell, ash --version prints an error such as
ash: 0: Illegal option --, and ash dependencies install reports
cannot open dependencies.
Cause: Git for Windows and MSYS2 ship an unrelated shell named ash, and it comes first
on your PATH.
Fix: use ashv3 or automated-security-helper, which are the same program as ASH's
ash.
Results differ between your machine and CI¶
Cause: the two runs differ in something ASH does not control:
- Scanner versions. A scanner on your
PATHis used if its version is in range, so your machine and CI can run different versions. - Database age. grype's and trivy's databases change daily.
- Platform. On Windows, semgrep and opengrep are SKIPPED.
- Diff scans. With
--changed-files-only, ASH reports findings in every file when git cannot list the changes: the base ref does not exist, as in a shallow clone, the directory is not in a git repository, or git is not installed. The log says which, such asgit diff against origin/main failed (rc=128); falling back to full scan. - Mode.
ash --mode precommitruns a smaller set of scanners.
Fix: run the same scanner versions in both places, for example with the same container image, and fetch enough history in CI for the diff.
Container mode¶
The container image does not build¶
Container mode builds ASH's image on your machine before it scans. Common causes:
- No container runtime. The scan exits 1 with
Unable to resolve an OCI runner. ASH tries finch, docker, nerdctl, and podman, in that order. Install one, or name it with--oci-runner. - A non-numeric user or group ID:
Container UID must be a numeric value. Pass numbers to--container-uidand--container-gid. - No revision to install. A package that recorded no commit, such as one built from a
source tarball, installs the release tag of its version, and a development or
pre-release version has none, so the build stops and asks for
--ash-revision-to-install. Pass a tag, a branch, or a full commit ID. Which ASH goes into the image lists where the revision comes from for each kind of install. - No access to the base image registry or the internet. The build pulls a Python base
image and downloads ASH and its scanners. To use a mirror of the base image, set
ASH_BASE_IMAGE_OVERRIDE.
Run container mode from a directory other than the project you scan, and name the
project with --source-dir.
To scan offline with an image you already built, add --no-build, as in
ash scan --mode container --offline --no-build. Without it, container mode builds the
image first, and the first offline build of a UTC day downloads the databases again,
which needs network access. See Run ASH offline.
With --custom-containerfile, ASH builds the ci target, which runs as root, even if you
pass --build-target non-root. On Windows, ASH builds ci unless you name a target. A
non-root build that would give its user UID 0 or GID 0, as a job running as root does,
stops with exit code 3 before anything is built: pass --build-target ci there.