ship-safe 8.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -15
- package/cli/agents/agentic-supply-chain-agent.js +463 -0
- package/cli/agents/deep-analyzer.js +473 -133
- package/cli/agents/index.js +3 -0
- package/cli/agents/orchestrator.js +13 -3
- package/cli/bin/ship-safe.js +5 -1
- package/cli/commands/audit.js +33 -1
- package/cli/commands/init.js +104 -0
- package/cli/commands/mcp.js +270 -0
- package/cli/commands/watch.js +142 -5
- package/cli/providers/llm-provider.js +50 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
23 security agents. 80+ attack classes. One command.
|
|
20
20
|
|
|
21
|
-
**Ship Safe
|
|
21
|
+
**Ship Safe v9.1.0** is an AI-powered security platform that runs 23 specialized agents in parallel against your codebase — covering secrets, injection vulnerabilities, auth bypass, SSRF, supply chain attacks, AI integration supply chain (Vercel-class attacks), memory poisoning, Hermes Agent security, Supabase RLS, Docker/Terraform/Kubernetes misconfigs, CI/CD pipeline poisoning, LLM/agentic AI security, MCP server misuse, RAG poisoning, PII compliance, vibe coding patterns, exception handling, Claude Managed Agent configs, and more. Full OWASP Agentic AI Top 10 mapping (ASI-01–ASI-10) enriches every finding. Live OSV.dev advisory feed surfaces actively exploited CVEs within hours of disclosure. OWASP 2025 scoring with EPSS exploit probability. LLM-powered deep analysis verifies exploitability of critical findings. Secrets verification probes provider APIs to check if leaked keys are still active.
|
|
22
22
|
|
|
23
|
-
**
|
|
23
|
+
**v9.1.0 highlights:** **AgenticSupplyChainAgent & Vercel Breach Checker** — new 23rd agent detects AI integration supply chain attacks (Vercel-class): unpinned AI CI actions, OAuth scope abuse in platform integrations, unsigned webhook handlers, and MCP/Hermes cross-boundary token forwarding. New public breach impact checker at /breach/vercel-april-2026 lets any Vercel user self-serve all four checks without the CLI. Full incident analysis published.
|
|
24
|
+
|
|
25
|
+
**v9.0.0:** **Agent Studio, Teams & Findings** — the web dashboard is now a full AI security operations platform. **Agent Studio** lets you build, configure, and deploy custom Hermes security agents from the UI — give each agent a role, tools, and memory, then deploy to a live container in one click. **Agent Console** provides a live SSE chat interface with ANSI color rendering and per-session run history. **Agent Teams** orchestrate multiple specialist agents (pen tester, secrets scanner, CVE analyst) under a lead agent that plans, delegates tasks in parallel, and synthesises an executive security report. **Agent Triggers** add webhook and cron-based automation per agent. The new **Findings Dashboard** aggregates all security findings across every agent run with severity charts, trend data, and one-click GitHub issue creation. Billing has moved to monthly subscriptions (Pro at $9/month, Team at $19/seat/month) with automatic plan downgrade on cancellation.
|
|
24
26
|
|
|
25
27
|
[Documentation](https://shipsafecli.com/docs) | [Blog](https://shipsafecli.com/blog) | [Pricing](https://shipsafecli.com/pricing)
|
|
26
28
|
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
## Quick Start
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
|
-
# Full security audit — secrets +
|
|
34
|
+
# Full security audit — secrets + 23 agents + deps + remediation plan
|
|
33
35
|
npx ship-safe audit .
|
|
34
36
|
|
|
35
37
|
# LLM-powered deep analysis (Anthropic, OpenAI, Google, Ollama, Gemma 4)
|
|
@@ -39,7 +41,7 @@ npx ship-safe audit . --deep
|
|
|
39
41
|
npx ship-safe audit . --agentic
|
|
40
42
|
npx ship-safe audit . --agentic 5 --agentic-target 85
|
|
41
43
|
|
|
42
|
-
# Red team scan (
|
|
44
|
+
# Red team scan (23 agents, 80+ attack classes)
|
|
43
45
|
npx ship-safe red-team .
|
|
44
46
|
|
|
45
47
|
# Scan only changed files (fast pre-commit & PR scanning)
|
|
@@ -51,7 +53,7 @@ npx ship-safe advisories .
|
|
|
51
53
|
|
|
52
54
|
# Continuous monitoring
|
|
53
55
|
npx ship-safe watch . # Lightweight file watcher
|
|
54
|
-
npx ship-safe watch . --deep # Full
|
|
56
|
+
npx ship-safe watch . --deep # Full 23-agent scan on every change
|
|
55
57
|
npx ship-safe watch . --deep --threshold 80 # Fail if score drops below threshold
|
|
56
58
|
npx ship-safe watch . --status # Show last deep-watch results
|
|
57
59
|
|
|
@@ -99,11 +101,11 @@ npx ship-safe audit .
|
|
|
99
101
|
|
|
100
102
|
```
|
|
101
103
|
════════════════════════════════════════════════════════════
|
|
102
|
-
Ship Safe
|
|
104
|
+
Ship Safe v9.0 — Full Security Audit
|
|
103
105
|
════════════════════════════════════════════════════════════
|
|
104
106
|
|
|
105
107
|
[Phase 1/4] Scanning for secrets... ✔ 49 found
|
|
106
|
-
[Phase 2/4] Running
|
|
108
|
+
[Phase 2/4] Running 23 security agents... ✔ 103 findings
|
|
107
109
|
[Phase 3/4] Auditing dependencies... ✔ 44 CVEs
|
|
108
110
|
[Phase 4/4] Computing security score... ✔ 25/100 F
|
|
109
111
|
|
|
@@ -130,7 +132,7 @@ npx ship-safe audit .
|
|
|
130
132
|
|
|
131
133
|
**What it runs:**
|
|
132
134
|
1. **Secret scan** — 50+ patterns with entropy scoring (API keys, passwords, tokens)
|
|
133
|
-
2. **
|
|
135
|
+
2. **23 security agents** — run in parallel with per-agent timeouts and framework-aware filtering
|
|
134
136
|
3. **Dependency audit** — npm/pip/bundler CVE scanning with EPSS exploit probability scores
|
|
135
137
|
4. **Secrets verification** — probes provider APIs (GitHub, Stripe, OpenAI, etc.) to check if leaked keys are still active
|
|
136
138
|
5. **Deep analysis** — LLM-powered taint analysis verifies exploitability of critical/high findings (optional)
|
|
@@ -165,7 +167,7 @@ npx ship-safe audit .
|
|
|
165
167
|
|
|
166
168
|
---
|
|
167
169
|
|
|
168
|
-
##
|
|
170
|
+
## 23 Security Agents
|
|
169
171
|
|
|
170
172
|
| Agent | Category | What It Detects |
|
|
171
173
|
|-------|----------|-----------------|
|
|
@@ -189,8 +191,9 @@ npx ship-safe audit .
|
|
|
189
191
|
| **CICDScanner** | CI/CD | OWASP CI/CD Top 10 — pipeline poisoning, unpinned actions, secret logging, self-hosted runners, script injection, AI agent danger flags |
|
|
190
192
|
| **APIFuzzer** | API | Routes without auth, missing input validation, mass assignment, unrestricted file upload, GraphQL introspection, debug endpoints, missing rate limiting, OpenAPI spec security issues |
|
|
191
193
|
| **ManagedAgentScanner** | AI/LLM | Claude Managed Agents misconfigurations — `always_allow` permission policies, unrestricted networking, bash without human confirmation, MCP servers over HTTP, hardcoded vault tokens, unpinned environment packages (ASI-03, ASI-04, ASI-05, ASI-07) |
|
|
192
|
-
| **HermesSecurityAgent**
|
|
193
|
-
| **AgentAttestationAgent**
|
|
194
|
+
| **HermesSecurityAgent** | AI/LLM | Hermes Agent deployments — tool registry poisoning, function-call injection (`<tool_call>` / `<function_calls>`), goal/plan hijacking, memory layer attacks, skill permission drift, sub-agent trust boundary violations, manifest attestation (ASI-01–ASI-10) |
|
|
195
|
+
| **AgentAttestationAgent** | Supply Chain | Agent manifest supply chain — unpinned versions (`latest`, `^`, `~`), missing integrity hashes on remote tool sources, unsigned manifests, `skipIntegrityCheck` bypass, dynamic `require()` of manifests from env vars, missing provenance fields (ASI-10, SLSA Level 0) |
|
|
196
|
+
| **AgenticSupplyChainAgent** *(new)* | Supply Chain | AI integration supply chain — over-privileged AI CI actions (Vercel/GitHub/Netlify), OAuth scope creep in AI platform integrations, unsigned AI webhook receivers (missing HMAC), MCP/Hermes cross-boundary token forwarding to third-party servers (ASI-02, ASI-06, ASI-09, CICD-SEC-8) |
|
|
194
197
|
|
|
195
198
|
**Post-processors:** ScoringEngine (8-category weighted scoring with OWASP Agentic AI Top 10 enrichment), VerifierAgent (secrets liveness verification), DeepAnalyzer (LLM-powered taint analysis)
|
|
196
199
|
|
|
@@ -204,7 +207,7 @@ npx ship-safe audit .
|
|
|
204
207
|
# Full audit with remediation plan + HTML report
|
|
205
208
|
npx ship-safe audit .
|
|
206
209
|
|
|
207
|
-
# Red team:
|
|
210
|
+
# Red team: 23 agents, 80+ attack classes
|
|
208
211
|
npx ship-safe red-team .
|
|
209
212
|
npx ship-safe red-team . --agents injection,auth # Run specific agents
|
|
210
213
|
npx ship-safe red-team . --html report.html # HTML report
|
|
@@ -461,7 +464,7 @@ npx ship-safe watch . --configs
|
|
|
461
464
|
# Lightweight file watcher — re-scans changed files on save
|
|
462
465
|
npx ship-safe watch .
|
|
463
466
|
|
|
464
|
-
# Deep watch — full
|
|
467
|
+
# Deep watch — full 23-agent orchestrator on every change
|
|
465
468
|
npx ship-safe watch . --deep
|
|
466
469
|
npx ship-safe watch . --deep --threshold 80 # Fail if score drops below threshold
|
|
467
470
|
npx ship-safe watch . --deep --debounce 2000 # Custom debounce in ms (default: 1000)
|
|
@@ -522,7 +525,7 @@ claude plugin add github:asamassekou10/ship-safe
|
|
|
522
525
|
|
|
523
526
|
| Command | Description |
|
|
524
527
|
|---------|-------------|
|
|
525
|
-
| `/ship-safe` | Full security audit —
|
|
528
|
+
| `/ship-safe` | Full security audit — 23 agents, remediation plan, auto-fix |
|
|
526
529
|
| `/ship-safe-scan` | Quick scan for leaked secrets |
|
|
527
530
|
| `/ship-safe-score` | Security health score (0-100) |
|
|
528
531
|
| `/ship-safe-deep` | LLM-powered deep taint analysis |
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgenticSupplyChainAgent
|
|
3
|
+
* ========================
|
|
4
|
+
*
|
|
5
|
+
* Detects supply chain attack vectors specific to AI integrations —
|
|
6
|
+
* the class of vulnerability exploited in the Vercel April 2026 incident.
|
|
7
|
+
*
|
|
8
|
+
* Four detection tracks:
|
|
9
|
+
* 1. Over-privileged AI actions in CI (GitHub Actions)
|
|
10
|
+
* 2. Excessive OAuth scopes in AI platform integrations
|
|
11
|
+
* 3. Webhook receivers that trust AI platform payloads without HMAC
|
|
12
|
+
* 4. Agent tools forwarding credentials cross-boundary (MCP / Hermes)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { BaseAgent, createFinding } from './base-agent.js';
|
|
18
|
+
|
|
19
|
+
// ── Track 1: AI actions in CI with overly broad scopes ───────────────────────
|
|
20
|
+
// Match action names that are AI-related — these get elevated scrutiny because
|
|
21
|
+
// a compromised AI CI action has access to secrets + can exfiltrate model outputs.
|
|
22
|
+
const AI_ACTION_NAME_RE = /uses\s*:\s*([\w.-]+\/[\w.-]*(?:ai|llm|copilot|claude|openai|anthropic|gpt|gemini|cursor|codeium|tabnine|hermes|codex|devin|agent|autopilot)[\w.-]*)@([\w./-]+)/gi;
|
|
23
|
+
|
|
24
|
+
// Broad write/admin scopes in the same workflow as an AI action
|
|
25
|
+
const BROAD_SCOPE_PATTERNS = [
|
|
26
|
+
{
|
|
27
|
+
rule: 'AI_CI_WRITE_ALL',
|
|
28
|
+
title: 'AI CI Action: Workflow Has write-all Permissions',
|
|
29
|
+
regex: /permissions\s*:\s*write-all/g,
|
|
30
|
+
severity: 'critical',
|
|
31
|
+
cwe: 'CWE-250',
|
|
32
|
+
owasp: 'ASI-02',
|
|
33
|
+
description: 'This workflow uses an AI action and has write-all permissions. A compromised AI action (e.g. via a malicious model response or prompt injection) can exfiltrate all repository secrets and push malicious code. This is the permission level abused in the Vercel April 2026 AI pipeline compromise.',
|
|
34
|
+
fix: 'Scope permissions to the minimum needed. If the AI action only reads code, use: permissions: { contents: read }',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
rule: 'AI_CI_ADMIN_SCOPE',
|
|
38
|
+
title: 'AI CI Action: Administration Write Permission',
|
|
39
|
+
regex: /administration\s*:\s*write/g,
|
|
40
|
+
severity: 'critical',
|
|
41
|
+
cwe: 'CWE-250',
|
|
42
|
+
owasp: 'ASI-02',
|
|
43
|
+
description: 'Administration write scope in a workflow that may include AI actions grants branch protection bypass. An AI agent with prompt injection can use this to push directly to protected branches.',
|
|
44
|
+
fix: 'Remove administration: write unless absolutely necessary. AI actions do not require administration scope.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
rule: 'AI_CI_SECRETS_WRITE',
|
|
48
|
+
title: 'AI CI Action: Secrets Write Permission',
|
|
49
|
+
regex: /secrets\s*:\s*write/g,
|
|
50
|
+
severity: 'critical',
|
|
51
|
+
cwe: 'CWE-250',
|
|
52
|
+
owasp: 'ASI-02',
|
|
53
|
+
description: 'Secrets write permission in a CI workflow. If an AI action is present, a prompt injection attack could cause the agent to overwrite repository secrets.',
|
|
54
|
+
fix: 'Remove secrets: write. AI actions should only read secrets via environment variables.',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
rule: 'AI_CI_PACKAGES_WRITE',
|
|
58
|
+
title: 'AI CI Action: Packages Write Permission',
|
|
59
|
+
regex: /packages\s*:\s*write/g,
|
|
60
|
+
severity: 'high',
|
|
61
|
+
cwe: 'CWE-829',
|
|
62
|
+
owasp: 'ASI-02',
|
|
63
|
+
confidence: 'medium',
|
|
64
|
+
description: 'Packages write permission in a workflow with AI actions. A supply chain attack via prompt injection could cause the AI agent to publish malicious packages to the GitHub Container Registry.',
|
|
65
|
+
fix: 'Separate package publishing into a dedicated workflow without AI actions. Use packages: read in AI workflows.',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
rule: 'AI_CI_UNPINNED_AI_ACTION',
|
|
69
|
+
title: 'AI CI Action: Unpinned AI Action (mutable tag)',
|
|
70
|
+
// Matches AI action uses: lines NOT pinned to a 40-char SHA
|
|
71
|
+
regex: /uses\s*:\s*[\w.-]*(?:ai|llm|copilot|claude|openai|anthropic|gpt|gemini|cursor|codeium|tabnine|hermes|codex|devin|agent|autopilot)[\w.-]*\/[\w.-]+@(?![\da-f]{40}\b)[\w./-]+/gi,
|
|
72
|
+
severity: 'critical',
|
|
73
|
+
cwe: 'CWE-829',
|
|
74
|
+
owasp: 'CICD-SEC-8',
|
|
75
|
+
description: 'An AI-related GitHub Action is not pinned to a full commit SHA. AI actions are high-value supply chain targets: a compromised action version can exfiltrate all secrets passed to it and inject malicious code into AI-generated PRs. This is the exact vector used against Vercel in April 2026.',
|
|
76
|
+
fix: 'Pin to the full 40-character commit SHA: uses: ai-vendor/action@a1b2c3d4e5f6... # v1.2.3',
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
// ── Track 2: Excessive OAuth scopes in AI platform integrations ──────────────
|
|
81
|
+
|
|
82
|
+
// Vercel integration config — checks for AI integrations with broad scopes
|
|
83
|
+
const VERCEL_AI_INTEGRATIONS = new Set([
|
|
84
|
+
'vercel-ai', 'v0', 'cursor', 'codeium', 'copilot', 'openai',
|
|
85
|
+
'anthropic', 'gemini', 'devin', 'sweep', 'cody', 'tabnine',
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// GitHub App manifest: dangerous scopes that AI apps rarely need
|
|
89
|
+
const DANGEROUS_GITHUB_APP_SCOPES = new Set([
|
|
90
|
+
'administration', 'organization_administration', 'secrets',
|
|
91
|
+
'organization_secrets', 'actions', 'organization_hooks',
|
|
92
|
+
'members', 'organization_plan',
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
// Netlify plugin scopes
|
|
96
|
+
const NETLIFY_AI_PLUGINS = new Set([
|
|
97
|
+
'@netlify/plugin-ai', 'netlify-plugin-openai', 'netlify-plugin-anthropic',
|
|
98
|
+
'netlify-plugin-langchain', 'netlify-plugin-vector-db',
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
// ── Track 3: Webhook receivers that skip HMAC ────────────────────────────────
|
|
102
|
+
// We look for route handlers that parse AI platform webhook payloads
|
|
103
|
+
// without verifying a signature.
|
|
104
|
+
|
|
105
|
+
const WEBHOOK_PATTERNS = [
|
|
106
|
+
{
|
|
107
|
+
rule: 'WEBHOOK_NO_HMAC_OPENAI',
|
|
108
|
+
title: 'Webhook: OpenAI Payload Without Signature Verification',
|
|
109
|
+
regex: /(?:openai|stripe|linear|vercel)[._-]?(?:webhook|event|payload|hook)/gi,
|
|
110
|
+
severity: 'high',
|
|
111
|
+
cwe: 'CWE-345',
|
|
112
|
+
owasp: 'ASI-06',
|
|
113
|
+
description: 'Route appears to handle AI platform webhook events. If the Stripe-Signature / OpenAI-Signature header is not verified via HMAC-SHA256, an attacker can forge arbitrary events to trigger AI agent actions (e.g., invoice.paid spoofing to grant premium access, or forging model completion events to inject malicious output).',
|
|
114
|
+
fix: 'Verify the webhook signature before processing: compare the HMAC-SHA256 of the raw body against the signature header using a constant-time comparison.',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
rule: 'WEBHOOK_RAW_BODY_NOT_USED',
|
|
118
|
+
title: 'Webhook: Parsed JSON Body Used for HMAC Input',
|
|
119
|
+
// Catches cases where body is JSON.parsed BEFORE signature check
|
|
120
|
+
regex: /(?:req|request|event)\.(?:body|json\(\))\s*(?:;|\n|\.)/g,
|
|
121
|
+
severity: 'medium',
|
|
122
|
+
cwe: 'CWE-345',
|
|
123
|
+
owasp: 'ASI-06',
|
|
124
|
+
confidence: 'low',
|
|
125
|
+
description: 'HMAC webhook verification requires the raw request body bytes — JSON.parse then re-stringify changes whitespace and property order, invalidating the signature check. Always read the raw buffer before any JSON parsing.',
|
|
126
|
+
fix: 'Use express.raw({ type: "application/json" }) or Buffer from readable stream before calling JSON.parse.',
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
// HMAC verification markers — if any of these appear near a webhook route, we
|
|
131
|
+
// suppress the Track 3 findings for that file (not a simple regex thing — handled
|
|
132
|
+
// in the custom analyze logic below).
|
|
133
|
+
const HMAC_MARKERS = [
|
|
134
|
+
/createHmac/,
|
|
135
|
+
/timingSafeEqual/,
|
|
136
|
+
/stripe\.webhooks\.constructEvent/,
|
|
137
|
+
/verifySignature/,
|
|
138
|
+
/webhook[Ss]ecret/,
|
|
139
|
+
/x-hub-signature/i,
|
|
140
|
+
/svix-signature/i,
|
|
141
|
+
/openai-beta-assistant/i,
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
// ── Track 4: Cross-boundary token forwarding in agent tool configs ────────────
|
|
145
|
+
|
|
146
|
+
const TOKEN_FORWARD_PATTERNS = [
|
|
147
|
+
{
|
|
148
|
+
rule: 'MCP_TOKEN_FORWARD_ENV',
|
|
149
|
+
title: 'MCP: Agent Tool Forwards Auth Token to Third-Party URL',
|
|
150
|
+
// env vars that look like bearer tokens / API keys passed to remote tool servers
|
|
151
|
+
regex: /(?:ANTHROPIC_API_KEY|OPENAI_API_KEY|GITHUB_TOKEN|VERCEL_TOKEN|LINEAR_API_KEY|SLACK_BOT_TOKEN|GH_PAT|CI_TOKEN)\s*[:=]\s*(?:\$\{[^}]+\}|["'][^"']{10,}["'])/g,
|
|
152
|
+
severity: 'high',
|
|
153
|
+
cwe: 'CWE-200',
|
|
154
|
+
owasp: 'ASI-09',
|
|
155
|
+
description: 'A high-value credential is set in an MCP server or agent tool configuration. If the tool server URL points to a third-party host, this credential will be transmitted to that host on every tool call — the data exfiltration vector exploited in the April 2026 Vercel incident where compromised AI integrations forwarded Vercel deployment tokens.',
|
|
156
|
+
fix: 'Never pass production credentials to third-party MCP servers. Use scoped, read-only tokens. Prefer official first-party integrations.',
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
rule: 'MCP_THIRD_PARTY_SERVER_WITH_AUTH',
|
|
160
|
+
title: 'MCP: Third-Party Server URL With Auth Headers',
|
|
161
|
+
regex: /(?:url|baseUrl|endpoint|server)\s*[:=]\s*["']https?:\/\/(?!localhost|127\.|0\.0\.0\.0|::1)([^"'/]+)[^"']*["'][^}]{0,200}(?:Authorization|Bearer|api[_-]?key|token)/gs,
|
|
162
|
+
severity: 'critical',
|
|
163
|
+
cwe: 'CWE-200',
|
|
164
|
+
owasp: 'ASI-09',
|
|
165
|
+
description: 'An MCP server configuration sends auth headers to a non-localhost URL. The remote MCP server receives every tool call result, including file contents and environment variable values. A compromised or malicious MCP server at this URL is a silent data exfiltration channel.',
|
|
166
|
+
fix: 'Audit this MCP server. Prefer self-hosted or officially verified servers. If third-party is required, use a dedicated secrets-free agent profile.',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
rule: 'HERMES_TOOL_EXFIL',
|
|
170
|
+
title: 'Hermes: Tool Config Forwards Credentials to Remote URL',
|
|
171
|
+
regex: /(?:tools?|plugin)\s*[:=]\s*\{[^}]{0,400}(?:url|endpoint)\s*[:=]\s*["']https?:\/\/(?!localhost|127\.|0\.0\.0\.0)[^"']+["'][^}]{0,200}(?:auth|token|key|secret|bearer)/gis,
|
|
172
|
+
severity: 'critical',
|
|
173
|
+
cwe: 'CWE-200',
|
|
174
|
+
owasp: 'ASI-09',
|
|
175
|
+
description: 'A Hermes agent tool configuration passes authentication material to a remote endpoint. Hermes tools execute with the full ambient credentials of the agent — a malicious tool server receives them all.',
|
|
176
|
+
fix: 'Audit all Hermes tool endpoints. Use allowlist-based URL validation and never inject high-privilege tokens into tool configs.',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
rule: 'AGENT_OAUTH_SCOPE_CREEP',
|
|
180
|
+
title: 'Agent Config: Dangerously Broad OAuth Scopes',
|
|
181
|
+
regex: /scopes?\s*[:=]\s*(?:\[|["'])(?:[^"'\]]*,\s*){3,}[^"'\]]*/g,
|
|
182
|
+
severity: 'high',
|
|
183
|
+
cwe: 'CWE-272',
|
|
184
|
+
owasp: 'ASI-02',
|
|
185
|
+
confidence: 'medium',
|
|
186
|
+
description: 'Agent OAuth configuration requests 4 or more scopes. AI agents should follow least-privilege: request only the scopes needed for the specific task. Broad scope sets increase the blast radius of a prompt injection attack.',
|
|
187
|
+
fix: 'Reduce to the minimum required scopes. Create separate agent profiles for different task types.',
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
export class AgenticSupplyChainAgent extends BaseAgent {
|
|
192
|
+
constructor() {
|
|
193
|
+
super(
|
|
194
|
+
'AgenticSupplyChainAgent',
|
|
195
|
+
'Detect AI integration supply chain attack vectors (over-privileged CI actions, OAuth scope abuse, unsigned webhooks, cross-boundary token forwarding)',
|
|
196
|
+
'supply-chain'
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async analyze(context) {
|
|
201
|
+
const { rootPath, files } = context;
|
|
202
|
+
const findings = [];
|
|
203
|
+
|
|
204
|
+
// ── Track 1: Over-privileged AI actions in CI ─────────────────────────────
|
|
205
|
+
const ciFiles = files.filter(f => {
|
|
206
|
+
const rel = path.relative(rootPath, f).replace(/\\/g, '/');
|
|
207
|
+
return rel.startsWith('.github/workflows/') && /\.ya?ml$/.test(f);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
for (const file of ciFiles) {
|
|
211
|
+
const content = this.readFile(file);
|
|
212
|
+
if (!content) continue;
|
|
213
|
+
|
|
214
|
+
// Check if this workflow uses any AI actions
|
|
215
|
+
const hasAiAction = AI_ACTION_NAME_RE.test(content);
|
|
216
|
+
AI_ACTION_NAME_RE.lastIndex = 0;
|
|
217
|
+
|
|
218
|
+
if (hasAiAction) {
|
|
219
|
+
// Flag broad scope patterns in this workflow
|
|
220
|
+
findings.push(...this.scanFileWithPatterns(file, BROAD_SCOPE_PATTERNS));
|
|
221
|
+
|
|
222
|
+
// Flag AI actions not pinned to a commit SHA
|
|
223
|
+
const lines = content.split('\n');
|
|
224
|
+
for (let i = 0; i < lines.length; i++) {
|
|
225
|
+
AI_ACTION_NAME_RE.lastIndex = 0;
|
|
226
|
+
let m;
|
|
227
|
+
while ((m = AI_ACTION_NAME_RE.exec(lines[i])) !== null) {
|
|
228
|
+
const ref = m[2];
|
|
229
|
+
// Warn if ref is NOT a 40-char hex SHA
|
|
230
|
+
if (!/^[0-9a-f]{40}$/i.test(ref)) {
|
|
231
|
+
findings.push(createFinding({
|
|
232
|
+
file,
|
|
233
|
+
line: i + 1,
|
|
234
|
+
column: m.index + 1,
|
|
235
|
+
severity: 'critical',
|
|
236
|
+
category: this.category,
|
|
237
|
+
rule: 'AI_CI_UNPINNED_AI_ACTION',
|
|
238
|
+
title: `AI CI Action Not Pinned to SHA: ${m[1]}@${ref}`,
|
|
239
|
+
description: `The AI action "${m[1]}" is referenced by a mutable tag ("${ref}") rather than a commit SHA. A supply chain attack via tag hijacking (as seen in April 2026) can replace this action with a credential stealer. All secrets passed to this action would be silently exfiltrated.`,
|
|
240
|
+
matched: m[0],
|
|
241
|
+
cwe: 'CWE-829',
|
|
242
|
+
owasp: 'CICD-SEC-8',
|
|
243
|
+
fix: `Pin to the full 40-character commit SHA: uses: ${m[1]}@<sha> # ${ref}`,
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
AI_ACTION_NAME_RE.lastIndex = 0;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── Track 2: Excessive OAuth scopes in AI platform integrations ───────────
|
|
253
|
+
|
|
254
|
+
// vercel.json / .vercel/project.json
|
|
255
|
+
for (const vercelConfig of ['vercel.json', '.vercel/project.json']) {
|
|
256
|
+
const p = path.join(rootPath, vercelConfig);
|
|
257
|
+
if (!fs.existsSync(p)) continue;
|
|
258
|
+
try {
|
|
259
|
+
const cfg = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
260
|
+
this._auditVercelIntegrations(p, cfg, findings);
|
|
261
|
+
} catch { /* skip parse errors */ }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// GitHub App manifests (app.yml, .github/app.yml, github-app.yml)
|
|
265
|
+
for (const appManifest of [
|
|
266
|
+
'app.yml', 'app.yaml',
|
|
267
|
+
'.github/app.yml', '.github/app.yaml',
|
|
268
|
+
'github-app.yml', 'github-app.yaml',
|
|
269
|
+
]) {
|
|
270
|
+
const p = path.join(rootPath, appManifest);
|
|
271
|
+
if (!fs.existsSync(p)) continue;
|
|
272
|
+
this._auditGitHubAppManifest(p, findings);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// netlify.toml
|
|
276
|
+
const netlifyPath = path.join(rootPath, 'netlify.toml');
|
|
277
|
+
if (fs.existsSync(netlifyPath)) {
|
|
278
|
+
this._auditNetlify(netlifyPath, findings);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── Track 3: Webhook receivers without HMAC ───────────────────────────────
|
|
282
|
+
const webhookFiles = files.filter(f => {
|
|
283
|
+
const rel = path.relative(rootPath, f).replace(/\\/g, '/');
|
|
284
|
+
return (
|
|
285
|
+
/webhook/i.test(rel) &&
|
|
286
|
+
!f.includes('node_modules') &&
|
|
287
|
+
/\.(js|ts|mjs|cjs)$/.test(f)
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
for (const file of webhookFiles) {
|
|
292
|
+
const content = this.readFile(file);
|
|
293
|
+
if (!content) continue;
|
|
294
|
+
|
|
295
|
+
const hasHmac = HMAC_MARKERS.some(re => re.test(content));
|
|
296
|
+
if (!hasHmac) {
|
|
297
|
+
// Only fire if the file actually looks like it handles AI/payment webhook events
|
|
298
|
+
const looksLikeWebhook = /(?:openai|anthropic|vercel|stripe|linear|github|slack).*(?:event|payload|hook)/i.test(content);
|
|
299
|
+
if (looksLikeWebhook) {
|
|
300
|
+
findings.push(createFinding({
|
|
301
|
+
file,
|
|
302
|
+
line: 1,
|
|
303
|
+
severity: 'high',
|
|
304
|
+
category: this.category,
|
|
305
|
+
rule: 'WEBHOOK_NO_HMAC_VERIFICATION',
|
|
306
|
+
title: 'AI Platform Webhook: No HMAC Signature Verification',
|
|
307
|
+
description: 'This webhook handler processes AI or payment platform events but no HMAC verification was detected. An attacker can POST forged events to trigger AI agent actions, grant access, or inject malicious payloads — without a valid signature. This was a secondary exploit path in the April 2026 Vercel incident.',
|
|
308
|
+
matched: path.basename(file),
|
|
309
|
+
cwe: 'CWE-345',
|
|
310
|
+
owasp: 'ASI-06',
|
|
311
|
+
fix: 'Verify the platform-specific signature header (e.g. Stripe-Signature, X-Hub-Signature-256) using HMAC-SHA256 with a constant-time comparison before processing any event.',
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Also scan for raw-body anti-pattern in verified webhook files
|
|
318
|
+
for (const file of webhookFiles) {
|
|
319
|
+
findings.push(...this.scanFileWithPatterns(file, [WEBHOOK_PATTERNS[1]]));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Track 4: Cross-boundary token forwarding ──────────────────────────────
|
|
323
|
+
const agentConfigFiles = files.filter(f => {
|
|
324
|
+
const rel = path.relative(rootPath, f).replace(/\\/g, '/');
|
|
325
|
+
const base = path.basename(f);
|
|
326
|
+
return (
|
|
327
|
+
!f.includes('node_modules') &&
|
|
328
|
+
(
|
|
329
|
+
/mcp[._-]?(?:server|config|settings)/i.test(base) ||
|
|
330
|
+
/\.hermes(?:rc|\.json|\.yaml|\.yml)$/.test(base) ||
|
|
331
|
+
/hermes[._-]?config/i.test(base) ||
|
|
332
|
+
/agent[._-]?config/i.test(base) ||
|
|
333
|
+
/claude[._-]?(?:md|settings|config)/i.test(base) ||
|
|
334
|
+
base === '.mcp.json' ||
|
|
335
|
+
base === 'mcp.json' ||
|
|
336
|
+
rel.includes('.claude/') ||
|
|
337
|
+
rel.includes('.hermes/')
|
|
338
|
+
)
|
|
339
|
+
);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
for (const file of agentConfigFiles) {
|
|
343
|
+
findings.push(...this.scanFileWithPatterns(file, TOKEN_FORWARD_PATTERNS));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Also scan workflow files for token forwarding to external MCP servers
|
|
347
|
+
for (const file of ciFiles) {
|
|
348
|
+
findings.push(...this.scanFileWithPatterns(file, [TOKEN_FORWARD_PATTERNS[0]]));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return findings;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
355
|
+
|
|
356
|
+
_auditVercelIntegrations(filePath, cfg, findings) {
|
|
357
|
+
// Vercel integrations can specify required scopes — check for overly broad ones
|
|
358
|
+
const integrations = cfg.integrations || cfg.extensions || [];
|
|
359
|
+
if (!Array.isArray(integrations)) return;
|
|
360
|
+
|
|
361
|
+
for (const integration of integrations) {
|
|
362
|
+
const name = (integration.name || integration.slug || '').toLowerCase();
|
|
363
|
+
const isAi = VERCEL_AI_INTEGRATIONS.has(name) ||
|
|
364
|
+
/ai|llm|copilot|claude|gpt|agent|cursor/.test(name);
|
|
365
|
+
if (!isAi) continue;
|
|
366
|
+
|
|
367
|
+
const scopes = integration.scopes || integration.permissions || [];
|
|
368
|
+
const broadScopes = (Array.isArray(scopes) ? scopes : Object.keys(scopes))
|
|
369
|
+
.filter(s => /write|admin|delete|deploy|secret/.test(String(s).toLowerCase()));
|
|
370
|
+
|
|
371
|
+
if (broadScopes.length > 0) {
|
|
372
|
+
findings.push(createFinding({
|
|
373
|
+
file: filePath,
|
|
374
|
+
line: 0,
|
|
375
|
+
severity: 'high',
|
|
376
|
+
category: this.category,
|
|
377
|
+
rule: 'VERCEL_AI_INTEGRATION_BROAD_SCOPE',
|
|
378
|
+
title: `Vercel AI Integration Overly Broad Scopes: ${name}`,
|
|
379
|
+
description: `The Vercel AI integration "${name}" requests write/admin scopes: ${broadScopes.join(', ')}. If this integration is compromised (e.g. via a trojanized update — the vector in the April 2026 Vercel incident), it can modify deployments, exfiltrate secrets, or inject malicious environment variables.`,
|
|
380
|
+
matched: broadScopes.join(', '),
|
|
381
|
+
cwe: 'CWE-272',
|
|
382
|
+
owasp: 'ASI-02',
|
|
383
|
+
fix: `Review whether "${name}" actually needs ${broadScopes.join(', ')} scope. Request read-only scopes where possible.`,
|
|
384
|
+
}));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
_auditGitHubAppManifest(filePath, findings) {
|
|
390
|
+
const content = this.readFile(filePath);
|
|
391
|
+
if (!content) return;
|
|
392
|
+
|
|
393
|
+
// Check for dangerous permission combinations
|
|
394
|
+
for (const scope of DANGEROUS_GITHUB_APP_SCOPES) {
|
|
395
|
+
const re = new RegExp(`${scope}\\s*:\\s*write`, 'gi');
|
|
396
|
+
if (re.test(content)) {
|
|
397
|
+
findings.push(createFinding({
|
|
398
|
+
file: filePath,
|
|
399
|
+
line: 0,
|
|
400
|
+
severity: 'high',
|
|
401
|
+
category: this.category,
|
|
402
|
+
rule: 'GITHUB_APP_DANGEROUS_SCOPE',
|
|
403
|
+
title: `GitHub App Manifest: Dangerous Scope "${scope}: write"`,
|
|
404
|
+
description: `The GitHub App manifest requests "${scope}: write". If this app integrates AI functionality, a supply chain compromise of the app (malicious version update, OAuth token theft) grants an attacker ${scope} write access across all installed repositories.`,
|
|
405
|
+
matched: `${scope}: write`,
|
|
406
|
+
cwe: 'CWE-272',
|
|
407
|
+
owasp: 'ASI-02',
|
|
408
|
+
fix: `Only request write access to scopes your app directly needs. Consider splitting AI-only functionality into a separate app with minimal permissions.`,
|
|
409
|
+
}));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Check if the app manifest includes a webhook URL without TLS
|
|
414
|
+
const webhookMatch = content.match(/webhook_url\s*:\s*["']?(http:\/\/[^"'\s]+)/i);
|
|
415
|
+
if (webhookMatch) {
|
|
416
|
+
findings.push(createFinding({
|
|
417
|
+
file: filePath,
|
|
418
|
+
line: 0,
|
|
419
|
+
severity: 'high',
|
|
420
|
+
category: this.category,
|
|
421
|
+
rule: 'GITHUB_APP_INSECURE_WEBHOOK',
|
|
422
|
+
title: 'GitHub App Manifest: Webhook URL Without TLS',
|
|
423
|
+
description: `The GitHub App webhook_url uses plain HTTP: "${webhookMatch[1]}". All event payloads (including code review comments and CI results) are transmitted unencrypted, and the HMAC signature over HTTP provides no protection against MITM injection.`,
|
|
424
|
+
matched: webhookMatch[1],
|
|
425
|
+
cwe: 'CWE-319',
|
|
426
|
+
owasp: 'A02:2021',
|
|
427
|
+
fix: 'Use an https:// webhook URL. GitHub will not deliver events to HTTP URLs in production.',
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
_auditNetlify(filePath, findings) {
|
|
433
|
+
const content = this.readFile(filePath);
|
|
434
|
+
if (!content) return;
|
|
435
|
+
|
|
436
|
+
// Look for AI plugins in netlify.toml [[plugins]] sections
|
|
437
|
+
const pluginMatches = content.matchAll(/\[\[plugins\]\][^\[]*package\s*=\s*["']([^"']+)["']/gs);
|
|
438
|
+
for (const m of pluginMatches) {
|
|
439
|
+
const pkg = m[1];
|
|
440
|
+
if (!NETLIFY_AI_PLUGINS.has(pkg) && !/ai|llm|openai|anthropic|langchain|vector/.test(pkg)) continue;
|
|
441
|
+
|
|
442
|
+
// Check if the plugin section has env vars containing secrets
|
|
443
|
+
const pluginSection = m[0];
|
|
444
|
+
if (/(?:api[_-]?key|token|secret)\s*=\s*["']?\$\{?(?:process\.env|env)\./i.test(pluginSection)) {
|
|
445
|
+
findings.push(createFinding({
|
|
446
|
+
file: filePath,
|
|
447
|
+
line: 0,
|
|
448
|
+
severity: 'high',
|
|
449
|
+
category: this.category,
|
|
450
|
+
rule: 'NETLIFY_AI_PLUGIN_SECRET_EXPOSURE',
|
|
451
|
+
title: `Netlify AI Plugin Exposes Secrets in Build Config: ${pkg}`,
|
|
452
|
+
description: `The Netlify AI plugin "${pkg}" receives secrets via the build configuration. Netlify plugins run in the build environment with access to all env vars. A compromised plugin version can exfiltrate every secret in your Netlify site.`,
|
|
453
|
+
matched: pkg,
|
|
454
|
+
cwe: 'CWE-312',
|
|
455
|
+
owasp: 'ASI-09',
|
|
456
|
+
fix: 'Pin the plugin to a specific version and verify it on npm before updating. Only pass the minimum required secrets.',
|
|
457
|
+
}));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export default AgenticSupplyChainAgent;
|