actions/unpinned-tag: recognize GitHub Immutable Releases per-tag instead of a static repo allow-list
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 5/5
- Geschätzter Aufwand
- Über eine Woche
- Anfängerfreundlichkeit
- 35/100
Rechercherichtung
Start with codeql/actions/ql/lib/codeql/actions/config/ConfigExtensions.qll, Config.qll, UseOfVersionedImmutableActionRef.qll, and UnpinnedActionsTag.ql, then review the CWE-829 fixtures and existing immutable-action data model. Validate the proposed query and ListUnpinnedActionRefs.ql changes, then inspect prepare-immutable-action-refs/ and the codeql-action init/analyze flow. Done requires production integration, tests, and a defined model-pack and API handling strategy.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
actions/unpinned-tag incorrectly flags Actions pinned to immutable release tags
Problem
The actions/unpinned-tag (UnpinnedActionsTag.ql, CWE-829) query requires third-party Actions
to be pinned to a full commit SHA, and flags any other ref (branch, floating major/minor tag,
semver tag, etc.) as unpinned. This is the right default, since a mutable tag can be repointed by
the publisher (accidentally or maliciously) after the fact.
However, GitHub now supports Immutable Releases and tags:
once enabled for a repository and a specific release is published, that release's tag can never
be moved or have its target changed while it exists, and — critically — if the tag/release is deleted, GitHub does not allow the same tag name to be recreated, so an attacker can never repoint it at a different (e.g. compromised) commit. Pinning to owner/[email protected] is then just as safe
as pinning to the commit SHA that tag pointed to at publish time — but the query still reports it
as an unpinned-tag finding, producing a false positive.
The existing immutableActionsDataModel(action) extensible predicate (consumed by
UseOfUnversionedImmutableAction.qll) does not solve this: it is a static, manually curated,
repository-level allow-list left over from an earlier, very limited Docker-image-based
immutable-actions preview. Immutable Releases are granted per tag/release, not per
repository — a repo can have some immutable releases and some ordinary mutable tags — so a
repo-level allow-list can never be accurate, and isn't maintainable at scale (every consuming repo
would need to keep it up to date for every third-party Action it uses).
Proposal
-
New extensible predicate, keyed on
(action, ref)not justaction.
AddimmutableActionRefsDataModel(string action, string ref)to
codeql/actions/ql/lib/codeql/actions/config/ConfigExtensions.qll(+Config.qll), where
actionis normalized toowner/repo(no sub-directory path — immutability is a property of
the repository + tag, so an entry forowner/repoalso covers sub-actions such as
owner/repo/path/to/action). -
New library predicate/class,
UseOfVersionedImmutableActionRef.qll, defining
isImmutableActionRef(step, action, ref). It normalizes the callee (stripping any
sub-directory path) before consulting the data model. -
Mutual exclusion in
UnpinnedActionsTag.ql: add
not isImmutableActionRef(step, nwo, version)alongside the existing
not isImmutableAction(step, nwo)exclusion. The two conditions are independent and
non-overlapping by construction (there's no "mutually exclusive query" primitive in CodeQL;
exclusivity is achieved purely by each side negating the other's firing condition). -
No new alert query. "This ref happens to point to an immutable release" is not itself a
finding — it's purely a suppression signal foractions/unpinned-tag. -
Pre-processing step to populate the data, since CodeQL analysis has no network access at
scan time and immutability is a live, per-tag fact:github/codeqlgains a small utility query,
actions/ql/src/utils/ListUnpinnedActionRefs.ql(@kind table), that reuses the existing
Actions extractor to enumerate every distinct(owner/repo, ref)pair used byuses:steps
and reusable workflow calls in the scanned repo that is not already pinned to a commit
SHA (excluding local/self refs and container image refs). This is "leveraging CodeQL
itself to find all the action refs to check", per the request — no separate YAML parser
needed.github/codeql-actiongets a new step (prototyped here as a standalone composite action,
prepare-immutable-action-refs/) that:- Runs the utility query above against the already-created
actions-language CodeQL
database (codeql query run+bqrs decode). - Filters results to version-like tags (
vX,vX.Y,vX.Y.Z) — branch names,latest,
main, etc. can never correspond to a GitHub Release. - For each remaining
(owner/repo, ref), calls
GET /repos/{owner}/{repo}/releases/tags/{ref}and reads theimmutableboolean directly
from the REST response (simpler than GraphQL, and works with an unauthenticated request
subject to standard rate limits, or authenticated via the workflow's token for the higher
limit). - Emits a CodeQL data extension YAML (
immutableActionRefsDataModel) containing only the
(action, ref)pairs confirmed immutable. Missing/404/false ⇒ default is always "treat as
mutable, still flag it" — fail-safe by construction, no allow-list to maintain. - The generated extension is then passed to
codeql database analyzeas a model pack.
- Runs the utility query above against the already-created
End-to-end validation performed
All of the following was implemented and verified against a downloaded CodeQL CLI bundle
(codeql-bundle-win64 v2.27.1) plus the live GitHub API — not just unit-tested in isolation:
-
Library/query unit tests (
actions/ql/test/query-tests/Security/CWE-829): added a fixture
workflow (immutable_release_tags.yml) exercising:- a repo-level match (
foo/[email protected], in the test data extension) → not
flagged - the same repo+tag referenced via a sub-directory action
(foo/immutable-release/[email protected]) → not flagged (proves theowner/repo
normalization works) - the same repo with a different, unlisted tag (
@v1.2.4) → still flagged - a completely unrelated repo (
foo/[email protected]) → still flagged
Full
CWE-829,CWE-829-untrusted-owner, andCWE-829-Lockfilequery-test suites pass
(11/11) with these changes. - a repo-level match (
-
Utility query validation: ran
ListUnpinnedActionRefs.qlagainst a real CodeQL database
built from the test fixtures and confirmed it returns exactly the deduplicated, normalized
(action, ref)candidates expected — e.g. bothfoo/[email protected]and
foo/immutable-release/[email protected]collapse to a single
("foo/immutable-release", "v1.2.3")row. -
Real-world, live-API end-to-end test (no mocking): built a throwaway workflow with
- uses: actions/checkout@v4 - uses: jessehouwing/[email protected] - uses: jessehouwing/azdo-marketplace@v6and ran the full prototype pipeline (
prepare-immutable-action-refs/prepare.sh) against a real
CodeQL database for it:- Confirmed via a live call to
GET /repos/jessehouwing/azdo-marketplace/releases/tags/v6.3.8that GitHub's REST API
returns"immutable": truefor that release. - Confirmed
GET /repos/jessehouwing/azdo-marketplace/releases/tags/v6returns404(no
release exists for that floating major tag) — correctly defaults to "mutable". - Confirmed
GET /repos/actions/checkout/releases/tags/v4also returns404—actions/checkout
doesn't publish releases under that exact tag scheme, so it's correctly left as a flagged,
unpinned tag rather than silently trusted. - The script produced:
extensions: - addsTo: pack: codeql/actions-all extensible: immutableActionRefsDataModel data: - ["jessehouwing/azdo-marketplace", "v6.3.8"] - Fed this generated file into the actual
UnpinnedActionsTag.qlquery
(codeql query run+--model-packs) against the same database:- Baseline (no immutable-refs data): both
jessehouwing/[email protected]and
@v6were flagged as unpinned. - With the generated extension applied: only
jessehouwing/azdo-marketplace@v6was
flagged;@v6.3.8was correctly suppressed.
- Baseline (no immutable-refs data): both
- Confirmed via a live call to
This confirms the full loop — CodeQL discovers candidate refs → live GitHub API confirms which are
immutable → generated data extension is consumed by the real query → false positive is eliminated
while true positives remain — works end-to-end, not just in theory.
Scope / what's not yet done
- The
github/codeqlside (predicate, library class, query wiring, docs, tests) is
implementation-ready. - The
github/codeql-actionside is currently a standalone prototype action
(prepare-immutable-action-refs/), not yet wired into the productioninit/analyzesteps.
Full integration would need:- A supported way to pass the generated model pack into the existing
analyzestep (e.g. a new
--model-packs-equivalent input). - Rate-limit/backoff handling for repos with many distinct refs.
- Unit/integration tests in
codeql-action's own test suite. - A decision on default enablement (opt-in vs. on-by-default) and caching of results across runs
to avoid re-querying the GitHub API on every scan.
- A supported way to pass the generated model pack into the existing
Why this matters
Without this, users following GitHub's own recommended best practice (Immutable Releases +
semver tags instead of hand-pinning commit SHAs) are incorrectly flagged by CodeQL, which either
trains them to ignore/dismiss actions/unpinned-tag alerts or discourages adoption of the safer,
more maintainable Immutable Releases feature.
- Vorherrschende Sprache
- CodeQL
- Sterne
- 10.1k
- Forks
- 2.1k
- Ø Merge
- 2 T. 18 Std.
- Gemergte PRs (30 T.)
- 153
Entwicklungsumgebung
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus github/codeql
-
agentic-workflows
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
Maintainer antworten meist innerhalb von 1 Tag
-
false-positive javascript
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
github/codeql#22632 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
C#: cs/simplifiable-boolean-expression false positive on Nullable<bool> compared with a literalOffen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
github/codeql#21637 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
false-positive
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
github/codeql#21076 · 3 Kommentare · 3 Reaktionen ·
Maintainer antworten meist innerhalb von 1 Tag
Ähnliche Issues
-
agentic-workflows
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
Maintainer antworten meist innerhalb von 1 Tag
-
self-care self-care:docs-build-time-investigator
Schwierigkeit 2/5 Ein halber Tag Anfängerfreundlichkeit 76/100
githubnext/gh-aw-cao#14191 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Client Service Attention Storage test-reliability
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
Azure/azure-sdk-for-js#40107 · 2 Kommentare · 1 Reaktion ·
Maintainer antworten meist innerhalb von 1 Tag
-
backlog
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 74/100
coder/xum#4702 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Maintainer antworten meist innerhalb von 1 Tag