Skip to content

Guardrails / Collectors

Security scan findings ​

Findings from whatever scanner the build already ran, read from the report it left behind and normalized into one shape, whether the tool was a SAST, an SCA, a secret detector or an infrastructure scanner.

Facts keyscan
Versionv1
Scriptscan.py
Timeout60 seconds

Inputs ​

InputDescriptionDefaultEnvironment
reportsComma separated globs matched against every file in the working directory and against bare file names, naming the scanner reports to read.*.sarif,*.sarif.json,trivy*.json,grype*.json,osv*.json,semgrep*.json,snyk*.jsonGUARDRAIL_INPUT_REPORTS
maxFindingsMaximum number of findings to carry in the facts. The counts still include findings that are dropped.200GUARDRAIL_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:

json
{
  "collect": ["scan"]
}
python
scan = guardrail.facts("scan")

Facts ​

These are the fields of the document scan collects. In a path, [] means each entry of the list before it, and [path] means a key of the object before it.

FactMeaning
sourcereport when a scanner report was read, none when there was none.
reasonWhy nothing was read, present only when source is none.
reportsEvery report that was read, in path order.
reports[].pathPath of the report, relative to the directory the CLI runs in.
reports[].modifiedWhen the report was last written, in ISO 8601, so you can spot a stale report.
reports[].formatFormat it was read as: sarif, trivy, grype or osv.
reports[].producer.nameScanner that wrote it, as the report names itself, or null when it names none.
reports[].producer.versionVersion of that scanner, or null when the report carries none.
reports[].findingsHow many findings that report carried.
counts.totalFindings across every report that was read.
counts.criticalFindings the scanner rated critical.
counts.highFindings rated high.
counts.mediumFindings rated medium.
counts.lowFindings rated low.
counts.infoFindings rated informational.
counts.unknownFindings the scanner gave no severity, or a severity that is not recognised.
kinds.sastFindings about the repository's own code.
kinds.scaFindings about a dependency.
kinds.secretFindings about a credential in the tree.
kinds.iacFindings about infrastructure code.
kinds.containerFindings about a container image.
kinds.unknownFindings that could not be classified.
findingsThe findings themselves, up to maxFindings.
findings[].idRule or vulnerability identifier the scanner gave it.
findings[].severitycritical, high, medium, low, info or unknown.
findings[].messageWhat the scanner said, truncated to 500 characters.
findings[].pathFile it was found in, relative to the working directory, or null.
findings[].lineLine it was found on, or null.
findings[].kindsast, sca, secret, iac, container or unknown.
findings[].package.nameDependency it is about, present only for a finding about one.
findings[].package.versionVersion of that dependency.
findings[].identifiersEvery CVE, GHSA or other identifier the scanner attached.
findings[].fixedInVersion that fixes it, or null when the scanner names none.
findings[].fingerprintStable identity of the finding across runs, so the same finding is recognisable in a later build.
droppedFindings 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 scan collector gathers from an example project:

json
{
  "source": "report",
  "reports": [
    {
      "path": "security-scan.sarif",
      "modified": "2026-03-04T10:15:00Z",
      "format": "sarif",
      "producer": {
        "name": "Semgrep OSS",
        "version": "1.86.0"
      },
      "findings": 1
    },
    {
      "path": "trivy.json",
      "modified": "2026-03-04T10:15:00Z",
      "format": "trivy",
      "producer": {
        "name": "trivy",
        "version": null
      },
      "findings": 1
    }
  ],
  "counts": {
    "critical": 1,
    "high": 1,
    "medium": 0,
    "low": 0,
    "info": 0,
    "unknown": 0,
    "total": 2
  },
  "kinds": {
    "sast": 1,
    "sca": 1,
    "secret": 0,
    "iac": 0,
    "container": 0,
    "unknown": 0
  },
  "findings": [
    {
      "id": "kotlin.lang.security.insecure-hostname-verifier",
      "severity": "high",
      "message": "Hostname verification is disabled, so any certificate is accepted.",
      "path": "service/src/main/kotlin/Queue.kt",
      "line": 1,
      "kind": "sast",
      "package": null,
      "identifiers": [],
      "fixedIn": null,
      "fingerprint": "262b267f2df80c2e"
    },
    {
      "id": "CVE-2021-44228",
      "severity": "critical",
      "message": "log4j-core: remote code execution via JNDI lookup",
      "path": "service/build.gradle.kts",
      "line": null,
      "kind": "sca",
      "package": {
        "name": "org.apache.logging.log4j:log4j-core",
        "version": "2.14.1"
      },
      "identifiers": [
        "CVE-2021-44228"
      ],
      "fixedIn": "2.15.0",
      "fingerprint": "c1d7cd4ef6d2fed9"
    }
  ],
  "dropped": 0
}

Collected for ​

GuardrailCategoryInputs
security/finding-budgetsecuritynone
security/fixable-vulnerabilitiessecuritynone
security/no-high-findingssecuritynone
security/scan-results-presentsecuritynone
supply-chain/no-critical-vulnerabilitiessupply-chainnone

All collectors

Buildnote Limited
Registered in England and Wales, Reg: 16140412