|
| 1 | +--- |
| 2 | +title: "Attesting SARIF scans" |
| 3 | +description: "Attest the output of any SARIF v2.1.0 security scanner — Checkov, Trivy, Semgrep, Snyk, CodeQL — to a Kosli trail, with optional rego-driven compliance." |
| 4 | +--- |
| 5 | + |
| 6 | +In this tutorial you'll attest SARIF scan results to a Kosli trail using the `system:sarif` attestation type. You'll work through three modes, each adding one piece on top of the last: |
| 7 | + |
| 8 | +1. **Raw fact** — attest the SARIF file, no policy. Records what the scan found. |
| 9 | +2. **Rego policy, two-step** — run a [rego](https://www.openpolicyagent.org/docs/latest/policy-language/) policy with `kosli evaluate`, then attest the SARIF file alongside the policy verdict. |
| 10 | +3. **Rego policy, one-step** — pass the `.rego` file directly to `kosli attest`, which runs the policy inline. |
| 11 | + |
| 12 | +By the end you'll have one Kosli trail with three SARIF attestations and a clear sense of which mode fits which situation. |
| 13 | + |
| 14 | +<Note> |
| 15 | +This tutorial uses [Checkov](https://www.checkov.io/) to scan a Terraform file, because Checkov is the most common SARIF-producing scanner customers ask us about. Every command after the scan step is identical for any other SARIF v2.1.0 producer — Trivy, Semgrep, Snyk, CodeQL, Bandit. Swap the scanner; the kosli commands don't change. |
| 16 | +</Note> |
| 17 | + |
| 18 | +<Steps> |
| 19 | + |
| 20 | +<Step title="Prerequisites"> |
| 21 | + |
| 22 | +To follow this tutorial you'll need: |
| 23 | + |
| 24 | +* [Install Checkov](https://www.checkov.io/2.Basics/Installing%20Checkov.html) — `pip install checkov` |
| 25 | +* [Install Kosli CLI](/getting_started/install) |
| 26 | +* [Get a Kosli API token](/getting_started/service-accounts) |
| 27 | + |
| 28 | +Set the environment variables: |
| 29 | + |
| 30 | +```shell |
| 31 | +export KOSLI_ORG=<your-personal-kosli-org-name> |
| 32 | +export KOSLI_API_TOKEN=<your-api-token> |
| 33 | +``` |
| 34 | + |
| 35 | +</Step> |
| 36 | + |
| 37 | +<Step title="Create a flow and trail"> |
| 38 | + |
| 39 | +Create a flow with a `system:sarif` slot in its template: |
| 40 | + |
| 41 | +```yaml flow_template.yml |
| 42 | +version: 1 |
| 43 | +trail: |
| 44 | + attestations: |
| 45 | + - name: security-scan |
| 46 | + type: system:sarif |
| 47 | +``` |
| 48 | +
|
| 49 | +```shell |
| 50 | +kosli create flow sarif-demo \ |
| 51 | + --template-file flow_template.yml \ |
| 52 | + --description "Demo of SARIF attestation" |
| 53 | +``` |
| 54 | + |
| 55 | +You should see: `flow 'sarif-demo' was created`. |
| 56 | + |
| 57 | +Begin a trail to attach attestations to: |
| 58 | + |
| 59 | +```shell |
| 60 | +kosli begin trail test-1 --flow sarif-demo |
| 61 | +``` |
| 62 | + |
| 63 | +You should see: `trail 'test-1' was begun`. |
| 64 | + |
| 65 | +</Step> |
| 66 | + |
| 67 | +<Step title="Get a SARIF file to attest"> |
| 68 | + |
| 69 | +Create a small Terraform file with a known misconfiguration so Checkov has something to find: |
| 70 | + |
| 71 | +```hcl main.tf |
| 72 | +resource "aws_s3_bucket" "example" { |
| 73 | + bucket = "my-tutorial-bucket" |
| 74 | + # Intentional findings: no versioning, no encryption, no logging. |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +Run Checkov to produce a SARIF file: |
| 79 | + |
| 80 | +```shell |
| 81 | +checkov -d . -o sarif --output-file-path . 2>/dev/null |
| 82 | +``` |
| 83 | + |
| 84 | +This writes `results_sarif.sarif` in the current directory. Inspect the first few lines if you'd like to see the SARIF structure — Kosli will parse it automatically. |
| 85 | + |
| 86 | +</Step> |
| 87 | + |
| 88 | +<Step title="Mode 1: Attest the SARIF file as a raw fact"> |
| 89 | + |
| 90 | +The simplest mode: attest the scan results with no compliance policy. The attestation records what Checkov found; compliance is structural only ("a SARIF file was attached"). |
| 91 | + |
| 92 | +```shell |
| 93 | +kosli attest system sarif \ |
| 94 | + --flow sarif-demo \ |
| 95 | + --trail test-1 \ |
| 96 | + --name security-scan \ |
| 97 | + --scan-results results_sarif.sarif |
| 98 | +``` |
| 99 | + |
| 100 | +You should see: `sarif attestation 'security-scan' is reported to trail: test-1`. |
| 101 | + |
| 102 | +Open the trail in the Kosli app (`https://app.kosli.com/<your-org>/flows/sarif-demo/trails/test-1`). The attestation tile shows: |
| 103 | + |
| 104 | +- Tool name and version — pulled dynamically from the SARIF file (so it reads "Checkov 3.2.x", not "Snyk") |
| 105 | +- High / medium / low finding counts |
| 106 | +- Expandable findings table with rule ID, message, file, and line for each finding |
| 107 | +- Compliance: ✓ compliant ("no extra policy evaluated — compliance based on system type JQ rules only") |
| 108 | + |
| 109 | +</Step> |
| 110 | + |
| 111 | +<Step title="Mode 2: Layer in a rego policy (two-step)"> |
| 112 | + |
| 113 | +The raw-fact mode is useful but customers usually want compliance gated on the findings — e.g. *"fail if any finding has severity 'error'"* (Checkov's word for high). For that we layer a rego policy on top. |
| 114 | + |
| 115 | +Create a policy file: |
| 116 | + |
| 117 | +```rego checkov.rego |
| 118 | +package policy |
| 119 | +
|
| 120 | +import rego.v1 |
| 121 | +
|
| 122 | +default allow = false |
| 123 | +
|
| 124 | +violations contains msg if { |
| 125 | + some result in input.runs[0].results |
| 126 | + result.level == "error" |
| 127 | + msg := sprintf("%s in %s:%d", [ |
| 128 | + result.ruleId, |
| 129 | + result.locations[0].physicalLocation.artifactLocation.uri, |
| 130 | + result.locations[0].physicalLocation.region.startLine, |
| 131 | + ]) |
| 132 | +} |
| 133 | +
|
| 134 | +allow if { |
| 135 | + count(violations) == 0 |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +The policy denies the attestation if any Checkov finding has severity `error` (high). Otherwise it allows. |
| 140 | + |
| 141 | +Run the policy against the SARIF file with `kosli evaluate input`: |
| 142 | + |
| 143 | +```shell |
| 144 | +kosli evaluate input \ |
| 145 | + --input-file results_sarif.sarif \ |
| 146 | + --policy checkov.rego \ |
| 147 | + --output json > eval.json |
| 148 | +``` |
| 149 | + |
| 150 | +This writes a JSON file containing `{ "allow": bool, "violations": [...] }`. Take a look — that's the verdict you're about to attest. |
| 151 | + |
| 152 | +Now attest the SARIF file together with the policy verdict and the policy file itself: |
| 153 | + |
| 154 | +```shell |
| 155 | +kosli attest system sarif \ |
| 156 | + --flow sarif-demo \ |
| 157 | + --trail test-1 \ |
| 158 | + --name security-scan-with-policy \ |
| 159 | + --scan-results results_sarif.sarif \ |
| 160 | + --evaluation-result eval.json \ |
| 161 | + --policy checkov.rego |
| 162 | +``` |
| 163 | + |
| 164 | +The `--policy` flag is required whenever you pass `--evaluation-result` — the policy file is uploaded to the audit trail so anyone reading the attestation later can see which rules were applied. |
| 165 | + |
| 166 | +Open the trail again. The new tile now shows: |
| 167 | + |
| 168 | +- The same findings table as mode 1 |
| 169 | +- A "Policy evaluation" section: verdict badge (allow / deny), violations as readable bullets |
| 170 | +- An inline view of the rego policy content (always embedded — no extra click needed to see the rules) |
| 171 | +- A download link for the `.rego` file |
| 172 | + |
| 173 | +The compliance verdict now comes from your policy, not from the structural check. |
| 174 | + |
| 175 | +</Step> |
| 176 | + |
| 177 | +<Step title="Mode 3: Same outcome, one command"> |
| 178 | + |
| 179 | +Running `kosli evaluate` and `kosli attest system sarif` as two separate commands is fine for CI pipelines that already have an explicit evaluate step. For the common case — "I have a SARIF file and a rego policy, please attest both" — pass the `.rego` file directly to `attest`: |
| 180 | + |
| 181 | +```shell |
| 182 | +kosli attest system sarif \ |
| 183 | + --flow sarif-demo \ |
| 184 | + --trail test-1 \ |
| 185 | + --name security-scan-inline \ |
| 186 | + --scan-results results_sarif.sarif \ |
| 187 | + --policy checkov.rego |
| 188 | +``` |
| 189 | + |
| 190 | +The CLI runs `kosli evaluate input` for you, embeds the result, uploads the policy as an attachment, and posts the attestation in one call. The tile in the app looks identical to mode 2. |
| 191 | + |
| 192 | +<Note> |
| 193 | +If the policy fails to compile or rego execution errors, the CLI aborts the whole call — no half-attested state. Fix the policy and re-run. |
| 194 | +</Note> |
| 195 | + |
| 196 | +</Step> |
| 197 | + |
| 198 | +</Steps> |
| 199 | + |
| 200 | +## What you've accomplished |
| 201 | + |
| 202 | +You now have a Kosli trail with three SARIF attestations covering the three customer modes: |
| 203 | + |
| 204 | +- **`security-scan`** — facts only, no compliance policy |
| 205 | +- **`security-scan-with-policy`** — facts plus an explicit rego policy evaluation |
| 206 | +- **`security-scan-inline`** — same as above, posted in one CLI call |
| 207 | + |
| 208 | +You've also seen how the SARIF system type's tile renders in each mode, and where the policy file lives in the audit trail. |
| 209 | + |
| 210 | +## Next steps |
| 211 | + |
| 212 | +- **Browse other system types** in the [catalogue](/attestation_types/system/catalogue) — system types is the strategic direction; over time more scanners will get first-class attestation tiles. |
| 213 | +- **Read the SARIF type reference** for the full data shape and version history: [System type: SARIF](/attestation_types/system/sarif). |
| 214 | +- **Adapt to your scanner** — replace Checkov with Trivy, Semgrep, Snyk, CodeQL, or any other SARIF v2.1.0 producer. The `kosli attest system sarif` commands are unchanged. |
| 215 | +- **Migrate from the snyk type** — if you currently use `kosli attest snyk` (even with non-Snyk SARIF), see the migration note on the [SARIF type page](/attestation_types/system/sarif). |
| 216 | +- **Wire into CI** — add the same commands to your GitHub Actions pipeline using the [Kosli GitHub Action](/integrations/actions). |
0 commit comments