{"id":"692b2abe22f526dc2cf7584b","title":"2025 11 29","path":"how-to/technical-articles/2025-11-29","contentMarkdown":"# Protecting project against the Shai-Hulud Exploit\n\n### Question Originally: [asked here](https://grok.com/c/c2eead79-7cc4-4bb1-892c-753b1bc9620b](https://grok.com/c/c2eead79-7cc4-4bb1-892c-753b1bc9620b)\n\n_I'm the owner of ngx-ramblers and I made [this commit the other day](https://github.com/nbarrett/ngx-ramblers/commit/73a827441618e274bca6bd8be0bde8e8a84a285f) to hopefully protect my codebase against the shai-hulud exploit on 25 Nov. However I am now looking at the [Post-mortem of Shai-Hulud attack on November 24th, 2025](https://posthog.com/blog/nov-24-shai-hulud-attack-post-mortem), and are seeing that there are a few ways that your CI pipeline can be vulnerable to malicious operators opening PRs on your project and running workflows that can harvest secrets on your main pipeline. Can you review the post mortem and then review my project (note there is a client and server portion) and tell me whether there's anything I should do to tighten it up?_\n\n### Review of the PostHog Shai-Hulud Post-Mortem\n\nThe Shai-Hulud attack (November 24, 2025) was a sophisticated supply chain compromise targeting npm packages like `posthog-node` (versions 4.18.1, 5.13.3, 5.11.3). It used malicious preinstall scripts to scan for secrets (via tools like Trufflehog), exfiltrate them by creating public GitHub repos, and self-propagate by publishing more malicious packages using stolen npm credentials. The core exploit leveraged GitHub Actions in CI pipelines, specifically:\n\n- **Attack Vector on PRs from Forks**: Attackers opened PRs to target repos, modifying workflow-triggering scripts (e.g., `assign-reviewers.js`) via `pull_request_target` events. This event runs workflows *in the context of the base repo* (trusted runner, access to secrets) but allows checking out the *PR head commit* (attacker-controlled code). This executed malicious payloads, stealing secrets like a broad-scope GitHub Personal Access Token (PAT) with repo write access, which was then used to alter workflows and exfiltrate more secrets (e.g., npm tokens).\n- **Key Vulnerabilities Highlighted**:\n  - `pull_request_target` workflows checking out untrusted PR code without isolation.\n  - Overly permissive PATs (e.g., full repo write) stored as secrets, enabling escalation.\n  - Workflows running repo-internal scripts that could be indirectly tampered with via PRs.\n  - Lack of PR scrutiny for workflow-modifying changes; delayed detection despite static analysis.\n- **Broader CI Risks**: Even without PRs, push/scheduled workflows can leak if secrets are mishandled (e.g., echoed in logs) or if dependencies introduce persistence (e.g., suspicious files like `truffleSecrets.json`).\n\n### **Recommended Mitigations** (directly from the post-mortem):\n- **Triggers & Isolation**: Avoid `pull_request_target` unless necessary; if used, never checkout PR head (use base repo code only) or run on untrusted runners. For PR testing, prefer `pull_request` (untrusted context, no secrets access).\n- **Permissions**: Set minimal workflow permissions (e.g., `permissions: { contents: read, actions: read }`). Use the ephemeral `GITHUB_TOKEN` (default, scoped to workflow) over long-lived PATs. Avoid write scopes unless essential.\n- **Secrets Management**: Use short-lived, environment-specific secrets. Revoke/rotate on suspicion. Scan logs for exfiltration (e.g., grep for \"preinstall\" or unexpected repo creates).\n- **Supply Chain**: Pin dependencies; use pnpm 10+ with `minimumReleaseAge` (e.g., 3 days) to block fresh malicious packages. Clean caches aggressively. Adopt trusted publisher models for releases.\n- **PR Protections**: Require security team reviews for workflow/PR changes. Enable branch protection rules blocking direct pushes to main.\n- **Detection**: Monitor for suspicious files (e.g., `cloud.json`, `.github/workflows/discussion.yaml`). Run audits (npm/pnpm) and secret scanners regularly.\n\n### Review of Your ngx-ramblers Project\n\nYour repo (https://github.com/nbarrett/ngx-ramblers) is a public, open-source Angular (client) + Express/MongoDB (server) framework for Ramblers Groups sites, with ~266 commits as of late October 2025. It uses AWS S3 for media, Markdown rendering, and various ngx libs for UI/features. The client/server split is clear, with deploy logic (including your new Shai-Hulud check) in `server/deploy/`.\n\n#### Your Recent Commit (73a8274)\nThis adds a proactive `check:shai-hulud` CLI (in `server/deploy/check-shai-hulud.ts`) that scans lockfiles (`package-lock.json`, `pnpm-lock.yaml`) for:\n- Known Shai-Hulud-compromised packages (hardcoded version ranges).\n- High/critical audit issues (via `npm audit`/`pnpm audit`, skippable).\n- Suspicious persistence files (e.g., `truffleSecrets.json`, rogue workflow YAMLs).\n\nIt exits non-zero on hits, with remediation tips, and includes a `README-check-shai-hulud.md` for usage (local/remote via `npx`). This is excellent for supply chain defense—aligns perfectly with post-mortem recs on auditing dependencies and detecting persistence. However, it doesn't touch CI workflows (no YAML changes), so it likely runs manually or in a deploy step, not automatically on PRs/push.\n\n#### CI Pipeline Analysis\nBased on your GitHub Actions runs page (as of November 29, 2025), you have three active workflows—all running successfully recently, with no failures or anomalies noted:\n\n| Workflow Name | Triggers | Recent Status | Potential Risks Noted |\n|---------------|----------|---------------|-----------------------|\n| **Deploy and Build Docker Image and Deploy to Fly.io** | `push` to `main`, `pre-main`, `deploy-fixes` | 20+ green runs (e.g., #378–#367, all ~1-2 min) | Deploys to Fly.io likely use secrets (e.g., FLY_API_TOKEN). Safe from PR exploits (no PR trigger). |\n| **CodeQL** | Scheduled (daily/weekly on `main`) | 6+ green runs (e.g., #66–#61) | GitHub's default security scanner. No custom secrets; analyzes code for vulns. Fully safe. |\n| **Deploy to Selected Environments** | Manual (workflow_dispatch) | 7+ green runs (e.g., #138–#132) | Targeted deploys (e.g., staging/prod). Secrets probable but user-initiated—low risk. |\n\n**Key Observations**:\n- **No PR Triggers**: Critically, *none* of your workflows use `pull_request` or `pull_request_target`. This eliminates the primary Shai-Hulud vector (malicious fork PRs running untrusted code to harvest secrets). Your pipeline only activates on trusted pushes, schedules, or manual runs—excellent baseline security.\n- **No Evident Exposures**: No runs tied to external contributions. Deploys (Docker/Fly.io) are push-gated, so only your commits trigger them. CodeQL is read-only analysis. If secrets are used (e.g., for Fly.io deploys or AWS S3), they're not runnable by outsiders.\n- **Client/Server Split**: Workflows appear repo-wide (no separate client/server YMLs visible), but your Shai-Hulud check is server-focused. Client (Angular) deps might need similar scanning if added to CI.\n- **Limitations in Review**: I couldn't pull full YAML details (GitHub tree/scraping quirks), but triggers/statuses confirm safety. If workflows use broad PATs or echo secrets, that's a gap—but unlikely given clean runs.\n\n**Overall Security Posture**: Strong against Shai-Hulud's CI angle. Your commit bolsters supply chain checks, and absence of PR workflows avoids the exploit entirely. No immediate \"harvest secrets via malicious PR\" risk.\n\n### Recommendations to Tighten It Up\nYour setup is already robust, but to align fully with the post-mortem and future-proof (e.g., if you add PR testing for contributors):\n\n1. **Add Safe PR Workflows (If Needed)**:\n   - For lint/test on PRs: Use `on: pull_request` (not `_target`) with `permissions: { contents: read }`. Checkout with `fetch-depth: 0` but no secrets access—use a separate untrusted job for PR head.\n   - Integrate your `check:shai-hulud` CLI as a step (e.g., `npm run check:shai-hulud -- --skip-audit=false`). Run audits on every PR/push.\n   - Example minimal YAML snippet (add to a new `ci.yml`):\n     ```\n     name: CI\n     on: [pull_request]\n     permissions: { contents: read }\n     jobs:\n       test:\n         runs-on: ubuntu-latest\n         steps:\n           - uses: actions/checkout@v4\n           - uses: actions/setup-node@v4\n             with: { node-version: 20 }\n           - run: npm ci  # Or pnpm install\n           - run: npm run check:shai-hulud  # Your script\n           - run: npm test  # Client/server tests\n     ```\n\n2. **Audit & Minimize Permissions/Secrets**:\n   - In existing deploys (e.g., Fly.io workflow): Explicitly set `permissions: { contents: write, id-token: write }` (only if needed for OIDC/auth). Default to `GITHUB_TOKEN`—avoid custom PATs.\n   - Rotate any deploy secrets (e.g., FLY_API_TOKEN, AWS keys). Use GitHub's OIDC for Fly.io/AWS to eliminate long-lived tokens.\n   - Enable \"Require approval for first-time contributors\" in repo settings for PRs.\n\n3. **Supply Chain Enhancements**:\n   - Your CLI is great—automate it in pushes/PRs. Add pnpm if not using (for `minimumReleaseAge=3d` in `.npmrc`).\n   - Pin all deps (e.g., `npm i --save-exact`). Run `npm audit --audit-level=high` weekly via scheduled workflow.\n   - Scan for persistence: Extend your CLI to check client `node_modules` too.\n\n4. **General Hardening**:\n   - Branch protection: Enforce reviews + status checks on `main`.\n   - Monitor: Set up Dependabot alerts for vulns; watch Actions logs for anomalies.\n   - If adding `pull_request_target` later (e.g., for auto-labeling): Isolate it—no checkouts, no scripts from PR.\n\nNothing urgent—your project dodged the bullet by design. If you share YAML snippets or add PR workflows, I can review deeper. Let me know how else to help!\n\n### Thanks! - 2 questions:\n\n1. I do use postbuild in my client package.json and postinstall in my server package.json. Is that a problem and could I do it differently?\n2. What's the difference between `on:pull_request` and `on:pull_request_target` (I do use `on: push: branches: [ \"main\", \"pre-main\" ] pull_request: branches: [ \"main\" ]`)\n\n### 1) Postbuild and Postinstall Scripts\nNo, your own `postbuild` (in client `package.json`) and `postinstall` (in server `package.json`) aren't inherently a problem—they're standard npm lifecycle hooks for running custom logic after building/installing (e.g., asset optimization or env setup). The Shai-Hulud exploit targeted *dependencies'* malicious `preinstall` scripts, which run automatically during `npm install` and could scan/exfiltrate secrets if a compromised package is pulled in.\n\n**Potential Risks**:\n- If a transitive dependency (deep in your lockfile) has a malicious lifecycle script, it could execute during CI/install without your knowledge.\n- Your scripts themselves are safe (you control them), but they might indirectly chain to untrusted code if they invoke deps.\n\n**Recommendations to Mitigate**:\n- **Audit Regularly**: Extend your `check:shai-hulud` CLI to flag any lifecycle scripts in deps (e.g., via `npm ls --json | jq` to parse and grep for \"scripts\" in package metadata). Run `npm audit` (or pnpm equivalent) in CI to catch vulns that include script exploits.\n- **Alternatives/Different Approaches**:\n  - **Move to Explicit Commands**: Instead of auto-running in `package.json`, invoke them manually in your CI workflows (e.g., `npm run build && npm run postbuild`). This gives you control—skip if suspicious.\n  - **Use Dedicated Scripts**: For server `postinstall`, consider a custom `setup` script that you call post-install, avoiding the hook entirely. For client `postbuild`, integrate into Angular's `ng build --prod` via a custom builder if needed.\n  - **Lock & Pin**: Ensure `package-lock.json` is committed and deps are pinned (e.g., `npm i --save-exact`). Use pnpm with `strict-peer-dependencies` and `minimumReleaseAge=3d` in `.npmrc` to delay fresh (potentially malicious) packages.\n  - **Sandbox Installs**: In CI, use `--ignore-scripts` flag during `npm ci` (then re-run safe scripts manually), but test thoroughly as it breaks legit deps.\n\nYour setup is low-risk overall, especially with your new checker— just automate audits to stay ahead.\n\n### 2) Difference Between `on: pull_request` and `on: pull_request_target`\nGreat that you're using `on: pull_request` (the safe default)—that's exactly what avoids the Shai-Hulud CI pitfalls. Here's the breakdown:\n\n| Event | Triggers On | Execution Context | Secret Access | Checkout Behavior | Risk Level |\n|-------|-------------|-------------------|---------------|-------------------|------------|\n| **`pull_request`** | PR opened, synchronized, reopened, or closed (to your repo branches like `main`). | Runs on a *fresh runner* in the *PR's fork context* (untrusted). Limited to read-only access to the base repo. | **No access** to repo secrets (e.g., deploy tokens)—they're masked. | Can safely checkout the PR head (attacker code) for testing/linting, but without privileges to abuse it. | **Low**: Ideal for CI tests. Can't harvest secrets or write back. |\n| **`pull_request_target`** | Same triggers as above, but *only* for PRs targeting your branches. | Runs on a *trusted runner* in the *base repo's context* (your code/secrets). | **Full access** to all repo secrets and `GITHUB_TOKEN` with write perms. | Allows checkout of PR head, but this mixes trusted env with untrusted code—huge risk if the PR modifies workflows/scripts. | **High**: Shai-Hulud exploited this to run malicious code from PRs, steal PATs, and escalate. Avoid unless essential (e.g., auto-merging). |\n\n**Your Config**: With `pull_request: branches: [ \"main\" ]`, you're golden—PRs from forks (e.g., malicious contributors) trigger safe, secret-free runs for validation. If you ever need secret-dependent PR features (rare), stick to `pull_request` and use OIDC for auth, or add a separate manual-approval job.","contentHtml":"<h1>Protecting project against the Shai-Hulud Exploit</h1>\n<h3>Question Originally: <a href=\"https://grok.com/c/c2eead79-7cc4-4bb1-892c-753b1bc9620b%5D(https://grok.com/c/c2eead79-7cc4-4bb1-892c-753b1bc9620b\">asked here</a></h3>\n<p><em>I&#39;m the owner of ngx-ramblers and I made <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/73a827441618e274bca6bd8be0bde8e8a84a285f\">this commit the other day</a> to hopefully protect my codebase against the shai-hulud exploit on 25 Nov. However I am now looking at the <a href=\"https://posthog.com/blog/nov-24-shai-hulud-attack-post-mortem\">Post-mortem of Shai-Hulud attack on November 24th, 2025</a>, and are seeing that there are a few ways that your CI pipeline can be vulnerable to malicious operators opening PRs on your project and running workflows that can harvest secrets on your main pipeline. Can you review the post mortem and then review my project (note there is a client and server portion) and tell me whether there&#39;s anything I should do to tighten it up?</em></p>\n<h3>Review of the PostHog Shai-Hulud Post-Mortem</h3>\n<p>The Shai-Hulud attack (November 24, 2025) was a sophisticated supply chain compromise targeting npm packages like <code>posthog-node</code> (versions 4.18.1, 5.13.3, 5.11.3). It used malicious preinstall scripts to scan for secrets (via tools like Trufflehog), exfiltrate them by creating public GitHub repos, and self-propagate by publishing more malicious packages using stolen npm credentials. The core exploit leveraged GitHub Actions in CI pipelines, specifically:</p>\n<ul>\n<li><strong>Attack Vector on PRs from Forks</strong>: Attackers opened PRs to target repos, modifying workflow-triggering scripts (e.g., <code>assign-reviewers.js</code>) via <code>pull_request_target</code> events. This event runs workflows <em>in the context of the base repo</em> (trusted runner, access to secrets) but allows checking out the <em>PR head commit</em> (attacker-controlled code). This executed malicious payloads, stealing secrets like a broad-scope GitHub Personal Access Token (PAT) with repo write access, which was then used to alter workflows and exfiltrate more secrets (e.g., npm tokens).</li>\n<li><strong>Key Vulnerabilities Highlighted</strong>:<ul>\n<li><code>pull_request_target</code> workflows checking out untrusted PR code without isolation.</li>\n<li>Overly permissive PATs (e.g., full repo write) stored as secrets, enabling escalation.</li>\n<li>Workflows running repo-internal scripts that could be indirectly tampered with via PRs.</li>\n<li>Lack of PR scrutiny for workflow-modifying changes; delayed detection despite static analysis.</li>\n</ul>\n</li>\n<li><strong>Broader CI Risks</strong>: Even without PRs, push/scheduled workflows can leak if secrets are mishandled (e.g., echoed in logs) or if dependencies introduce persistence (e.g., suspicious files like <code>truffleSecrets.json</code>).</li>\n</ul>\n<h3><strong>Recommended Mitigations</strong> (directly from the post-mortem):</h3>\n<ul>\n<li><strong>Triggers &amp; Isolation</strong>: Avoid <code>pull_request_target</code> unless necessary; if used, never checkout PR head (use base repo code only) or run on untrusted runners. For PR testing, prefer <code>pull_request</code> (untrusted context, no secrets access).</li>\n<li><strong>Permissions</strong>: Set minimal workflow permissions (e.g., <code>permissions: { contents: read, actions: read }</code>). Use the ephemeral <code>GITHUB_TOKEN</code> (default, scoped to workflow) over long-lived PATs. Avoid write scopes unless essential.</li>\n<li><strong>Secrets Management</strong>: Use short-lived, environment-specific secrets. Revoke/rotate on suspicion. Scan logs for exfiltration (e.g., grep for &quot;preinstall&quot; or unexpected repo creates).</li>\n<li><strong>Supply Chain</strong>: Pin dependencies; use pnpm 10+ with <code>minimumReleaseAge</code> (e.g., 3 days) to block fresh malicious packages. Clean caches aggressively. Adopt trusted publisher models for releases.</li>\n<li><strong>PR Protections</strong>: Require security team reviews for workflow/PR changes. Enable branch protection rules blocking direct pushes to main.</li>\n<li><strong>Detection</strong>: Monitor for suspicious files (e.g., <code>cloud.json</code>, <code>.github/workflows/discussion.yaml</code>). Run audits (npm/pnpm) and secret scanners regularly.</li>\n</ul>\n<h3>Review of Your ngx-ramblers Project</h3>\n<p>Your repo (<a href=\"https://github.com/nbarrett/ngx-ramblers\">https://github.com/nbarrett/ngx-ramblers</a>) is a public, open-source Angular (client) + Express/MongoDB (server) framework for Ramblers Groups sites, with ~266 commits as of late October 2025. It uses AWS S3 for media, Markdown rendering, and various ngx libs for UI/features. The client/server split is clear, with deploy logic (including your new Shai-Hulud check) in <code>server/deploy/</code>.</p>\n<h4>Your Recent Commit (73a8274)</h4>\n<p>This adds a proactive <code>check:shai-hulud</code> CLI (in <code>server/deploy/check-shai-hulud.ts</code>) that scans lockfiles (<code>package-lock.json</code>, <code>pnpm-lock.yaml</code>) for:</p>\n<ul>\n<li>Known Shai-Hulud-compromised packages (hardcoded version ranges).</li>\n<li>High/critical audit issues (via <code>npm audit</code>/<code>pnpm audit</code>, skippable).</li>\n<li>Suspicious persistence files (e.g., <code>truffleSecrets.json</code>, rogue workflow YAMLs).</li>\n</ul>\n<p>It exits non-zero on hits, with remediation tips, and includes a <code>README-check-shai-hulud.md</code> for usage (local/remote via <code>npx</code>). This is excellent for supply chain defense—aligns perfectly with post-mortem recs on auditing dependencies and detecting persistence. However, it doesn&#39;t touch CI workflows (no YAML changes), so it likely runs manually or in a deploy step, not automatically on PRs/push.</p>\n<h4>CI Pipeline Analysis</h4>\n<p>Based on your GitHub Actions runs page (as of November 29, 2025), you have three active workflows—all running successfully recently, with no failures or anomalies noted:</p>\n<table>\n<thead>\n<tr>\n<th>Workflow Name</th>\n<th>Triggers</th>\n<th>Recent Status</th>\n<th>Potential Risks Noted</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><strong>Deploy and Build Docker Image and Deploy to Fly.io</strong></td>\n<td><code>push</code> to <code>main</code>, <code>pre-main</code>, <code>deploy-fixes</code></td>\n<td>20+ green runs (e.g., #378–#367, all ~1-2 min)</td>\n<td>Deploys to Fly.io likely use secrets (e.g., FLY_API_TOKEN). Safe from PR exploits (no PR trigger).</td>\n</tr>\n<tr>\n<td><strong>CodeQL</strong></td>\n<td>Scheduled (daily/weekly on <code>main</code>)</td>\n<td>6+ green runs (e.g., #66–#61)</td>\n<td>GitHub&#39;s default security scanner. No custom secrets; analyzes code for vulns. Fully safe.</td>\n</tr>\n<tr>\n<td><strong>Deploy to Selected Environments</strong></td>\n<td>Manual (workflow_dispatch)</td>\n<td>7+ green runs (e.g., #138–#132)</td>\n<td>Targeted deploys (e.g., staging/prod). Secrets probable but user-initiated—low risk.</td>\n</tr>\n</tbody></table>\n<p><strong>Key Observations</strong>:</p>\n<ul>\n<li><strong>No PR Triggers</strong>: Critically, <em>none</em> of your workflows use <code>pull_request</code> or <code>pull_request_target</code>. This eliminates the primary Shai-Hulud vector (malicious fork PRs running untrusted code to harvest secrets). Your pipeline only activates on trusted pushes, schedules, or manual runs—excellent baseline security.</li>\n<li><strong>No Evident Exposures</strong>: No runs tied to external contributions. Deploys (Docker/Fly.io) are push-gated, so only your commits trigger them. CodeQL is read-only analysis. If secrets are used (e.g., for Fly.io deploys or AWS S3), they&#39;re not runnable by outsiders.</li>\n<li><strong>Client/Server Split</strong>: Workflows appear repo-wide (no separate client/server YMLs visible), but your Shai-Hulud check is server-focused. Client (Angular) deps might need similar scanning if added to CI.</li>\n<li><strong>Limitations in Review</strong>: I couldn&#39;t pull full YAML details (GitHub tree/scraping quirks), but triggers/statuses confirm safety. If workflows use broad PATs or echo secrets, that&#39;s a gap—but unlikely given clean runs.</li>\n</ul>\n<p><strong>Overall Security Posture</strong>: Strong against Shai-Hulud&#39;s CI angle. Your commit bolsters supply chain checks, and absence of PR workflows avoids the exploit entirely. No immediate &quot;harvest secrets via malicious PR&quot; risk.</p>\n<h3>Recommendations to Tighten It Up</h3>\n<p>Your setup is already robust, but to align fully with the post-mortem and future-proof (e.g., if you add PR testing for contributors):</p>\n<ol>\n<li><p><strong>Add Safe PR Workflows (If Needed)</strong>:</p>\n<ul>\n<li>For lint/test on PRs: Use <code>on: pull_request</code> (not <code>_target</code>) with <code>permissions: { contents: read }</code>. Checkout with <code>fetch-depth: 0</code> but no secrets access—use a separate untrusted job for PR head.</li>\n<li>Integrate your <code>check:shai-hulud</code> CLI as a step (e.g., <code>npm run check:shai-hulud -- --skip-audit=false</code>). Run audits on every PR/push.</li>\n<li>Example minimal YAML snippet (add to a new <code>ci.yml</code>):<pre><code>name: CI\non: [pull_request]\npermissions: { contents: read }\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with: { node-version: 20 }\n      - run: npm ci  # Or pnpm install\n      - run: npm run check:shai-hulud  # Your script\n      - run: npm test  # Client/server tests\n</code></pre>\n</li>\n</ul>\n</li>\n<li><p><strong>Audit &amp; Minimize Permissions/Secrets</strong>:</p>\n<ul>\n<li>In existing deploys (e.g., Fly.io workflow): Explicitly set <code>permissions: { contents: write, id-token: write }</code> (only if needed for OIDC/auth). Default to <code>GITHUB_TOKEN</code>—avoid custom PATs.</li>\n<li>Rotate any deploy secrets (e.g., FLY_API_TOKEN, AWS keys). Use GitHub&#39;s OIDC for Fly.io/AWS to eliminate long-lived tokens.</li>\n<li>Enable &quot;Require approval for first-time contributors&quot; in repo settings for PRs.</li>\n</ul>\n</li>\n<li><p><strong>Supply Chain Enhancements</strong>:</p>\n<ul>\n<li>Your CLI is great—automate it in pushes/PRs. Add pnpm if not using (for <code>minimumReleaseAge=3d</code> in <code>.npmrc</code>).</li>\n<li>Pin all deps (e.g., <code>npm i --save-exact</code>). Run <code>npm audit --audit-level=high</code> weekly via scheduled workflow.</li>\n<li>Scan for persistence: Extend your CLI to check client <code>node_modules</code> too.</li>\n</ul>\n</li>\n<li><p><strong>General Hardening</strong>:</p>\n<ul>\n<li>Branch protection: Enforce reviews + status checks on <code>main</code>.</li>\n<li>Monitor: Set up Dependabot alerts for vulns; watch Actions logs for anomalies.</li>\n<li>If adding <code>pull_request_target</code> later (e.g., for auto-labeling): Isolate it—no checkouts, no scripts from PR.</li>\n</ul>\n</li>\n</ol>\n<p>Nothing urgent—your project dodged the bullet by design. If you share YAML snippets or add PR workflows, I can review deeper. Let me know how else to help!</p>\n<h3>Thanks! - 2 questions:</h3>\n<ol>\n<li>I do use postbuild in my client package.json and postinstall in my server package.json. Is that a problem and could I do it differently?</li>\n<li>What&#39;s the difference between <code>on:pull_request</code> and <code>on:pull_request_target</code> (I do use <code>on: push: branches: [ &quot;main&quot;, &quot;pre-main&quot; ] pull_request: branches: [ &quot;main&quot; ]</code>)</li>\n</ol>\n<h3>1) Postbuild and Postinstall Scripts</h3>\n<p>No, your own <code>postbuild</code> (in client <code>package.json</code>) and <code>postinstall</code> (in server <code>package.json</code>) aren&#39;t inherently a problem—they&#39;re standard npm lifecycle hooks for running custom logic after building/installing (e.g., asset optimization or env setup). The Shai-Hulud exploit targeted <em>dependencies&#39;</em> malicious <code>preinstall</code> scripts, which run automatically during <code>npm install</code> and could scan/exfiltrate secrets if a compromised package is pulled in.</p>\n<p><strong>Potential Risks</strong>:</p>\n<ul>\n<li>If a transitive dependency (deep in your lockfile) has a malicious lifecycle script, it could execute during CI/install without your knowledge.</li>\n<li>Your scripts themselves are safe (you control them), but they might indirectly chain to untrusted code if they invoke deps.</li>\n</ul>\n<p><strong>Recommendations to Mitigate</strong>:</p>\n<ul>\n<li><strong>Audit Regularly</strong>: Extend your <code>check:shai-hulud</code> CLI to flag any lifecycle scripts in deps (e.g., via <code>npm ls --json | jq</code> to parse and grep for &quot;scripts&quot; in package metadata). Run <code>npm audit</code> (or pnpm equivalent) in CI to catch vulns that include script exploits.</li>\n<li><strong>Alternatives/Different Approaches</strong>:<ul>\n<li><strong>Move to Explicit Commands</strong>: Instead of auto-running in <code>package.json</code>, invoke them manually in your CI workflows (e.g., <code>npm run build &amp;&amp; npm run postbuild</code>). This gives you control—skip if suspicious.</li>\n<li><strong>Use Dedicated Scripts</strong>: For server <code>postinstall</code>, consider a custom <code>setup</code> script that you call post-install, avoiding the hook entirely. For client <code>postbuild</code>, integrate into Angular&#39;s <code>ng build --prod</code> via a custom builder if needed.</li>\n<li><strong>Lock &amp; Pin</strong>: Ensure <code>package-lock.json</code> is committed and deps are pinned (e.g., <code>npm i --save-exact</code>). Use pnpm with <code>strict-peer-dependencies</code> and <code>minimumReleaseAge=3d</code> in <code>.npmrc</code> to delay fresh (potentially malicious) packages.</li>\n<li><strong>Sandbox Installs</strong>: In CI, use <code>--ignore-scripts</code> flag during <code>npm ci</code> (then re-run safe scripts manually), but test thoroughly as it breaks legit deps.</li>\n</ul>\n</li>\n</ul>\n<p>Your setup is low-risk overall, especially with your new checker— just automate audits to stay ahead.</p>\n<h3>2) Difference Between <code>on: pull_request</code> and <code>on: pull_request_target</code></h3>\n<p>Great that you&#39;re using <code>on: pull_request</code> (the safe default)—that&#39;s exactly what avoids the Shai-Hulud CI pitfalls. Here&#39;s the breakdown:</p>\n<table>\n<thead>\n<tr>\n<th>Event</th>\n<th>Triggers On</th>\n<th>Execution Context</th>\n<th>Secret Access</th>\n<th>Checkout Behavior</th>\n<th>Risk Level</th>\n</tr>\n</thead>\n<tbody><tr>\n<td><strong><code>pull_request</code></strong></td>\n<td>PR opened, synchronized, reopened, or closed (to your repo branches like <code>main</code>).</td>\n<td>Runs on a <em>fresh runner</em> in the <em>PR&#39;s fork context</em> (untrusted). Limited to read-only access to the base repo.</td>\n<td><strong>No access</strong> to repo secrets (e.g., deploy tokens)—they&#39;re masked.</td>\n<td>Can safely checkout the PR head (attacker code) for testing/linting, but without privileges to abuse it.</td>\n<td><strong>Low</strong>: Ideal for CI tests. Can&#39;t harvest secrets or write back.</td>\n</tr>\n<tr>\n<td><strong><code>pull_request_target</code></strong></td>\n<td>Same triggers as above, but <em>only</em> for PRs targeting your branches.</td>\n<td>Runs on a <em>trusted runner</em> in the <em>base repo&#39;s context</em> (your code/secrets).</td>\n<td><strong>Full access</strong> to all repo secrets and <code>GITHUB_TOKEN</code> with write perms.</td>\n<td>Allows checkout of PR head, but this mixes trusted env with untrusted code—huge risk if the PR modifies workflows/scripts.</td>\n<td><strong>High</strong>: Shai-Hulud exploited this to run malicious code from PRs, steal PATs, and escalate. Avoid unless essential (e.g., auto-merging).</td>\n</tr>\n</tbody></table>\n<p><strong>Your Config</strong>: With <code>pull_request: branches: [ &quot;main&quot; ]</code>, you&#39;re golden—PRs from forks (e.g., malicious contributors) trigger safe, secret-free runs for validation. If you ever need secret-dependent PR features (rare), stick to <code>pull_request</code> and use OIDC for auth, or add a separate manual-approval job.</p>\n"}