Repository navigation
feat: route access on the code-graph worklist, boundary suggestions and checks, boundary claims in the hypothesis ledger - #50
Merged
Conversation
…t boundaries from them The worklist now asks codegraph_auth_boundary in the same graph process as the attack surface and the routes, as an optional call: a graph that does not publish it still yields the worklist, unchanged. Each entry carries the level of every route the graph classified (public, authenticated, elevated, or unknown with its reason), with the basis and the guard text it rests on. Levels are carried only under the classified verdict; any other verdict withholds them and carries the graph's note, and unknown always renders as unknown, never as public. The annotate prompt's worklist block shows the level beside each route and adds a 'Boundaries the graph suggests' section for the rows it shows: a caller boundary per classified route, naming the cited guard as what enforces it, and a data, process, egress or filesystem boundary per sink class the handler reaches, anchored at the example sink. Without a graph the prompt is byte-identical, as before.
'guardlink validate --code-graph' and guardlink_validate with code_graph compare each @boundary with what the graph measured, as warnings: - boundary-access-contradicted: the description (or an @assumes on the inner asset) states a login or an admin check, and a route on the inner side is classified below it; - boundary-access-unknown: the same claim where the graph could not decide a route's level, reported unknown with its reason, never as public; - boundary-missing: a route handler reaches a classified sink and no asset its file names declares a boundary to the outside; - boundary-unused: no declared flow crosses the boundary and no entry point the graph recognises reaches either side. The outer side is inferred as 'guardlink paths' infers entries: an undeclared endpoint, or a declared External.* asset. A boundary between two inside assets has no known direction, so the access checks skip it. A route belongs to an asset when its handler's file names the asset. The access checks need the graph's classified verdict; the others need only a usable worklist. Without the flag nothing is consulted and the output is unchanged. None of this reaches the SARIF export.
…cted A @boundary is a claim a probe can test from its outer side. The hypothesis ledger now records that outcome against the boundary's claim key: - 'guardlink hypothesis contradict|support <target>' records it, with evidence; a contradiction is held to the confirmation's bar. A target is the claim key, the #id, or the file:line of the @boundary. - 'guardlink hypothesis boundaries [--json]' lists every boundary as unverified, supported, contradicted or retest (guardlink.boundary-claims/v1). - Outcomes expire with the code as exposure outcomes do: supported lapses to unverified, contradicted asks for a retest. - 'confirm --from-scan' records a finding whose claim key names a boundary as that boundary's outcome (it used to report it stale), and reads boundary_claim_key / boundaryClaimKey beside an exposure's key, recording both. A boundary key alone never falls to the coarse exposure joins. 'contradicted' unless the finding says boundary_outcome: supported. Boundary and exposure outcomes share the entries list; the outcome says which kind an entry is, and the reader refuses an entry that mixes them. A boundary outcome never moves an exposure's state and nothing is written to source for one. A ledger holding one reads as corrupt to an older GuardLink, which refuses to write it rather than dropping the entry. Also documents the worklist access levels, boundary suggestions and 'validate --code-graph' in the README, the reference and the changelog.
…e after the boundary-check flow
The boundary checks add one flow, #codegraph -> #cli, which draws one more
edge. Rendered both diagrams with mermaid@11 under the dashboard's initialize
options at a confirmed 1440x900 viewport and counted the SVG: filtered
{nodes:34, links:89, clusters:8}, whole model {nodes:48, links:180}. Chrome
and measureLegibility agree on both pairs.
Animesh-Sri-bugb
force-pushed
the
fm/fx-guardlink-worklist-boundaries
branch
from
October 1, 2026 19:29
5f23550 to
949319f
Compare
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.
Rebased onto
mainafter #49 (SARIF enrichment) merged, so the diff below is only this PR's five commits. The two PRs touched some of the same files; the combined tree is what was tested.What
A
@boundaryclaims that trust changes between two sides, and its description says what holds the line. Until now nothing checked one. This PR has the optional code graph inform boundaries and check them, and lets the hypothesis ledger record the result of testing one.Route access levels on the worklist.
loadWorklistasks the code graph'scodegraph_auth_boundaryin the same process as the attack surface and the routes. The query is an optional call: a server that does not publish it still yields the worklist. Each entry gainsaccess[](levelpublic | authenticated | elevated | unknown, basis,unknown_reason, a summary of the evidence, the guard text with itsfile:line, and caveats). The worklist gainsauth_verdict,access_withheld_reasonandresolver_density. Levels are carried only under theclassifiedverdict. Any other verdict withholds them and the prompt says why, andunknownis never rendered as public.guardlink_worklistreturns these fields too.Boundary suggestions in the annotate prompt. For the rows it shows, the worklist block adds Boundaries the graph suggests:
data_layer,exec,network,filesystem), anchored at the example sink.Boundary checks.
guardlink validate --code-graph, andguardlink_validatewithcode_graph: true, emit four warnings:boundary-access-contradicted: a boundary, or an@assumeson its inner asset, says "authenticated" or "admin", and a route on the inner side is classified below that;boundary-access-unknown: the same claim, on a route the graph could not classify;boundary-missing: a route handler reaches a sink, and no asset its file names has a boundary to the outside;boundary-unused: no declared flow crosses the boundary, and no recognised entry point reaches either side.The outer side is inferred the way
guardlink pathsinfers entries: an undeclared endpoint, or anExternal.*asset. A boundary between two inside assets has no known direction, so the access checks skip it.Boundary claims in the hypothesis ledger.
guardlink hypothesis contradict|support <key | #id | file:line> --evidence …records an outcome. A contradiction is held to the same evidence bar as a confirmation.guardlink hypothesis boundaries [--json]lists every boundary's state (guardlink.boundary-claims/v1): unverified, supported, contradicted, or retest. Outcomes expire with the code, the way exposure outcomes do.confirm --from-scanrecords a finding whose claim key names a@boundaryas that boundary's outcome. It also readsboundary_claim_key/boundaryClaimKeyplaced beside an exposure's key, and then records both.Compatibility
validatewithout the flag andguardlink_validatewithoutcode_graphare unchanged.codegraph_auth_boundary: the rendered worklist is byte-identical. A test asserts this, for MCP andbravosstand-ins alike.guardlink.hypotheses/v1, and boundary and exposure outcomes shareentries, told apart by their outcome;--from-scan, a finding stamped with a boundary's claim key used to be reported stale. It is now recorded as that boundary's outcome.Review notes
statedAccessreads the claimed level from the description's words (login/authenticated/API key → authenticated; admin/role/permission → elevated). It skips text that says the line is open ("unauthenticated", "anonymous", "no login"). It is a heuristic, so every warning quotes the word it matched.Verification (local; CI did not run)
main(which now includes feat(sarif): carry each finding's declared context, additively; rewrite SPEC §6 #49) plus this branch:npm testreports 114 files, 2043 passed, 1 skipped. The skipped test is the live-graph test, which needs real graph tooling.npm run lintandtsc --noEmitare clean.tests/boundary-check.test.ts(12): every check, the verdict gates, the no-graph path, andvalidate --code-graphthrough the real CLI against a stand-incodegraph-mcp;tests/hypothesis-boundary.test.ts(13): resolving targets, evidence rules, expiry, reader validation, every scan-import route, and the CLI round trip;tests/codegraph.test.ts: access levels, withheld verdicts, the unchanged worklist when the query is unsupported, suggestions, both transports, and the MCP surfaces.guardlink validate . --artifacts: current and drawable.guardlink diff origin/main: +4 flows. Two exposures moved down a few lines because the doc-block grew; the risk is unchanged.tests/query-views.test.tswas re-measured in Chrome, not bumped. I rendered both diagrams with mermaid@11 under the dashboard's options at 1440×900: filtered {34 nodes, 89 links, 8 clusters}, whole model {48 nodes, 180 links}. The counter agrees.codegraph_auth_boundary's answer (verdict,verdict_note,resolver_density, androutes[].accesswithlevel,basis,unknown_reason,summary,evidence[]andcaveats[]).