Add Package Manager Guard (PMG) to CI workflows - #61
Conversation
The thirdeye-frontend module is part of the Maven reactor and uses frontend-maven-plugin to shell out to yarn (install / build / test). Both CI workflows therefore install npm packages during the Maven build, so they are in scope for PMG's install-time malware blocking. Changes to maven.yml and thirdeye_tests.yml: - add `permissions: contents: read` - add safedep/pmg@v1 in server mode, placed after setup-java so the JDK download is not routed through the proxy - add a final `pmg proxy stop --fail-on-violation` step with `if: always()` In thirdeye_tests.yml the enforce step is deliberately kept last, after the Codecov upload: stopping the proxy leaves HTTP_PROXY set in the job environment, so any network step ordered after it would point at a dead proxy. Note that PMG does not support Maven, so this guards the npm/yarn side only; jars resolved from Maven Central are unaffected (the JVM ignores HTTP_PROXY). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| # routed through the proxy. `mvn test` drives yarn install / yarn run test | ||
| # in the thirdeye-frontend module, which is what gets guarded here. | ||
| - name: Set up Package Manager Guard | ||
| uses: safedep/pmg@v1 |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
GitHub Actions step uses mutable version tag @v1 instead of pinned commit SHA, allowing tag to be silently repointed to malicious code and stealing repository secrets.
More details about this
The GitHub Actions step uses safedep/pmg@v1, which pins the action to a mutable version tag instead of a specific commit. An attacker who controls the safedep/pmg repository could silently repoint the v1 tag to malicious code without any notification. When this workflow runs, it would execute the attacker's code with access to your repository secrets (including SAFEDEP_API_KEY and SAFEDEP_TENANT_ID visible in the with block), allowing them to exfiltrate credentials, modify your codebase, or compromise your build artifacts.
Here's a concrete attack scenario:
- Attacker compromises the safedep organization and gains write access to the pmg repository
- Attacker force-pushes malicious code to the commit that
v1tag currently points to, or moves thev1tag to a new malicious commit - Your next workflow run fetches the updated
v1tag and executes the attacker's code - The malicious code reads
secrets.SAFEDEP_API_KEYandsecrets.SAFEDEP_TENANT_IDfrom the step environment and sends them to an attacker-controlled server - Attacker now has valid credentials to access your supply chain security scanning infrastructure
To resolve this comment:
✨ Commit fix suggestion
| uses: safedep/pmg@v1 | |
| uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1 |
View step-by-step instructions
- Replace the mutable GitHub Action reference
uses: safedep/pmg@v1with a full 40-character commit SHA for the exactv1release you intend to trust, for exampleuses: safedep/pmg@<full-commit-sha>. - Keep the version as a comment so the workflow stays readable, for example
uses: safedep/pmg@<full-commit-sha> # v1. - Get the correct SHA from the
safedep/pmgrepository’sv1tag or release page, and make sure it is the commit that currently backs that version. Pinning to a commit SHA prevents the action owner from silently changing what runs later under the same tag.
💬 Ignore this finding
Leave a nosemgrep comment directly above or at the end of line 45 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.
| # routed through the proxy. The thirdeye-frontend module shells out to yarn | ||
| # via frontend-maven-plugin, so the Maven build below is what gets guarded. | ||
| - name: Set up Package Manager Guard | ||
| uses: safedep/pmg@v1 |
There was a problem hiding this comment.
Semgrep identified an issue in your code:
GitHub Actions step uses a mutable version tag (v1) which can be silently repointed by the action owner, enabling supply-chain attacks and exposing passed secrets.
More details about this
The step uses safedep/pmg@v1, which references the action using a mutable version tag (v1) instead of a pinned commit SHA. If the maintainer of safedep/pmg updates the v1 tag to point to a different commit, your workflow will silently run the new code without any notification or approval. An attacker who compromises the safedep/pmg repository could push malicious code, update the v1 tag, and inject it into your CI/CD pipeline—potentially stealing your secrets (SAFEDEP_API_KEY and SAFEDEP_TENANT_ID are passed to this step), exfiltrating source code, or deploying compromised artifacts.
For example:
- Attacker gains access to the
safedep/pmgrepository - Attacker modifies the action code to exfiltrate
${{ secrets.SAFEDEP_API_KEY }}and uploads it to an attacker-controlled server - Attacker force-pushes this malicious code to the repository and updates the
v1tag to point to the new commit - Your next workflow run automatically uses the malicious code because
v1now points to the attacker's commit - The step executes with access to your secrets and the attacker receives your API key
To resolve this comment:
🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.
💬 Ignore this finding
Leave a nosemgrep comment directly above or at the end of line 47 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.
CI result:
|
| Step | Result |
|---|---|
| Set up Package Manager Guard | ✅ success |
Unit Test (mvn test) |
❌ failure |
| Upload coverage to Codecov | ⏭️ skipped |
| Enforce PMG policy | ✅ success |
PMG worked correctly
From the Maven reactor summary:
thirdeye-frontend .......... SUCCESS [04:06 min]
thirdeye-spi ............... SUCCESS [ 22.628 s]
thirdeye-pinot ............. FAILURE [01:55 min]
thirdeye-frontend — the only module PMG touches — built successfully in 4m06s with the proxy active, meaning yarn install and yarn run test both completed through PMG. And pmg proxy stop --fail-on-violation exited clean, so PMG found zero violations across the 2098-entry lockfile. The "PMG may block a package" risk in the description did not materialise.
The actual failure is pre-existing
thirdeye-pinot fails at compile, in Java code PMG never sees:
[ERROR] AnomalyGraphGenerator.java:[126,30] cannot access org.jfree.ui.Drawable
class file for org.jfree.ui.Drawable not found
[ERROR] AnomalyGraphGenerator.java:[127,9] cannot access org.jfree.util.PublicCloneable
class file for org.jfree.util.PublicCloneable not found
Those classes live in jcommon, a transitive dependency of jfreechart 1.0.19 (declared in the root POM, no exclusions). It is a Maven dependency-resolution problem, and Maven's jar resolution does not pass through PMG at all — the JVM ignores HTTP_PROXY. A broken proxy would also surface as a transfer/resolve error, not a missing-class compile error.
Supporting evidence: the two prior PR runs of this workflow (Dec 2025, security/sca-fix-…) also failed. Last green run was Sept 2025. I could not diff root causes — those logs are past GitHub's 90-day retention — so I'll only claim the workflow was already red, not that it was red for this same reason.
Fixing the jfreechart/jcommon break is out of scope for this PR.
Two things still unverified
- The Codecov step never ran (skipped after the test failure), so whether
curlworks through the PMG proxy is still untested. It'll be exercised on the first run where tests pass. maven.ymlisdisabled_inactivity— GitHub auto-disabled it after 60 days of repo inactivity because of itsschedule:trigger, and it has no run history. The PMG step added there is correct but inert until someone re-enables the workflow.
What
Adds SafeDep PMG install-time malware blocking to the two CI workflows that install packages.
maven.ymlthirdeye_tests.ymlthirdeye_tests-workflow-run.ymlcancel-workflow-runs, installs nothingEach modified workflow gets:
permissions: contents: readsafedep/pmg@v1withserver-mode: true, placed aftersetup-javaso the JDK download isn't routed through the proxypmg proxy stop --fail-on-violationstep withif: always()Why this repo is in scope
It looks like a pure Java/Maven repo, and PMG does not support Maven — so the initial read was "out of scope."
It isn't.
thirdeye-frontendis a module in the root reactor, andthirdeye-frontend/pom.xmlusesfrontend-maven-pluginto shell out toyarn install,yarn run build, andyarn run test. So both workflows install npm packages during the Maven build — just indirectly. PMG's server mode catches this because it exportsHTTP_PROXYand those env vars propagate to the child yarn process.Coverage limits — please read
This guards the npm/yarn side only. Maven's own jar resolution from Central is not covered: PMG works via
HTTP_PROXY, and the JVM ignores that env var by default. Five of six modules and the entire Java dependency graph are untouched by this change. Covering those needs a manifest scanner (SafeDepvetsupports Maven) — out of scope here.Risks
PMG may block a package and fail the build.
thirdeye-frontend/yarn.lockis a 2021-era tree with 2098 entries. Spot-checkedrc(pinned 1.2.8) andfaker(pinned 3.1.0) — both clean, predating their respective incidents. That's 2 of 2098, so if anything else is flagged in SafeDep's threat intel the job fails. That's PMG working as intended, but expect it.Codecov step.
bash <(curl -s https://codecov.io/bash)now routes through the PMG proxy, since curl honorsHTTP_PROXY. Expected to pass through fine, but it's the one behavior not confirmed from the docs.thirdeye_tests.yml. Stopping the proxy leavesHTTP_PROXYset in the job env, so any network step ordered after it would point at a dead proxy. There's an inline comment saying so.Notes
SAFEDEP_API_KEY/SAFEDEP_TENANT_IDaren't configured they resolve to empty strings (the action's default) and PMG runs local-only.maven.ymltriggers onpull_request, and fork PRs never receive secrets, so those runs are always local-only regardless.safedep/pmg@v1is a moving tag. This repo pinspotiuk/cancel-workflow-runsto a full SHA — worth pinning PMG the same way if that's a deliberate convention.Pre-existing issue, not introduced here
Both workflows use
actions/checkout@v2andactions/setup-java@v1, which are effectively EOL. CI may already be failing for this reason — worth confirming the baseline before attributing any red run to PMG.🤖 Generated with Claude Code