# SARIF Desk > Paste one SARIF 2.1.0 static-analysis log — the machine-readable report a code scanner emits — and > work it four ways in one sitting: call each finding real or not, plan the fixes in order, cut the > noise, write the release-gate report. A real SARIF reader runs free in the browser first, and the > model's answer is held to what that reader found. URL: https://sarif-desk.skillsafe.ai/ API tutorial: https://sarif-desk.skillsafe.ai/api.html Token management: https://sarif-desk.skillsafe.ai/tokens.html Model: `gpt-terra` (currently resolves to `gpt-5.6-terra`). Metered; signed-in users only for the AI lanes. The browser's own read of the log is free and needs no account. ## The work object One SARIF 2.1.0 log. Every tool that emits SARIF is in scope — CodeQL, Semgrep, Trivy, ESLint, Bandit, gitleaks, checkov, Snyk, Brakeman, tfsec — and so is a log that merges several of them, which is what a real GitHub Actions workflow usually uploads. The log is pasted or dropped as a file. It is never executed; it is parsed as data. ## What runs free in the browser A hand-written SARIF 2.1.0 reader. It is not a JSON viewer: it resolves the three things in the specification that decide what a result actually says, each of which different scanners lean on differently. 1. **Effective level.** The specification's precedence is `result.level`, then the rule's `defaultConfiguration.level`, then a fallback that depends on `result.kind` — a result whose kind is `pass` or `review` defaults to `none`, not to `warning`. CodeQL omits `result.level` almost everywhere and relies entirely on the rule default, so a reader that only looks at `result.level` grades a critical injection as unset. 2. **Message resolution.** A result's message may carry no text at all: `message.id` names an entry in the rule's `messageStrings`, and `message.arguments` fill its `{0}` slots. Reading only `message.text` loses the message on every tool that does this. 3. **Rule resolution.** A result's rule is reachable through `ruleId`, through `ruleIndex` into `tool.driver.rules`, or through `rule.index` with `rule.toolComponent` pointing at a tool extension rather than the driver. Miss the index forms and the rule's description, its `security-severity` and its help text all disappear. It also reads artifacts, invocations, `originalUriBaseIds`, `automationDetails`, `versionControlProvenance`, taxonomies, code flows, fingerprints, suppressions and `baselineState`, and flattens every run into one comparable list with a stable ref (`R-001`) per result. ### Twenty-nine deterministic checks (SD01–SD29) Facts about the log, not opinions about the code. A selection of the ones that matter most: - **SD03** a run with no `results` array — the tool did not report, which is not the same as reporting nothing. Treating it as a clean scan is the most common way a broken pipeline passes a gate. - **SD04** a result whose rule resolves through none of the three paths: no description, no help, no security severity. - **SD06** a result with no physical location — it cannot be annotated on a pull request, so nobody ever fixes it. - **SD08** absolute paths from the scanning machine, which no viewer can map onto a checkout. - **SD09** a `uriBaseId` the run never defines in `originalUriBaseIds`. - **SD12** a result graded `warning` only by the specification's fallback — the severity was invented by the reader, not by the tool. - **SD15/SD16** results with no fingerprint, which cannot be tracked between scans, so every reformat makes the whole report read as new. - **SD17/SD18** suppressions, and suppressions with no justification — indistinguishable from silencing a build. - **SD23** one rule at 40% or more of the whole report: usually a scoping problem, not that many separate defects. - **SD25** exact duplicate results, which merged reports and re-run matrices produce and which inflate every count a gate compares against. - **SD27/SD28** a run that did not complete, or scanner errors of its own. Coverage is incomplete by exactly that much. - **SD29** a live-looking credential embedded in the report's own snippet or message. A secret scanner's SARIF is itself a secret: it is in the artifact store, in the pull-request comment it produced, and in whatever chat it was pasted into. A **report-confidence score** rolls these up. It grades how much the *log* can be trusted to describe the code, which is a different question from what the code does. ### The digest — and why it exists Real SARIF logs are megabytes and mostly one rule repeated. Sending a prefix of one would send a fragment of a single rule's output, and the model would grade a repository it never saw. So the whole log is parsed locally and what is sent is a digest: - every total computed over **all** results, not over the sample; - the catalog of rules that produced results, with level, security-severity, precision and tags; - a stratified sample of results — worst band first, capped at three per rule so a nine-hundred-hit lint rule cannot crowd out one critical injection, with every rule represented at least once; - a coverage note that states the sample size against the true total, so the reply cannot present a sample as a census. The exact digest is readable and copyable in the page before anything is spent. ## The four lanes All four take the same log. `task` selects the lane. | `task` | What it answers | Verdicts | | --- | --- | --- | | `triage` | Which of these findings are real? | `mostly-real`, `mixed`, `mostly-noise` | | `remediate` | What gets fixed, in what order? | `plan-ready`, `plan-with-unknowns`, `cannot-plan` | | `tune` | Which rules are earning their noise? | `tunable`, `partially-tunable`, `already-tight` | | `report` | Does this ship? | `pass`, `pass-with-conditions`, `block` | - **triage** returns one call per sampled result: true positive, false positive, needs context, duplicate or suppress, with confidence, exploitability, reasoning and — when confidence is not high — the evidence that would settle it. - **remediate** returns an ordered plan whose steps name the refs they close, with a control, the specific change, an effort size, an owner and a verification that could actually fail. A credential rotation always comes before a code fix. `accepted_risks` is required. - **tune** returns a per-rule policy in your scanner's own syntax — CodeQL, Semgrep, ESLint, SonarQube, Trivy or prose — with the arithmetic behind each expected reduction. A rule carrying a `security-severity` or a `security` tag is never suppressed or disabled to reduce noise; it is scoped or downgraded, and what is given up is stated. - **report** returns the gate decision and the document that goes in the ticket, plus a plain-text release note. A blocker in the browser's own checks can force `block` on its own, because an untrustworthy report cannot clear a release. Handoffs are buttons: the triage lane's confirmed findings pre-fill the remediation lane; its false positives pre-fill the noise lane; either pre-fills the gate report. The log itself never changes. ## How the answer is held to the file - **Reconciliation.** Every check the browser raised is sent with the run and must come back `confirmed`, `cleared` with a reason, or `not-assessed`. A check the reply never addressed is shown as unanswered, and a check the reply invented is shown as noise. - **Ref audit.** Every ref the reply quotes is resolved back against the parsed log. A ref that is not in the report is labelled fabricated rather than drawn as a finding. A ref that exists but was not in the digest is labelled as a guess. - **Counts come from the browser.** The hit count beside each rule in the noise policy is the one the reader measured, not one the model was asked to remember. ## Exports Copy-all and a `.md` download, plus the artifacts that matter in this domain: the flattened results as CSV with resolved levels and bands; the rule catalog as CSV; the report checks as CSV; per-lane CSVs for the calls, the plan and the rule policy; and a **valid reduced `.sarif` log** with suppressed and duplicate results dropped and the rule metadata carried across — the artifact you feed back into the pipeline, because a triage that ends in prose changes nothing in CI. ## History Runs are stored in a declared collection (`sruns`) on the user's account, with `localStorage` as a mirror for instant paint. Semantic search over past runs embeds the title, the tool name and the summary, so "the CodeQL run with the injection in billing" finds it. The loop this exists for is real: triage Tuesday's scan, fix four things, read Thursday's scan next to what you called last time. ## What this app does not do - It does not run a scanner. It reads a report a scanner already produced. - It does not read your source. Only what the report embeds — snippets, paths, line numbers — is available, and the lanes say so rather than pretending otherwise. - It does not publish, upload or share anything. The parse, the checks and the digest happen in the browser; only the digest is sent, and only when you run a lane. ## Sources A derived work built from four skills, credited in full: - [@trailofbits/sarif-parsing](https://skillsafe.ai/skill/@trailofbits/sarif-parsing/) — reading a SARIF log and saying what is in it. - [@ghostsecurity/ghost-validate](https://skillsafe.ai/skill/@ghostsecurity/ghost-validate/) — deciding whether a reported finding is real. - [@wshobson/threat-mitigation-mapping](https://skillsafe.ai/skill/@wshobson/threat-mitigation-mapping/) — mapping findings onto controls and a remediation order. - [@mohitmishra786/static-analysis](https://skillsafe.ai/skill/@mohitmishra786/static-analysis/) — triaging noisy analyser output and tuning what it reports. Hosted on SkillSafe. Built with the SkillSafe Apps SDK.