ci: integrate SafeDep PMG package guard into all workflow jobs - #488
ci: integrate SafeDep PMG package guard into all workflow jobs#488vanshika-verma-rzp wants to merge 4 commits into
Conversation
Route package installs through PMG's persistent proxy to auto-block malicious packages at install time across every CI job. - node.js.yml: build job - ci.yml: test job - ci.yml: publish-npm job Each job starts safedep/pmg@v1 in server-mode after checkout and ends with 'pmg proxy stop --fail-on-violation' (if: always()) to enforce the policy and flush events.
| steps: | ||
| - uses: actions/checkout@v4 | ||
| # Start PMG in server mode to block malicious packages at install time | ||
| - uses: safedep/pmg@v1 |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
The safedep/pmg@v1 step trusts a mutable tag and can silently change between workflow runs. A repointed tag could execute code with access to the PMG secrets and modify dependencies or build artifacts.
More details about this
The workflow starts the safedep/pmg GitHub Action with the mutable v1 tag. The action owner can move v1 to a different commit without changing this workflow, so a future run may execute attacker-controlled action code before npm install and before the later build and test steps. Because this step receives secrets.SAFEDEP_API_KEY and secrets.SAFEDEP_TENANT_ID, that code could read those values, send them to an external server, modify the workspace or package.json, and tamper with dependencies or build artifacts.
A plausible attack is:
- An attacker compromises the
safedeprepository or obtains permission to update itsv1tag. - They repoint
v1to a malicious commit and wait for a push or pull-request workflow run on this repository. - The
uses: safedep/pmg@v1step runs the malicious code with the workflow's token and access toSAFEDEP_API_KEYandSAFEDEP_TENANT_ID. - The malicious action exfiltrates those secrets, then alters files used by
npm install,npm run build --if-present, ornpm testto introduce a backdoor into the resulting package or build output.
To resolve this comment:
✨ Commit fix suggestion
| - uses: safedep/pmg@v1 | |
| # Replace the placeholder with the verified 40-character commit SHA for the v1 release. | |
| - uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1 |
View step-by-step instructions
- Resolve the trusted commit SHA for the
v1release ofsafedep/pmg. - Replace the mutable tag with that exact 40-character commit SHA, while keeping the version comment for readability:
uses: safedep/pmg@<40-character-commit-sha> # v1 - Keep the existing
withconfiguration unchanged. Pinning the action prevents the referenced code from changing silently if thev1tag is moved.
💬 Ignore this finding
Leave a nosemgrep comment directly above or at the end of line 25 like so // nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.
You can view more details about this finding in the Semgrep AppSec Platform.
| steps: | ||
| - uses: actions/checkout@v4 | ||
| # Start PMG in server mode to block malicious packages at install time | ||
| - uses: safedep/pmg@v1 |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
Both jobs execute safedep/pmg@v1, whose tag can be silently repointed to attacker-controlled code. The action runs with SafeDep credentials and, in publish-npm, npm publishing secrets, allowing secret theft or unauthorized package releases.
More details about this
The test and publish-npm jobs run safedep/pmg@v1, where v1 is a mutable tag rather than an immutable action revision. The action executes with SAFEDEP_API_KEY and SAFEDEP_TENANT_ID; in publish-npm, it also runs before npm publish with access to NPM_TOKEN and NPM_SECRET_KEY.
A plausible attack is:
- An attacker compromises the
safedep/pmgrepository or its release process and moves thev1tag to a malicious commit. - A normal test or version-tag push starts either job, and GitHub fetches the new code behind
safedep/pmg@v1. - The malicious action runs inside the workflow and can read the
SAFEDEP_*secrets; duringpublish-npm, it can also exfiltrateNPM_TOKENandNPM_SECRET_KEYor alter the workspace beforenpm publish. - Using the stolen npm credentials, the attacker can publish unauthorized package versions or otherwise abuse the package's release identity.
To resolve this comment:
✨ Commit fix suggestion
| - uses: safedep/pmg@v1 | |
| - uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1 |
View step-by-step instructions
- Resolve the trusted commit SHA for the
safedep/pmgv1release from the action’s official repository or release metadata. - Replace both mutable references with the same 40-character commit SHA, keeping the version comment for readability:
uses: safedep/pmg@<40-character-commit-sha> # v1 - Verify that the pinned commit provides the required
server-mode,api-key, andtenant-idinputs before applying it to both the test andpublish-npmjobs.
💬 Ignore this finding
Leave a nosemgrep comment directly above or at the end of line 55 like so // nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.
You can view more details about this finding in the Semgrep AppSec Platform.
| steps: | ||
| - uses: actions/checkout@v4 | ||
| # Start PMG in server mode to block malicious packages at install time | ||
| - uses: safedep/pmg@v1 |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
The CI workflow runs safedep/pmg from the mutable v1 tag, so a retag or compromised action release can execute attacker-controlled code. The action receives SafeDep secrets and runs before dependency installation and other build steps, creating a path to credential theft and build tampering.
More details about this
uses: safedep/pmg@v1 runs the safedep/pmg action from the mutable v1 tag. Because that tag can be moved by the action owner—or by an account that compromises the repository—the workflow may execute different code on a later run without any change to this repository.
A plausible attack is:
- An attacker compromises the
safedep/pmgrelease account and movesv1to a malicious commit. - A push to
masteror a pull request starts thetestjob, and GitHub resolvessafedep/pmg@v1to that malicious commit. - The action runs before
npm install, with access tosecrets.SAFEDEP_API_KEYandsecrets.SAFEDEP_TENANT_IDthrough itswithinputs. Malicious action code could exfiltrate those values and alter the runner environment or dependencies used by the laternpm install, tests, and coverage steps. - The stolen SafeDep credentials could then be used to access or abuse the associated tenant outside this workflow, while the compromised runner could tamper with build or test artifacts.
To resolve this comment:
✨ Commit fix suggestion
| - uses: safedep/pmg@v1 | |
| - uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1 |
View step-by-step instructions
- Resolve
safedep/pmgversionv1to its trusted 40-character commit SHA from the official repository or release history. - Replace the mutable tag in the workflow with that commit SHA and retain the version as a comment, for example:
uses: safedep/pmg@<40-character-commit-sha> # v1 - Apply the same full-SHA pinning to any other third-party actions in this workflow that use tags such as
@v4or@v3, includingactions/checkout,actions/setup-node, andcodecov/codecov-action.
💬 Ignore this finding
Leave a nosemgrep comment directly above or at the end of line 22 like so // nosemgrep: yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag
Take care to validate that this is not a true positive finding before ignoring it.
Learn more about ignoring code, files and folders here.
You can view more details about this finding in the Semgrep AppSec Platform.
| api-key: ${{ secrets.PMG_PUBLIC_REPOS_TOKEN }} | ||
| tenant-id: ${{ secrets.PMG_TENANT_ID }} | ||
|
|
||
| - uses: actions/setup-node@v4 |
There was a problem hiding this comment.
GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-github-action compromises. Pin the reference to a full 40-character commit SHA instead, e.g. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608.
🧹 Fixed in commit 9427ddf 🧹
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Setup PMG proxy | ||
| uses: safedep/pmg@v1 |
There was a problem hiding this comment.
GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-github-action compromises. Pin the reference to a full 40-character commit SHA instead, e.g. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608.
⭐ Removed in commit d54d503 ⭐
| api-key: ${{ secrets.PMG_PUBLIC_REPOS_TOKEN }} | ||
| tenant-id: ${{ secrets.PMG_TENANT_ID }} | ||
|
|
||
| - uses: actions/setup-node@v4 |
There was a problem hiding this comment.
GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-github-action compromises. Pin the reference to a full 40-character commit SHA instead, e.g. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608.
⭐ Fixed in commit 9427ddf ⭐
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Setup PMG proxy | ||
| uses: safedep/pmg@v1 |
There was a problem hiding this comment.
GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-github-action compromises. Pin the reference to a full 40-character commit SHA instead, e.g. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608.
🧁 Removed in commit d54d503 🧁
Brings this repo onto the same integration used in ai-playbook and i18nify, with the enforcement fix from blade. - Setup step renamed to "Setup PMG proxy" and given `id: pmg-setup`, so the enforce step can tell whether setup actually ran. - Enforce step runs `--fail-on-violation` only when setup succeeded. With a bare `if: always()`, any failure before the PMG step makes GitHub skip setup while still running enforce, which then dies with `pmg: command not found` (exit 127) and buries the real error. - Removed additions that are not part of the reference integration: `permissions:` blocks, workflow comments, pinned action SHAs and non-standard step names. - Added pmg-test.yml, byte-identical to the copy in ai-playbook and i18nify, which demonstrates the proxy blocking a known-malicious package and syncing the event to SafeDep Cloud. The workflow files are now the master versions plus the two PMG steps and nothing else: 45 lines added, none removed or modified. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
| run: | | ||
| curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash | ||
| export NVM_DIR="$HOME/.nvm" | ||
| [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" | ||
| nvm install 20 | ||
| echo "$NVM_DIR/versions/node/$(nvm version 20)/bin" >> $GITHUB_PATH | ||
|
|
There was a problem hiding this comment.
A run: step pipes the output of curl or wget directly into a shell interpreter. This is the "curl | bash" install pattern — if the remote server is compromised or the URL is hijacked, an attacker can execute arbitrary code in your CI runner. Consider downloading the file first, verifying its checksum or signature, and then executing it.
🧼 Removed in commit d54d503 🧼
| run: | | ||
| curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash | ||
| export NVM_DIR="$HOME/.nvm" | ||
| [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" | ||
| nvm install 20 | ||
| echo "$NVM_DIR/versions/node/$(nvm version 20)/bin" >> $GITHUB_PATH | ||
|
|
There was a problem hiding this comment.
A run: step pipes the output of curl or wget directly into a shell interpreter. This is the "curl | bash" install pattern — if the remote server is compromised or the URL is hijacked, an attacker can execute arbitrary code in your CI runner. Consider downloading the file first, verifying its checksum or signature, and then executing it.
🚀 Removed in commit d54d503 🚀
pmg-test.yml was added alongside the PMG integration purely to prove the proxy behaves correctly inside this repository's own CI environment. It ran two jobs: one installing a known-clean package to confirm PMG does not block legitimate traffic, and one installing the deliberately-flagged [email protected] to confirm the block is caught and `pmg proxy stop --fail-on-violation` fails the job as intended. That validation is now complete across every repository in this rollout, so the workflow has served its purpose. Leaving it in place would mean a permanent CI job that installs a deliberately-flagged package on every push and pull request - burning runner time and producing a red check that is expected-to-fail, which is exactly the kind of noise that trains people to ignore CI signal. The PMG integration itself is untouched. The safedep/pmg setup steps and the `pmg proxy stop --fail-on-violation` enforcement steps in this repository's real build and test workflows remain exactly as they were. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Summary
Integrates SafeDep PMG (Package Manager Guard) into all CI jobs so that package installs are routed through PMG's persistent proxy, which auto-blocks any package flagged as malicious at install time.
In CI, PMG is non-interactive: flagged packages are always blocked (never prompted), and the run fails on a violation.
Changes
.github/workflows/node.js.ymlbuildnpm install.github/workflows/ci.ymltestnpm install.github/workflows/ci.ymlpublish-npmnpm ciEvery job now follows the documented pattern:
safedep/pmg@v1inserver-mode: true, placed right afteractions/checkoutand before language setup / installs so installs are intercepted automatically.pmg proxy stop --fail-on-violationstep withif: always(), which fails the job on a block and flushes events even if an earlier step failed.Credentials are wired via repo secrets
SAFEDEP_API_KEYandSAFEDEP_TENANT_IDto link runs to SafeDep Cloud.Scope note
Prerequisites before merge
Two repository secrets should be configured for cloud event sync:
SAFEDEP_API_KEYSAFEDEP_TENANT_IDVerification
git diffreviewed: additions are limited strictly to the PMG start/stop steps.Discussion thread: Slack