Skip to content

Add Package Manager Guard (PMG) to CI workflows - #61

Open
vanshika-verma-rzp wants to merge 1 commit into
masterfrom
security/add-pmg-workflows
Open

Add Package Manager Guard (PMG) to CI workflows#61
vanshika-verma-rzp wants to merge 1 commit into
masterfrom
security/add-pmg-workflows

Conversation

@vanshika-verma-rzp

Copy link
Copy Markdown

What

Adds SafeDep PMG install-time malware blocking to the two CI workflows that install packages.

Workflow Change
maven.yml PMG added
thirdeye_tests.yml PMG added
thirdeye_tests-workflow-run.yml Not changed — only runs cancel-workflow-runs, installs nothing

Each modified workflow gets:

  • permissions: contents: read
  • safedep/pmg@v1 with server-mode: true, placed after setup-java so the JDK download isn't routed through the proxy
  • a final pmg proxy stop --fail-on-violation step with if: 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-frontend is a module in the root reactor, and thirdeye-frontend/pom.xml uses frontend-maven-plugin to shell out to yarn install, yarn run build, and yarn run test. So both workflows install npm packages during the Maven build — just indirectly. PMG's server mode catches this because it exports HTTP_PROXY and 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 (SafeDep vet supports Maven) — out of scope here.

Risks

  1. PMG may block a package and fail the build. thirdeye-frontend/yarn.lock is a 2021-era tree with 2098 entries. Spot-checked rc (pinned 1.2.8) and faker (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.

  2. Codecov step. bash <(curl -s https://codecov.io/bash) now routes through the PMG proxy, since curl honors HTTP_PROXY. Expected to pass through fine, but it's the one behavior not confirmed from the docs.

⚠️ The enforce step must stay last in thirdeye_tests.yml. Stopping the proxy leaves HTTP_PROXY set 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 secrets are optional — if SAFEDEP_API_KEY / SAFEDEP_TENANT_ID aren't configured they resolve to empty strings (the action's default) and PMG runs local-only. maven.yml triggers on pull_request, and fork PRs never receive secrets, so those runs are always local-only regardless.
  • PMG's dependency cooldown defaults to on (5 days). The lockfile is committed and pinned, so this shouldn't fire.
  • safedep/pmg@v1 is a moving tag. This repo pins potiuk/cancel-workflow-runs to 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@v2 and actions/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

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Attacker compromises the safedep organization and gains write access to the pmg repository
  2. Attacker force-pushes malicious code to the commit that v1 tag currently points to, or moves the v1 tag to a new malicious commit
  3. Your next workflow run fetches the updated v1 tag and executes the attacker's code
  4. The malicious code reads secrets.SAFEDEP_API_KEY and secrets.SAFEDEP_TENANT_ID from the step environment and sends them to an attacker-controlled server
  5. Attacker now has valid credentials to access your supply chain security scanning infrastructure

To resolve this comment:

✨ Commit fix suggestion

Suggested change
uses: safedep/pmg@v1
uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1
View step-by-step instructions
  1. Replace the mutable GitHub Action reference uses: safedep/pmg@v1 with a full 40-character commit SHA for the exact v1 release you intend to trust, for example uses: safedep/pmg@<full-commit-sha>.
  2. Keep the version as a comment so the workflow stays readable, for example uses: safedep/pmg@<full-commit-sha> # v1.
  3. Get the correct SHA from the safedep/pmg repository’s v1 tag 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Attacker gains access to the safedep/pmg repository
  2. Attacker modifies the action code to exfiltrate ${{ secrets.SAFEDEP_API_KEY }} and uploads it to an attacker-controlled server
  3. Attacker force-pushes this malicious code to the repository and updates the v1 tag to point to the new commit
  4. Your next workflow run automatically uses the malicious code because v1 now points to the attacker's commit
  5. 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.

@vanshika-verma-rzp

Copy link
Copy Markdown
Author

CI result: unit-test failed, but not because of PMG

Step-level breakdown of run 31871890792:

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

  1. The Codecov step never ran (skipped after the test failure), so whether curl works through the PMG proxy is still untested. It'll be exercised on the first run where tests pass.
  2. maven.yml is disabled_inactivity — GitHub auto-disabled it after 60 days of repo inactivity because of its schedule: trigger, and it has no run history. The PMG step added there is correct but inert until someone re-enables the workflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant