Skip to content
Skip to content
Documentation

Frequently asked questions

Short answers, each with a link to the page that has the details. The config examples are keys to add to your ASH config file. ash config init writes one, .ashrc.yaml, in the current directory.

Why does my first scan exit 1?

At least one scanner is MISSING: its tool is not installed, or ASH found the tool only inside the scanned directory, such as in the project's .venv, and did not run it (see Tools inside the scanned directory). A scan with a MISSING scanner is incomplete, and an incomplete scan exits 1 even when it also found something. Run ash dependencies install to install the tools it can install. A scanner with nothing to scan shows as SKIPPED whether or not its tool is installed, so npm-audit and cdk-nag need Node.js, and cfn-nag needs Ruby with a C compiler, only in a project they have something to scan in: CloudFormation templates for cdk-nag and cfn-nag, and a lockfile beside a package.json for npm-audit. To scan without a tool you cannot install, leave its scanner out with ash --exclude-scanners cdk-nag,npm-audit. See A scanner shows MISSING and Exit codes.

In a project without CloudFormation templates, cdk-nag shows as SKIPPED in this scan, with or without Node.js, so the exit code is detect-secrets' verdict alone:

ash --scanners cdk-nag,detect-secrets --no-progress

How do I install ASH, and which version should I pin?

Install from GitHub, with a git URL or with Homebrew from the repository's tap; Install ASH shows each method. ASH is not published on PyPI. The PyPI project named automated-security-helper is not published by this project, so do not install ASH by that name. Pin an exact release tag or a commit. The floating v3 tag moves to every new 3.x release, including releases with breaking changes. A pin fixes ASH's own code, not every dependency: uv, pipx, and pip resolve ASH's dependencies again when they install it. Homebrew installs the release its formula names, the latest one, with the dependency versions locked in that release's uv.lock.

Is there a container image?

ASH publishes no image. ash build-image builds one from a release tag, and ash --mode container builds one and runs the scan in it. ASH uses the first of finch, docker, nerdctl, and podman that it finds on your PATH, or the one you name with --oci-runner. The default image runs as a non-root user whose UID and GID you can set; --build-target ci builds the image that runs as root, which a job running as root needs. See Run ASH in a container.

Which scanners run on my code?

In local and container mode, every enabled scanner runs on every scan. A scanner that finds nothing of its kind to check, such as actionlint in a repository without GitHub Actions workflows, shows as SKIPPED, whether or not its tool is installed. Scanner coverage lists the languages and file types each scanner checks, and the scanners reference lists each scanner's options.

Does ASH scan my CDK app?

It scans the CloudFormation templates the app synthesizes, not the CDK source. Run cdk synth before the scan, so that the templates exist under cdk.out. ASH scans the *.template.json files there even when a .gitignore excludes cdk.out, at the root of the scanned directory or inside your CDK app's directory, and the rest of cdk.out stays out of the scan. The same holds for an assembly that cdk synth -o writes to another directory. cdk-nag, cfn-nag, cfn-lint, and the other CloudFormation scanners then evaluate the templates. To check, look for a template in the list of files the scan read:

mkdir -p cdk.out
echo '{"Resources": {"Bucket": {"Type": "AWS::S3::Bucket"}}}' > cdk.out/App.template.json
echo cdk.out >> .gitignore
ash --scanners detect-secrets --offline --no-progress
grep cdk.out/App.template.json .ash/ash_output/ash-scan-set-files-list.txt

A scanner hangs. What can I do?

ASH stops a scanner's tool after its scan_timeout, 1800 seconds by default. Set a lower value under that scanner's options, scanners.<name>.options.scan_timeout, and see A scanner hangs for common causes.

How do I suppress a finding?

Add a suppression with the finding's rule ID, its path, and your reason:

global_settings:
  suppressions:
    - rule_id: B602
      path: src/build.py
      reason: The command is a constant string; no user input reaches it.

ash report shows each finding's rule ID. You can add expiration with a date in quotes, such as "2027-06-30"; from that date the suppression stops applying, and ash config lint reports it as expired. ASH also honors inline ash-ignore comments, and scanners honor their own, such as bandit's # nosec. See Suppress findings.

How do I keep files or directories out of the results?

Add them to global_settings.ignore_paths, each with a reason:

