Repository navigation
Workflow policy: pull_request reachability rules (supersedes #682) and a ParseScalar-exact guard check - #686
Conversation
…ar-exact guard check Fold the assertions of #682 that tests/test_workflow_policy.py did not yet hold into it as rules on the parsed workflows, each with planted negative controls and accepted variants: - pull-request-environment: no `environment:` on a job that a pull_request-family event reaches (#682's event set; local ./ and $/ reusable-workflow calls followed transitively; then a workflow_call trigger alone), whatever its if: - pull-request-remote-workflow: such a job calls no reusable workflow except through ./.github/workflows/ or $/.github/workflows/ - comment-event-secret: no secret other than GITHUB_TOKEN in a workflow that issue_comment, pull_request_review or pull_request_review_comment triggers (the pull-request-secret reference set: decoded values, raw text, inherit) Repair round 2, the P2 residuals on #681: excludes_pull_request now follows actions/runner's TemplateReader.ParseScalar (d7bc179baf11). Literal whitespace around a whole expression, including the line break a `|` block scalar keeps, no longer counts as excluding pull requests, and a `}}` inside a string literal no longer refuses a valid guard. Qualify the Dependabot token sentence in docs/decisions/2026-09-22-github-automation-closure.md (read-only by default; it can be raised) and record the change in docs/decisions/2026-10-04-ci-least-privilege.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecords Hot-file protocol (docs/lanes.md): main's manifests/evidence.json with the three files this branch changed re-registered through host_receipts.register_file; component_matrix.py --write and new_host_grand_list.py --write changed nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aeb5ba2597
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…HUB_TOKEN by index Cross-family review of #686 at aeb5ba2 (one repair round): - P1: secret_references() found expressions with the regex \$\{\{(.*?)\}\}, which stops at a `}}` inside a string literal, so `${{ format('a}}b{0}', toJSON(secrets)) }}` and the same with secrets['NPM_TOKEN'] passed comment-event-secret for all three comment and review events, and pull-request-secret on pull_request. It now reads each expression as expressions() delimits it, with expression_end(), the actions/runner TemplateReader.ParseScalar scan (d7bc179baf11); an unclosed expression is read to the end. - P2: secrets['GITHUB_TOKEN'] was a violation while secrets.GITHUB_TOKEN passed. Index access by one string literal (SECRET_INDEX, single or double quotes) now names the secret it indexes, so the token passes in both forms; the whole context and an index that is not one literal stay violations. New planted controls: the reviewer's env form under issue_comment, pull_request_review and pull_request_review_comment, an indexed secret after a quoted `}}` in a called workflow's secrets mapping, a computed index, and the env form under pull_request. New accepted variants: the token by single- and double-quoted index. The decision record's section gains the review round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… three files Hot-file protocol, merge path (docs/lanes.md): main moved during the review round and its #688 edits docs/decisions/2026-09-22-github-automation-closure.md (30 lines at 465; this branch's sentence, now at 1653, merged cleanly) and .github/workflows/security-scan.yml. manifests/evidence.json is main's copy with tests/test_workflow_policy.py and the two decision records re-registered through host_receipts.register_file; component_matrix.py --write and new_host_grand_list.py --write changed nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude session native-agent-stack-5f: landing at head Observed main Required checks at this head: 8 pass . Unresolved review threads: 0. |
|
Claude session native-agent-stack-5f: post-merge observation. Landed as |
…ds branch (hot-file protocol: main's registry; PR rows re-registered last) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y (387 pieces, 80 not wired, 39 own entry) The key under tui.model_availability_nux is one more not-wired piece (client state, not configuration). Recounted on the tree merged with main 54eb892 (#674, #683, #686) by the GPT-6.1 Sol worker; reviewed by the coordinator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… row, held status, TUI notice key (#687) * Codex dated holds (R6): one pin row with a host-agnostic 0.159.3 hold until 2026-11-04 - adoption/pins-linux-x86_64.json: the codex row carries holds[] with one entry, 0.159.3 until 2026-11-04 (X18, the #626 landing exception). Wrapper URL and SHA-256 are the row's values before f77a35e (from 85543ef, #580), re-verified against a registry download; the linux-x64 platform_dependency (row shape) is first recorded here from the npm registry. - scripts/adoption_status.py --pinned-versions: a probe naming a hold's version before its until date (UTC) is held (hold_* fields, summary "held", "held until <date> (<reason>)"), not drift; on and after until it is mismatched with the expiry stated. Malformed holds are skipped. The default run, the exit code and the existing keys are unchanged. - tools/adoption/apply_codex_lane.py still refuses any codex but CODEX_VERSION; the refusal now names a matching hold and its until date. - tests/test_pin_holds.py: every hold complete, until a real date after today (UTC) and at most 90 days away, at most one hold per row (the OSV ignoreUntil precedent), with mutants. - adoption/templates/codex.config.template.toml: "gpt-6.1-sol" = 4 under [tui.model_availability_nux], cited from openai/codex rust-v0.160.0 (MODEL_AVAILABILITY_NUX_MAX_SHOW_COUNT = 4). - docs/decisions/2026-10-04-codex-dated-holds.md: design, alternatives, precedent, overturn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Codex dated holds: stack.json reference and evidence registry (hot-file protocol, last commit) manifests/stack.json's codex row carries no install data, so its freshness refers to the pins row's dated hold instead of copying it. manifests/evidence.json takes main's copy (6af8e55) and re-registers this branch's changed files plus its new test and decision record, as #626 did; component_matrix.py and new_host_grand_list.py --write reproduced their outputs unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * New-WSL client-config record: recount after the gpt-6.1-sol notice key (387 pieces, 80 not wired, 39 own entry) The key under tui.model_availability_nux is one more not-wired piece (client state, not configuration). Recounted on the tree merged with main 54eb892 (#674, #683, #686) by the GPT-6.1 Sol worker; reviewed by the coordinator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Registry: re-register the PR-owned files after the recount (last commit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hot-file protocol: reset manifests/evidence.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reapply this branch's manifests/evidence.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * adoption_status: one hold schema for run time and the pins test (cross-family P1) pin_holds() now keeps only holds that hold_schema_problems() accepts. The same validator is imported by tests/test_pin_holds.py. It requires the wrapper url and sha256, and the platform package's shape, version, url, digest and binary check when the pin has one. So a hold with malformed install metadata is ignored, and the version is reported as drift instead of held. Failing-first: the three missing-field cases failed before the fix; 16 malformed-metadata cases pass after it. Built by the GPT-6.1 Sol worker; reviewed by the coordinator (Claude Opus). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Registry: re-register the fix's files (last commit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Hot-file protocol: reset manifests/evidence.json manifests/stack.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Reapply this branch's manifests/evidence.json manifests/stack.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Scout <scout@local> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Scope
6e166809; to be closed after this merges). It folds the Workflow hardening: no pull-request job reads a secret or holds a new write scope #682 assertions that CI least privilege: permissions {} by default, cache-mode none on pull requests, workflow policy tripwire #681'stests/test_workflow_policy.pydid not yet hold into that module as three rules on the parsed workflows, each with planted negative controls and accepted variants. It also fixes the coordinator's two P2 residuals on CI least privilege: permissions {} by default, cache-mode none on pull requests, workflow policy tripwire #681'sexcludes_pull_requestand qualifies the Dependabot token wording.6af8e55bd9e8f51aaafbf0304e510c11aaa71a6a(origin/main, CI least privilege: permissions {} by default, cache-mode none on pull requests, workflow policy tripwire #681). Head:33276786006a664b3c309b7d626ade654cec76ca, which mergesorigin/mainatb629b5b4(Retire the WSL retrieval lock as package-lock.json.frozen (Dependabot alert 17) #688, New-WSL: Codex 0.160.0 profile pins, main-checkout Codex trust grant, Opus 5.5 model (follow-up to #626) #683) through the hot-file protocol's merge path, after one review repair round (see "Cross-family review ofaeb5ba25" below).lane:foundationtests/test_workflow_policy.py;docs/decisions/2026-10-04-ci-least-privilege.md(new dated section);docs/decisions/2026-09-22-github-automation-closure.md(one sentence qualified);manifests/evidence.json(the three rows re-registered, in the last commit only, perdocs/lanes.md). No workflow changes.The three rules (in
RULES;check_directory()now reads the directory as a whole):pull-request-environmentenvironment:on a job that a pull_request-family event reaches, whatever itsif:environment-on-a-pull-request-job,environment-behind-an-if-guard,environment-on-a-comment-job,environment-in-a-called-workflow(two files: apull_requestcaller and aworkflow_callcallee)environment-off-the-pull-request-family(push only)pull-request-remote-workflow./.github/workflows/{file}or$/.github/workflows/{file}remote-reusable-workflow(pinned by SHA, so zizmor'sunpinned-usespasses it),remote-call-in-a-reusable-workflowremote-call-off-the-pull-request-family,local-reusable-workflow-callandsame-commit-call-with-the-dollar-form(two files each)comment-event-secretGITHUB_TOKEN(named,secrets: inheritor the whole context; decoded values, then the raw text, aspull-request-secretreads them) in a workflow thatissue_comment,pull_request_revieworpull_request_review_commenttriggersissue-comment-secret,review-secrets-inherit,review-comment-secret-behind-an-escape(secrets); from the review round:issue-comment-whole-context-after-a-quoted-marker,review-whole-context-after-a-quoted-marker,review-comment-whole-context-after-a-quoted-marker,comment-call-passing-an-indexed-secret-after-a-quoted-marker,comment-secret-by-a-computed-indexcomment-workflow-with-the-job-token; from the review round:comment-workflow-with-the-job-token-by-index,comment-workflow-with-the-job-token-by-a-double-quoted-indexReachability (
reachable()). The pull_request family is #682's event set:pull_request,pull_request_target,pull_request_review,pull_request_review_comment,issue_commentandworkflow_run. A workflow is reached by one of those triggers, and then, as #682 computed it, through every./or$/call that a reached job makes, transitively and whatever the callee's own triggers. After that, aworkflow_calltrigger alone counts, as #681'sPULL_REQUEST_EVENTSalready counts it. The reason is thatadoption/scaffold/.github/workflows/sota-sources.yml.templatecallssota-sources-gate.ymlfrom other repositories'pull_requestworkflows. This last step is stricter than #682 (see open points).The two job rules do not credit
if:. The module's one accepted guard,github.event_name != 'pull_request', still letsissue_commentand review runs through, and in a called workflowgithub.event_nameis the caller's. A new test,test_reachability_follows_local_calls_and_names_the_caller, checks two things: a violation in a callee names its caller, and a call is followed through a$/hop to a callee that has noworkflow_calltrigger.On the base's 21 workflows, no job declares
environment:, no job calls a reusable workflow, and no workflow has a comment or review trigger. All three rules therefore pass on the real tree with no exemption.The P2 residuals on #681's
excludes_pull_request(repair round 2). The helper now follows actions/runner'sTemplateReader.ParseScalar. A value either holds no${{(a bare expression), or opens one at its first character and closes it, quote-aware, at its last. The expression's text outside string literals may hold no delimiter.strip()accepted literal whitespace around a whole expression.ParseScalarkeeps that whitespace as a literal segment of a truthyformat()string.upload-guard-with-a-leading-space,upload-guard-with-a-trailing-space,upload-guard-in-a-clipped-block-scalar(the line break a|scalar keeps),save-guard-with-a-leading-spaceandsave-guard-with-a-trailing-space." ${{ ... }}","${{ ... }} "and"${{ ... }}\n".}}substring check refused valid guards.ParseScalarcloses an expression only at a}}outside a single-quoted literal.upload-guard-with-a-quoted-closing-markerandsave-guard-with-a-quoted-closing-marker.contains('a}}b', 'a'), bare and wrapped.${{in a bare value (contains(github.head_ref, '${{ github.sha }}')) still starts a segment, becauseParseScalarfinds the first${{withIndexOf, whatever the quotes.Dependabot wording (the P2 in the review of #682 at
846cb03e).docs/decisions/2026-09-22-github-automation-closure.mdsaid "fork and Dependabot PRs get a read-only token". It now says that Dependabot's token is read-only by default (it can be raised), citing GitHub's "ChangingGITHUB_TOKENpermissions". No other test or document onmainstates the Dependabot token. The check was a scan of every tracked.md,.py,.json,.yml,.yaml,.tomland.txtfile for "Dependabot" within 250 characters of "read-only". #682's own wording never reachedmain.Cross-family review of
aeb5ba25(FINDINGS; one repair round, ind73e617e5). Both findings reproduced before the fix. They sit in the secrets scan thatpull-request-secret,comment-event-secretandtest_pull_request_jobs_hold_no_secret_and_no_writeshare.secret_references()delimited expressions with\$\{\{(.*?)\}\}, which stops at a}}inside a string literal. As a result,TOKEN: "${{ format('a}}b{0}', toJSON(secrets)) }}"gave no violation under any of the three comment/review events, nor underpull_request. The same held forsecrets['NPM_TOKEN']after the marker, including in a called workflow'ssecrets:mapping.tests/test_workflow_policy.py:567addsexpressions(), which delimits asParseScalardoes, viaexpression_end()(:483, now taking a start offset). An expression that never closes is read to the end.secret_references()(:589) reads those spans (:598).secrets['GITHUB_TOKEN']was flagged whilesecrets.GITHUB_TOKENpassed, although index and property syntax access the same value (Contexts reference).SECRET_INDEX(:586) normalizes index access by one single- or double-quoted literal, unescaping'', to the name it indexes, before the token exception applies (:600).secrets,toJSON(secrets),secrets.*) and an index that is not one literal (secrets[format('{0}_TOKEN', 'GITHUB')]) stay violations, whatever they compute.:994–:1008: the reviewer'senvform under each of the three events, an indexed secret after a quoted}}in a call'ssecrets:mapping, and a computed index, allcomment-event-secret; plus theenvform underpull_request,pull-request-secret.:1100–:1102: the token by single- and by double-quoted index.test_secret_references_delimit_expressions_as_parse_scalar_does(:1352): 5 allowed and 9 refused forms, plus the span split.docs/decisions/2026-10-04-ci-least-privilege.md:291records the round.leaf.yml(on: workflow_dispatch) is not a GitHub-runnable callee: it is deliberate, to show that a call is followed whatever the callee's triggers, as the record states.Map of #682's assertions (class
PullRequestReachableJobsTestsat6e166809):on:names apull_request*event,issue_commentorworkflow_run; local reusable workflows are followed transitivelyPULL_REQUEST_FAMILYandreachable(), which also countsworkflow_callalone (stricter)unparseablerule (StrictLoaderTests.test_constructs_outside_the_subset_are_rejected; plantedanchor-and-alias,flow-mapping,tag,multi-line-plain-scalar,duplicate-permissions-key,tab-indentation). Quoted keys and escapes are decoded rather than refused (plantedsecrets-inherit-under-a-quoted-key,secrets-inherit-under-an-escaped-key,secret-behind-a-u-escape,secret-behind-an-x-escape)on:yields eventsload_workflowrequireson, andtriggers()reports any other form asunparseablejobs()reads: headers at two spaces, keys at four, and a matching header countload_workflowrequires a non-empty mapping of job mappings./.github/workflows/xis a workflow file here./or$/path names a file at the caller's commit, and every file present is checked on its own;reachable()follows the call when the file is presenttest_the_loader_agrees_with_pyyamlcompares the whole parsed tree (now 84 texts)${{ }}naming thesecretscontext (secrets.X,secrets['X'],toJSON(secrets), comment lines included) in apull_requestworkflow or a calleepull-request-secret(decoded values, then the raw text with comments) forpull_requestandworkflow_callworkflowspull_request_targetorworkflow_runworkflowdangerous-triggerbans both triggersissue_comment,pull_request_revieworpull_request_review_commentworkflowcomment-event-secretsecrets:key (secrets: inherit, a mapping, or aworkflow_calldeclaration)secrets: inherit: #681'spull-request-secret, pluscomment-event-secrethere. A mapping that forwards${{ secrets.X }}is caught by that reference. A declaration with no use, or literal values, reads no secret and is not carriedsecrets.GITHUB_TOKENrefusedgithub-token-secret), andcomment-event-secretfollows that, now in both access forms (secrets.GITHUB_TOKEN,secrets['GITHUB_TOKEN']). It is the job's own token, alsogithub.token, bounded bypull-request-write-scopeandWRITE_GRANTSenvironment:on a reachable jobpull-request-environmentpull-request-remote-workflowPULL_REQUEST_SECRET_SOURCE_ALLOWLIST(empty), where stale entries failEXEMPTIONSandtest_each_exemption_is_still_needed, which now runs over the directory-wide passPULL_REQUEST_WRITE_ALLOWLISTin both directions (the two SARIF uploads)pull-request-write-scopeandWRITE_GRANTS(test_write_grants_are_the_reviewed_inventory, an equality over every job of every workflow)write-allis refused, and so is any workflow-level writewrites()treats any non-mapping form as a write, andworkflow-permissions-not-emptyrequires a top-level{}workflow-permissions-missing, andwrites()counts the default as a writeunparseable, andwrites()counts any access other thanreadornoneas a writeif:is not credited for write scopesgithub.event_name != 'pull_request'conjunct, now matchingParseScalarexactly (repair round 2). Both SARIF uploads pass through itPULL_REQUEST_EXPOSUREwordingdocs/decisions/2026-10-04-ci-least-privilege.md, "Reachability rules and repair round 2". The Dependabot qualification is applied to the one such sentence onmainSOTA sources
All were read on 2026-10-04 at 08:18Z; GitHub Docs were fetched through the docs article API. The older page URLs named in the brief redirect to the ones below.
GitHub Docs, Secure use reference (formerly "Security hardening for GitHub Actions"):
pull_request_targetandworkflow_runworkflow triggers, when used with the checkout of an untrusted pull request, expose the repository to security compromises";GitHub Docs, Using secrets in GitHub Actions: "With the exception of
GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository"; "Secrets are not automatically passed to reusable workflows".GitHub Docs, reusable workflows:
secrets: inherit; "If you includeenvironmentin the reusable workflow at the job level, the environment secret will be used, and not the secret passed from the caller workflow".githubcontext": "thegithubcontext is always associated with the caller workflow".jobs.<job_id>.uses: the$/,./and{owner}/{repo}/...@{ref}forms; a same-repository call is "from the same commit as the caller workflow". Alsojobs.<job_id>.secrets.inherit.GitHub Docs, Managing environments for deployment and Deployments and environments, "Environment secrets": "Secrets stored in an environment are only available to workflow jobs that reference the environment. If the environment requires approval, a job cannot access environment secrets until one of the required reviewers approves it."
GitHub Docs, Events that trigger workflows:
issue_commentruns the default branch's workflow;pull_request_reviewandpull_request_review_comment: "Workflows in forked repositories";workflow_run"is able to access secrets and write tokens, even if the previous workflow was not".GitHub Docs, Troubleshooting Dependabot on GitHub Actions, "Changing
GITHUB_TOKENpermissions": "By default, GitHub Actions workflows triggered by Dependabot get aGITHUB_TOKENwith read-only permissions. You can use thepermissionskey in your workflow to increase the access for the token".zizmor
docs/audits.mdatv1.30.1(99a054ed9283c90abdd2d5b9fb5101d27dde9783), the version.github/requirements-ci.txtpins:dangerous-triggers(pull_request_target,workflow_run);secrets-inherit;unpinned-uses(branch or tag refs; a SHA-pinned remote call passes);self-repository, new in 1.30.0: prefer$/to./, which is why both forms are local here.None of them flags an
environment:on a pull request job or a secret in a comment-triggered workflow.actions/runner
src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateReader.csatd7bc179baf11a02110b46cfbbc4040f74ac3f60a,ParseScalarandAddString: the scan that the repair-round-2 helper and, since the review round,expressions()mirror.GitHub Docs, Contexts reference, "Available contexts": "you can access context information using one of two syntaxes. Index syntax:
github['sha']. Property dereference syntax:github.sha" (P2). Evaluate expressions in workflows and actions: "Literals" (strings take single quotes; "Wrapping with double quotes (") will throw an error") andformat("Escape curly braces using double braces") (P1). Both read 2026-10-04 at 14:15Z.Implementation references: CI least privilege: permissions {} by default, cache-mode none on pull requests, workflow policy tripwire #681's module at
6af8e55b(the loader,workflow_secret_references, the planted-control pattern andEXEMPTIONS), and Workflow hardening: no pull-request job reads a secret or holds a new write scope #682's reachability computation at6e166809.Evidence-class table
mainatb629b5b4(merged in, including #688'ssecurity-scan.ymlchange), and both modules passlocal_integrationpython3 -m unittest tests.test_workflow_policy tests.test_workflow_hardening: 138 tests, OK (skipped=3), exit 0 (base6af8e55b: 136, exit 0)syntheticPlantedViolationTests)syntheticsyntheticaeb5ba25before the fix:[]for the quoted-marker forms under all three events andpull_request, andcomment-event-secretforsecrets['GITHUB_TOKEN']syntheticcheck_directory()probe on that headlocal_integrationuv run --no-project --with pyyaml==6.0.3 python -m unittest tests.test_workflow_policy.StrictLoaderTests.test_the_loader_agrees_with_pyyaml: OK, exit 0ParseScalar's scan; zizmor's auditssource_reviewaeb5ba25: 8/8 required green (20 pass, 5 skipping); this head is nextMutation results (at head
33276786; identical to the run before the merge). Each case runsPlantedViolationTests(12 tests) on a scratch copy of the module, with.githublinked read-only. The worktree file's hash did not change.pull-request-environmentnever firesenvironment-on-a-pull-request-job,environment-behind-an-if-guard,environment-on-a-comment-jobandenvironment-in-a-called-workflow, plustest_reachability_follows_local_calls_and_names_the_callerpull-request-remote-workflownever firesremote-reusable-workflowandremote-call-in-a-reusable-workflowcomment-event-secretnever firesissue-comment-secret,review-secrets-inherit,review-comment-secret-behind-an-escape, and the 5 review-roundcomment-event-secretcontrolsexcludes_pull_requestreverted to #681'sreachable()stops following callsworkflow_callstep still reaches the planted calleesecret_references()back on the\$\{\{(.*?)\}\}regex (P1 reverted)secrets:mapping, andpull_request) and 3 unit casesLocal commands run
All ran with
nice -n 19and a scratchTMPDIRoutside the checkout, at head33276786(origin/mainb629b5b4merged in):Commits:
0a876732b(content),aeb5ba259(registry),d73e617e5(review-round content), then332767860, the last commit. It mergesorigin/mainatb629b5b4, takes main'smanifests/evidence.jsonand re-registers the three files, perdocs/lanes.md's merge path. Main's #688 had editeddocs/decisions/2026-09-22-github-automation-closure.md30 lines at 465, which merged cleanly with this branch's sentence, now at 1653. The registry differs from main's in exactly those three rows.Decision record
docs/decisions/2026-10-04-ci-least-privilege.md, section "Reachability rules and repair round 2 (2026-10-04, superseding #682)". It covers the rules, the reachability computation, why these three rules, repair round 2, the Dependabot wording, what #682 held that is not carried, zizmor as the alternative, the overturn conditions, the evidence class and the sources.Host evidence
Not applicable: no file under
evidence/hosts/changes.Checklist
version comment (no floating tags). No workflow or action changes.
permissions: {}and grant each jobonly what it needs (
contents: read, or an explicitly justified addition). No workflow changes; the tripwire now also refuses environments and remote reusable workflows on pull_request-family jobs.added without a documented owner.
moved or overwritten).
Open points
workflow_callalone counts. Workflow hardening: no pull-request job reads a secret or holds a new write scope #682 reached a reusable workflow only through a caller in this repository. This PR also counts anyworkflow_callworkflow, as CI least privilege: permissions {} by default, cache-mode none on pull requests, workflow policy tripwire #681'sPULL_REQUEST_EVENTSdoes, becausesota-sources-gate.ymlis called from other repositories'pull_requestworkflows. Reversing it is one line (drop the second seed set inreachable()); the decision record names the overturn condition.GITHUB_TOKENstays allowed incomment-event-secret. The brief reads "nosecrets.*references", and Workflow hardening: no pull-request job reads a secret or holds a new write scope #682 refusedsecrets.GITHUB_TOKEN. This PR reusesworkflow_secret_referencesfor consistency withpull-request-secret. The token is alsogithub.token, which no secrets scan sees, and its scopes are bounded bypull-request-write-scopeandWRITE_GRANTS. This is the coordinator's decision to make; changing it touches one call site.pull-request-write-scopeand the cache rules creditgithub.event_name != 'pull_request'. That guard does not excludeissue_commentor review runs that reach aworkflow_callcallee.WRITE_GRANTSstill pins every write grant. The new rules do not creditif:, so they do not inherit this gap.comment-event-secretcovers the three events the brief names.issues,discussionanddiscussion_commentalso run the default branch's workflow on outside text, and are not covered../callee missing from the directory is not reported (Workflow hardening: no pull-request job reads a secret or holds a new write scope #682 reported it); see the map above.mainand rerun OpenHands resolver: host driver, one owner issue to one draft PR (stages 1-2, offline-tested) #489'sgate_readsderivation and breadth-cap tests.validatejob'spython3 -m unitteststep.🤖 Generated with Claude Code