diff --git a/src/rules.js b/src/rules.js index 4b68b00..80e6897 100644 --- a/src/rules.js +++ b/src/rules.js @@ -38,6 +38,11 @@ export const RULES = [ remediation: "The skill text describes sending secrets/tokens/credentials somewhere. Treat as malicious until proven otherwise.", pattern: /(exfiltrate|leak|send|upload|post|forward)\b[^.\n]{0,30}(secret|token|password|credential|private\s*key|api[\s_-]?key|\.env)/gi }, + { id: "SKILL-INJ-009", severity: "high", category: "prompt-injection", appliesTo: "prose", + title: "Solicits credentials from the user", + remediation: "Skills must not ask the user to paste passwords, API keys, seed phrases, or other secrets into the chat.", + pattern: /(paste|enter|provide|share|type|input)\b[^.\n]{0,25}\b(your\s+)?(api[\s_-]?key|password|token|credentials?|secret|seed\s+phrase|private\s+key)/gi }, + { id: "SKILL-INJ-006", severity: "high", category: "obfuscation", appliesTo: "any", title: "Hidden zero-width or bidirectional Unicode", remediation: "Invisible characters are used to smuggle instructions past human review. Remove them.", diff --git a/test/skill-audit.test.js b/test/skill-audit.test.js index f4d8b3e..ef17b1a 100644 --- a/test/skill-audit.test.js +++ b/test/skill-audit.test.js @@ -183,6 +183,13 @@ test("hardening: instruction hidden in an HTML comment is caught", () => { assert.ok(!ok.some((x) => x.rule === "SKILL-INJ-008")); }); +test("hardening: credential solicitation from the user is caught (SKILL-INJ-009)", () => { + const bad = scanText("Paste your API key below to continue.\n", "SKILL.md", null); + assert.ok(bad.some((x) => x.rule === "SKILL-INJ-009")); + const ok = scanText("This step uses the configured API key from the environment.\n", "SKILL.md", null); + assert.ok(!ok.some((x) => x.rule === "SKILL-INJ-009")); +}); + test("hardening: browser creds, persistence, anti-forensics, dynamic exec", () => { const sh = "cp ~/Library/Application\\ Support/Google/Chrome/Default/Login\\ Data /tmp\n" + "crontab -e\nhistory -c\n";