@elliotllliu/agent-shield 0.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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -0
  3. package/README.zh-CN.md +130 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +265 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config.d.ts +24 -0
  8. package/dist/config.js +91 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/discover.d.ts +9 -0
  11. package/dist/discover.js +143 -0
  12. package/dist/discover.js.map +1 -0
  13. package/dist/llm/anthropic.d.ts +10 -0
  14. package/dist/llm/anthropic.js +67 -0
  15. package/dist/llm/anthropic.js.map +1 -0
  16. package/dist/llm/index.d.ts +10 -0
  17. package/dist/llm/index.js +41 -0
  18. package/dist/llm/index.js.map +1 -0
  19. package/dist/llm/ollama.d.ts +9 -0
  20. package/dist/llm/ollama.js +61 -0
  21. package/dist/llm/ollama.js.map +1 -0
  22. package/dist/llm/openai.d.ts +10 -0
  23. package/dist/llm/openai.js +66 -0
  24. package/dist/llm/openai.js.map +1 -0
  25. package/dist/llm/prompt.d.ts +3 -0
  26. package/dist/llm/prompt.js +31 -0
  27. package/dist/llm/prompt.js.map +1 -0
  28. package/dist/llm/types.d.ts +23 -0
  29. package/dist/llm/types.js +3 -0
  30. package/dist/llm/types.js.map +1 -0
  31. package/dist/llm-analyzer.d.ts +13 -0
  32. package/dist/llm-analyzer.js +169 -0
  33. package/dist/llm-analyzer.js.map +1 -0
  34. package/dist/reporter/badge.d.ts +7 -0
  35. package/dist/reporter/badge.js +50 -0
  36. package/dist/reporter/badge.js.map +1 -0
  37. package/dist/reporter/json.d.ts +3 -0
  38. package/dist/reporter/json.js +5 -0
  39. package/dist/reporter/json.js.map +1 -0
  40. package/dist/reporter/terminal.d.ts +2 -0
  41. package/dist/reporter/terminal.js +64 -0
  42. package/dist/reporter/terminal.js.map +1 -0
  43. package/dist/rules/backdoor.d.ts +2 -0
  44. package/dist/rules/backdoor.js +57 -0
  45. package/dist/rules/backdoor.js.map +1 -0
  46. package/dist/rules/credential-hardcode.d.ts +2 -0
  47. package/dist/rules/credential-hardcode.js +57 -0
  48. package/dist/rules/credential-hardcode.js.map +1 -0
  49. package/dist/rules/crypto-mining.d.ts +2 -0
  50. package/dist/rules/crypto-mining.js +41 -0
  51. package/dist/rules/crypto-mining.js.map +1 -0
  52. package/dist/rules/data-exfil.d.ts +2 -0
  53. package/dist/rules/data-exfil.js +61 -0
  54. package/dist/rules/data-exfil.js.map +1 -0
  55. package/dist/rules/env-leak.d.ts +2 -0
  56. package/dist/rules/env-leak.js +43 -0
  57. package/dist/rules/env-leak.js.map +1 -0
  58. package/dist/rules/excessive-perms.d.ts +2 -0
  59. package/dist/rules/excessive-perms.js +50 -0
  60. package/dist/rules/excessive-perms.js.map +1 -0
  61. package/dist/rules/hidden-files.d.ts +2 -0
  62. package/dist/rules/hidden-files.js +52 -0
  63. package/dist/rules/hidden-files.js.map +1 -0
  64. package/dist/rules/index.d.ts +5 -0
  65. package/dist/rules/index.js +53 -0
  66. package/dist/rules/index.js.map +1 -0
  67. package/dist/rules/mcp-manifest.d.ts +2 -0
  68. package/dist/rules/mcp-manifest.js +270 -0
  69. package/dist/rules/mcp-manifest.js.map +1 -0
  70. package/dist/rules/network-ssrf.d.ts +2 -0
  71. package/dist/rules/network-ssrf.js +51 -0
  72. package/dist/rules/network-ssrf.js.map +1 -0
  73. package/dist/rules/obfuscation.d.ts +2 -0
  74. package/dist/rules/obfuscation.js +51 -0
  75. package/dist/rules/obfuscation.js.map +1 -0
  76. package/dist/rules/phone-home.d.ts +2 -0
  77. package/dist/rules/phone-home.js +38 -0
  78. package/dist/rules/phone-home.js.map +1 -0
  79. package/dist/rules/privilege.d.ts +2 -0
  80. package/dist/rules/privilege.js +111 -0
  81. package/dist/rules/privilege.js.map +1 -0
  82. package/dist/rules/prompt-injection.d.ts +2 -0
  83. package/dist/rules/prompt-injection.js +323 -0
  84. package/dist/rules/prompt-injection.js.map +1 -0
  85. package/dist/rules/reverse-shell.d.ts +2 -0
  86. package/dist/rules/reverse-shell.js +53 -0
  87. package/dist/rules/reverse-shell.js.map +1 -0
  88. package/dist/rules/sensitive-read.d.ts +2 -0
  89. package/dist/rules/sensitive-read.js +53 -0
  90. package/dist/rules/sensitive-read.js.map +1 -0
  91. package/dist/rules/skill-risks.d.ts +2 -0
  92. package/dist/rules/skill-risks.js +148 -0
  93. package/dist/rules/skill-risks.js.map +1 -0
  94. package/dist/rules/supply-chain.d.ts +6 -0
  95. package/dist/rules/supply-chain.js +105 -0
  96. package/dist/rules/supply-chain.js.map +1 -0
  97. package/dist/rules/tool-shadowing.d.ts +2 -0
  98. package/dist/rules/tool-shadowing.js +129 -0
  99. package/dist/rules/tool-shadowing.js.map +1 -0
  100. package/dist/rules/toxic-flow.d.ts +2 -0
  101. package/dist/rules/toxic-flow.js +160 -0
  102. package/dist/rules/toxic-flow.js.map +1 -0
  103. package/dist/rules/typosquatting.d.ts +2 -0
  104. package/dist/rules/typosquatting.js +56 -0
  105. package/dist/rules/typosquatting.js.map +1 -0
  106. package/dist/scanner/files.d.ts +5 -0
  107. package/dist/scanner/files.js +105 -0
  108. package/dist/scanner/files.js.map +1 -0
  109. package/dist/scanner/index.d.ts +6 -0
  110. package/dist/scanner/index.js +198 -0
  111. package/dist/scanner/index.js.map +1 -0
  112. package/dist/score.d.ts +14 -0
  113. package/dist/score.js +35 -0
  114. package/dist/score.js.map +1 -0
  115. package/dist/types.d.ts +60 -0
  116. package/dist/types.js +2 -0
  117. package/dist/types.js.map +1 -0
  118. package/dist/yaml-simple.d.ts +6 -0
  119. package/dist/yaml-simple.js +98 -0
  120. package/dist/yaml-simple.js.map +1 -0
  121. package/package.json +72 -0