global_settings:
  ignore_paths:
    - path: tests/fixtures/**
      reason: Test data with deliberate findings.

ASH passes the ignore paths to every scanner. Some scanners skip those files and others scan them, and either way ASH drops every finding located in an ignored path. ASH also reads .gitignore, so directories it excludes, such as .venv/ and node_modules/ in most projects, are not scanned. If they are not in .gitignore, add them to ignore_paths. See Suppressions, ignore paths, and scanner exclusions.

Does ASH scan its own output, or the templates in cdk.out?

ASH leaves its own output out of the scan, whether or not .gitignore lists it: the output directory of the current scan, and .ash/ash_output under the scanned directory and under the directory ASH runs from. In a git checkout, regular files committed to one of those directories are still scanned (a committed link is not), and ASH warns that its output directory holds committed files. Outside a git checkout, or when git cannot list the repository, only the current scan's output directory is left out: an earlier run's .ash/ash_output is scanned like the rest of the tree, and ASH warns once per scan while the scan set still holds files in it, naming it and saying how to leave it out: delete it, or list it in an ignore file such as .gitignore. Adding it to global_settings.ignore_paths leaves its findings out, but the scan still reads it, so the warning repeats until the directory is deleted or an ignore file lists it. The output directory is recognized when it or the scanned directory is given through a link above the tree, as /var is on macOS. A scan does not delete or overwrite a committed file: when git tracks one where a scan writes its output (ash_aggregated_results.json, ash-ignore-report.txt, ash-scan-set-files-list.txt, ash-scan-set-run.json, ash.log, ash.log.jsonl, .ash_previous_results_restored, or anything under analysis/, reports/, scanners/, converted/, .ash_previous_results/ or .ash_previous_results.partial/), the scan exits 3 before it deletes or writes anything, in every mode and from the MCP server. When git cannot list the repository the output directory is in (git is not installed, or it fails on the repository), any file at one of those paths stops the scan the same way, and the message names git's error and says to let git read the repository or remove the old results. A report over existing results (--use-existing) is stopped only where it writes: the results file, the logs, reports/, and analysis/ with --inspect or converted/ with --cleanup. A .ash/ash_output that is a link, or that a link inside the scanned directory leads to, is not taken as ASH's output, however the path to it is spelled, and a scan into an output directory git tracks as a link exits 3. The trivy-repo plugin still reads those directories, and ASH drops the findings it reports there, except in committed files. Any other directory is scanned, even one holding an earlier run's or another shard's results; ASH warns when a directory holds ash_aggregated_results.json, or both ash-ignore-report.txt and ash-scan-set-files-list.txt. Write shard output outside the scanned directory, or list it in .gitignore. CloudFormation templates that cdk synth writes (*.template.json) are scanned even when .gitignore excludes cdk.out, so cdk-nag and cfn-nag check them; the rest of the cdk.out directory stays excluded. The snyk-code plugin first refuses a run when a file in the directory it would test matches an ignore path, then tests a temporary copy of the source scan set with the ignore paths left out; converted outputs are tested in place.

How do I scan files that a .gitignore excludes?

Pass --no-honor-ignore-files, or set honor_ignore_files: false in a config file outside the scanned tree. A config file inside the repository being scanned cannot change this setting. See Ignore files and the scan set.

How do I fail only on high-severity findings, or never fail on findings?

Set the threshold under global_settings, and fail_on_findings at the top level of the file, not under global_settings:

fail_on_findings: true
global_settings:
  severity_threshold: HIGH

With fail_on_findings: false, or --no-fail-on-findings on the command line, findings never fail the scan. An incomplete scan, or one with a vulnerability database or offline ruleset past its age bound, still exits 1. See Set the gate.

Why do two scanners report the same problem?

ASH runs scanners whose checks overlap and does not deduplicate their findings: semgrep and opengrep run the same ruleset, grype and trivy both check dependencies, and detect-secrets and gitleaks both look for secrets. Each finding carries its scanner's own rule ID, so a suppression for one does not cover the other. To keep one of a pair, turn the other off by setting scanners.opengrep.enabled to false in the config or by passing --exclude-scanners opengrep. See Choose scanners.

Does ASH work on Windows?

Local mode installs and runs on Windows, but semgrep and opengrep are off there and show as SKIPPED, and there is no scanner sandbox. If another program named ash comes first on your PATH (Git for Windows and MSYS2 ship a shell by that name), run ASH as ashv3, which is the same command. Container mode is not tested on Windows. See Install ASH.

Which GitHub Actions permissions does ASH need?

The job that calls the reusable workflow run-ash-security-scan.yml must grant contents: read, checks: write for its JUnit check, and pull-requests: write for its pull request comment. GitHub refuses to start a call that grants fewer. Uploading results to code scanning outside same-repository pull requests needs security-events: write, granted to a separate job that calls upload-ash-sarif.yml. See Run ASH in GitHub Actions.

Can ASH run offline or in an air-gapped network?

Yes. With --offline, ASH runs every scanner in its offline mode and installs nothing, except npm-audit, which does not run offline. Some scanners still make requests; only the scanner sandbox, or container mode with --offline, removes the network itself. The rules and vulnerability databases must be in place first; an image built with ash build-image --offline contains them. Each database has an age bound, from a day to a month, and by default a scan fails when one is past it (an image built with OFFLINE=YES only warns for trivy's database), so plan to rebuild offline images often. See Run ASH offline and Content databases.

npm, yarn, and pnpm audit a lockfile by asking the registry, so in an offline scan npm-audit is MISSING, and the scan exits 1, for a project with a lockfile beside a package.json. For a project without one it is SKIPPED. To scan such a project offline, add --exclude-scanners npm-audit:

echo '{"name": "demo", "version": "1.0.0"}' > package.json
echo '{"name": "demo", "version": "1.0.0", "lockfileVersion": 3, "packages": {}}' > package-lock.json
ash --offline --scanners npm-audit,detect-secrets --no-progress

What does a scan send over the network?

ASH itself sends no telemetry. The scanners download rules and vulnerability databases, and some send data to their own services: for example, semgrep sends usage metrics to Semgrep when it uses registry rules, and npm-audit sends your dependency tree to the npm registry. See what leaves the machine during a scan.

Can I use ASH programmatically?

Yes. run_ash_scan runs a scan from Python:

from automated_security_helper.core.enums import RunMode
from automated_security_helper.interactions.run_ash_scan import run_ash_scan

results = run_ash_scan(
    source_dir="/path/to/code",
    output_dir="/path/to/output",
    mode=RunMode.local,
    # Return the results even when there are actionable findings
    fail_on_findings=False,
)
print(f"{results.metadata.summary_stats.actionable} actionable findings")

It returns the results only when the scan's exit code is 0. Otherwise it raises SystemExit with that code, as ash scan exits with it, which is why the example passes fail_on_findings=False. An incomplete scan still raises. A keyword that run_ash_scan does not take raises TypeError. See Python API.