Repository navigation
ci: allow only the reviewed GitHub Actions (#650) - #838
Merged
Merged
Conversation
HugoXK-bot
reviewed
Nov 15, 2026
HugoXK-bot
left a comment
Collaborator
There was a problem hiding this comment.
Verdict: PASS at bb1c165
Written-by: HugoXK-bot
Posted-by: HugoXK-bot
Read the tip bb1c165b112bede7145855eba0e39386b382e6ce (tree 2c8a75b2, 6 files +260/−15, T3; scripts/lib/action-pins.mjs blob cdd90fe8 matches the contents API).
Findings, none blocking:
scripts/lib/action-pins.mjs:35-58—defaultAllowlistPath()resolves from the module's own location rather thancwd, andreadAllowlistthrows on a missing, unparsable, empty, duplicate-bearing or wildcard-bearing file. A gate that allowed everything when its data was unreadable would be worse than no gate.scripts/lib/action-pins.mjs:67-72—repositoryOfreduces auses:value toowner/repoand drops the sub-path, which matches the documented rule thatgithub/codeql-action/initis covered by its repository.scripts/action-pins.mjs:17-30— a malformed list exits 1 before any scan;:44-56separatesnot-pinnedfromnot-allowed, so the hint tells the author to change the list, not to re-pin..github/agent/action-allowlist.json:1-14— ten exact names, no wildcard;test/actionPins.test.ts:377-406asserts both directions (used-but-unlisted and listed-but-unused), which is what keeps a list from rotting into decoration.- The ordering property is real and the body states it honestly:
.github/workflows/ci.yml:526-539checks the base tree out atbase/and runsnode base/scripts/action-pins.mjs, so this PR's own job judges pins with the base checker and the list starts enforcing on the first PR after this merge. Reviewing the list is the only gate on its contents; that is the design, not a gap to paper over. docs/security.md:790-846— the repository setting (allowed_actions,sha_pinning_required) is named as the owner's write, with the two API calls and the four-step add-an-action procedure; no agent writes that setting.
Ran (exact tip tree; blob hash above verified):
npm run check— lint 11 baseline warnings, typecheck clean, 2189/2189 tests, build oknode scripts/action-pins.mjs— ok (74 pinned references across 8 workflow files, all on the 10-action allowlist; 4 actions: write job(s) gated on the actor)node scripts/pr-lint.mjs --pr 838— ok (#838, T3, 6 files)node scripts/changelog.mjs check— 62 fragments oknode scripts/version.mjs check— 0.3.6 oknode scripts/secret-scan.mjs— no suspected value in the tracked files
Not run: flipping the repository setting (owner action), a live new-action PR, Docker, production.
CI at this tip: everything green except review-gate failure 12:21Z, which is the pre-verdict state this review replaces.
A pin stops a tag from moving; it cannot stop a pull request from adding a new third-party action pinned to a commit it chose. So the second half of the rule is a list: only the actions this project has read are allowed. - `.github/agent/action-allowlist.json` names the ten `owner/repo` actions the workflows use. Exact names only: `actions/*` is refused as malformed, because a wildcard re-opens the hole the list closes. A sub-path belongs to its repository, so `github/codeql-action/init` is covered by its entry. - `readAllowlist` throws on an unreadable or malformed file instead of returning an empty list; `checkWorkflow`/`scanWorkflows` take the list and report `not-allowed`, and the CLI reads it relative to its own module, so in CI (where the checker runs from the base checkout) the list is the reviewed base copy and a pull request cannot grant itself an action in the same commit that uses it. - `docs/security.md` records the setting (`allowed_actions: selected`, SHA pinning stays on), the API calls, and the four-step procedure for adding an action; flipping the repository setting remains the owner's action. - The suite grew from 2183 to 2189 tests: the allowlist is asserted in both directions against the real workflow tree, a malformed list is refused rather than read as "allow everything", and the three behaviour tests were written red on live main 7d1e3ef first. Written-by: HugoXK Author: Fledge Platform Dev-1
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.
Written-by: HugoXK
Fixes #650
Tip
bb1c165b112bede7145855eba0e39386b382e6ce, tree2c8a75b2c14ce8a3989d654415bb7601aee9ead8, base livemain7d1e3ef1f809286768ace84ec420a1d8d576fa69. Risk T3 (risk-gatebasis.github/agent/action-allowlist.json), 6 files.What was wrong
Pinning and allowlisting answer different questions. A 40-hex pin says "this action cannot change under us"; it says nothing about who wrote it. A pull request can add a brand-new third-party action pinned to a commit it chose, and that commit runs on our runner with our token — pinned, reviewable in shape, and still an exfiltration. The repository policy is currently
allowed_actions: allwithsha_pinning_required: true, so "anything is allowed if pinned" is the whole rule.What changed
.github/agent/action-allowlist.json(new) names the tenowner/repoactions the workflow tree actually uses:actions/attest-build-provenance,actions/checkout,actions/dependency-review-action,actions/download-artifact,actions/setup-node,actions/upload-artifact,anchore/sbom-action,aquasecurity/trivy-action,github/codeql-action,raven-actions/actionlint. Exact names only —actions/*is refused as malformed, because a wildcard re-opens the hole the list closes. A sub-path belongs to its repository, sogithub/codeql-action/initis covered by thegithub/codeql-actionentry.scripts/lib/action-pins.mjs—readAllowlistthrows on a missing, unparsable, empty, duplicate-bearing or wildcard-bearing file instead of returning[];repositoryOfmaps auses:value to itsowner/repo;checkWorkflow/scanWorkflowstake the list and add anot-allowedfinding. The list path is resolved from the module's own location, so in CI — where the checker runs from the base checkout since ci: execute gate decisions from the base branch, not the pull request head #640 — the list read is the base-branch copy.scripts/lib/action-pins.d.mts— the types the TypeScript suite builds against, including the new problem kind.scripts/action-pins.mjs— loads the list from its own module (never the working directory), reportsnot-allowedwith the fix in the hint, and names the list size in the green line. A malformed list is a red job, not a skipped check.docs/security.md— new "Allowed GitHub Actions" section: the two-setting table (allowed_actions,sha_pinning_required), the two API calls that set them, and the four-step procedure for adding an action (T3 issue with pin and reason → one-line list entry in its own reviewed change → land it → then the workflow PR). Flipping the repository setting stays the owner's action.test/actionPins.test.ts— the three behaviour tests were written red on livemainfirst and are kept in the branch history by patch (artifacts/650-red-test.patchon my side): an action outside the list is refused even when perfectly pinned, an allowlisted action passes and a sub-path belongs to its repository, and widening the list is what admits an action. Two more assert the list against the real workflow tree in both directions — an action used but not listed fails, and an entry no workflow uses fails — and one asserts a malformed list refuses to load.The ordering property is deliberate and worth stating in the review: because both the checker and the list come from the base tree, a pull request cannot grant itself an action in the same commit that uses it. The entry lands first in its own reviewed change; the workflow that uses the action comes after.
Ran:
npm run checkatbb1c165— exit 0, 2189 of 2189 tests pass (lint, typecheck, test, build), logartifacts/650-check.lognode scripts/action-pins.mjsatbb1c165—action-pins: ok (74 pinned references across 8 workflow files, all on the 10-action allowlist; 4 actions: write job(s) gated on the actor)node scripts/action-pins.mjs --workflows <a temp directory holding one step withstranger/exfiltrate@>— exit 1, and the hint names the file to edit:this action is not on the reviewed list; adding it needs a separate reviewed change to .github/agent/action-allowlist.jsonmain7d1e3ef1before the change: the allowlist tests fail (0 !== 1, the action is not a finding), captured inartifacts/650-red-test.patchnode scripts/risk-gate.mjs --base 7d1e3ef1… --head HEAD --no-fetch—risk-tier: T3; basis: .github/agent/action-allowlist.jsongit archiveof this tree (752 entries), extracted into an empty repo,git write-tree→2c8a75b2c14ce8a3989d654415bb7601aee9ead8, the same treeNot run:
PATCH /repos/HugoXK/fledge/actions/permissionswith{"allowed_actions":"selected"}plus theselected-actionspattern list is the owner's action by the rule that repository settings are never an agent's write. Until it is flipped the list is enforced by CI alone: it stops a pull request from merging with an unreviewed action, it does not stop a workflow from starting if one is already on a branch.action-pinsjob in this PR's CI runs the checker from the base tree, which has no allowlist code yet (the property ci: execute gate decisions from the base branch, not the pull request head #640 established), so this run judges pins only. The list starts enforcing on the first pull request built after this merges. Reviewing this PR is therefore the only gate on the list's contents — which is the intent, not a gap to paper over.Rollback
Revert the squash commit. The workflow tree is untouched, so reverting restores exactly the current behaviour: pins checked, allowlist not. If the allowlist turns out to be too narrow in a way that cannot wait for a normal reviewed change, editing the JSON on the base branch is a one-line, T3, revertible fix — and it is the only supported way, not a workflow-level bypass.
Docs
docs/security.mdgains the "Allowed GitHub Actions" section (setting, API, procedure). No other doc owns this path: the doc-ownership table mapssrc/auth,src/gateway,src/repos,src/app.ts,bridge/anddeploy/, none of which are touched here.Changelog-not-needed: this changes which GitHub Actions our CI may run, not anything a Fledge user sees or does; no
changelog.dfragment belongs in the release notes for it.Review focus
owner/repo, sub-path covered by its repository" the right granularity, givengithub/codeql-actionpublishesinitandanalyzeseparately?maintainer_can_modify: true