@@ -0,0 +1,64 @@
1
+ import chalk from "chalk";
2
+ import { riskLabel } from "../score.js";
3
+ const SEVERITY_ICON = {
4
+ critical: chalk.red("🔴 CRITICAL"),
5
+ warning: chalk.yellow("🟡 WARNING"),
6
+ info: chalk.blue("🟢 INFO"),
7
+ };
8
+ const SEVERITY_LINE = {
9
+ critical: chalk.red,
10
+ warning: chalk.yellow,
11
+ info: chalk.dim,
12
+ };
13
+ export function printReport(result) {
14
+ const { target, filesScanned, linesScanned, findings, score, duration } = result;
15
+ console.log();
16
+ console.log(chalk.bold("🛡️ AgentShield Security Report"));
17
+ console.log(chalk.dim(`📁 Scanned: ${target} (${filesScanned} files, ${formatLines(linesScanned)})`));
18
+ console.log();
19
+ // Group by severity
20
+ const bySeverity = groupBy(findings, (f) => f.severity);
21
+ for (const severity of ["critical", "warning", "info"]) {
22
+ const group = bySeverity[severity];
23
+ if (!group || group.length === 0)
24
+ continue;
25
+ console.log(`${SEVERITY_ICON[severity]} (${group.length})`);
26
+ for (let i = 0; i < group.length; i++) {
27
+ const f = group[i];
28
+ const prefix = i < group.length - 1 ? " ├─" : " └─";
29
+ const loc = f.line ? `${f.file}:${f.line}` : f.file;
30
+ const colorize = SEVERITY_LINE[f.severity] || chalk.white;
31
+ console.log(colorize(`${prefix} ${loc} — [${f.rule}] ${f.message}`));
32
+ if (f.possibleFalsePositive) {
33
+ const fpPrefix = i < group.length - 1 ? " │ " : " ";
34
+ console.log(chalk.dim(`${fpPrefix}⚠️ Likely false positive: ${f.falsePositiveReason}`));
35
+ }
36
+ if (f.evidence) {
37
+ const ePrefix = i < group.length - 1 ? " │ " : " ";
38
+ console.log(chalk.dim(`${ePrefix}${f.evidence}`));
39
+ }
40
+ }
41
+ console.log();
42
+ }
43
+ // Score
44
+ const scoreColor = score >= 90 ? chalk.green : score >= 70 ? chalk.yellow : score >= 40 ? chalk.hex("#FF8800") : chalk.red;
45
+ console.log(scoreColor(`✅ Score: ${score}/100 (${riskLabel(score)})`));
46
+ console.log(chalk.dim(`⏱ ${duration}ms`));
47
+ console.log();
48
+ }
49
+ function formatLines(n) {
50
+ if (n >= 1000)
51
+ return `${(n / 1000).toFixed(1)}K lines`;
52
+ return `${n} lines`;
53
+ }
54
+ function groupBy(arr, fn) {
55
+ const result = {};
56
+ for (const item of arr) {
57
+ const key = fn(item);
58
+ if (!result[key])
59
+ result[key] = [];
60
+ result[key].push(item);
61
+ }
62
+ return result;
63
+ }
64
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/reporter/terminal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const backdoorRule: Rule;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Rule: backdoor
3
+ * Detects eval(), new Function(), child_process.exec() with dynamic strings
4
+ */
5
+ const BACKDOOR_PATTERNS = [
6
+ // JavaScript/TypeScript
7
+ { pattern: /\beval\s*\(/, desc: "eval() with dynamic input", severity: "critical" },
8
+ { pattern: /new\s+Function\s*\(/, desc: "new Function() constructor", severity: "critical" },
9
+ { pattern: /child_process\s*\.\s*exec\s*\(/, desc: "child_process.exec() — use execFile instead", severity: "critical" },
10
+ { pattern: /child_process\s*\.\s*spawn\s*\(.*\bshell\s*:\s*true/, desc: "spawn() with shell: true", severity: "critical" },
11
+ { pattern: /execSync\s*\(\s*`/, desc: "execSync() with template literal", severity: "critical" },
12
+ { pattern: /execSync\s*\(\s*[^"']/, desc: "execSync() with dynamic string", severity: "warning" },
13
+ { pattern: /require\s*\(\s*[^"'`]/, desc: "dynamic require()", severity: "warning" },
14
+ { pattern: /import\s*\(\s*[^"'`]/, desc: "dynamic import()", severity: "warning" },
15
+ // Python
16
+ { pattern: /\bexec\s*\(\s*[^"']/, desc: "Python exec() with dynamic input", severity: "critical" },
17
+ { pattern: /\bos\.system\s*\(/, desc: "os.system() — use subprocess.run instead", severity: "critical" },
18
+ { pattern: /subprocess\.call\s*\(\s*[^[\]].*shell\s*=\s*True/, desc: "subprocess with shell=True", severity: "critical" },
19
+ { pattern: /\b__import__\s*\(/, desc: "dynamic __import__()", severity: "warning" },
20
+ // Shell
21
+ { pattern: /\$\(curl\s/, desc: "command substitution with curl", severity: "critical" },
22
+ { pattern: /\beval\s+\$/, desc: "shell eval with variable", severity: "critical" },
23
+ { pattern: /bash\s+-c\s+\$/, desc: "bash -c with variable", severity: "critical" },
24
+ ];
25
+ export const backdoorRule = {
26
+ id: "backdoor",
27
+ name: "Dynamic Code Execution",
28
+ description: "Detects eval(), exec(), and other dynamic code execution patterns",
29
+ run(files) {
30
+ const findings = [];
31
+ for (const file of files) {
32
+ if (file.ext === ".json" || file.ext === ".yaml" || file.ext === ".yml" || file.ext === ".toml" || file.ext === ".md")
33
+ continue;
34
+ for (let i = 0; i < file.lines.length; i++) {
35
+ const line = file.lines[i];
36
+ const trimmed = line.trimStart();
37
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
38
+ continue;
39
+ for (const { pattern, desc, severity } of BACKDOOR_PATTERNS) {
40
+ if (pattern.test(line)) {
41
+ findings.push({
42
+ rule: "backdoor",
43
+ severity,
44
+ file: file.relativePath,
45
+ line: i + 1,
46
+ message: desc,
47
+ evidence: line.trim().slice(0, 120),
48
+ });
49
+ break; // one finding per line
50
+ }
51
+ }
52
+ }
53
+ }
54
+ return findings;
55
+ },
56
+ };
57
+ //# sourceMappingURL=backdoor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backdoor.js","sourceRoot":"","sources":["../../src/rules/backdoor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const credentialHardcodeRule: Rule;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Rule: credential-hardcode
3
+ * Detects hardcoded API keys, tokens, and passwords in source code.
4
+ */
5
+ const CREDENTIAL_PATTERNS = [
6
+ // Generic API keys / tokens
7
+ { pattern: /["'](?:sk|pk|api[_-]?key|token|secret)[_-]?[a-zA-Z]*["']\s*[:=]\s*["'][a-zA-Z0-9_\-/.]{20,}["']/, desc: "Hardcoded API key/token" },
8
+ // AWS
9
+ { pattern: /AKIA[0-9A-Z]{16}/, desc: "Hardcoded AWS Access Key ID" },
10
+ // GitHub
11
+ { pattern: /ghp_[a-zA-Z0-9]{36}/, desc: "Hardcoded GitHub personal access token" },
12
+ { pattern: /github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/, desc: "Hardcoded GitHub fine-grained token" },
13
+ // Slack
14
+ { pattern: /xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24}/, desc: "Hardcoded Slack bot token" },
15
+ { pattern: /xoxp-[0-9]{10,}-[0-9]{10,}-[0-9]{10,}-[a-f0-9]{32}/, desc: "Hardcoded Slack user token" },
16
+ // OpenAI
17
+ { pattern: /sk-[a-zA-Z0-9]{20,}T3BlbkFJ[a-zA-Z0-9]{20,}/, desc: "Hardcoded OpenAI API key" },
18
+ // Stripe
19
+ { pattern: /sk_live_[a-zA-Z0-9]{24,}/, desc: "Hardcoded Stripe live key" },
20
+ // Generic password assignment
21
+ { pattern: /(?:password|passwd|pwd)\s*[:=]\s*["'][^"']{8,}["']/i, desc: "Hardcoded password" },
22
+ // Private key block
23
+ { pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/, desc: "Embedded private key" },
24
+ ];
25
+ export const credentialHardcodeRule = {
26
+ id: "credential-hardcode",
27
+ name: "Hardcoded Credentials",
28
+ description: "Detects API keys, tokens, passwords, and private keys in source code",
29
+ run(files) {
30
+ const findings = [];
31
+ for (const file of files) {
32
+ if (file.ext === ".md")
33
+ continue;
34
+ for (let i = 0; i < file.lines.length; i++) {
35
+ const line = file.lines[i];
36
+ const trimmed = line.trimStart();
37
+ if (trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("*"))
38
+ continue;
39
+ for (const { pattern, desc } of CREDENTIAL_PATTERNS) {
40
+ if (pattern.test(line)) {
41
+ findings.push({
42
+ rule: "credential-hardcode",
43
+ severity: "critical",
44
+ file: file.relativePath,
45
+ line: i + 1,
46
+ message: desc,
47
+ evidence: line.trim().replace(/["'][a-zA-Z0-9_\-/.]{10,}["']/g, '"***"').slice(0, 120),
48
+ });
49
+ break;
50
+ }
51
+ }
52
+ }
53
+ }
54
+ return findings;
55
+ },
56
+ };
57
+ //# sourceMappingURL=credential-hardcode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-hardcode.js","sourceRoot":"","sources":["../../src/rules/credential-hardcode.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,mBAAmB,GAA6C;IACpE,4BAA4B;IAC5B,EAAE,OAAO,EAAE,iGAAiG,EAAE,IAAI,EAAE,yBAAyB,EAAE;IAC/I,MAAM;IACN,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,6BAA6B,EAAE;IACpE,SAAS;IACT,EAAE,OAAO,EAAE,qBAAqB,EAAE,IAAI,EAAE,wCAAwC,EAAE;IAClF,EAAE,OAAO,EAAE,4CAA4C,EAAE,IAAI,EAAE,qCAAqC,EAAE;IACtG,QAAQ;IACR,EAAE,OAAO,EAAE,4CAA4C,EAAE,IAAI,EAAE,2BAA2B,EAAE;IAC5F,EAAE,OAAO,EAAE,oDAAoD,EAAE,IAAI,EAAE,4BAA4B,EAAE;IACrG,SAAS;IACT,EAAE,OAAO,EAAE,6CAA6C,EAAE,IAAI,EAAE,0BAA0B,EAAE;IAC5F,SAAS;IACT,EAAE,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE,2BAA2B,EAAE;IAC1E,8BAA8B;IAC9B,EAAE,OAAO,EAAE,qDAAqD,EAAE,IAAI,EAAE,oBAAoB,EAAE;IAC9F,oBAAoB;IACpB,EAAE,OAAO,EAAE,+CAA+C,EAAE,IAAI,EAAE,sBAAsB,EAAE;CAC3F,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAS;IAC1C,EAAE,EAAE,qBAAqB;IACzB,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,sEAAsE;IAEnF,GAAG,CAAC,KAAoB;QACtB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK;gBAAE,SAAS;YAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;gBAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;gBACjC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAE7F,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,mBAAmB,EAAE,CAAC;oBACpD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACvB,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,qBAAqB;4BAC3B,QAAQ,EAAE,UAAU;4BACpB,IAAI,EAAE,IAAI,CAAC,YAAY;4BACvB,IAAI,EAAE,CAAC,GAAG,CAAC;4BACX,OAAO,EAAE,IAAI;4BACb,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,gCAAgC,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;yBACvF,CAAC,CAAC;wBACH,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const cryptoMiningRule: Rule;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Rule: crypto-mining
3
+ * Detects cryptocurrency mining patterns.
4
+ */
5
+ const MINING_PATTERNS = [
6
+ { pattern: /stratum\+tcp:\/\/|stratum\+ssl:\/\//, desc: "Stratum mining protocol URL" },
7
+ { pattern: /xmrig|cpuminer|minerd|minergate|coinhive|cryptonight/i, desc: "Known mining software reference" },
8
+ { pattern: /\bmining_pool\b|\bpool_url\b|\bpool_address\b/i, desc: "Mining pool configuration" },
9
+ { pattern: /\bmonero\b.*\bwallet\b|\bwallet\b.*\bmonero\b/i, desc: "Monero wallet reference" },
10
+ { pattern: /crypto\.createHash.*\bwhile\b.*true|for\s*\(\s*;\s*;\s*\).*hash/i, desc: "Infinite loop with hashing — potential mining" },
11
+ ];
12
+ export const cryptoMiningRule = {
13
+ id: "crypto-mining",
14
+ name: "Cryptocurrency Mining",
15
+ description: "Detects mining pool connections, known miners, and mining-related patterns",
16
+ run(files) {
17
+ const findings = [];
18
+ for (const file of files) {
19
+ if (file.ext === ".md")
20
+ continue;
21
+ for (let i = 0; i < file.lines.length; i++) {
22
+ const line = file.lines[i];
23
+ for (const { pattern, desc } of MINING_PATTERNS) {
24
+ if (pattern.test(line)) {
25
+ findings.push({
26
+ rule: "crypto-mining",
27
+ severity: "critical",
28
+ file: file.relativePath,
29
+ line: i + 1,
30
+ message: desc,
31
+ evidence: line.trim().slice(0, 120),
32
+ });
33
+ break;
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return findings;
39
+ },
40
+ };
41
+ //# sourceMappingURL=crypto-mining.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-mining.js","sourceRoot":"","sources":["../../src/rules/crypto-mining.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,eAAe,GAA6C;IAChE,EAAE,OAAO,EAAE,qCAAqC,EAAE,IAAI,EAAE,6BAA6B,EAAE;IACvF,EAAE,OAAO,EAAE,uDAAuD,EAAE,IAAI,EAAE,iCAAiC,EAAE;IAC7G,EAAE,OAAO,EAAE,gDAAgD,EAAE,IAAI,EAAE,2BAA2B,EAAE;IAChG,EAAE,OAAO,EAAE,gDAAgD,EAAE,IAAI,EAAE,yBAAyB,EAAE;IAC9F,EAAE,OAAO,EAAE,kEAAkE,EAAE,IAAI,EAAE,+CAA+C,EAAE;CACvI,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,4EAA4E;IAEzF,GAAG,CAAC,KAAoB;QACtB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK;gBAAE,SAAS;YAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;gBAE5B,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,eAAe,EAAE,CAAC;oBAChD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACvB,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,eAAe;4BACrB,QAAQ,EAAE,UAAU;4BACpB,IAAI,EAAE,IAAI,CAAC,YAAY;4BACvB,IAAI,EAAE,CAAC,GAAG,CAAC;4BACX,OAAO,EAAE,IAAI;4BACb,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;yBACpC,CAAC,CAAC;wBACH,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const dataExfilRule: Rule;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Rule: data-exfil
3
+ * Detects code that reads sensitive files AND sends HTTP requests — a classic exfiltration pattern.
4
+ */
5
+ const SENSITIVE_READ_RE = /readFile|readFileSync|fs\.read|open\(|\.ssh|\.env|credentials|\.aws|\.kube|\.npmrc|\.gitconfig|\.openclaw/i;
6
+ const HTTP_SEND_RE = /fetch\s*\(|axios\.|http\.request|https\.request|XMLHttpRequest|\.post\s*\(|\.put\s*\(|urllib|requests\.(post|put|patch)|curl\s/i;
7
+ const DYNAMIC_URL_RE = /fetch\s*\(\s*`[^`]*\$\{|fetch\s*\(\s*[a-zA-Z_]\w*\s*[+,)]/;
8
+ export const dataExfilRule = {
9
+ id: "data-exfil",
10
+ name: "Data Exfiltration",
11
+ description: "Detects patterns where sensitive data is read and sent over HTTP",
12
+ run(files) {
13
+ const findings = [];
14
+ for (const file of files) {
15
+ if (file.ext === ".json" || file.ext === ".yaml" || file.ext === ".yml" || file.ext === ".toml" || file.ext === ".md")
16
+ continue;
17
+ const content = file.content;
18
+ const hasSensitiveRead = SENSITIVE_READ_RE.test(content);
19
+ const hasHttpSend = HTTP_SEND_RE.test(content);
20
+ // Critical: same file reads sensitive data AND sends it out
21
+ if (hasSensitiveRead && hasHttpSend) {
22
+ // Find the specific lines
23
+ const readLines = [];
24
+ const sendLines = [];
25
+ for (let i = 0; i < file.lines.length; i++) {
26
+ const line = file.lines[i];
27
+ if (SENSITIVE_READ_RE.test(line))
28
+ readLines.push(i + 1);
29
+ if (HTTP_SEND_RE.test(line))
30
+ sendLines.push(i + 1);
31
+ }
32
+ if (readLines.length > 0 && sendLines.length > 0) {
33
+ findings.push({
34
+ rule: "data-exfil",
35
+ severity: "critical",
36
+ file: file.relativePath,
37
+ line: sendLines[0],
38
+ message: `Reads sensitive data (line ${readLines.join(",")}) and sends HTTP request (line ${sendLines.join(",")}) — possible exfiltration`,
39
+ evidence: file.lines[sendLines[0] - 1]?.trim().slice(0, 120),
40
+ });
41
+ }
42
+ }
43
+ // Warning: dynamic URL construction in fetch/request calls
44
+ for (let i = 0; i < file.lines.length; i++) {
45
+ const line = file.lines[i];
46
+ if (DYNAMIC_URL_RE.test(line)) {
47
+ findings.push({
48
+ rule: "data-exfil",
49
+ severity: "warning",
50
+ file: file.relativePath,
51
+ line: i + 1,
52
+ message: "Dynamic URL construction in HTTP request — potential SSRF",
53
+ evidence: line.trim().slice(0, 120),
54
+ });
55
+ }
56
+ }
57
+ }
58
+ return findings;
59
+ },
60
+ };
61
+ //# sourceMappingURL=data-exfil.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-exfil.js","sourceRoot":"","sources":["../../src/rules/data-exfil.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const envLeakRule: Rule;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Rule: env-leak
3
+ * Detects process.env access combined with HTTP send — leaking environment variables.
4
+ */
5
+ const ENV_ACCESS_RE = /process\.env\b|os\.environ|getenv\s*\(/i;
6
+ const HTTP_SEND_RE = /fetch\s*\(|axios\.|http\.request|https\.request|requests\.(post|put|patch)|\.post\s*\(|curl\s/i;
7
+ export const envLeakRule = {
8
+ id: "env-leak",
9
+ name: "Environment Variable Leak",
10
+ description: "Detects process.env access combined with outbound HTTP requests",
11
+ run(files) {
12
+ const findings = [];
13
+ for (const file of files) {
14
+ if (file.ext === ".json" || file.ext === ".yaml" || file.ext === ".yml" || file.ext === ".md")
15
+ continue;
16
+ const hasEnvAccess = ENV_ACCESS_RE.test(file.content);
17
+ const hasHttpSend = HTTP_SEND_RE.test(file.content);
18
+ if (hasEnvAccess && hasHttpSend) {
19
+ const envLines = [];
20
+ const sendLines = [];
21
+ for (let i = 0; i < file.lines.length; i++) {
22
+ const line = file.lines[i];
23
+ if (ENV_ACCESS_RE.test(line))
24
+ envLines.push(i + 1);
25
+ if (HTTP_SEND_RE.test(line))
26
+ sendLines.push(i + 1);
27
+ }
28
+ if (envLines.length > 0 && sendLines.length > 0) {
29
+ findings.push({
30
+ rule: "env-leak",
31
+ severity: "critical",
32
+ file: file.relativePath,
33
+ line: sendLines[0],
34
+ message: `Reads environment variables (line ${envLines.join(",")}) and sends HTTP request (line ${sendLines.join(",")}) — possible env leak`,
35
+ evidence: file.lines[sendLines[0] - 1]?.trim().slice(0, 120),
36
+ });
37
+ }
38
+ }
39
+ }
40
+ return findings;
41
+ },
42
+ };
43
+ //# sourceMappingURL=env-leak.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-leak.js","sourceRoot":"","sources":["../../src/rules/env-leak.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,aAAa,GAAG,yCAAyC,CAAC;AAChE,MAAM,YAAY,GAAG,gGAAgG,CAAC;AAEtH,MAAM,CAAC,MAAM,WAAW,GAAS;IAC/B,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,2BAA2B;IACjC,WAAW,EAAE,iEAAiE;IAE9E,GAAG,CAAC,KAAoB;QACtB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAI,CAAC,GAAG,KAAK,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK;gBAAE,SAAS;YAExG,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtD,MAAM,WAAW,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAEpD,IAAI,YAAY,IAAI,WAAW,EAAE,CAAC;gBAChC,MAAM,QAAQ,GAAa,EAAE,CAAC;gBAC9B,MAAM,SAAS,GAAa,EAAE,CAAC;gBAE/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;oBAC5B,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;wBAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBACnD,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;wBAAE,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBACrD,CAAC;gBAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChD,QAAQ,CAAC,IAAI,CAAC;wBACZ,IAAI,EAAE,UAAU;wBAChB,QAAQ,EAAE,UAAU;wBACpB,IAAI,EAAE,IAAI,CAAC,YAAY;wBACvB,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;wBAClB,OAAO,EAAE,qCAAqC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,kCAAkC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,uBAAuB;wBAC5I,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;qBAC9D,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const excessivePermsRule: Rule;
@@ -0,0 +1,50 @@
1
+ import matter from "gray-matter";
2
+ /**
3
+ * Rule: excessive-perms
4
+ * Detects skills that request too many or dangerous permissions.
5
+ */
6
+ const DANGEROUS_PERMS = new Set(["exec", "admin", "root", "sudo", "network", "browser"]);
7
+ const MAX_REASONABLE_PERMS = 5;
8
+ export const excessivePermsRule = {
9
+ id: "excessive-perms",
10
+ name: "Excessive Permissions",
11
+ description: "Detects skills requesting too many or dangerous permissions",
12
+ run(files) {
13
+ const findings = [];
14
+ const skillMd = files.find((f) => f.relativePath === "SKILL.md" || f.relativePath.endsWith("/SKILL.md"));
15
+ if (!skillMd)
16
+ return findings;
17
+ let permissions = [];
18
+ try {
19
+ const { data } = matter(skillMd.content);
20
+ if (Array.isArray(data.permissions)) {
21
+ permissions = data.permissions.filter((p) => typeof p === "string");
22
+ }
23
+ }
24
+ catch {
25
+ return findings;
26
+ }
27
+ // Check for dangerous permissions
28
+ for (const perm of permissions) {
29
+ if (DANGEROUS_PERMS.has(perm.toLowerCase())) {
30
+ findings.push({
31
+ rule: "excessive-perms",
32
+ severity: "warning",
33
+ file: skillMd.relativePath,
34
+ message: `Requests dangerous permission: '${perm}'`,
35
+ });
36
+ }
37
+ }
38
+ // Check for excessive number of permissions
39
+ if (permissions.length > MAX_REASONABLE_PERMS) {
40
+ findings.push({
41
+ rule: "excessive-perms",
42
+ severity: "warning",
43
+ file: skillMd.relativePath,
44
+ message: `Requests ${permissions.length} permissions (threshold: ${MAX_REASONABLE_PERMS}) — review if all are necessary`,
45
+ });
46
+ }
47
+ return findings;
48
+ },
49
+ };
50
+ //# sourceMappingURL=excessive-perms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excessive-perms.js","sourceRoot":"","sources":["../../src/rules/excessive-perms.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AAGjC;;;GAGG;AAEH,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;AACzF,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAE/B,MAAM,CAAC,MAAM,kBAAkB,GAAS;IACtC,EAAE,EAAE,iBAAiB;IACrB,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,6DAA6D;IAE1E,GAAG,CAAC,KAAoB;QACtB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CACxB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,KAAK,UAAU,IAAI,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,CAC7E,CAAC;QACF,IAAI,CAAC,OAAO;YAAE,OAAO,QAAQ,CAAC;QAE9B,IAAI,WAAW,GAAa,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACzC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;gBACpC,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,kCAAkC;QAClC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;YAC/B,IAAI,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;gBAC5C,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,iBAAiB;oBACvB,QAAQ,EAAE,SAAS;oBACnB,IAAI,EAAE,OAAO,CAAC,YAAY;oBAC1B,OAAO,EAAE,mCAAmC,IAAI,GAAG;iBACpD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,4CAA4C;QAC5C,IAAI,WAAW,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;YAC9C,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,iBAAiB;gBACvB,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,OAAO,CAAC,YAAY;gBAC1B,OAAO,EAAE,YAAY,WAAW,CAAC,MAAM,4BAA4B,oBAAoB,iCAAiC;aACzH,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const hiddenFilesRule: Rule;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Rule: hidden-files
3
+ * Detects exposed .env files and other hidden config files that shouldn't be committed.
4
+ */
5
+ const DANGEROUS_FILES = [
6
+ { pattern: /^\.env($|\.)/, desc: "Environment file with secrets" },
7
+ { pattern: /^\.env\.local$/, desc: "Local environment file" },
8
+ { pattern: /^\.env\.production$/, desc: "Production environment file" },
9
+ { pattern: /^\.htpasswd$/, desc: "Apache password file" },
10
+ { pattern: /^\.htaccess$/, desc: "Apache config file" },
11
+ { pattern: /^\.pgpass$/, desc: "PostgreSQL password file" },
12
+ { pattern: /^\.netrc$/, desc: "Network credentials file" },
13
+ ];
14
+ const SECRET_IN_ENV_RE = /^[A-Z_]+=(?!$).*(?:key|secret|token|password|credential|auth)/i;
15
+ export const hiddenFilesRule = {
16
+ id: "hidden-files",
17
+ name: "Hidden/Secret Files",
18
+ description: "Detects .env files and other hidden configs that may leak secrets",
19
+ run(files) {
20
+ const findings = [];
21
+ for (const file of files) {
22
+ const basename = file.relativePath.split("/").pop() || "";
23
+ for (const { pattern, desc } of DANGEROUS_FILES) {
24
+ if (pattern.test(basename)) {
25
+ findings.push({
26
+ rule: "hidden-files",
27
+ severity: "critical",
28
+ file: file.relativePath,
29
+ message: `${desc} found in repository — should be in .gitignore`,
30
+ });
31
+ // Check for actual secrets in the file
32
+ for (let i = 0; i < file.lines.length; i++) {
33
+ const line = file.lines[i];
34
+ if (SECRET_IN_ENV_RE.test(line) && !line.trimStart().startsWith("#")) {
35
+ findings.push({
36
+ rule: "hidden-files",
37
+ severity: "critical",
38
+ file: file.relativePath,
39
+ line: i + 1,
40
+ message: "Hardcoded secret in environment file",
41
+ evidence: line.replace(/=.*/, "=***").slice(0, 80),
42
+ });
43
+ }
44
+ }
45
+ break;
46
+ }
47
+ }
48
+ }
49
+ return findings;
50
+ },
51
+ };
52
+ //# sourceMappingURL=hidden-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hidden-files.js","sourceRoot":"","sources":["../../src/rules/hidden-files.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,eAAe,GAAG;IACtB,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,+BAA+B,EAAE;IAClE,EAAE,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,wBAAwB,EAAE;IAC7D,EAAE,OAAO,EAAE,qBAAqB,EAAE,IAAI,EAAE,6BAA6B,EAAE;IACvE,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,sBAAsB,EAAE;IACzD,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,oBAAoB,EAAE;IACvD,EAAE,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,0BAA0B,EAAE;IAC3D,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,0BAA0B,EAAE;CAC3D,CAAC;AAEF,MAAM,gBAAgB,GACpB,gEAAgE,CAAC;AAEnE,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,cAAc;IAClB,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EAAE,mEAAmE;IAEhF,GAAG,CAAC,KAAoB;QACtB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;YAE1D,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,eAAe,EAAE,CAAC;gBAChD,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC3B,QAAQ,CAAC,IAAI,CAAC;wBACZ,IAAI,EAAE,cAAc;wBACpB,QAAQ,EAAE,UAAU;wBACpB,IAAI,EAAE,IAAI,CAAC,YAAY;wBACvB,OAAO,EAAE,GAAG,IAAI,gDAAgD;qBACjE,CAAC,CAAC;oBAEH,uCAAuC;oBACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;wBAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;wBAC5B,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;4BACrE,QAAQ,CAAC,IAAI,CAAC;gCACZ,IAAI,EAAE,cAAc;gCACpB,QAAQ,EAAE,UAAU;gCACpB,IAAI,EAAE,IAAI,CAAC,YAAY;gCACvB,IAAI,EAAE,CAAC,GAAG,CAAC;gCACX,OAAO,EAAE,sCAAsC;gCAC/C,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;6BACnD,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;oBACD,MAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Rule } from "../types.js";
2
+ /** All registered rules */
3
+ export declare const rules: Rule[];
4
+ /** Get a rule by ID */
5
+ export declare function getRule(id: string): Rule | undefined;
@@ -0,0 +1,53 @@
1
+ import { sensitiveReadRule } from "./sensitive-read.js";
2
+ import { backdoorRule } from "./backdoor.js";
3
+ import { dataExfilRule } from "./data-exfil.js";
4
+ import { privilegeRule } from "./privilege.js";
5
+ import { supplyChainRule } from "./supply-chain.js";
6
+ import { obfuscationRule } from "./obfuscation.js";
7
+ import { envLeakRule } from "./env-leak.js";
8
+ import { cryptoMiningRule } from "./crypto-mining.js";
9
+ import { reverseShellRule } from "./reverse-shell.js";
10
+ import { typosquattingRule } from "./typosquatting.js";
11
+ import { hiddenFilesRule } from "./hidden-files.js";
12
+ import { excessivePermsRule } from "./excessive-perms.js";
13
+ import { phoneHomeRule } from "./phone-home.js";
14
+ import { credentialHardcodeRule } from "./credential-hardcode.js";
15
+ import { networkSsrfRule } from "./network-ssrf.js";
16
+ import { mcpManifestRule } from "./mcp-manifest.js";
17
+ import { promptInjection } from "./prompt-injection.js";
18
+ import { toolShadowing } from "./tool-shadowing.js";
19
+ import { skillRisks } from "./skill-risks.js";
20
+ import { toxicFlow } from "./toxic-flow.js";
21
+ /** All registered rules */
22
+ export const rules = [
23
+ // Original 5
24
+ dataExfilRule,
25
+ backdoorRule,
26
+ privilegeRule,
27
+ supplyChainRule,
28
+ sensitiveReadRule,
29
+ // New 10
30
+ obfuscationRule,
31
+ envLeakRule,
32
+ cryptoMiningRule,
33
+ reverseShellRule,
34
+ typosquattingRule,
35
+ hiddenFilesRule,
36
+ excessivePermsRule,
37
+ phoneHomeRule,
38
+ credentialHardcodeRule,
39
+ networkSsrfRule,
40
+ mcpManifestRule,
41
+ // Prompt injection & tool shadowing
42
+ promptInjection,
43
+ toolShadowing,
44
+ // Skill risk assessment
45
+ skillRisks,
46
+ // Toxic flow analysis
47
+ toxicFlow,
48
+ ];
49
+ /** Get a rule by ID */
50
+ export function getRule(id) {
51
+ return rules.find((r) => r.id === id);
52
+ }
53
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,2BAA2B;AAC3B,MAAM,CAAC,MAAM,KAAK,GAAW;IAC3B,aAAa;IACb,aAAa;IACb,YAAY;IACZ,aAAa;IACb,eAAe;IACf,iBAAiB;IACjB,SAAS;IACT,eAAe;IACf,WAAW;IACX,gBAAgB;IAChB,gBAAgB;IAChB,iBAAiB;IACjB,eAAe;IACf,kBAAkB;IAClB,aAAa;IACb,sBAAsB;IACtB,eAAe;IACf,eAAe;IACf,oCAAoC;IACpC,eAAe;IACf,aAAa;IACb,wBAAwB;IACxB,UAAU;IACV,sBAAsB;IACtB,SAAS;CACV,CAAC;AAEF,uBAAuB;AACvB,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const mcpManifestRule: Rule;