security-mcp 1.1.3 → 1.3.1
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 +164 -185
- package/defaults/checklists/ai.json +20 -1
- package/defaults/checklists/api.json +35 -1
- package/defaults/checklists/infra.json +34 -1
- package/defaults/checklists/mobile.json +23 -1
- package/defaults/checklists/payments.json +15 -1
- package/defaults/checklists/web.json +11 -1
- package/defaults/control-catalog.json +200 -0
- package/defaults/security-policy.json +2 -2
- package/dist/cli/index.js +82 -5
- package/dist/cli/install.js +36 -6
- package/dist/cli/onboarding.js +6 -0
- package/dist/gate/baseline.js +82 -7
- package/dist/gate/catalog.js +10 -2
- package/dist/gate/checks/ai.js +757 -39
- package/dist/gate/checks/auth-deep.js +935 -0
- package/dist/gate/checks/business-logic.js +751 -0
- package/dist/gate/checks/ci-pipeline.js +399 -4
- package/dist/gate/checks/crypto.js +423 -2
- package/dist/gate/checks/dependencies.js +571 -15
- package/dist/gate/checks/graphql.js +201 -19
- package/dist/gate/checks/infra.js +246 -1
- package/dist/gate/checks/injection-deep.js +848 -0
- package/dist/gate/checks/k8s.js +114 -1
- package/dist/gate/checks/mobile-android.js +917 -3
- package/dist/gate/checks/mobile-ios.js +797 -5
- package/dist/gate/checks/required-artifacts.js +194 -0
- package/dist/gate/checks/runtime.js +178 -0
- package/dist/gate/checks/secrets.js +244 -13
- package/dist/gate/checks/supply-chain-deep.js +787 -0
- package/dist/gate/checks/web-nextjs.js +572 -48
- package/dist/gate/diff.js +17 -5
- package/dist/gate/evidence.js +8 -1
- package/dist/gate/exceptions.js +131 -9
- package/dist/gate/policy.js +282 -129
- package/dist/mcp/audit-chain.js +122 -28
- package/dist/mcp/auth.js +169 -0
- package/dist/mcp/learning.js +129 -4
- package/dist/mcp/model-router.js +158 -21
- package/dist/mcp/orchestration.js +186 -51
- package/dist/mcp/server.js +608 -94
- package/dist/repo/fs.js +24 -1
- package/dist/repo/search.js +31 -6
- package/dist/review/store.js +52 -1
- package/package.json +7 -7
- package/prompts/SECURITY_PROMPT.md +73 -0
- package/skills/_TEMPLATE/SKILL.md +99 -0
- package/skills/advanced-dos-tester/SKILL.md +109 -0
- package/skills/agentic-loop-exploiter/SKILL.md +368 -0
- package/skills/ai-llm-redteam/SKILL.md +104 -0
- package/skills/ai-model-supply-chain-agent/SKILL.md +103 -0
- package/skills/algorithm-implementation-reviewer/SKILL.md +98 -0
- package/skills/android-penetration-tester/SKILL.md +455 -46
- package/skills/anti-replay-tester/SKILL.md +106 -0
- package/skills/appsec-code-auditor/SKILL.md +120 -0
- package/skills/artifact-integrity-analyst/SKILL.md +441 -0
- package/skills/attack-navigator/SKILL.md +467 -8
- package/skills/auth-session-hacker/SKILL.md +128 -0
- package/skills/aws-penetration-tester/SKILL.md +456 -0
- package/skills/azure-penetration-tester/SKILL.md +490 -3
- package/skills/binary-auth-validator/SKILL.md +111 -0
- package/skills/bot-detection-specialist/SKILL.md +109 -0
- package/skills/business-logic-attacker/SKILL.md +231 -0
- package/skills/capec-code-mapper/SKILL.md +84 -0
- package/skills/cert-pin-rotation-specialist/SKILL.md +112 -0
- package/skills/cicd-pipeline-hijacker/SKILL.md +405 -0
- package/skills/ciso-orchestrator/SKILL.md +454 -43
- package/skills/cloud-infra-specialist/SKILL.md +118 -0
- package/skills/compliance-gap-analyst/SKILL.md +422 -0
- package/skills/compliance-grc/SKILL.md +85 -0
- package/skills/compliance-lifecycle-tracker/SKILL.md +84 -0
- package/skills/credential-stuffing-specialist/SKILL.md +102 -0
- package/skills/crypto-pki-specialist/SKILL.md +87 -0
- package/skills/csa-ccm-mapper/SKILL.md +84 -0
- package/skills/csf2-governance-mapper/SKILL.md +84 -0
- package/skills/deep-link-fuzzer/SKILL.md +109 -0
- package/skills/dependency-confusion-attacker/SKILL.md +415 -0
- package/skills/device-integrity-aggregator/SKILL.md +108 -0
- package/skills/dos-resilience-tester/SKILL.md +97 -0
- package/skills/dread-scorer/SKILL.md +84 -0
- package/skills/egress-policy-enforcer/SKILL.md +99 -0
- package/skills/evidence-collector/SKILL.md +98 -0
- package/skills/file-upload-attacker/SKILL.md +109 -0
- package/skills/gcp-penetration-tester/SKILL.md +459 -2
- package/skills/git-history-secret-scanner/SKILL.md +106 -0
- package/skills/iam-privesc-graph-builder/SKILL.md +152 -0
- package/skills/incident-responder/SKILL.md +111 -0
- package/skills/injection-specialist/SKILL.md +131 -0
- package/skills/ios-security-auditor/SKILL.md +282 -0
- package/skills/json-ambiguity-tester/SKILL.md +0 -0
- package/skills/k8s-container-escaper/SKILL.md +384 -0
- package/skills/key-management-lifecycle-analyst/SKILL.md +98 -0
- package/skills/kill-switch-engineer/SKILL.md +102 -0
- package/skills/linddun-privacy-analyst/SKILL.md +102 -0
- package/skills/logic-race-fuzzer/SKILL.md +443 -0
- package/skills/mobile-api-network-attacker/SKILL.md +421 -0
- package/skills/mobile-binary-hardener/SKILL.md +102 -0
- package/skills/mobile-security-specialist/SKILL.md +85 -0
- package/skills/mobile-webview-auditor/SKILL.md +96 -0
- package/skills/model-extraction-attacker/SKILL.md +219 -0
- package/skills/multipart-abuse-tester/SKILL.md +84 -0
- package/skills/oauth-pkce-specialist/SKILL.md +104 -0
- package/skills/parser-exhaustion-tester/SKILL.md +142 -0
- package/skills/pentest-infra/SKILL.md +141 -0
- package/skills/pentest-social/SKILL.md +201 -0
- package/skills/pentest-team/SKILL.md +134 -0
- package/skills/pentest-web-api/SKILL.md +151 -0
- package/skills/privacy-flow-analyst/SKILL.md +234 -0
- package/skills/prompt-injection-specialist/SKILL.md +394 -0
- package/skills/quantum-migration-planner/SKILL.md +96 -0
- package/skills/rag-poisoning-specialist/SKILL.md +358 -0
- package/skills/registry-mirror-enforcer/SKILL.md +84 -0
- package/skills/rotation-validation-agent/SKILL.md +112 -0
- package/skills/samm-assessor/SKILL.md +85 -0
- package/skills/secrets-mask-bypass-tester/SKILL.md +100 -0
- package/skills/senior-security-engineer/SKILL.md +370 -2
- package/skills/serialization-memory-attacker/SKILL.md +332 -0
- package/skills/session-timeout-tester/SKILL.md +161 -0
- package/skills/slsa-level3-enforcer/SKILL.md +112 -0
- package/skills/slsa-provenance-enforcer/SKILL.md +102 -0
- package/skills/ssrf-detection-validator/SKILL.md +108 -0
- package/skills/step-up-auth-enforcer/SKILL.md +84 -0
- package/skills/stride-pasta-analyst/SKILL.md +420 -0
- package/skills/supply-chain-devsecops/SKILL.md +98 -0
- package/skills/threat-infrastructure-analyst/SKILL.md +84 -0
- package/skills/threat-modeler/SKILL.md +85 -0
- package/skills/tls-certificate-auditor/SKILL.md +573 -18
- package/skills/token-reuse-detector/SKILL.md +95 -0
- package/skills/trike-risk-modeler/SKILL.md +84 -0
- package/skills/unicode-homograph-tester/SKILL.md +84 -0
- package/skills/waf-rule-lifecycle-agent/SKILL.md +97 -0
- package/skills/webhook-security-tester/SKILL.md +102 -0
- package/skills/zero-trust-architect/SKILL.md +109 -0
package/dist/gate/baseline.js
CHANGED
|
@@ -5,7 +5,39 @@
|
|
|
5
5
|
import { execFile } from "node:child_process";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
8
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
8
9
|
import { join } from "node:path";
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// HMAC integrity helpers — TM-013 fix
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// HMAC-SHA256 requires at least 32 bytes (256 bits) per NIST SP 800-107 §5.3.4.
|
|
14
|
+
const HMAC_MIN_KEY_BYTES = 32;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the HMAC key from env, or null if not configured.
|
|
17
|
+
* Throws if the key is present but too short.
|
|
18
|
+
*/
|
|
19
|
+
function getHmacKey() {
|
|
20
|
+
const key = process.env["SECURITY_POLICY_HMAC_KEY"];
|
|
21
|
+
if (!key)
|
|
22
|
+
return null;
|
|
23
|
+
if (Buffer.byteLength(key, "utf-8") < HMAC_MIN_KEY_BYTES) {
|
|
24
|
+
throw new Error(`SECURITY_POLICY_HMAC_KEY is too short (${Buffer.byteLength(key, "utf-8")} bytes). ` +
|
|
25
|
+
`Provide at least ${HMAC_MIN_KEY_BYTES} bytes — generate one with: ` +
|
|
26
|
+
`node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`);
|
|
27
|
+
}
|
|
28
|
+
return key;
|
|
29
|
+
}
|
|
30
|
+
function signBaseline(json, key) {
|
|
31
|
+
return createHmac("sha256", key).update(json, "utf-8").digest("hex");
|
|
32
|
+
}
|
|
33
|
+
function verifyBaselineHmac(json, stored, key) {
|
|
34
|
+
const expected = createHmac("sha256", key).update(json, "utf-8").digest("hex");
|
|
35
|
+
const storedBuf = Buffer.from(stored, "hex");
|
|
36
|
+
const expectedBuf = Buffer.from(expected, "hex");
|
|
37
|
+
if (storedBuf.length !== expectedBuf.length)
|
|
38
|
+
return false;
|
|
39
|
+
return timingSafeEqual(storedBuf, expectedBuf);
|
|
40
|
+
}
|
|
9
41
|
const execFileAsync = promisify(execFile);
|
|
10
42
|
const BASELINE_DIR = join(process.cwd(), ".mcp", "baselines");
|
|
11
43
|
async function ensureDir(dir) {
|
|
@@ -32,37 +64,53 @@ export async function getCommitHash() {
|
|
|
32
64
|
/**
|
|
33
65
|
* Saves a gate result as baseline for the given commit hash.
|
|
34
66
|
* Also updates the latest baseline copy.
|
|
67
|
+
*
|
|
68
|
+
* TM-013 fix: When SECURITY_POLICY_HMAC_KEY is set, the serialised payload is
|
|
69
|
+
* HMAC-SHA256 signed and the signature is stored in the envelope. Unsigned
|
|
70
|
+
* writes are still permitted when no key is configured (graceful degradation),
|
|
71
|
+
* but loadBaseline will reject a previously-signed file whose signature no
|
|
72
|
+
* longer matches (tamper detection).
|
|
35
73
|
*/
|
|
36
74
|
export async function saveBaseline(runId, result, commitHash) {
|
|
37
75
|
await ensureDir(BASELINE_DIR);
|
|
38
76
|
const payload = { runId, commitHash, savedAt: new Date().toISOString(), result };
|
|
39
77
|
const json = JSON.stringify(payload, null, 2);
|
|
78
|
+
// Sign if a key is available
|
|
79
|
+
const hmacKey = getHmacKey();
|
|
80
|
+
const envelope = hmacKey
|
|
81
|
+
? JSON.stringify({ payload, hmacSha256: signBaseline(json, hmacKey) }, null, 2)
|
|
82
|
+
: json;
|
|
40
83
|
// Write to temp file then rename (atomic)
|
|
41
84
|
const safehash = commitHash.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
42
85
|
const targetPath = join(BASELINE_DIR, `${safehash}.json`);
|
|
43
86
|
const latestPath = join(BASELINE_DIR, "latest.json");
|
|
44
|
-
const tmpPath = `${targetPath}.tmp`;
|
|
87
|
+
const tmpPath = `${targetPath}.${randomBytes(8).toString("hex")}.tmp`;
|
|
45
88
|
try {
|
|
46
|
-
await writeFile(tmpPath,
|
|
89
|
+
await writeFile(tmpPath, envelope, "utf-8");
|
|
47
90
|
await rename(tmpPath, targetPath);
|
|
48
91
|
}
|
|
49
92
|
catch {
|
|
50
93
|
// fallback: write directly
|
|
51
|
-
await writeFile(targetPath,
|
|
94
|
+
await writeFile(targetPath, envelope, "utf-8").catch(() => { });
|
|
52
95
|
}
|
|
53
96
|
// Update latest (best-effort atomic)
|
|
54
|
-
const latestTmp = `${latestPath}.tmp`;
|
|
97
|
+
const latestTmp = `${latestPath}.${randomBytes(8).toString("hex")}.tmp`;
|
|
55
98
|
try {
|
|
56
|
-
await writeFile(latestTmp,
|
|
99
|
+
await writeFile(latestTmp, envelope, "utf-8");
|
|
57
100
|
await rename(latestTmp, latestPath);
|
|
58
101
|
}
|
|
59
102
|
catch {
|
|
60
|
-
await writeFile(latestPath,
|
|
103
|
+
await writeFile(latestPath, envelope, "utf-8").catch(() => { });
|
|
61
104
|
}
|
|
62
105
|
}
|
|
63
106
|
/**
|
|
64
107
|
* Loads a baseline by commit hash, or the latest baseline if no hash given.
|
|
65
108
|
* Returns null if no baseline exists or it's corrupted.
|
|
109
|
+
*
|
|
110
|
+
* TM-013 fix: If the file is stored in the HMAC envelope format AND
|
|
111
|
+
* SECURITY_POLICY_HMAC_KEY is configured, the HMAC is verified before the
|
|
112
|
+
* payload is returned. A tampered baseline (missing or wrong HMAC) is
|
|
113
|
+
* rejected — the gate will run without a baseline rather than trust forged data.
|
|
66
114
|
*/
|
|
67
115
|
export async function loadBaseline(commitHash) {
|
|
68
116
|
await ensureDir(BASELINE_DIR);
|
|
@@ -76,7 +124,34 @@ export async function loadBaseline(commitHash) {
|
|
|
76
124
|
}
|
|
77
125
|
try {
|
|
78
126
|
const raw = await readFile(filePath, "utf-8");
|
|
79
|
-
const
|
|
127
|
+
const top = JSON.parse(raw);
|
|
128
|
+
// Detect envelope format (has both "payload" and "hmacSha256")
|
|
129
|
+
if ("payload" in top && "hmacSha256" in top) {
|
|
130
|
+
const envelope = top;
|
|
131
|
+
const hmacKey = getHmacKey();
|
|
132
|
+
if (hmacKey) {
|
|
133
|
+
// Re-serialise the inner payload the same way saveBaseline did
|
|
134
|
+
const expectedInput = JSON.stringify(envelope.payload, null, 2);
|
|
135
|
+
if (!verifyBaselineHmac(expectedInput, envelope.hmacSha256, hmacKey)) {
|
|
136
|
+
console.error("[baseline] HMAC verification failed — baseline may have been tampered. Ignoring.");
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Key not configured: we can't verify, but we can warn
|
|
142
|
+
console.warn("[baseline] Baseline is signed but SECURITY_POLICY_HMAC_KEY is not set — skipping HMAC verification.");
|
|
143
|
+
}
|
|
144
|
+
return envelope.payload.result ?? null;
|
|
145
|
+
}
|
|
146
|
+
// Legacy format (unsigned) — parse directly
|
|
147
|
+
const parsed = top;
|
|
148
|
+
const hmacKey = getHmacKey();
|
|
149
|
+
if (hmacKey) {
|
|
150
|
+
// A key is configured but the file is unsigned — reject it to prevent
|
|
151
|
+
// an attacker from stripping the HMAC wrapper to bypass verification.
|
|
152
|
+
console.error("[baseline] SECURITY_POLICY_HMAC_KEY is set but baseline is unsigned — ignoring to prevent tampering bypass.");
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
80
155
|
return parsed.result ?? null;
|
|
81
156
|
}
|
|
82
157
|
catch {
|
package/dist/gate/catalog.js
CHANGED
|
@@ -23,8 +23,16 @@ async function readJsonWithFallback(relPath, fallbackName) {
|
|
|
23
23
|
".mcp/catalog/control-catalog.json": "SECURITY_GATE_CONTROL_CATALOG"
|
|
24
24
|
};
|
|
25
25
|
const overrideEnv = overrideEnvMap[relPath];
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
const overridePath = overrideEnv ? process.env[overrideEnv] : undefined;
|
|
27
|
+
if (overridePath) {
|
|
28
|
+
// Guard against path traversal (VULN-003 / CWE-22): resolve() + startsWith() is required;
|
|
29
|
+
// join() alone normalises '..' but does not prevent escape from the project directory.
|
|
30
|
+
const cwd = process.cwd();
|
|
31
|
+
const resolved = resolve(cwd, overridePath);
|
|
32
|
+
if (resolved !== cwd && !resolved.startsWith(cwd + "/")) {
|
|
33
|
+
throw new Error(`${overrideEnv} path escapes the project directory`);
|
|
34
|
+
}
|
|
35
|
+
return await readFile(resolved, "utf-8");
|
|
28
36
|
}
|
|
29
37
|
try {
|
|
30
38
|
return await readFile(join(process.cwd(), relPath), "utf-8");
|