You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every third-party action is referenced by a mutable ref (major-version tag, named release tag, or a Docker :latest tag) rather than a pinned commit SHA. The affected workflows are exactly the ones that receive the most sensitive secrets and privileges: code-lint-and-prettify.yml feeds the GitHub App private key (secrets.VILNACRM_APP_PRIVATE_KEY) to tibdex/github-app-token@v2 and the code-signing key (secrets.GPG_PRIVATE_KEY / GPG_PASSPHRASE) to crazy-max/ghaction-import-gpg@v6, then runs super-linter/super-linter@v7.1.0 with FIX_* autofix and pushes a GPG-signed commit back to the PR branch via stefanzweifel/git-auto-commit-action@v5. template-sync.yml and autorelease.yml hand the App token to AndreasAugustin/actions-template-sync@v2, TriPSs/conventional-changelog-action@v5 and actions/create-release@v1. graphql-diff.yml uses kamilkisiela/graphql-inspector@release-1701263349990 (a moveable release ref) and openapi-diff.yml uses uses: docker://openapitools/openapi-diff:latest (a mutable image tag pulled at runtime). Because tags/branches/:latest are re-pointable, any upstream repo takeover, tag-move, or malicious release of any of these actions executes attacker code inside a job holding the org's App private key, the GPG signing key, and contents:write.
Severity: MEDIUM • Category: CICD-SEC-4 Poisoned Pipeline Execution / CICD-SEC-3 Dependency Chain Abuse (unpinned actions) Found by an authorized automated adversarial pentest loop and confirmed by 2 independent skeptic verifiers (unanimous).
Attacker compromises (or performs a tag/release re-point on) any one of the pinned-by-tag third-party actions above (e.g. stefanzweifel/git-auto-commit-action's v5 tag, or the openapitools/openapi-diff:latest image). 2. On the next PR/push/schedule, the runner resolves the mutable ref to the attacker's code. 3. In code-lint-and-prettify.yml the malicious step reads the already-imported GPG private key from the agent and $GPG_PASSPHRASE (written to GITHUB_ENV) plus steps.generate_token.outputs.token and exfiltrates them (e.g. curl to attacker host), or uses the in-job contents:write token to push a signed commit to a protected branch. 4. Result: theft of the VilnaCRM GitHub App private key (org-wide contents:write) and the commit-signing key, plus ability to push verified/signed malicious commits. No pinning + no allowed-actions policy means there is no second control.
Suggested remediation
Pin every third-party action to a full 40-char commit SHA (keep the human tag as a trailing comment), replace docker://openapitools/openapi-diff:latest with a digest-pinned image, enable Dependabot for github-actions to bump the SHAs, and turn on the repo/org 'Allow select actions' + 'require actions pinned to SHA' policy. Move secret-handling into a minimal, isolated job.
Verification notes
Verifier 1 (CONFIRMED, exploitable=true, duplicate=false): Verified all cited lines. Every third-party action is pinned to a mutable ref, not a commit SHA: code-lint-and-prettify.yml uses tibdex/github-app-token@v2 (line 19, receives secrets.VILNACRM_APP_PRIVATE_KEY line 22), crazy-max/ghaction-import-gpg@v6 (line 34, receives GPG_PRIVATE_KEY/GPG_PASSPHRASE lines 36-37), super-linter/super-linter@v7.1.0 (line 91, FIX_* autofix + generate_token output line 105), and stefanzweifel/git-auto-commit-action@v5 (line 112, pushes a GPG-signed commit). Critically GPG_PASSPHRASE is exported to GITHUB_ENV on line 50, so any subsequent step in that job (including a compromised action) can read it, plus the imported GPG secret key material and steps.generate_token.outputs.token. template-sync.yml pins tibdex/github-app-token@v2 (14) and AndreasAugustin/actions-template-sync@v2 (25) with the App token on a schedule. autorelease.yml pins tibdex/github-app-token@v1 (15), TriPSs/conventional-changelog-action@v5 (26), actions/create-release@v1 (32) with the App token on push-to-main. graphql-diff.yml pins kamilkisiela/graphql-inspector@release-1701263349990 (67, a moveable release ref) and EndBug/add-and-commit@v9 (60) in a job with contents:write. openapi-diff.yml uses docker://openapitools/openapi-diff:latest (70, mutable image tag pulled at runtime). codecov.yml pins codecov/codecov-action@v4 (49, receives CODECOV_TOKEN). No SHA pinning is present anywhere and there is no in-repo allowed-actions restriction acting as a second control, so nothing mitigates a mutable-ref resolution. The privileged secrets are genuinely reachable by the unpinned actions on internal-branch PRs, push-to-main, and scheduled runs (fork PRs are excluded from secrets but that does not close the path). This matches OWASP CICD-SEC-4/SEC-3 and GitHub's own hardening guidance to pin to full-length SHAs. It is materially different from known issues [Security] critical: OAuth2/JWT access tokens silently escalate to ROLE_SERVICE; issuer/audience validation skipped; session-revocation bypassed #312-[Security] low: Account-lockout failure counter uses non-atomic read-modify-write (race allows extra guesses) #324 (none address action pinning / supply-chain). The sole caveat: exploitation requires an external precondition (upstream action takeover, tag/release re-point, or malicious :latest push), which is inherent to this supply-chain finding class rather than a mitigation in the present code; the vulnerable configuration itself is present, reachable, and unguarded. Severity medium is appropriate given the sensitivity (org-wide App private key with contents:write plus commit-signing GPG key). Should reference NFR umbrella Enterprise readiness audit: close remaining gaps to reach 5/5 on all quality attributes and NFRs #348/[NFR] Accessibility — score 3/5: HTML-only emails, untranslated/typo'd confirmation template, no lang attributes, recipient locale not applied #349-441 while remaining its own actionable issue.
Verifier 2 (CONFIRMED, exploitable=true, duplicate=false): All cited references verified in the actual files. code-lint-and-prettify.yml pins tibdex/github-app-token@v2 (fed secrets.VILNACRM_APP_PRIVATE_KEY), crazy-max/ghaction-import-gpg@v6 (fed GPG_PRIVATE_KEY/GPG_PASSPHRASE), super-linter/super-linter@v7.1.0 (autofix + App token), and stefanzweifel/git-auto-commit-action@v5 (pushes GPG-signed commit) — all mutable tags. GPG_PASSPHRASE is also written to GITHUB_ENV, exposing it to any subsequent step in the job. template-sync.yml pins tibdex/github-app-token@v2 and AndreasAugustin/actions-template-sync@v2 (App token). autorelease.yml pins tibdex/github-app-token@v1, TriPSs/conventional-changelog-action@v5, actions/create-release@v1. graphql-diff.yml uses kamilkisiela/graphql-inspector@release-1701263349990 (moveable release ref) plus EndBug/add-and-commit@v9 with contents:write. openapi-diff.yml uses docker://openapitools/openapi-diff:latest (mutable image). codecov.yml uses codecov/codecov-action@v4 with CODECOV_TOKEN. There is no SHA pinning, no least-privilege permissions block on the secret-bearing lint job, and no allowed-actions policy — so no second control exists. This is a genuine CICD-SEC-3/4 unpinned-dependency exposure: a takeover/tag-move/malicious release of any of these actions runs attacker code in a job holding the org GitHub App private key, the GPG signing key, and write privileges. It is reachable in the present code (workflows run on PR/push/schedule) and is not mitigated by any existing hardening. Not a duplicate of [Security] critical: OAuth2/JWT access tokens silently escalate to ROLE_SERVICE; issuer/audience validation skipped; session-revocation bypassed #312-[Security] low: Account-lockout failure counter uses non-atomic read-modify-write (race allows extra guesses) #324 (all application-layer auth/crypto). Concrete and specific enough to warrant its own actionable issue while referencing the NFR umbrella. Severity medium is appropriate.
Related
Part of the enterprise security-hardening effort — umbrella tracker #348; security NFR issues #426 (securability), #441 (vulnerability), #362 (confidentiality), #389 (integrity). Prior security wave: #312–#324. Not a duplicate of the already-fixed items in that range.
Acceptance = the exploit path is closed AND a regression test (unit/Behat/Schemathesis) proves it stays closed.
Summary
Every third-party action is referenced by a mutable ref (major-version tag, named release tag, or a Docker :latest tag) rather than a pinned commit SHA. The affected workflows are exactly the ones that receive the most sensitive secrets and privileges: code-lint-and-prettify.yml feeds the GitHub App private key (secrets.VILNACRM_APP_PRIVATE_KEY) to tibdex/github-app-token@v2 and the code-signing key (secrets.GPG_PRIVATE_KEY / GPG_PASSPHRASE) to crazy-max/ghaction-import-gpg@v6, then runs super-linter/super-linter@v7.1.0 with FIX_* autofix and pushes a GPG-signed commit back to the PR branch via stefanzweifel/git-auto-commit-action@v5. template-sync.yml and autorelease.yml hand the App token to AndreasAugustin/actions-template-sync@v2, TriPSs/conventional-changelog-action@v5 and actions/create-release@v1. graphql-diff.yml uses kamilkisiela/graphql-inspector@release-1701263349990 (a moveable release ref) and openapi-diff.yml uses uses: docker://openapitools/openapi-diff:latest (a mutable image tag pulled at runtime). Because tags/branches/
:latestare re-pointable, any upstream repo takeover, tag-move, or malicious release of any of these actions executes attacker code inside a job holding the org's App private key, the GPG signing key, and contents:write.Severity: MEDIUM • Category: CICD-SEC-4 Poisoned Pipeline Execution / CICD-SEC-3 Dependency Chain Abuse (unpinned actions)
Found by an authorized automated adversarial pentest loop and confirmed by 2 independent skeptic verifiers (unanimous).
Affected code / location
.github/workflows/code-lint-and-prettify.yml:19,34,91,112; template-sync.yml:14,25; autorelease.yml:15,26,32; graphql-diff.yml:60,67; openapi-diff.yml:50,70; codecov.yml:49
Exploit scenario
Suggested remediation
Pin every third-party action to a full 40-char commit SHA (keep the human tag as a trailing comment), replace docker://openapitools/openapi-diff:latest with a digest-pinned image, enable Dependabot for github-actions to bump the SHAs, and turn on the repo/org 'Allow select actions' + 'require actions pinned to SHA' policy. Move secret-handling into a minimal, isolated job.
Verification notes
Related
Part of the enterprise security-hardening effort — umbrella tracker #348; security NFR issues #426 (securability), #441 (vulnerability), #362 (confidentiality), #389 (integrity). Prior security wave: #312–#324. Not a duplicate of the already-fixed items in that range.
Acceptance = the exploit path is closed AND a regression test (unit/Behat/Schemathesis) proves it stays closed.