GitHub Actions Cache Poisoning and Runner-Memory Credential Extraction
Threat Model, Attack-Chain Reconstruction, and Detection Engineering
Classification: Defensive security research
Scope: This paper analyzes a publicly documented class of CI/CD attack against GitHub Actions. It reconstructs the attack chain from disclosed incidents (CVE-2025-30066 and related events), explains the underlying trust-boundary and memory-handling weaknesses, and provides authored detection and mitigation guidance. It deliberately does not provide a weaponized exploit or a novel credential-stealer. The memory-extraction primitive is discussed only at the level already present in the referenced CVE advisories and vendor disclosures, and only in service of detection.
Abstract
GitHub Actions concentrates two properties that make it an attractive target: privileged automation (deploy keys, package-registry tokens, cloud credentials via OIDC) and a shared, cross-trust-boundary build cache. Two independent primitives have been weaponized against maintainer CI/CD in 2024–2026.
The first, cache poisoning, abuses the fact that GitHub does not segregate the Actions cache by event type, workflow, or trust level. Code that runs in a low-trust context can therefore plant artifacts that a later high-trust workflow restores and executes. The second, runner-memory credential extraction, abuses the fact that decrypted secrets live as plaintext in the Runner.Worker process heap, where any code in the job can read them via /proc/<pid>/mem. This bypasses GitHub's log-masking entirely; masking filters only log output.
Chained, these give an attacker a durable, low-noise path from an outside contribution to signed releases and cloud infrastructure. This paper details the mechanics, corrects several common imprecisions in how the vector is described, and provides runtime, CI-side, and cloud-side detection and mitigation.
1. Background
1.1 The Actions execution model
Each Actions job runs on a runner (GitHub-hosted ephemeral VM, or self-hosted). The orchestrating process is Runner.Listener, which spawns a Runner.Worker process per job. Runner.Worker is a .NET application; it is the process to which the job's secrets are handed, and it is therefore the process that holds those secrets in memory for the life of the job.
1.2 Secrets and log masking: what masking does and does not do
Repository/organization/environment secrets are encrypted at rest and decrypted at runtime, then injected into the job. GitHub's "masking" registers each secret value with the runner so that any occurrence of that literal string in log output is replaced with ***.
The critical property for this threat model: masking is a log-output filter. It is applied to bytes on their way to the log stream. It provides no confidentiality for the secret in memory, in environment variables of arbitrary processes, or once the value has been transformed (e.g., re-encoded) so it no longer matches the registered literal. Any code that executes in the job can read the plaintext directly and re-emit it in an encoded form that masking will not recognize.
1.3 The Actions cache
The actions/cache family stores compressed archives keyed by a user-supplied key and an internal version (a hash of the path set and compression parameters). Two behaviors matter:
- Restore keys / prefix matching. A cache lookup may fall back to
restore-keys, restoring an entry whose key shares a prefix. In the current cache backend, near-prefix matches make it feasible to seed an entry that a future miss will fall back onto. - No trust segregation. GitHub does not partition the cache by event type, workflow identity, or trust level. An entry written by a low-trust job is visible to a high-trust job in the same repository that shares the key/version. This is the structural root of cache poisoning; it is "working as intended" in GitHub's model.
Writes are authorized by an Actions Runtime (ARC) token. Historically this token could write to the cache for hours after its originating job finished, which allowed slow, deliberate poisoning. GitHub has since constrained post-job cache writes, and forced eviction behaves differently than it once did (see §3.2). The net effect today is that poisoning generally must occur while a build is running: this raises the operational bar but does not close the vector.
1.4 The OIDC model
Rather than storing static cloud keys, a workflow with permissions: id-token: write can request a short-lived JSON Web Token from GitHub's OIDC provider. The request is made at runtime using two job-scoped environment variables: ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN. The resulting JWT carries an issuer (token.actions.githubusercontent.com), an audience (aud), and a subject (sub) that encodes repo owner, repo name, ref type, and ref value (e.g., repo:acme-org/payments-api:ref:refs/heads/main). It also carries custom claims such as repository_id, repository_owner_id, job_workflow_ref, and environment.
The workflow presents this JWT to a cloud provider (e.g., AWS sts:AssumeRoleWithWebIdentity). The provider validates aud/sub against a preconfigured trust policy and, on success, returns short-lived cloud credentials scoped to the job's duration.
2. Correcting three common imprecisions
Accurate defense depends on an accurate model. Three points are frequently stated loosely:
- "Covert forks poison the cache." Fork pull requests do not share the upstream repository's cache, run with a read-only
GITHUB_TOKEN, and receive no secrets by default. A fork alone cannot poison an upstream cache. The real precondition is code execution in a trusted context: most commonly apull_request_targetworkflow that checks out and runs attacker-controlled code (or is script-injectable via${{ github.head_ref }}and similar), or a compromised dependency/reusable action that executes inside a default-branch job. The fork is the delivery vehicle for untrusted code; the vulnerability is the privileged workflow that executes it. - "Memory extraction steals the OIDC token before masking hides it." Masking never protects anything in memory, so there is nothing to "beat." An attacker with execution in a job that holds
id-token: writedoes not need memory reading to obtain an OIDC JWT at all; they can mint a fresh one directly from theACTIONS_ID_TOKEN_REQUEST_*environment variables. The/proc/<pid>/memtechnique is valuable for the things that cannot simply be requested: the masked repository/organization secrets, theGITHUB_TOKEN, and cloud credentials already exchanged and resident in memory. - "Log-masking is a security control." It is a hygiene feature that reduces accidental disclosure. It is not a boundary. Any threat model that relies on masking to protect secrets from in-job code is unsound.
3. Primitive A: Cache poisoning
3.1 The trust-boundary defect
Because caches are shared across trust levels within a repository, an attacker who achieves execution in any job that can write the cache can plant a poisoned entry that a different, more privileged job later restores. The poisoned entry is typically a dependency tree (node_modules, a language toolchain cache, a Bazel repository cache) that will be executed or linked by the privileged job. Because the privileged job restores what looks like a legitimate, correctly-keyed cache at normal speed, there is little to alert a maintainer.
3.2 Write-window and eviction mechanics
Two mechanics govern feasibility:
- Write window. The ARC token's post-job write capability has been tightened over time (from a multi-hour window down to constraints that force poisoning to happen during an active run). This is why modern chains fill and poison within a single run or across closely-timed runs.
- Eviction. Repositories have a cache size cap (10 GB by default). An attacker can upload junk to force least-recently-used eviction of the legitimate entry, then write the poisoned entry under the same key. Forced-eviction timing has changed (immediate eviction on cap breach in late 2025), which affects how quickly an attacker can knock out a legitimate entry within one run.
Adnan Khan's Cacheract demonstrated automating the eviction-and-replace sequence; the ActionsCacheBlasting research documents the ARC-token behavior.
3.3 Reconstructed case: Angular dev-infra
The disclosed Angular chain is the canonical illustration and maps cleanly to the trust-boundary defect:
- Script injection via an untrusted
${{ github.head_ref }}expansion in apull_request_targetworkflow yielded execution in a trusted context. - Cache filling with junk forced eviction of the legitimate entry.
- A poisoned
node_moduleswas written under the legitimate cache key. - A later scheduled workflow restored the poisoned cache and, in doing so, exposed a privileged token (
NG_RENOVATE_USER_ACCESS_TOKEN) belonging to a GitHub App with administrative access.
The significance is the provenance-laundering property: an attacker can tamper with build inputs such that the resulting artifact still carries valid, signed provenance (up to SLSA L3), and the workflow itself retains almost no forensic trace.
3.4 2026 escalation
The TanStack incident (May 2026) shows the class maturing into self-propagating supply-chain malware. Poisoned CI steals CI/CD secrets, which are then used to publish poisoned packages, and those packages compromise downstream CI in turn. Cache poisoning is repeatedly "the way in," combined with one or two other Actions weaknesses.
4. Primitive B: Runner-memory credential extraction
4.1 The heap artifact
Secrets handed to Runner.Worker are held in the .NET runtime heap. In the disclosed incidents, secret values are recoverable as JSON objects of the shape:
{"value":"<secret>","isSecret":true}
This is the indicator-of-compromise shape that detection keys on. It is public in the CVE advisory and multiple vendor analyses.
4.2 The read primitive
The technique uses the proc filesystem in three steps, all of which are legitimate kernel interfaces:
- Enumerate
/proc/*/cmdline(orps) to find the PID ofRunner.Worker. - Read
/proc/<pid>/mapsto identify readable memory regions. - Open
/proc/<pid>/mem, iterate readable regions, and scan for theisSecretJSON shape (and/or harvest/proc/*/environfor environment-resident credentials).
Because the values are recovered from memory and then re-encoded (the tj-actions payload used double base64), the re-emitted bytes no longer match the masked literals, so they pass straight through the log filter. In the public-repo case, the workflow log itself was the exfiltration channel: no attacker callback was required, which is why network-egress controls alone would not have caught the main variant.
4.3 Disclosed incidents
tj-actions/changed-files(CVE-2025-30066, March 2025). Tags were retroactively repointed to a malicious commit; the injected step fetched a Python memory-dumper from a gist and printed double-base64 secrets into logs. ~23,000 repositories referenced the action.trivy-action(2026). Reused the same/proc/<pid>/memtechnique againstRunner.Worker, plus/proc/*/environharvesting, confirming the primitive as a reusable component rather than a one-off.
5. Chaining the primitives
The two primitives compose into a durable, low-noise chain:
- Foothold: untrusted code executes in a trusted context (script injection in
pull_request_target, or a poisoned dependency/action on a privileged branch). This is where a fork-based contribution actually enters the picture: as the source of the untrusted code, not as a cache writer. - Persistence via poisoning: instead of a noisy immediate theft, the foothold poisons a cache that a later, more privileged scheduled or release workflow will restore. This decouples the compromise from the moment of theft and launders it through a legitimate-looking build.
- Credential harvest in the privileged job: when the privileged workflow restores and executes the poisoned artifact, the attacker code runs with the privileged job's secrets and permissions. It reads
Runner.Workermemory for masked secrets andGITHUB_TOKEN, harvests/proc/*/environ, and, if the job holdsid-token: write, mints a fresh OIDC JWT from the request env vars. - Cloud pivot: a stolen or freshly-minted OIDC JWT is exchanged for cloud credentials only if the cloud trust policy's
sub/audconditions match the privileged job's identity. This is the single most important defensive chokepoint on the cloud side (see §7.2): an over-broadsubcondition (wildcards, or trust onpull_requestrefs) turns "code ran in CI" into "code touched production." - Exfiltration: via logs (public repos), or via network egress (private repos / self-hosted).
The OIDC-specific nuance from §2 holds here: memory reading is not the OIDC path; env-var minting is. Memory reading is the path to everything OIDC alone does not grant.
6. Detection engineering
Detection must sit inside the runner at runtime, because pre-build controls (SAST, SCA, dependency scanning) operate on code and manifests and cannot observe what executes during the build. The controls below are authored for this threat and grouped by layer.
6.1 Runtime (eBPF / Falco)
Reading another process's memory is the highest-fidelity signal, because no legitimate build tool reads /proc/<pid>/mem of Runner.Worker.
- rule: CI Runner Cross-Process Memory Read
desc: A process is reading /proc/<pid>/mem of another process on an Actions runner.
condition: >
open_read and fd.name glob "/proc/*/mem"
and not fd.name glob "/proc/*/self/mem"
and not proc.name in (allowed_mem_readers)
output: >
Cross-process memory read on runner
(reader=%proc.name pid=%proc.pid cmd=%proc.cmdline
target=%fd.name parent=%proc.pname user=%user.name)
priority: CRITICAL
tags: [ci_cd, credential_theft, T1003]
- list: allowed_mem_readers
items: [ps, top, gdb, prometheus] # profile and tighten per environment
Companion rule for environment harvesting:
- rule: CI Runner Foreign Environ Read
desc: A process is reading /proc/<pid>/environ of another process.
condition: >
open_read and fd.name glob "/proc/*/environ"
and not fd.name glob "/proc/*/self/environ"
output: >
Foreign process environ read on runner
(reader=%proc.name pid=%proc.pid cmd=%proc.cmdline target=%fd.name)
priority: HIGH
tags: [ci_cd, credential_access, T1552.007]
Behavioral companions that raise confidence when correlated with the above:
- A build step spawning
python/sudothat fetches a script from a raw gist/pastebin host the build had never contacted, then pipes it to an interpreter. - Anomalous
base64invocations on the output of a memory/environread (the re-encode-to-defeat-masking step). - Unexpected outbound egress to non-allowlisted destinations (catches the private-repo variant; will not catch the log-only variant, so it must not be the sole control).
6.2 auditd fallback (no eBPF)
Where Falco/eBPF is unavailable, an auditd watch on mem/environ opens plus process-context capture gives a coarser but usable signal:
-a always,exit -F arch=b64 -S openat -F success=1 -F path=/proc -k proc_mem_access
Filter downstream for resolved targets matching /proc/<pid>/mem and /proc/<pid>/environ where the accessor is not the target's own PID. Expect tuning cost; profile legitimate debuggers/monitors first.
6.3 CI-side signals
- Action integrity. Alert on any workflow referencing a third-party action by mutable tag rather than a full commit SHA; alert on a tag being repointed to a new commit (the tj-actions vector).
- Cache anomalies. Track cache write volume per run; a job that writes near the 10 GB cap (eviction stuffing) is anomalous. Alert on a cache key being rewritten by a job whose event/trust level differs from the job that normally populates it.
- Privileged-context untrusted checkout. Static-scan workflows for
pull_request_targetcombined with a checkout ofgithub.event.pull_request.heador unquoted${{ github.head_ref }}/${{ github.event.* }}expansions inrun:blocks.
6.4 Cloud-side signals
- AWS. In CloudTrail, alert on
AssumeRoleWithWebIdentityevents whose tokensubdoes not match the expected repo/ref/environment, whose source workflow is unexpected, or that originate outside normal deploy windows. Run IAM Access Analyzer with external-access findings and filter toFederatedprincipals pointing attoken.actions.githubusercontent.com; any role whosesubcondition matches more than one repo/branch pair is a narrowing candidate. - General. Alert on cloud credentials minted via OIDC being used from IPs or in API patterns inconsistent with the runner's expected behavior.
6.5 Detection matrix (stage → control)
| Attack stage | Primary control | Layer |
|---|---|---|
| Untrusted code in trusted context | pull_request_target/injection static scan | CI-side |
| Cache eviction stuffing | Per-run cache-write volume anomaly | CI-side |
| Poisoned entry restored by privileged job | Cache key rewrite across trust levels | CI-side |
Runner.Worker memory read | Cross-process /proc/*/mem read rule | Runtime |
environ harvesting | Foreign /proc/*/environ read rule | Runtime |
| Re-encode to defeat masking | Anomalous base64 on read output | Runtime |
| Network exfiltration | Egress allowlist violation | Runtime |
| OIDC → cloud pivot | AssumeRoleWithWebIdentity sub/aud mismatch | Cloud-side |
7. Mitigations
7.1 CI-side hardening
- Pin every third-party action to a full commit SHA, not a tag. This directly defeats the tag-repointing vector.
- Least-privilege
GITHUB_TOKEN. Setpermissions:explicitly at the top of each workflow; default tocontents: read; grantid-token: writeonly in the specific job that needs it. - Treat
pull_request_targetas dangerous. Never check out and execute PR head code in apull_request_targetworkflow. Never interpolategithub.event.*values directly into shell; pass them throughenv:and quote. - Do not cache across trust boundaries. The most robust defense against poisoning is to not restore caches in privileged/release workflows at all; do a cold install on release. The performance cost is negligible relative to a full trust compromise.
- Segregate trust zones. Keep untrusted-input workflows in a separate scope from release/deploy workflows so they cannot share a cache namespace or secrets.
- Ephemeral, isolated runners. Prefer single-use runners; for self-hosted, never run untrusted workflows on persistent runners. Apply a runtime agent (e.g., an egress-auditing / behavior-monitoring hardening step) with sudo disabled and an egress policy at minimum in audit mode, ideally in block mode for release workflows.
- Make logs private where feasible, to remove the log-as-exfil channel; treat this as defense-in-depth, not a boundary.
7.2 OIDC / cloud-side hardening
This is the chokepoint that limits blast radius even if a runner is fully compromised.
- Narrow the
subcondition. Trust a specific repo and ref/environment, e.g.repo:acme-org/payments-api:ref:refs/heads/mainor, better,...:environment:production. Replace any wildcardsubpatterns. - Never condition production trust on
pull_request. Anyone who can open a PR (including external contributors on public repos) runs under that identity. - Prefer protected environments over branch filters. Environments enforce approval rules and encode cleanly into the
subclaim. - Pin
aud. Require the expected audience (e.g.,sts.amazonaws.com). - Use additional claims (
repository_id,repository_owner_id,job_workflow_ref) for defense-in-depth on reusable-workflow setups where the defaultsubis ambiguous. - Minimize session scope and duration on the assumed role; grant only the permissions the deploy actually needs.
7.3 Cache-specific
- Assume any restored cache is attacker-controllable unless the cache is written and read entirely within a single trust level.
- Where caching is retained, scope keys tightly and monitor for cross-trust key rewrites and eviction stuffing (see §6.3).
8. US and EU compliance implications
The controls in §§6–7 are not only good engineering; for many organizations they are the difference between a defensible compliance posture and a reportable violation. This section maps the threat class onto the obligations that most often apply to software producers and their CI/CD. It is engineering-oriented guidance, not legal advice; scope and applicability are fact-specific.
The unifying point: a cache-poisoning-plus-memory-extraction compromise is simultaneously (a) a secure-development-lifecycle failure (build-environment integrity, third-party component trust) and (b) a reportable incident the moment stolen credentials are used or poisoned artifacts ship. Both halves are now regulated on both sides of the Atlantic.
8.1 United States
NIST SSDF (SP 800-218). The SSDF is the federal baseline for secure software development and remains foundational after the 2025 executive-order changes (below). This exact threat maps onto several SSDF practices: PO.5 (secure the build/CI environment and enforce separation of trust), PS.1–PS.3 (protect code and generate/verify provenance so tampered build inputs are detectable), PW.4 (reuse only well-secured third-party components, i.e., pin actions by SHA), and RV.1–RV.3 (vulnerability intake and response, i.e., a coordinated disclosure process and the ability to react to a compromised dependency such as tj-actions/changed-files). The §7 mitigations are, in effect, SSDF evidence.
The attestation regime and its 2025 reset. EO 14028 (2021) remains in force and drives the still-open FAR software-security case (Case 2023-002/0021). EO 14144 (Jan 16, 2025) had added machine-readable attestations, submission of compliance artifacts to CISA, CISA validation, and referral of failed attestations to the Attorney General. EO 14306 (June 6, 2025) then scaled much of that back: it pared the artifact-validation and RSAA-submission apparatus and the directive toward a mandatory FAR attestation clause, while explicitly retaining the SSDF as the standard and directing a NIST/NCCoE consortium plus an SSDF update (preliminary Dec 1, 2025; final by Mar 31, 2026). Net current state for a supplier: federal agencies still collect the CISA Secure Software Development Attestation (Common) Form grounded in SSDF; there is no finalized FAR clause programmatically mandating it; and the enforcement emphasis has shifted away from CISA artifact sampling. Treat the SSDF attestation as a live requirement whose enforcement mechanics are unsettled.
False Claims Act exposure. This is the sharp edge and it did not go away with EO 14306. Under the DOJ Civil Cyber-Fraud Initiative, attesting to secure-development practices you do not actually follow is an FCA risk. If a supplier signs an SSDF attestation while, say, running unpinned mutable action tags and restoring shared caches into release workflows, a subsequent cache-poisoning breach can convert an aspirational attestation into an alleged false claim. The §7 hardening is the substantiation that keeps an attestation truthful.
C-SCRM and control catalogs. NIST SP 800-161 (Cybersecurity Supply Chain Risk Management) and the SP 800-53 SR (Supply Chain Risk Management) and SA (System and Services Acquisition) control families cover exactly this: provenance, integrity of the development pipeline, and third-party component risk. Organizations under FedRAMP or SP 800-53 baselines should map cache-integrity and action-pinning controls to SR-3/SR-4/SR-11 and SA-11/SA-15.
CMMC (DoD suppliers). For any path involving DoD contracts or SBIR/xTech awards, CMMC is now contractual. The 32 CFR program rule took effect Dec 16, 2024; the 48 CFR/DFARS acquisition rule (DFARS Case 2019-D041) took effect Nov 10, 2025, making Phase 1 Level 1/Level 2 self-assessments and posted SPRS scores a pre-award condition on most new contracts (clause 252.204-7021, provision 252.204-7025). Phase 2's mandatory third-party Level 2 assessment (originally Nov 10, 2026) is on hold as of July 13, 2026 pending a DoD review; NIST SP 800-171 self-assessment is enforced during the hold. For CUI-handling pipelines, the relevant 800-171 controls include configuration management (CM), system/communications protection (SC), and system/information integrity (SI), under which build-pipeline integrity, secret protection, and runtime monitoring sit.
Incident-disclosure clocks. Public companies face SEC Form 8-K Item 1.05 disclosure of material cybersecurity incidents within four business days of a materiality determination; a CI/CD compromise that exposes signing keys or ships poisoned releases can meet that bar. Sector regimes add their own duties (e.g., HIPAA breach notification where health data is downstream of the stolen credentials) alongside state breach-notification statutes.
8.2 European Union
Cyber Resilience Act (Regulation (EU) 2024/2847). The CRA is binding law for "products with digital elements" placed on the EU market and is the most directly relevant EU regime for software producers. It phases in: in force since Dec 2024; conformity-assessment-body/notifying-authority provisions from June 11, 2026; Article 14 reporting of actively exploited vulnerabilities and severe incidents to ENISA and the relevant CSIRT from Sept 11, 2026, with a 24-hour early-warning; and the full essential requirements, including secure development, vulnerability handling (Annex I, Part II), and a machine-readable SBOM covering at least top-level dependencies, from Dec 11, 2027. A shipped poisoned artifact is precisely an "actively exploited" condition, and the mishandled-build-integrity failures are essential-requirement gaps. Penalties reach €15M or 2.5% of worldwide annual turnover, plus market withdrawal. Practical implication: the SBOM and coordinated-disclosure pipeline you build to answer "which releases contain the compromised component, and when" is both a §6/§7 detection artifact and CRA evidence. The Sept 2026 24-hour clock is unmeetable if detection relies on a maintainer noticing anomalies weeks later.
NIS2 Directive (Directive (EU) 2022/2555). For essential and important entities, NIS2 makes supply-chain security an explicit risk-management obligation (Article 21(2), including third-party and CI/CD security) and imposes staged incident reporting: a 24-hour early warning, a 72-hour incident notification, and a one-month final report. It also attaches accountability to management bodies. The transposition deadline was Oct 17, 2024; national implementation has been uneven, so applicability and exact wording vary by member state. A cache-poisoning supply-chain incident at a covered entity implicates both the Article 21 measures and the reporting timeline.
GDPR (Regulation (EU) 2016/679). Secrets themselves are usually not personal data, but the credentials stolen from runner memory frequently unlock systems that hold it. If the OIDC/cloud pivot leads to a personal-data breach, GDPR's 72-hour notification to the supervisory authority (Article 33) and, where high-risk, notification to data subjects (Article 34) are triggered; administrative fines reach up to €20M or 4% of global turnover for serious breaches. The credential-theft chain is thus a plausible predicate for a GDPR breach, not merely a security-hygiene issue.
DORA (Regulation (EU) 2022/2554). For EU financial entities and their ICT third-party providers (applicable since Jan 17, 2025), DORA imposes ICT risk-management, third-party-risk, and major-incident-reporting duties. A CI/CD tooling or software vendor selling into the EU financial sector may fall in scope as an ICT third-party provider, making pipeline integrity and incident reporting contractual and regulatory obligations rather than optional.
8.3 Cross-cutting: detection speed is a compliance control
Every regime above now runs on a short clock: SEC's four business days, NIS2's 24h/72h, the CRA's 24-hour early warning (from Sept 2026), and GDPR's 72 hours. None of these are satisfiable with a detection strategy that depends on a human eventually spotting odd build behavior. The runtime detection in §6 (cross-process /proc/*/mem and /proc/*/environ reads, egress and re-encoding anomalies, cloud-side AssumeRoleWithWebIdentity sub/aud mismatch alerts) is what makes the reporting windows achievable and the attestations truthful. In other words, the engineering controls and the compliance obligations are the same program viewed from two angles.
| Regime | Trigger relevant to this threat | Clock / duty |
|---|---|---|
| SEC 8-K Item 1.05 (US) | Material incident (e.g., shipped poisoned release, key exposure) | 4 business days after materiality determination |
| CMMC / DFARS 252.204-7021 (US DoD) | Pre-award posture; CUI pipeline integrity | Self-assessment + SPRS (Phase 1 live) |
| NIST SSDF attestation (US federal) | Secure-development attestation truthfulness | Ongoing; FCA exposure if false |
| CRA Art. 14 (EU) | Actively exploited vuln / severe incident | 24h early warning (from Sept 11, 2026) |
| NIS2 (EU) | Supply-chain incident at covered entity | 24h warning / 72h notice / 1-month report |
| GDPR Art. 33/34 (EU) | Personal-data breach via stolen credentials | 72h to supervisory authority |
| DORA (EU financial) | Major ICT incident at financial entity/provider | Staged major-incident reporting |
9. Conclusion
The GitHub Actions cache-poisoning-plus-memory-extraction vector is not a single bug; it is the composition of two structural properties: a cache shared across trust boundaries, and secrets held as readable plaintext in a process any job step can inspect. Log masking provides only a false sense of protection. The defensive posture that follows is therefore also structural: (1) do not execute untrusted code in privileged contexts, (2) do not restore caches across trust boundaries, (3) assume in-job code can read every secret and constrain what those secrets can do via tight OIDC trust conditions, and (4) instrument the runner itself, because the theft happens during the build where only runtime controls can see it. The single highest-leverage control is narrowing OIDC sub/aud trust conditions: it caps the blast radius of an otherwise total runner compromise.
References
- A. Khan, "The Monsters in Your Build Cache — GitHub Actions Cache Poisoning," 2024. https://adnanthekhan.com/2024/05/06/the-monsters-in-your-build-cache-github-actions-cache-poisoning/
- A. Khan, "Turning Almost Nothing into a Supply Chain Compromise of Angular with GitHub Actions Cache Poisoning," 2026. https://adnanthekhan.com/posts/angular-compromise-through-dev-infra/
- AdnaneKhan, "ActionsCacheBlasting" (research PoC / ARC token behavior). https://github.com/AdnaneKhan/ActionsCacheBlasting
- HackTricks Cloud, "GH Actions — Cache Poisoning" (cache v2 prefix-hit and forced-eviction behavior). https://cloud.hacktricks.wiki/en/pentesting-ci-cd/github-security/abusing-github-actions/gh-actions-cache-poisoning.html
- GitHub Security Advisory GHSA-mw4p-6x4p-x5m5 / CVE-2025-30066, "tj-actions/changed-files." https://github.com/tj-actions/changed-files/security/advisories/GHSA-mw4p-6x4p-x5m5
- Snyk, "Trivy GitHub Actions Supply Chain Compromise." https://snyk.io/articles/trivy-github-actions-supply-chain-compromise/
- Cycode, "GitHub Actions Supply Chain Attacks: How They Work and How to Stop Them." https://cycode.com/blog/github-actions-supply-chain-attack/
- StepSecurity, "Harden-Runner detection: tj-actions/changed-files action is compromised." https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised
- Sysdig, "tj-actions/changed-files with Falco Actions" (memory-dump detection rule). https://www.sysdig.com/blog/tj-actions-changed-files-with-falco-actions
- GitHub Docs, "OpenID Connect reference" and "About security hardening with OpenID Connect." https://docs.github.com/en/actions/reference/security/oidc
- Cloud Upload, "Hardening GitHub Actions OIDC trust policies on AWS." https://cloudupload.tech/blog/hardening-github-actions-oidc
- Hive Security, "The Cache That Bites Back: GitHub Actions Cache Poisoning Attacks." https://hivesecurity.gitlab.io/blog/github-actions-cache-poisoning-supply-chain/
- NIST, "SP 800-218: Secure Software Development Framework (SSDF)." https://csrc.nist.gov/projects/ssdf
- The White House, "EO on Strengthening and Promoting Innovation in the Nation's Cybersecurity" (EO 14144, Jan 16, 2025). https://bidenwhitehouse.archives.gov/briefing-room/presidential-actions/2025/01/16/executive-order-on-strengthening-and-promoting-innovation-in-the-nations-cybersecurity/
- WilmerHale, "New Executive Order Modifies Cybersecurity Requirements..." (EO 14306, June 6, 2025). https://www.wilmerhale.com/en/insights/client-alerts/20250613-new-executive-order-modifies-cybersecurity-requirements-to-be-imposed-on-federal-contractors-and-subcontractors
- Baker Tilly, "Navigating amendments to Executive Orders 13694 and 14144." https://www.bakertilly.com/insights/navigating-amendments-executive-orders-13694-14144
- CMMC final rule / 48 CFR effective date and phase status (Nov 10, 2025; Phase 2 hold as of July 13, 2026). https://www.encomputers.com/2025/03/cmmc-compliance-timeline-deadlines/
- Regulation (EU) 2024/2847 (Cyber Resilience Act) — SBOM, vulnerability handling, and phased timeline (Art. 14 reporting from Sept 11, 2026; essential requirements from Dec 11, 2027). https://sbomify.com/compliance/eu-cra/
- Directive (EU) 2022/2555 (NIS2) — supply-chain risk management (Art. 21) and incident reporting. https://eur-lex.europa.eu/eli/dir/2022/2555/oj
- Regulation (EU) 2016/679 (GDPR) — breach notification (Arts. 33–34). https://eur-lex.europa.eu/eli/reg/2016/679/oj
- Regulation (EU) 2022/2554 (DORA) — ICT risk management and third-party risk. https://eur-lex.europa.eu/eli/reg/2022/2554/oj
Prepared August 2026. All technical claims are sourced to public advisories, vendor disclosures, and published research; none derive from independent testing against affected systems. Compliance discussion in §8 is engineering-oriented guidance, not legal advice.