Secrets in the checkout
Secrets found in the checkout, read from the report a secret scanner already left behind, or collected by running gitleaks or trufflehog when the runner carries one and no report is there. Only the rule, the file, the line and a fingerprint are collected: the matched value never is, because these facts are attached to the run event.
| Facts key | secrets |
| Version | v1 |
| Script | secrets.py |
| Timeout | 300 seconds |
Tools
This collector reads what these tools report, but it never installs them. If a tool isn't on the runner, the evidence is missing and the guardrails that need these facts skip instead of failing.
| Tool | Needed | Description |
|---|---|---|
gitleaks | optional | Secret detector, run over the checkout when no report is there. Preferred over trufflehog because it reports SARIF. |
trufflehog | optional | Secret detector that verifies what it finds against the service it belongs to, run when gitleaks is not there. |
Installing gitleaks
Install gitleaks with brew install gitleaks, or docker run --rm -v $(pwd):/repo zricethezav/gitleaks:latest detect --source /repo, or run the gitleaks/gitleaks-action@v2 step before this one and leave its report in the checkout.
Installing trufflehog
Install trufflehog with brew install trufflehog, or curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin, or run the trufflesecurity/trufflehog@main step before this one.
Inputs
| Input | Description | Default | Environment |
|---|---|---|---|
reports | Comma separated globs matched against every file in the working directory and against bare file names, naming the secret scanner reports to read. Narrow it when several reports are present and only one of them should decide. | gitleaks*.sarif,gitleaks*.json,trufflehog*.json,.secrets.baseline | GUARDRAIL_INPUT_REPORTS |
scanHistory | Whether a tool the collector runs scans the repository's git history instead of the working tree. Scanning the history finds a secret that was committed and later removed, at the cost of reading the whole log. Ignored when a report was found, because the report is what the pipeline already acted on. | false | GUARDRAIL_INPUT_SCANHISTORY |
maxFindings | Maximum number of findings to carry in the facts. The counts still include findings that are dropped. | 200 | GUARDRAIL_INPUT_MAXFINDINGS |
When a guardrail declares an input with the same name, it passes its value through. That means you set these values in the guardrail's configuration in buildnote.json.
A guardrail asks for these facts by name and reads them back by the same name:
{
"collect": ["secrets"]
}secrets = guardrail.facts("secrets")Facts
These are the fields of the document secrets collects. In a path, [] means each entry of the list before it, and [path] means a key of the object before it.
| Fact | Meaning |
|---|---|
source | report when a secret scanner's report was read, tool when the collector ran one itself, none when there was neither. |
reason | Why nothing was collected, present only when source is none. It names the globs that matched no readable report and the tool to install. |
reports | Every report that was read, in path order. |
reports[].path | Path of the report, relative to the directory the CLI runs in. |
reports[].modified | When the report was last written, in ISO 8601, so you can spot a stale report. |
reports[].format | Format it was read as: sarif, gitleaks, trufflehog or detect-secrets. |
tool.name | Tool that was run, present only when no report was found and one was on the PATH. |
tool.version | Version that tool reported when the CLI probed it, so you know which scanner decided this build was clean. |
tool.path | Where on the PATH it was resolved to. |
tool.args | Arguments it was run with, so the same scan runs by hand. |
tool.exitCode | Code it exited with. A secret detector exits non zero when it finds something, so this is a result rather than a failure. |
tool.durationMs | How long the invocation took. |
counts.total | Secrets found, across every report that was read or everything the tool printed. |
counts.bySeverity.critical | Findings the scanner verified against the service the credential belongs to, so it is known to be live. |
counts.bySeverity.high | Findings reported without being verified, which is the usual case for a secret detection. |
counts.bySeverity.medium | Findings the report rated medium. |
counts.bySeverity.low | Findings the report rated low. |
counts.bySeverity.info | Findings the report rated informational. |
counts.bySeverity.unknown | Findings the report gave no severity, or a severity that is not recognised. |
findings | The findings themselves, up to maxFindings. |
findings[].id | Rule the scanner matched, such as aws-access-token, or the detector that matched it. |
findings[].severity | critical, high, medium, low, info or unknown. |
findings[].message | What the scanner said about the rule, truncated to 500 characters. Never the value it matched. |
findings[].path | File the secret was found in, relative to the working directory, or null. |
findings[].line | Line it was found on, or null. |
findings[].fingerprint | Stable identity of the finding across runs, derived from the rule, the file and the line, so the same secret is recognisable in a later build without carrying it. |
dropped | Findings left out because maxFindings was reached. |
If a collector can't finish, it prints what it gathered so far along with an incomplete key that says why. Facts after the point where it stopped are missing, so a check that depends on them should read incomplete first.
Example facts
Here are the facts the secrets collector gathers from an example project:
{
"source": "report",
"reports": [
{
"path": "gitleaks.sarif",
"modified": "2026-03-04T10:15:00Z",
"format": "sarif"
}
],
"counts": {
"total": 1,
"bySeverity": {
"critical": 0,
"high": 1,
"medium": 0,
"low": 0,
"info": 0,
"unknown": 0
}
},
"findings": [
{
"id": "aws-access-token",
"severity": "high",
"message": "aws-access-token has detected a secret in service/src/main/resources/application.yaml.",
"path": "service/src/main/resources/application.yaml",
"line": 14,
"fingerprint": "19d0004f552705be"
}
],
"dropped": 0
}Collected for
| Guardrail | Category | Inputs |
|---|---|---|
secrets/detector-ran | secrets | none |
secrets/no-hardcoded-credentials | secrets | none |