Repository navigation
feat(reach): export reach_analysis in the model JSON and agent-reach results in the pentest SARIF - #54
Merged
Conversation
…results in the pentest SARIF The reach verbs (@agentS, @reaches, @effects, @gates) were in the parsed model, but the two joins a consumer needs from them, can-minus-may and whether an effect is gated, were only reachable one lookup at a time, and SARIF carried none of it. Model JSON (SPEC §5.5). `guardlink parse`, `report --format json`, `.guardlink/model.json`, MCP `guardlink_parse` and `guardlink://model` now carry a derived `reach_analysis` block: `version: 1`, a `summary`, `unentitled_reaches` with each near miss and its reason, and every mutating effect with `gated`, the gates that cover it and the gates on its asset that do not. Every row names its claim by index into the arrays beside it and by claim key. The block is written only when the model declares a reach, effect or gate, so other models export the same bytes; the annotation hash never moves. Gating (SPEC §3.2.1). A gate covers an effect on its asset when it names no capability, or when every reach bound to the effect's code (same file and structure-layer anchor, i.e. one doc-block) is the capability it names. With no reach bound there, a scoped gate does not count (`capability-unknown`). New lookup form `ungated effects [for <asset>]`; `unentitled reaches` rows gain `claim_key`. SARIF (SPEC §6.8). The pentest profile appends one `guardlink/agent-reach` review result per reach and per non-read effect, after the boundary claims, with the actor, agent flag, capability, asset, effect, `gated` and gates in `properties`. The list is complete on purpose: §6.1 forbids any result derived from @entitles, and a gate suppresses nothing, so the unentitled and ungated subsets come from `reach_analysis`, joined on the claim key. The github profile is unchanged byte for byte; the three existing pentest fixtures gain the rule only. Also: `reaches`, `effects` and `gates` are canonically ordered in `.guardlink/model.json` like every other relation. The new support-desk fixture keeps its sources in one directory because the parser reads files in traversal order, which is not stable across directories. Verified: npm run build && npm test (119 files, 2191 passed, 1 skipped), npm run lint, guardlink validate . --artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
PR #53 added
@agents,@reaches,@effectsand@gatesto the parsed model, and listed SARIF and consumer work as follow-ups. This PR is that follow-up. It puts what the verbs declare into the outputs other tools read: the model JSON,guardlink_lookupand the SARIFpentestprofile. All three key each claim by the same claim key, so a consumer can join them.1.
reach_analysisin the model JSON (new SPEC §5.5)guardlink parse,report --format json,.guardlink/model.json, MCPguardlink_parseand theguardlink://modelresource now carry a derived block:indexinto the array next to it, andclaim_key.versionis bumped when a key is renamed or removed, or when a rule changes what a row means. Adding a key does not bump it.2. Gated and ungated effects (SPEC §3.2.1)
A gate covers an effect on its asset when either of these holds:
When no reach is bound to the effect's code, nothing in the model says which capability reaches the effect, so a capability-scoped gate does not cover it. Its near miss reports
capability-unknown. The other blocker isother-capability. Both rules err toward reporting the effect, the same way the can-minus-may join does.Lookup gains a new form,
ungated effects [for <asset>], andunentitled reachesrows now carryclaim_key.3.
guardlink/agent-reachin thepentestSARIF profile (SPEC §6.8)The profile now has one more rule, appended after
guardlink/boundary-claim. It adds onekind: "review",level: "none"result for each@agents/@reachesclaim, then one for each@effectsthat is notread, appended after the boundary claims.propertiescarries:subject(reachoreffect)gated,gatesandgate_near_missesrelatedLocationspoints at the gates and at the bound claims.Please review this choice: the list is complete, not only unentitled reaches and ungated effects. Two existing rules force that:
@entitles, andtests/actor-entitlement.test.tspins this for the pentest profile too. If only unentitled reaches got results, adding an@entitleswould remove a result, and that is the mechanism §6.1 rules out.So an entitled reach gets a result just like an unentitled one, and nothing on that result says whether it is entitled. A gated effect also stays in the list, with its gates, because the gate is what a probe should try to get past. A consumer that wants the unentitled or ungated subset joins on
claimKeytoreach_analysis. A new test checks that the pentest export is byte-identical with and without entitlements on the reach fixture.The
githubprofile is unchanged, byte for byte. The three existing pentest fixtures change only by the added rule.Also
.guardlink/model.jsonnow writesreaches,effectsandgatesin canonical order, like every other relation. feat(reach): declare what an embedded agent can reach with @agents, @reaches, @effects and @gates #53 left them in scan order.guardlink sarif --profile pentestsummary line reports the agent-reach count when it is non-zero.docs/GUARDLINK_REFERENCE.md(new section Reach in the exports), the lookup tool description, the instruction template's lookup list and CHANGELOG are updated.Tests
tests/fixtures/support-desk/, with its pentest export pinned intests/fixtures/sarif-pentest/support-desk.sarif. It has five unentitled reaches, covering each near-miss reason, and seven mutating effects: one gated by a scoped gate, one gated by an unscoped gate, and five ungated, one of them with acapability-unknownnear miss.FIXTURE.mdtabulates them.tests/reach-export.test.ts(new, 13 tests) covers:other-capability;parse,report --format json,model.jsonindexed into the ordered arrays, MCPguardlink_parse, andlookup.tests/sarif-pentest.test.tsgains an agent-reach block covering:reach_analysis;gatedbut never the result count;One note for reviewers: the parser reads files in fast-glob's traversal order. That order is not stable across directories, so a multi-directory project's array order, and with it its SARIF result order, can differ between runs. I put the new fixture's sources in one directory and did not change the parser, because changing it would move result order everywhere.
Verification
npm run build && npm test: 119 files, 2191 passed, 1 skippednpm run lint: cleannode dist/cli/index.js validate . --artifacts: artifacts current and drawable, validation passed with the same 16 unmitigated exposures as beforeguardlink diff HEAD~2: one added flow (ThreatModel → #sarif via agentReachResults), risk unchangedThe second commit regenerates the synced instruction files and graph artifacts.