security-mcp 1.3.1 → 1.3.3
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 +356 -885
- package/defaults/cloud-controls/aws.json +10712 -0
- package/defaults/cloud-controls/azure.json +7201 -0
- package/defaults/cloud-controls/gcp.json +4061 -0
- package/defaults/control-catalog.json +24 -0
- package/dist/ci/pr-gate.js +22 -5
- package/dist/cli/index.js +73 -2
- package/dist/cli/install.js +4 -55
- package/dist/cli/onboarding.js +18 -10
- package/dist/gate/checks/agentic-instructions.js +515 -0
- package/dist/gate/checks/ai-governance.js +132 -0
- package/dist/gate/checks/ai.js +1 -1
- package/dist/gate/checks/cloud-controls.js +69 -0
- package/dist/gate/checks/crypto.js +1 -1
- package/dist/gate/checks/data-platform.js +954 -0
- package/dist/gate/checks/dependencies.js +14 -3
- package/dist/gate/checks/docker-deep.js +1236 -0
- package/dist/gate/checks/gitops.js +724 -0
- package/dist/gate/checks/iac.js +1230 -0
- package/dist/gate/checks/k8s.js +841 -1
- package/dist/gate/checks/secrets.js +49 -37
- package/dist/gate/cloud-controls/apply.js +115 -0
- package/dist/gate/cloud-controls/bicep.js +36 -0
- package/dist/gate/cloud-controls/cfn.js +125 -0
- package/dist/gate/cloud-controls/detect.js +104 -0
- package/dist/gate/cloud-controls/hcl.js +140 -0
- package/dist/gate/cloud-controls/types.js +87 -0
- package/dist/gate/exceptions.js +78 -7
- package/dist/gate/findings.js +15 -2
- package/dist/gate/policy.js +40 -3
- package/dist/gate/threat-intel.js +6 -0
- package/dist/mcp/audit-chain.js +9 -0
- package/dist/mcp/model-router.js +3 -3
- package/dist/mcp/orchestration.js +194 -41
- package/dist/mcp/server.js +124 -17
- package/dist/mcp/tool-audit.js +193 -0
- package/dist/repo/fs.js +14 -1
- package/dist/review/store.js +4 -2
- package/dist/tests/run.js +124 -1
- package/package.json +3 -3
- package/skills/advanced-dos-tester/SKILL.md +9 -0
- package/skills/agentic-instruction-auditor/SKILL.md +111 -0
- package/skills/agentic-loop-exploiter/SKILL.md +9 -0
- package/skills/ai-llm-redteam/SKILL.md +9 -0
- package/skills/ai-model-supply-chain-agent/SKILL.md +9 -0
- package/skills/algorithm-implementation-reviewer/SKILL.md +9 -0
- package/skills/android-penetration-tester/SKILL.md +9 -0
- package/skills/anti-replay-tester/SKILL.md +9 -0
- package/skills/appsec-code-auditor/SKILL.md +9 -0
- package/skills/artifact-integrity-analyst/SKILL.md +9 -0
- package/skills/attack-navigator/SKILL.md +9 -0
- package/skills/auth-session-hacker/SKILL.md +9 -0
- package/skills/aws-penetration-tester/SKILL.md +54 -0
- package/skills/azure-penetration-tester/SKILL.md +52 -0
- package/skills/binary-auth-validator/SKILL.md +9 -0
- package/skills/bot-detection-specialist/SKILL.md +9 -0
- package/skills/business-logic-attacker/SKILL.md +9 -0
- package/skills/capec-code-mapper/SKILL.md +9 -0
- package/skills/cert-pin-rotation-specialist/SKILL.md +9 -0
- package/skills/cicd-pipeline-hijacker/SKILL.md +9 -0
- package/skills/ciso-orchestrator/SKILL.md +11 -0
- package/skills/cloud-infra-specialist/SKILL.md +9 -0
- package/skills/compliance-gap-analyst/SKILL.md +9 -0
- package/skills/compliance-grc/SKILL.md +9 -0
- package/skills/compliance-lifecycle-tracker/SKILL.md +9 -0
- package/skills/container-hardening-auditor/SKILL.md +125 -0
- package/skills/credential-stuffing-specialist/SKILL.md +9 -0
- package/skills/crypto-pki-specialist/SKILL.md +9 -0
- package/skills/csa-ccm-mapper/SKILL.md +9 -0
- package/skills/csf2-governance-mapper/SKILL.md +9 -0
- package/skills/data-platform-auditor/SKILL.md +125 -0
- package/skills/deep-link-fuzzer/SKILL.md +9 -0
- package/skills/dependency-confusion-attacker/SKILL.md +9 -0
- package/skills/device-integrity-aggregator/SKILL.md +9 -0
- package/skills/dos-resilience-tester/SKILL.md +9 -0
- package/skills/dread-scorer/SKILL.md +9 -0
- package/skills/egress-policy-enforcer/SKILL.md +9 -0
- package/skills/evidence-collector/SKILL.md +9 -0
- package/skills/file-upload-attacker/SKILL.md +9 -0
- package/skills/gcp-penetration-tester/SKILL.md +51 -0
- package/skills/git-history-secret-scanner/SKILL.md +9 -0
- package/skills/gitops-delivery-auditor/SKILL.md +120 -0
- package/skills/iac-security-auditor/SKILL.md +125 -0
- package/skills/iam-privesc-graph-builder/SKILL.md +9 -0
- package/skills/incident-responder/SKILL.md +9 -0
- package/skills/injection-specialist/SKILL.md +9 -0
- package/skills/ios-security-auditor/SKILL.md +9 -0
- package/skills/json-ambiguity-tester/SKILL.md +0 -0
- package/skills/k8s-container-escaper/SKILL.md +22 -0
- package/skills/key-management-lifecycle-analyst/SKILL.md +9 -0
- package/skills/kill-switch-engineer/SKILL.md +9 -0
- package/skills/linddun-privacy-analyst/SKILL.md +9 -0
- package/skills/logic-race-fuzzer/SKILL.md +9 -0
- package/skills/mobile-api-network-attacker/SKILL.md +9 -0
- package/skills/mobile-binary-hardener/SKILL.md +9 -0
- package/skills/mobile-security-specialist/SKILL.md +9 -0
- package/skills/mobile-webview-auditor/SKILL.md +9 -0
- package/skills/model-extraction-attacker/SKILL.md +9 -0
- package/skills/multipart-abuse-tester/SKILL.md +9 -0
- package/skills/oauth-pkce-specialist/SKILL.md +9 -0
- package/skills/parser-exhaustion-tester/SKILL.md +9 -0
- package/skills/pentest-infra/SKILL.md +9 -0
- package/skills/pentest-social/SKILL.md +9 -0
- package/skills/pentest-team/SKILL.md +9 -0
- package/skills/pentest-web-api/SKILL.md +9 -0
- package/skills/privacy-flow-analyst/SKILL.md +9 -0
- package/skills/prompt-injection-specialist/SKILL.md +9 -0
- package/skills/quantum-migration-planner/SKILL.md +9 -0
- package/skills/rag-poisoning-specialist/SKILL.md +9 -0
- package/skills/registry-mirror-enforcer/SKILL.md +9 -0
- package/skills/rotation-validation-agent/SKILL.md +9 -0
- package/skills/samm-assessor/SKILL.md +9 -0
- package/skills/secrets-mask-bypass-tester/SKILL.md +9 -0
- package/skills/senior-security-engineer/SKILL.md +11 -0
- package/skills/serialization-memory-attacker/SKILL.md +9 -0
- package/skills/session-timeout-tester/SKILL.md +9 -0
- package/skills/slsa-level3-enforcer/SKILL.md +9 -0
- package/skills/slsa-provenance-enforcer/SKILL.md +9 -0
- package/skills/ssrf-detection-validator/SKILL.md +9 -0
- package/skills/step-up-auth-enforcer/SKILL.md +9 -0
- package/skills/stride-pasta-analyst/SKILL.md +9 -0
- package/skills/supply-chain-devsecops/SKILL.md +9 -0
- package/skills/threat-infrastructure-analyst/SKILL.md +9 -0
- package/skills/threat-modeler/SKILL.md +9 -0
- package/skills/tls-certificate-auditor/SKILL.md +9 -0
- package/skills/token-reuse-detector/SKILL.md +9 -0
- package/skills/trike-risk-modeler/SKILL.md +9 -0
- package/skills/unicode-homograph-tester/SKILL.md +9 -0
- package/skills/waf-rule-lifecycle-agent/SKILL.md +9 -0
- package/skills/webhook-security-tester/SKILL.md +9 -0
- package/skills/zero-trust-architect/SKILL.md +9 -0
|
@@ -417,6 +417,30 @@
|
|
|
417
417
|
"frameworks": ["OWASP LLM Top 10 2025", "NIST AI RMF"],
|
|
418
418
|
"evidence": ["human_in_loop_for_actions", "tool_allowlist_router"]
|
|
419
419
|
},
|
|
420
|
+
{
|
|
421
|
+
"id": "AI_AGENTIC_INSTRUCTION_INTEGRITY",
|
|
422
|
+
"description": "Agentic-instruction files (SKILL.md, .claude, CLAUDE.md, AGENTS.md, .cursorrules, .mcp.json, copilot-instructions) are scanned for prompt-override, exfiltration, tool-poisoning, persistence, hidden-character, and credential-harvest payloads before any AI agent ingests the repository.",
|
|
423
|
+
"automation": "tooling",
|
|
424
|
+
"surfaces": ["ai", "agentic"],
|
|
425
|
+
"frameworks": ["OWASP LLM Top 10 2025", "MITRE ATLAS"],
|
|
426
|
+
"required_scanners": []
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
"id": "AI_LLM_ALGORITHMIC_FAIRNESS",
|
|
430
|
+
"description": "ML decision systems affecting people carry fairness/bias evaluation (disparate impact, equalized odds, demographic parity) and representativeness evidence.",
|
|
431
|
+
"automation": "evidence",
|
|
432
|
+
"surfaces": ["ai"],
|
|
433
|
+
"frameworks": ["EU AI Act", "NIST AI RMF", "ISO 42001"],
|
|
434
|
+
"evidence": ["fairness_evaluation", "bias_testing_present"]
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
"id": "AI_SHADOW_AI_DATA_LEAKAGE",
|
|
438
|
+
"description": "Secrets and PII are redacted/tokenized before reaching any LLM payload, preventing shadow-AI data leakage to model providers.",
|
|
439
|
+
"automation": "evidence",
|
|
440
|
+
"surfaces": ["ai"],
|
|
441
|
+
"frameworks": ["NIST AI RMF", "OWASP LLM Top 10 2025"],
|
|
442
|
+
"evidence": ["pii_redaction_before_llm", "secret_dlp_guard"]
|
|
443
|
+
},
|
|
420
444
|
{
|
|
421
445
|
"id": "SLSA_L3_PROVENANCE",
|
|
422
446
|
"description": "Build artifacts have signed SLSA Level 3 provenance from a hermetic, ephemeral CI build.",
|
package/dist/ci/pr-gate.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { resolve } from "node:path";
|
|
1
3
|
import { runPrGate } from "../gate/policy.js";
|
|
2
4
|
// Allow safe git revision operators (~ and ^) plus ref/path characters. CWE-88.
|
|
3
5
|
const SAFE_REF_RE = /^[a-zA-Z0-9_./~^-]+$/;
|
|
@@ -24,7 +26,13 @@ function safeEnvTargets(envVar) {
|
|
|
24
26
|
return true;
|
|
25
27
|
});
|
|
26
28
|
}
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Run the policy gate using configuration from environment variables.
|
|
31
|
+
* Exported so the `security-mcp ci:pr-gate` CLI subcommand can invoke it,
|
|
32
|
+
* while `node dist/ci/pr-gate.js` (and `npm run ci:pr-gate`) still run it directly.
|
|
33
|
+
* Exits the process: code 2 when the gate fails, 0 when it passes.
|
|
34
|
+
*/
|
|
35
|
+
export async function runGateFromEnv() {
|
|
28
36
|
const baseRef = safeEnvRef("SECURITY_GATE_BASE_REF", "origin/main");
|
|
29
37
|
const headRef = safeEnvRef("SECURITY_GATE_HEAD_REF", "HEAD");
|
|
30
38
|
const policyPath = process.env.SECURITY_GATE_POLICY || ".mcp/policies/security-policy.json";
|
|
@@ -37,7 +45,16 @@ async function main() {
|
|
|
37
45
|
process.exit(2);
|
|
38
46
|
}
|
|
39
47
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
// Auto-run only when executed directly (node dist/ci/pr-gate.js / npm run ci:pr-gate),
|
|
49
|
+
// not when imported by the CLI dispatcher.
|
|
50
|
+
const invokedDirectly = process.argv[1] !== undefined &&
|
|
51
|
+
fileURLToPath(import.meta.url) === resolve(process.argv[1]);
|
|
52
|
+
if (invokedDirectly) {
|
|
53
|
+
try {
|
|
54
|
+
await runGateFromEnv();
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error("security gate crashed:", err);
|
|
58
|
+
process.exit(3);
|
|
59
|
+
}
|
|
60
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -12,11 +12,14 @@
|
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { dirname, resolve } from "node:path";
|
|
15
|
-
import { existsSync } from "node:fs";
|
|
15
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { homedir, platform } from "node:os";
|
|
17
17
|
import { runInstall } from "./install.js";
|
|
18
18
|
import { main as runServer } from "../mcp/server.js";
|
|
19
19
|
import { notifyIfUpdateAvailable } from "./update.js";
|
|
20
|
+
import { autoHardenTree } from "../gate/cloud-controls/apply.js";
|
|
21
|
+
import { runGateFromEnv } from "../ci/pr-gate.js";
|
|
22
|
+
import { signPolicyFile } from "../gate/policy.js";
|
|
20
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
24
|
const require = createRequire(import.meta.url);
|
|
22
25
|
function getVersion() {
|
|
@@ -58,6 +61,9 @@ COMMANDS
|
|
|
58
61
|
install-global Install using the globally installed security-mcp binary
|
|
59
62
|
config Print MCP config JSON for manual editor setup
|
|
60
63
|
doctor Verify the installation is working correctly
|
|
64
|
+
autoharden Auto-apply FSBP/CIS hardening fixes to Terraform (use --dry-run to preview)
|
|
65
|
+
ci:pr-gate Run the policy gate against the current diff (for CI/pre-commit)
|
|
66
|
+
sign-policy Sign the policy file with SECURITY_POLICY_HMAC_KEY for tamper protection
|
|
61
67
|
|
|
62
68
|
OPTIONS (install)
|
|
63
69
|
--claude-code Write config for Claude Code only
|
|
@@ -93,6 +99,13 @@ EXAMPLES
|
|
|
93
99
|
# Verify installation health:
|
|
94
100
|
npx -y security-mcp@latest doctor
|
|
95
101
|
|
|
102
|
+
# Run the policy gate in CI (fails the build on HIGH/CRITICAL findings):
|
|
103
|
+
npx -y security-mcp@latest ci:pr-gate
|
|
104
|
+
|
|
105
|
+
# Sign the policy file so tampering is detected at gate startup:
|
|
106
|
+
export SECURITY_POLICY_HMAC_KEY="$(openssl rand -hex 32)"
|
|
107
|
+
npx -y security-mcp@latest sign-policy
|
|
108
|
+
|
|
96
109
|
# Print JSON config snippet:
|
|
97
110
|
npx -y security-mcp@latest config
|
|
98
111
|
security-mcp config --use-global-binary
|
|
@@ -179,6 +192,50 @@ function runDoctor() {
|
|
|
179
192
|
process.exit(1);
|
|
180
193
|
}
|
|
181
194
|
}
|
|
195
|
+
async function runAutoHarden(dryRun) {
|
|
196
|
+
const report = await autoHardenTree({ write: !dryRun });
|
|
197
|
+
const verb = dryRun ? "Would apply" : "Applied";
|
|
198
|
+
process.stdout.write(`\nsecurity-mcp autoharden v${VERSION}\n`);
|
|
199
|
+
process.stdout.write("=".repeat(40) + "\n\n");
|
|
200
|
+
process.stdout.write(`${verb} ${report.applied.length} fix(es) across ${report.filesChanged.length} file(s).\n`);
|
|
201
|
+
for (const fix of report.applied) {
|
|
202
|
+
process.stdout.write(` [FIX] ${fix.ruleId} ${fix.resource} (${fix.file})\n`);
|
|
203
|
+
}
|
|
204
|
+
for (const m of report.manual) {
|
|
205
|
+
process.stdout.write(` [MANUAL] ${m.ruleId} ${m.resource} (${m.file}) — ${m.reason}\n`);
|
|
206
|
+
if (m.snippet)
|
|
207
|
+
process.stdout.write(` ${m.snippet}\n`);
|
|
208
|
+
}
|
|
209
|
+
if (dryRun)
|
|
210
|
+
process.stdout.write("\nDry run — no files were modified. Re-run without --dry-run to apply.\n");
|
|
211
|
+
process.stdout.write("\n");
|
|
212
|
+
}
|
|
213
|
+
// Minimum HMAC key length, mirrors POLICY_HMAC_MIN_KEY_BYTES in src/gate/policy.ts.
|
|
214
|
+
const POLICY_HMAC_MIN_KEY_BYTES = 32;
|
|
215
|
+
function runSignPolicy() {
|
|
216
|
+
const key = process.env["SECURITY_POLICY_HMAC_KEY"];
|
|
217
|
+
if (!key || Buffer.byteLength(key, "utf-8") < POLICY_HMAC_MIN_KEY_BYTES) {
|
|
218
|
+
process.stderr.write(`Error: SECURITY_POLICY_HMAC_KEY must be set and at least ${POLICY_HMAC_MIN_KEY_BYTES} bytes.\n` +
|
|
219
|
+
"Generate one with: openssl rand -hex 32\n");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
const policyPath = process.env["SECURITY_GATE_POLICY"] || ".mcp/policies/security-policy.json";
|
|
223
|
+
if (!existsSync(policyPath)) {
|
|
224
|
+
process.stderr.write(`Error: policy file not found at "${policyPath}".\n` +
|
|
225
|
+
"Create one first (cp node_modules/security-mcp/defaults/security-policy.json .mcp/policies/), " +
|
|
226
|
+
"or set SECURITY_GATE_POLICY to its path.\n");
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
const raw = readFileSync(policyPath, "utf-8");
|
|
230
|
+
const signature = signPolicyFile(raw, key);
|
|
231
|
+
// 0o600 — keep the sidecar non-world-readable, consistent with data-at-rest hardening.
|
|
232
|
+
writeFileSync(`${policyPath}.hmac`, signature + "\n", { mode: 0o600 });
|
|
233
|
+
process.stdout.write(`\nsecurity-mcp sign-policy v${VERSION}\n`);
|
|
234
|
+
process.stdout.write("=".repeat(40) + "\n\n");
|
|
235
|
+
process.stdout.write(` [SIGNED] ${policyPath}\n`);
|
|
236
|
+
process.stdout.write(` [WROTE] ${policyPath}.hmac\n\n`);
|
|
237
|
+
process.stdout.write("Commit both files so CI can verify policy integrity at gate startup.\n\n");
|
|
238
|
+
}
|
|
182
239
|
async function main() {
|
|
183
240
|
const args = process.argv.slice(2);
|
|
184
241
|
const useGlobalBinary = args.includes("--use-global-binary");
|
|
@@ -191,7 +248,8 @@ async function main() {
|
|
|
191
248
|
process.exit(0);
|
|
192
249
|
}
|
|
193
250
|
const command = args[0] ?? "serve";
|
|
194
|
-
if (command === "serve") {
|
|
251
|
+
if (command === "serve" || command === "ci:pr-gate") {
|
|
252
|
+
// Non-blocking: keep stdout reserved for protocol/JSON output.
|
|
195
253
|
void notifyIfUpdateAvailable(VERSION);
|
|
196
254
|
}
|
|
197
255
|
else {
|
|
@@ -245,6 +303,19 @@ async function main() {
|
|
|
245
303
|
runDoctor();
|
|
246
304
|
break;
|
|
247
305
|
}
|
|
306
|
+
case "autoharden": {
|
|
307
|
+
await runAutoHarden(args.includes("--dry-run"));
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
case "ci:pr-gate": {
|
|
311
|
+
// Reads SECURITY_GATE_* env vars; exits non-zero when the gate fails.
|
|
312
|
+
await runGateFromEnv();
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case "sign-policy": {
|
|
316
|
+
runSignPolicy();
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
248
319
|
default: {
|
|
249
320
|
process.stderr.write(`Unknown command: ${command}\nRun with --help for usage.\n`);
|
|
250
321
|
process.exit(1);
|
package/dist/cli/install.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync } from "node:fs";
|
|
7
7
|
import { dirname, join, resolve } from "node:path";
|
|
8
8
|
import { homedir, platform } from "node:os";
|
|
9
|
-
import * as https from "node:https";
|
|
10
9
|
import { fileURLToPath } from "node:url";
|
|
11
10
|
import { runOnboarding, installSecurityTools, commandExists, SECURITY_TOOLS } from "./onboarding.js";
|
|
12
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -172,60 +171,10 @@ function installSkill(dryRun) {
|
|
|
172
171
|
* Mirrors the same pattern used for security tool binary downloads in onboarding.ts.
|
|
173
172
|
*/
|
|
174
173
|
// CWE-22: only alphanumeric, hyphens, and dots allowed in skill names
|
|
175
|
-
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (!SAFE_SKILL_NAME_RE.test(skillName)) {
|
|
180
|
-
process.stdout.write(` [error] invalid skill name "${skillName}" — skipping download\n`);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
try {
|
|
184
|
-
const { hostname } = new URL(url);
|
|
185
|
-
if (!ALLOWED_SKILL_HOSTS.has(hostname)) {
|
|
186
|
-
process.stdout.write(` [error] blocked skill download from unauthorized host "${hostname}"\n`);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
process.stdout.write(` [error] invalid skill URL "${url}" — skipping download\n`);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const skillDest = resolveHome(`~/.claude/skills/${skillName}/SKILL.md`);
|
|
195
|
-
if (dryRun) {
|
|
196
|
-
process.stdout.write(` [dry-run] would download skill "${skillName}" from ${url} → ${skillDest}\n`);
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const MAX_SKILL_BYTES = 512 * 1024; // 512 KB — skills are markdown files
|
|
200
|
-
const content = await new Promise((resolve) => {
|
|
201
|
-
const req = https.get(url, { headers: { "User-Agent": "security-mcp" } }, (res) => {
|
|
202
|
-
if ((res.statusCode ?? 500) >= 400) {
|
|
203
|
-
res.resume();
|
|
204
|
-
resolve(null);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
let body = "";
|
|
208
|
-
res.setEncoding("utf8");
|
|
209
|
-
res.on("data", (chunk) => {
|
|
210
|
-
body += chunk;
|
|
211
|
-
if (Buffer.byteLength(body, "utf8") > MAX_SKILL_BYTES) {
|
|
212
|
-
req.destroy();
|
|
213
|
-
resolve(null);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
res.on("end", () => resolve(body));
|
|
217
|
-
});
|
|
218
|
-
req.on("error", () => resolve(null));
|
|
219
|
-
req.setTimeout(10000, () => { req.destroy(); resolve(null); });
|
|
220
|
-
});
|
|
221
|
-
if (!content) {
|
|
222
|
-
process.stdout.write(` [error] failed to download skill "${skillName}" from ${url}\n`);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
mkdirSync(dirname(skillDest), { recursive: true });
|
|
226
|
-
writeFileSync(skillDest, content, "utf-8");
|
|
227
|
-
process.stdout.write(` installed skill: ${skillDest}\n`);
|
|
228
|
-
}
|
|
174
|
+
// REMOVED downloadSkill(): an unused, integrity-free network skill installer
|
|
175
|
+
// (no sha256, no content sanitization) that, if ever wired up, would bypass every
|
|
176
|
+
// protection in orchestration.ensureSkill. Skills are bundled in the package and
|
|
177
|
+
// resolved locally by ensureSkill; there is no need for an unauthenticated fetcher.
|
|
229
178
|
/**
|
|
230
179
|
* Eagerly install the orchestrator skill (bundled in the package) plus record
|
|
231
180
|
* its version so orchestration.ensure_skill can detect future updates.
|
package/dist/cli/onboarding.js
CHANGED
|
@@ -340,11 +340,21 @@ async function installFromGitHub(tool, os) {
|
|
|
340
340
|
print(` Integrity verified (SHA-256 matched).`);
|
|
341
341
|
}
|
|
342
342
|
else {
|
|
343
|
-
print(`
|
|
343
|
+
print(` ABORT: checksum file found but no entry for ${fileName} — refusing to install an unverified binary (CWE-494).`);
|
|
344
|
+
try {
|
|
345
|
+
unlinkSync(tmpFile);
|
|
346
|
+
}
|
|
347
|
+
catch { /* ignore cleanup failure */ }
|
|
348
|
+
return false;
|
|
344
349
|
}
|
|
345
350
|
}
|
|
346
351
|
else {
|
|
347
|
-
print(`
|
|
352
|
+
print(` ABORT: no checksum file in release assets — refusing to install an unverified binary (CWE-494).`);
|
|
353
|
+
try {
|
|
354
|
+
unlinkSync(tmpFile);
|
|
355
|
+
}
|
|
356
|
+
catch { /* ignore cleanup failure */ }
|
|
357
|
+
return false;
|
|
348
358
|
}
|
|
349
359
|
const destDir = "/usr/local/bin";
|
|
350
360
|
if (tool.tarball) {
|
|
@@ -432,12 +442,10 @@ async function tryDnf(tool) {
|
|
|
432
442
|
print(` sudo ${mgr} install -y trivy`);
|
|
433
443
|
return run("sudo", [mgr, "install", "-y", "trivy"]);
|
|
434
444
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return run("bash", ["-c", tool.installScript]);
|
|
440
|
-
}
|
|
445
|
+
// REMOVED (CWE-78/CWE-494): the `curl … | sudo sh` install-script strategy piped an
|
|
446
|
+
// unpinned, live-fetched script into root with no checksum — a compromise of the upstream
|
|
447
|
+
// repo or a MITM yielded root RCE on `security-mcp install`. Tools are now installed only
|
|
448
|
+
// via OS package managers or the checksum-verified GitHub-release path (installFromGitHub).
|
|
441
449
|
async function tryWinget(tool) {
|
|
442
450
|
if (!tool.winget || !commandExists("winget"))
|
|
443
451
|
return false;
|
|
@@ -461,10 +469,10 @@ async function installSingleTool(tool, os) {
|
|
|
461
469
|
print(`\n Installing ${tool.displayName}...`);
|
|
462
470
|
const strategies = [];
|
|
463
471
|
if (os === "macos") {
|
|
464
|
-
strategies.push(() => tryBrew(tool), () => tryPip(tool), () => tryGoInstall(tool), () =>
|
|
472
|
+
strategies.push(() => tryBrew(tool), () => tryPip(tool), () => tryGoInstall(tool), () => installFromGitHub(tool, os));
|
|
465
473
|
}
|
|
466
474
|
else if (os === "linux") {
|
|
467
|
-
strategies.push(() => tryApt(tool), () => tryDnf(tool), () => tryPip(tool), () => tryGoInstall(tool), () =>
|
|
475
|
+
strategies.push(() => tryApt(tool), () => tryDnf(tool), () => tryPip(tool), () => tryGoInstall(tool), () => installFromGitHub(tool, os));
|
|
468
476
|
}
|
|
469
477
|
else {
|
|
470
478
|
// Windows
|