Summary
check_workflow_timestamp_api.cjs validates that the .md source has not changed since compilation by comparing hash(.md frontmatter) against the hash stored in the gh-aw-metadata comment of the .lock.yml. It does not verify that the .lock.yml YAML content matches what the compiler would produce from the current .md. An attacker with repository write access can edit the .lock.yml directly — for example changing issues: read to issues: write, widening the network allowlist, or removing the AWF firewall container — while leaving the .md source and the frontmatter_hash comment unchanged. The tampered file passes all activation checks and the agent job runs with the elevated configuration. Confirmed at v0.68.1; the class of weakness was first noted in specs/workflow-lock-metadata-tampering-2026-02-14.md at v0.43.23 with status "Requires Validation".
Affected Area
Compiled workflow integrity boundary — the boundary between the .md source (author-controlled intent) and the .lock.yml (runtime-executed artifact). Specifically: actions/setup/js/check_workflow_timestamp_api.cjs and actions/setup/js/frontmatter_hash_pure.cjs.
Reproduction Outline
- Identify a compiled
.lock.yml in a repository where you have write access (or a PR review path that does not diff .lock.yml carefully).
- Edit one line in the
.lock.yml YAML body, e.g. change issues: read → issues: write in the agent job's permissions block.
- Leave the
# gh-aw-metadata: {"frontmatter_hash":"..."} comment line unchanged.
- Commit and push the tampered
.lock.yml (the .md source is untouched).
- Trigger the workflow —
check_workflow_timestamp_api.cjs computes hash(.md frontmatter) and compares it to the stored comment hash; both match, so stale_lock_file_failed = false.
- The agent job runs with the elevated permission (
issues: write) with no runtime error or integrity failure.
Observed Behavior
stale_lock_file_failed remains false for a tampered .lock.yml when the .md source is unchanged. The frontmatter_hash embedded in the gh-aw-metadata comment is never recomputed against the .lock.yml YAML body. GitHub Actions' YAML parser silently ignores all comments, so the integrity metadata stored in comments provides no tamper evidence.
Expected Behavior
The activation check-lock-file step should detect that the .lock.yml YAML content no longer corresponds to what the compiler would produce from the current .md source, and fail the workflow with an integrity error.
Security Relevance
The precondition (repository write access) narrows exploitability, but PRs routinely include .lock.yml changes that reviewers may not scrutinize as carefully as the .md source. The documented model implies integrity guarantees ("validates declarative configuration artifacts", "Validate Lock File" activation step) that the current check does not provide. Beyond permission elevation, an attacker could widen network allowlists or remove the AWF firewall container — both meaningful security boundary changes invisible to the runtime check.
Suggested Mitigations
- Extend the hash to cover
.lock.yml YAML content: Include the compiled YAML body (or a canonical recompilation of .md) in the integrity check, not just the .md frontmatter hash.
- Move integrity metadata to a verified YAML field: YAML comments are silently ignored; a top-level YAML field would at minimum survive round-trips and could be verified.
- Add PR-time recompilation CI check: Automatically recompile every modified
.md and diff against the committed .lock.yml; fail the PR if any difference is found.
- Document the current trust model: Clarify that
.lock.yml files require the same code-review scrutiny as .md files and that frontmatter_hash is currently advisory-only.
Additional Context
If this design is intentional (e.g., the hash is meant only as a staleness guard, not a full content integrity check), that assumption should be documented explicitly in the security model and architecture documentation so repository operators understand the current trust boundary.
gh-aw version: v0.68.1
Original finding: https://github.com/githubnext/gh-aw-security/issues/1847
Generated by File Issue · ● 244K · ◷
Summary
check_workflow_timestamp_api.cjsvalidates that the.mdsource has not changed since compilation by comparinghash(.md frontmatter)against the hash stored in thegh-aw-metadatacomment of the.lock.yml. It does not verify that the.lock.ymlYAML content matches what the compiler would produce from the current.md. An attacker with repository write access can edit the.lock.ymldirectly — for example changingissues: readtoissues: write, widening the network allowlist, or removing the AWF firewall container — while leaving the.mdsource and thefrontmatter_hashcomment unchanged. The tampered file passes all activation checks and the agent job runs with the elevated configuration. Confirmed at v0.68.1; the class of weakness was first noted inspecs/workflow-lock-metadata-tampering-2026-02-14.mdat v0.43.23 with status "Requires Validation".Affected Area
Compiled workflow integrity boundary — the boundary between the
.mdsource (author-controlled intent) and the.lock.yml(runtime-executed artifact). Specifically:actions/setup/js/check_workflow_timestamp_api.cjsandactions/setup/js/frontmatter_hash_pure.cjs.Reproduction Outline
.lock.ymlin a repository where you have write access (or a PR review path that does not diff.lock.ymlcarefully)..lock.ymlYAML body, e.g. changeissues: read→issues: writein theagentjob'spermissionsblock.# gh-aw-metadata: {"frontmatter_hash":"..."}comment line unchanged..lock.yml(the.mdsource is untouched).check_workflow_timestamp_api.cjscomputeshash(.md frontmatter)and compares it to the stored comment hash; both match, sostale_lock_file_failed = false.issues: write) with no runtime error or integrity failure.Observed Behavior
stale_lock_file_failedremainsfalsefor a tampered.lock.ymlwhen the.mdsource is unchanged. Thefrontmatter_hashembedded in thegh-aw-metadatacomment is never recomputed against the.lock.ymlYAML body. GitHub Actions' YAML parser silently ignores all comments, so the integrity metadata stored in comments provides no tamper evidence.Expected Behavior
The activation
check-lock-filestep should detect that the.lock.ymlYAML content no longer corresponds to what the compiler would produce from the current.mdsource, and fail the workflow with an integrity error.Security Relevance
The precondition (repository write access) narrows exploitability, but PRs routinely include
.lock.ymlchanges that reviewers may not scrutinize as carefully as the.mdsource. The documented model implies integrity guarantees ("validates declarative configuration artifacts", "Validate Lock File" activation step) that the current check does not provide. Beyond permission elevation, an attacker could widen network allowlists or remove the AWF firewall container — both meaningful security boundary changes invisible to the runtime check.Suggested Mitigations
.lock.ymlYAML content: Include the compiled YAML body (or a canonical recompilation of.md) in the integrity check, not just the.mdfrontmatter hash..mdand diff against the committed.lock.yml; fail the PR if any difference is found..lock.ymlfiles require the same code-review scrutiny as.mdfiles and thatfrontmatter_hashis currently advisory-only.Additional Context
If this design is intentional (e.g., the hash is meant only as a staleness guard, not a full content integrity check), that assumption should be documented explicitly in the security model and architecture documentation so repository operators understand the current trust boundary.
gh-aw version: v0.68.1
Original finding: https://github.com/githubnext/gh-aw-security/issues/1847