@elliotllliu/agentshield 0.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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +115 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +43 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/reporter/json.d.ts +3 -0
  7. package/dist/reporter/json.js +5 -0
  8. package/dist/reporter/json.js.map +1 -0
  9. package/dist/reporter/terminal.d.ts +2 -0
  10. package/dist/reporter/terminal.js +60 -0
  11. package/dist/reporter/terminal.js.map +1 -0
  12. package/dist/rules/backdoor.d.ts +2 -0
  13. package/dist/rules/backdoor.js +57 -0
  14. package/dist/rules/backdoor.js.map +1 -0
  15. package/dist/rules/credential-hardcode.d.ts +2 -0
  16. package/dist/rules/credential-hardcode.js +57 -0
  17. package/dist/rules/credential-hardcode.js.map +1 -0
  18. package/dist/rules/crypto-mining.d.ts +2 -0
  19. package/dist/rules/crypto-mining.js +41 -0
  20. package/dist/rules/crypto-mining.js.map +1 -0
  21. package/dist/rules/data-exfil.d.ts +2 -0
  22. package/dist/rules/data-exfil.js +61 -0
  23. package/dist/rules/data-exfil.js.map +1 -0
  24. package/dist/rules/env-leak.d.ts +2 -0
  25. package/dist/rules/env-leak.js +43 -0
  26. package/dist/rules/env-leak.js.map +1 -0
  27. package/dist/rules/excessive-perms.d.ts +2 -0
  28. package/dist/rules/excessive-perms.js +50 -0
  29. package/dist/rules/excessive-perms.js.map +1 -0
  30. package/dist/rules/hidden-files.d.ts +2 -0
  31. package/dist/rules/hidden-files.js +52 -0
  32. package/dist/rules/hidden-files.js.map +1 -0
  33. package/dist/rules/index.d.ts +5 -0
  34. package/dist/rules/index.js +40 -0
  35. package/dist/rules/index.js.map +1 -0
  36. package/dist/rules/network-ssrf.d.ts +2 -0
  37. package/dist/rules/network-ssrf.js +51 -0
  38. package/dist/rules/network-ssrf.js.map +1 -0
  39. package/dist/rules/obfuscation.d.ts +2 -0
  40. package/dist/rules/obfuscation.js +51 -0
  41. package/dist/rules/obfuscation.js.map +1 -0
  42. package/dist/rules/phone-home.d.ts +2 -0
  43. package/dist/rules/phone-home.js +38 -0
  44. package/dist/rules/phone-home.js.map +1 -0
  45. package/dist/rules/privilege.d.ts +2 -0
  46. package/dist/rules/privilege.js +111 -0
  47. package/dist/rules/privilege.js.map +1 -0
  48. package/dist/rules/reverse-shell.d.ts +2 -0
  49. package/dist/rules/reverse-shell.js +53 -0
  50. package/dist/rules/reverse-shell.js.map +1 -0
  51. package/dist/rules/sensitive-read.d.ts +2 -0
  52. package/dist/rules/sensitive-read.js +53 -0
  53. package/dist/rules/sensitive-read.js.map +1 -0
  54. package/dist/rules/supply-chain.d.ts +6 -0
  55. package/dist/rules/supply-chain.js +105 -0
  56. package/dist/rules/supply-chain.js.map +1 -0
  57. package/dist/rules/typosquatting.d.ts +2 -0
  58. package/dist/rules/typosquatting.js +56 -0
  59. package/dist/rules/typosquatting.js.map +1 -0
  60. package/dist/scanner/files.d.ts +5 -0
  61. package/dist/scanner/files.js +67 -0
  62. package/dist/scanner/files.js.map +1 -0
  63. package/dist/scanner/index.d.ts +3 -0
  64. package/dist/scanner/index.js +24 -0
  65. package/dist/scanner/index.js.map +1 -0
  66. package/dist/score.d.ts +14 -0
  67. package/dist/score.js +35 -0
  68. package/dist/score.js.map +1 -0
  69. package/dist/types.d.ts +42 -0
  70. package/dist/types.js +2 -0
  71. package/dist/types.js.map +1 -0
  72. package/package.json +33 -0
@@ -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,40 @@
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
+ /** All registered rules */
17
+ export const rules = [
18
+ // Original 5
19
+ dataExfilRule,
20
+ backdoorRule,
21
+ privilegeRule,
22
+ supplyChainRule,
23
+ sensitiveReadRule,
24
+ // New 10
25
+ obfuscationRule,
26
+ envLeakRule,
27
+ cryptoMiningRule,
28
+ reverseShellRule,
29
+ typosquattingRule,
30
+ hiddenFilesRule,
31
+ excessivePermsRule,
32
+ phoneHomeRule,
33
+ credentialHardcodeRule,
34
+ networkSsrfRule,
35
+ ];
36
+ /** Get a rule by ID */
37
+ export function getRule(id) {
38
+ return rules.find((r) => r.id === id);
39
+ }
40
+ //# 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;AAEpD,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;CAChB,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 networkSsrfRule: Rule;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Rule: network-ssrf
3
+ * Detects Server-Side Request Forgery patterns — user-controlled URLs in HTTP requests.
4
+ */
5
+ const SSRF_PATTERNS = [
6
+ // Template literals in fetch/request
7
+ { pattern: /fetch\s*\(\s*`[^`]*\$\{/, desc: "fetch() with template literal URL — potential SSRF", severity: "warning" },
8
+ { pattern: /axios\.\w+\s*\(\s*`[^`]*\$\{/, desc: "axios with template literal URL — potential SSRF", severity: "warning" },
9
+ { pattern: /http\.request\s*\(\s*`[^`]*\$\{/, desc: "http.request with template literal URL — potential SSRF", severity: "warning" },
10
+ // URL constructed from user input
11
+ { pattern: /new\s+URL\s*\(\s*(?:req\.|request\.|params\.|query\.|body\.)/, desc: "URL from request parameters — potential SSRF", severity: "critical" },
12
+ { pattern: /fetch\s*\(\s*(?:req\.|request\.|params\.|query\.|body\.)/, desc: "fetch() with request parameter — potential SSRF", severity: "critical" },
13
+ // Redirect to user-controlled URL
14
+ { pattern: /redirect\s*\(\s*(?:req\.|request\.|params\.|query\.)/, desc: "Open redirect — user-controlled redirect URL", severity: "warning" },
15
+ // Internal network access patterns
16
+ { pattern: /127\.0\.0\.1|0\.0\.0\.0|localhost.*fetch|fetch.*localhost/i, desc: "Request to localhost — verify if intentional", severity: "warning" },
17
+ { pattern: /169\.254\.169\.254/, desc: "AWS metadata endpoint access — potential SSRF", severity: "critical" },
18
+ ];
19
+ export const networkSsrfRule = {
20
+ id: "network-ssrf",
21
+ name: "Server-Side Request Forgery",
22
+ description: "Detects user-controlled URLs in HTTP requests and internal network access",
23
+ run(files) {
24
+ const findings = [];
25
+ for (const file of files) {
26
+ if (file.ext === ".json" || file.ext === ".yaml" || file.ext === ".yml" || file.ext === ".md")
27
+ continue;
28
+ for (let i = 0; i < file.lines.length; i++) {
29
+ const line = file.lines[i];
30
+ const trimmed = line.trimStart();
31
+ if (trimmed.startsWith("//") || trimmed.startsWith("#"))
32
+ continue;
33
+ for (const { pattern, desc, severity } of SSRF_PATTERNS) {
34
+ if (pattern.test(line)) {
35
+ findings.push({
36
+ rule: "network-ssrf",
37
+ severity,
38
+ file: file.relativePath,
39
+ line: i + 1,
40
+ message: desc,
41
+ evidence: line.trim().slice(0, 120),
42
+ });
43
+ break;
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return findings;
49
+ },
50
+ };
51
+ //# sourceMappingURL=network-ssrf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-ssrf.js","sourceRoot":"","sources":["../../src/rules/network-ssrf.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,aAAa,GAA+E;IAChG,qCAAqC;IACrC,EAAE,OAAO,EAAE,yBAAyB,EAAE,IAAI,EAAE,oDAAoD,EAAE,QAAQ,EAAE,SAAS,EAAE;IACvH,EAAE,OAAO,EAAE,8BAA8B,EAAE,IAAI,EAAE,kDAAkD,EAAE,QAAQ,EAAE,SAAS,EAAE;IAC1H,EAAE,OAAO,EAAE,iCAAiC,EAAE,IAAI,EAAE,yDAAyD,EAAE,QAAQ,EAAE,SAAS,EAAE;IACpI,kCAAkC;IAClC,EAAE,OAAO,EAAE,8DAA8D,EAAE,IAAI,EAAE,8CAA8C,EAAE,QAAQ,EAAE,UAAU,EAAE;IACvJ,EAAE,OAAO,EAAE,0DAA0D,EAAE,IAAI,EAAE,iDAAiD,EAAE,QAAQ,EAAE,UAAU,EAAE;IACtJ,kCAAkC;IAClC,EAAE,OAAO,EAAE,sDAAsD,EAAE,IAAI,EAAE,8CAA8C,EAAE,QAAQ,EAAE,SAAS,EAAE;IAC9I,mCAAmC;IACnC,EAAE,OAAO,EAAE,4DAA4D,EAAE,IAAI,EAAE,8CAA8C,EAAE,QAAQ,EAAE,SAAS,EAAE;IACpJ,EAAE,OAAO,EAAE,oBAAoB,EAAE,IAAI,EAAE,+CAA+C,EAAE,QAAQ,EAAE,UAAU,EAAE;CAC/G,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,cAAc;IAClB,IAAI,EAAE,6BAA6B;IACnC,WAAW,EAAE,2EAA2E;IAExF,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,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;oBAAE,SAAS;gBAElE,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,aAAa,EAAE,CAAC;oBACxD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACvB,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,cAAc;4BACpB,QAAQ;4BACR,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 obfuscationRule: Rule;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Rule: obfuscation
3
+ * Detects base64 decoding + eval/exec combos and other obfuscation patterns.
4
+ */
5
+ const OBFUSCATION_PATTERNS = [
6
+ { pattern: /atob\s*\(.*\beval\b|eval\s*\(.*\batob\b/, desc: "atob() + eval() combo", severity: "critical" },
7
+ { pattern: /Buffer\.from\s*\([^)]*,\s*["']base64["']\).*\beval\b/, desc: "Base64 decode + eval()", severity: "critical" },
8
+ { pattern: /Buffer\.from\s*\([^)]*,\s*["']base64["']\).*\bexec\b/, desc: "Base64 decode + exec()", severity: "critical" },
9
+ { pattern: /\bString\.fromCharCode\s*\(/, desc: "String.fromCharCode() — potential obfuscation", severity: "warning" },
10
+ { pattern: /\\x[0-9a-f]{2}\\x[0-9a-f]{2}\\x[0-9a-f]{2}/, desc: "Hex-encoded string sequence", severity: "warning" },
11
+ { pattern: /\\u00[0-9a-f]{2}\\u00[0-9a-f]{2}/, desc: "Unicode-escaped string sequence", severity: "warning" },
12
+ ];
13
+ export const obfuscationRule = {
14
+ id: "obfuscation",
15
+ name: "Code Obfuscation",
16
+ description: "Detects base64+eval combos, hex encoding, and other obfuscation techniques",
17
+ run(files) {
18
+ const findings = [];
19
+ for (const file of files) {
20
+ if (file.ext === ".json" || file.ext === ".yaml" || file.ext === ".yml" || file.ext === ".md")
21
+ continue;
22
+ // Check multi-line patterns across entire content
23
+ for (const { pattern, desc, severity } of OBFUSCATION_PATTERNS.slice(0, 3)) {
24
+ if (pattern.test(file.content)) {
25
+ // Find the line with eval/exec
26
+ for (let i = 0; i < file.lines.length; i++) {
27
+ if (/\beval\b|\bexec\b/.test(file.lines[i])) {
28
+ findings.push({ rule: "obfuscation", severity, file: file.relativePath, line: i + 1, message: desc, evidence: file.lines[i].trim().slice(0, 120) });
29
+ break;
30
+ }
31
+ }
32
+ }
33
+ }
34
+ // Per-line patterns
35
+ for (let i = 0; i < file.lines.length; i++) {
36
+ const line = file.lines[i];
37
+ const trimmed = line.trimStart();
38
+ if (trimmed.startsWith("//") || trimmed.startsWith("#"))
39
+ continue;
40
+ for (const { pattern, desc, severity } of OBFUSCATION_PATTERNS.slice(3)) {
41
+ if (pattern.test(line)) {
42
+ findings.push({ rule: "obfuscation", severity, file: file.relativePath, line: i + 1, message: desc, evidence: line.trim().slice(0, 120) });
43
+ break;
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return findings;
49
+ },
50
+ };
51
+ //# sourceMappingURL=obfuscation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"obfuscation.js","sourceRoot":"","sources":["../../src/rules/obfuscation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const phoneHomeRule: Rule;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Rule: phone-home
3
+ * Detects periodic timers combined with HTTP requests — "calling home" patterns.
4
+ */
5
+ const TIMER_RE = /setInterval\s*\(|cron\s*\(|schedule\s*\(|setTimeout.*setInterval|recurring|periodic/i;
6
+ const HTTP_RE = /fetch\s*\(|axios\.|http\.request|https\.request|\.post\s*\(|\.get\s*\(/i;
7
+ export const phoneHomeRule = {
8
+ id: "phone-home",
9
+ name: "Phone Home / Beacon",
10
+ description: "Detects periodic timers combined with HTTP requests (heartbeat/beacon pattern)",
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 hasTimer = TIMER_RE.test(file.content);
17
+ const hasHttp = HTTP_RE.test(file.content);
18
+ if (hasTimer && hasHttp) {
19
+ // Find the timer line
20
+ for (let i = 0; i < file.lines.length; i++) {
21
+ if (TIMER_RE.test(file.lines[i])) {
22
+ findings.push({
23
+ rule: "phone-home",
24
+ severity: "warning",
25
+ file: file.relativePath,
26
+ line: i + 1,
27
+ message: "Periodic timer + HTTP request — possible beacon/phone-home pattern",
28
+ evidence: file.lines[i].trim().slice(0, 120),
29
+ });
30
+ break;
31
+ }
32
+ }
33
+ }
34
+ }
35
+ return findings;
36
+ },
37
+ };
38
+ //# sourceMappingURL=phone-home.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"phone-home.js","sourceRoot":"","sources":["../../src/rules/phone-home.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,QAAQ,GAAG,sFAAsF,CAAC;AACxG,MAAM,OAAO,GAAG,yEAAyE,CAAC;AAE1F,MAAM,CAAC,MAAM,aAAa,GAAS;IACjC,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EAAE,gFAAgF;IAE7F,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,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAE3C,IAAI,QAAQ,IAAI,OAAO,EAAE,CAAC;gBACxB,sBAAsB;gBACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC3C,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;wBAClC,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,YAAY;4BAClB,QAAQ,EAAE,SAAS;4BACnB,IAAI,EAAE,IAAI,CAAC,YAAY;4BACvB,IAAI,EAAE,CAAC,GAAG,CAAC;4BACX,OAAO,EAAE,oEAAoE;4BAC7E,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;yBAC9C,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 privilegeRule: Rule;
@@ -0,0 +1,111 @@
1
+ import matter from "gray-matter";
2
+ /**
3
+ * Rule: privilege
4
+ * Compares declared permissions in SKILL.md vs actual API usage in code.
5
+ */
6
+ // Map of capabilities to code patterns that indicate their use
7
+ const CAPABILITY_PATTERNS = {
8
+ exec: /child_process|execSync|exec\(|spawn\(|os\.system|subprocess|ShellAction/i,
9
+ read: /readFile|readFileSync|fs\.read|open\(.*["']r/i,
10
+ write: /writeFile|writeFileSync|fs\.write|appendFile|open\(.*["']w/i,
11
+ web_fetch: /fetch\s*\(|axios|http\.request|https\.request|requests\.(get|post)/i,
12
+ browser: /puppeteer|playwright|selenium|webdriver|BrowserAction/i,
13
+ network: /net\.connect|dgram|WebSocket|Socket/i,
14
+ };
15
+ export const privilegeRule = {
16
+ id: "privilege",
17
+ name: "Privilege Mismatch",
18
+ description: "Compares declared permissions in SKILL.md against actual code behavior",
19
+ run(files) {
20
+ const findings = [];
21
+ // Find SKILL.md
22
+ const skillMd = files.find((f) => f.relativePath === "SKILL.md" || f.relativePath.endsWith("/SKILL.md"));
23
+ if (!skillMd) {
24
+ // No SKILL.md — can't check permissions
25
+ findings.push({
26
+ rule: "privilege",
27
+ severity: "info",
28
+ file: ".",
29
+ message: "No SKILL.md found — permission analysis skipped",
30
+ });
31
+ return findings;
32
+ }
33
+ // Parse frontmatter
34
+ let meta = {};
35
+ try {
36
+ const { data } = matter(skillMd.content);
37
+ meta = data;
38
+ }
39
+ catch {
40
+ // not valid frontmatter
41
+ }
42
+ // Extract declared permissions (from frontmatter or body)
43
+ const declaredPerms = new Set();
44
+ if (Array.isArray(meta.permissions)) {
45
+ for (const p of meta.permissions) {
46
+ if (typeof p === "string")
47
+ declaredPerms.add(p.toLowerCase());
48
+ }
49
+ }
50
+ // Also scan SKILL.md body for permission keywords
51
+ const bodyPermsMatch = skillMd.content.match(/permissions?:\s*([\w,\s]+)/i);
52
+ if (bodyPermsMatch) {
53
+ for (const p of bodyPermsMatch[1].split(/[,\s]+/)) {
54
+ if (p.trim())
55
+ declaredPerms.add(p.trim().toLowerCase());
56
+ }
57
+ }
58
+ // Scan code files for actual capability usage
59
+ const codeFiles = files.filter((f) => f.ext !== ".md" && f.ext !== ".json" && f.ext !== ".yaml" && f.ext !== ".yml");
60
+ const usedCapabilities = new Set();
61
+ const capabilityLocations = {};
62
+ for (const file of codeFiles) {
63
+ for (const [cap, pattern] of Object.entries(CAPABILITY_PATTERNS)) {
64
+ for (let i = 0; i < file.lines.length; i++) {
65
+ if (pattern.test(file.lines[i])) {
66
+ usedCapabilities.add(cap);
67
+ if (!capabilityLocations[cap])
68
+ capabilityLocations[cap] = [];
69
+ capabilityLocations[cap].push({ file: file.relativePath, line: i + 1 });
70
+ }
71
+ }
72
+ }
73
+ }
74
+ // Report undeclared capabilities
75
+ for (const cap of usedCapabilities) {
76
+ if (declaredPerms.size > 0 && !declaredPerms.has(cap)) {
77
+ const locations = capabilityLocations[cap] || [];
78
+ const first = locations[0];
79
+ findings.push({
80
+ rule: "privilege",
81
+ severity: "warning",
82
+ file: first?.file || skillMd.relativePath,
83
+ line: first?.line,
84
+ message: `Code uses '${cap}' capability but SKILL.md doesn't declare it (found in ${locations.length} location${locations.length > 1 ? "s" : ""})`,
85
+ });
86
+ }
87
+ }
88
+ // Report declared but unused permissions
89
+ for (const perm of declaredPerms) {
90
+ if (!usedCapabilities.has(perm) && CAPABILITY_PATTERNS[perm]) {
91
+ findings.push({
92
+ rule: "privilege",
93
+ severity: "info",
94
+ file: skillMd.relativePath,
95
+ message: `SKILL.md declares '${perm}' permission but code doesn't appear to use it`,
96
+ });
97
+ }
98
+ }
99
+ // Report used capabilities as info
100
+ if (usedCapabilities.size > 0) {
101
+ findings.push({
102
+ rule: "privilege",
103
+ severity: "info",
104
+ file: skillMd.relativePath,
105
+ message: `Detected capabilities: ${[...usedCapabilities].join(", ")}`,
106
+ });
107
+ }
108
+ return findings;
109
+ },
110
+ };
111
+ //# sourceMappingURL=privilege.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privilege.js","sourceRoot":"","sources":["../../src/rules/privilege.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const reverseShellRule: Rule;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Rule: reverse-shell
3
+ * Detects reverse shell patterns — outbound socket connections piped to shell.
4
+ */
5
+ const REVERSE_SHELL_PATTERNS = [
6
+ // Shell
7
+ { pattern: /\/dev\/tcp\/\d+\.\d+\.\d+\.\d+/, desc: "/dev/tcp reverse shell" },
8
+ { pattern: /bash\s+-i\s+>&\s*\/dev\/tcp/, desc: "Bash interactive reverse shell" },
9
+ { pattern: /nc\s+-e\s+\/bin\/(ba)?sh/, desc: "Netcat reverse shell" },
10
+ { pattern: /ncat\s+.*-e\s+\/bin/, desc: "Ncat reverse shell" },
11
+ { pattern: /mkfifo\s+.*\/tmp\/.*nc\s/, desc: "Named pipe + netcat reverse shell" },
12
+ // Python
13
+ { pattern: /socket\.connect\s*\(.*\bsubprocess\b|subprocess.*socket\.connect/i, desc: "Python socket + subprocess reverse shell" },
14
+ { pattern: /pty\.spawn.*\/bin\/(ba)?sh/i, desc: "Python pty.spawn shell" },
15
+ // Node.js
16
+ { pattern: /net\.connect\s*\(.*child_process|child_process.*net\.connect/i, desc: "Node.js net.connect + child_process" },
17
+ { pattern: /new\s+net\.Socket\s*\(\).*\.connect\s*\(.*\.pipe\s*\(/i, desc: "Node.js Socket pipe to process" },
18
+ ];
19
+ export const reverseShellRule = {
20
+ id: "reverse-shell",
21
+ name: "Reverse Shell",
22
+ description: "Detects outbound socket connections piped to a shell process",
23
+ run(files) {
24
+ const findings = [];
25
+ for (const file of files) {
26
+ if (file.ext === ".json" || file.ext === ".yaml" || file.ext === ".yml" || file.ext === ".md")
27
+ continue;
28
+ // Check both per-line and multi-line (for patterns spanning lines)
29
+ for (let i = 0; i < file.lines.length; i++) {
30
+ const line = file.lines[i];
31
+ for (const { pattern, desc } of REVERSE_SHELL_PATTERNS) {
32
+ if (pattern.test(line)) {
33
+ findings.push({
34
+ rule: "reverse-shell",
35
+ severity: "critical",
36
+ file: file.relativePath,
37
+ line: i + 1,
38
+ message: desc,
39
+ evidence: line.trim().slice(0, 120),
40
+ });
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ // Multi-line: check content for socket+subprocess combos
46
+ if (REVERSE_SHELL_PATTERNS.some(({ pattern }) => pattern.test(file.content))) {
47
+ // Already caught per-line
48
+ }
49
+ }
50
+ return findings;
51
+ },
52
+ };
53
+ //# sourceMappingURL=reverse-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reverse-shell.js","sourceRoot":"","sources":["../../src/rules/reverse-shell.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,sBAAsB,GAA6C;IACvE,QAAQ;IACR,EAAE,OAAO,EAAE,gCAAgC,EAAE,IAAI,EAAE,wBAAwB,EAAE;IAC7E,EAAE,OAAO,EAAE,6BAA6B,EAAE,IAAI,EAAE,gCAAgC,EAAE;IAClF,EAAE,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE,sBAAsB,EAAE;IACrE,EAAE,OAAO,EAAE,qBAAqB,EAAE,IAAI,EAAE,oBAAoB,EAAE;IAC9D,EAAE,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE,mCAAmC,EAAE;IAClF,SAAS;IACT,EAAE,OAAO,EAAE,mEAAmE,EAAE,IAAI,EAAE,0CAA0C,EAAE;IAClI,EAAE,OAAO,EAAE,6BAA6B,EAAE,IAAI,EAAE,wBAAwB,EAAE;IAC1E,UAAU;IACV,EAAE,OAAO,EAAE,+DAA+D,EAAE,IAAI,EAAE,qCAAqC,EAAE;IACzH,EAAE,OAAO,EAAE,wDAAwD,EAAE,IAAI,EAAE,gCAAgC,EAAE;CAC9G,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,eAAe;IACrB,WAAW,EAAE,8DAA8D;IAE3E,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,mEAAmE;YACnE,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,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,sBAAsB,EAAE,CAAC;oBACvD,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;YAED,yDAAyD;YACzD,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;gBAC7E,0BAA0B;YAC5B,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 sensitiveReadRule: Rule;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Rule: sensitive-read
3
+ * Detects code that reads sensitive files like SSH keys, AWS credentials, etc.
4
+ */
5
+ const SENSITIVE_PATTERNS = [
6
+ { pattern: /~\/\.ssh\/id_rsa|\.ssh\/id_rsa|ssh_key|id_ed25519/i, desc: "SSH private key" },
7
+ { pattern: /~\/\.ssh\/known_hosts/i, desc: "SSH known_hosts" },
8
+ { pattern: /~\/\.aws\/credentials|AWS_SECRET_ACCESS_KEY|aws_secret/i, desc: "AWS credentials" },
9
+ { pattern: /~\/\.env\b|process\.env\b.*(?:SECRET|KEY|TOKEN|PASSWORD|CREDENTIAL)/i, desc: "environment secrets" },
10
+ { pattern: /~\/\.openclaw\/openclaw\.json|openclaw\.json/i, desc: "OpenClaw config" },
11
+ { pattern: /~\/\.gnupg|gpg.*private/i, desc: "GPG private key" },
12
+ { pattern: /~\/\.kube\/config|kubeconfig/i, desc: "Kubernetes config" },
13
+ { pattern: /\/etc\/shadow|\/etc\/passwd/i, desc: "system passwords" },
14
+ { pattern: /~\/\.docker\/config\.json/i, desc: "Docker credentials" },
15
+ { pattern: /~\/\.npmrc|_authToken/i, desc: "npm auth token" },
16
+ { pattern: /~\/\.gitconfig|\.git-credentials/i, desc: "Git credentials" },
17
+ { pattern: /~\/\.netrc/i, desc: "netrc credentials" },
18
+ ];
19
+ export const sensitiveReadRule = {
20
+ id: "sensitive-read",
21
+ name: "Sensitive File Read",
22
+ description: "Detects reads of SSH keys, credentials, API tokens, and other secrets",
23
+ run(files) {
24
+ const findings = [];
25
+ for (const file of files) {
26
+ // Skip non-code files (allow checking shell scripts, JS, TS, Python)
27
+ if (file.ext === ".json" || file.ext === ".yaml" || file.ext === ".yml" || file.ext === ".toml")
28
+ continue;
29
+ if (file.relativePath === "SKILL.md")
30
+ continue;
31
+ for (let i = 0; i < file.lines.length; i++) {
32
+ const line = file.lines[i];
33
+ // Skip comments
34
+ if (line.trimStart().startsWith("//") || line.trimStart().startsWith("#"))
35
+ continue;
36
+ for (const { pattern, desc } of SENSITIVE_PATTERNS) {
37
+ if (pattern.test(line)) {
38
+ findings.push({
39
+ rule: "sensitive-read",
40
+ severity: "warning",
41
+ file: file.relativePath,
42
+ line: i + 1,
43
+ message: `Accesses ${desc}`,
44
+ evidence: line.trim().slice(0, 120),
45
+ });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return findings;
51
+ },
52
+ };
53
+ //# sourceMappingURL=sensitive-read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sensitive-read.js","sourceRoot":"","sources":["../../src/rules/sensitive-read.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,MAAM,kBAAkB,GAA6C;IACnE,EAAE,OAAO,EAAE,oDAAoD,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC1F,EAAE,OAAO,EAAE,wBAAwB,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC9D,EAAE,OAAO,EAAE,yDAAyD,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAC/F,EAAE,OAAO,EAAE,sEAAsE,EAAE,IAAI,EAAE,qBAAqB,EAAE;IAChH,EAAE,OAAO,EAAE,+CAA+C,EAAE,IAAI,EAAE,iBAAiB,EAAE;IACrF,EAAE,OAAO,EAAE,0BAA0B,EAAE,IAAI,EAAE,iBAAiB,EAAE;IAChE,EAAE,OAAO,EAAE,+BAA+B,EAAE,IAAI,EAAE,mBAAmB,EAAE;IACvE,EAAE,OAAO,EAAE,8BAA8B,EAAE,IAAI,EAAE,kBAAkB,EAAE;IACrE,EAAE,OAAO,EAAE,4BAA4B,EAAE,IAAI,EAAE,oBAAoB,EAAE;IACrE,EAAE,OAAO,EAAE,wBAAwB,EAAE,IAAI,EAAE,gBAAgB,EAAE;IAC7D,EAAE,OAAO,EAAE,mCAAmC,EAAE,IAAI,EAAE,iBAAiB,EAAE;IACzE,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,mBAAmB,EAAE;CACtD,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,gBAAgB;IACpB,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EAAE,uEAAuE;IAEpF,GAAG,CAAC,KAAoB;QACtB,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,qEAAqE;YACrE,IAAI,IAAI,CAAC,GAAG,KAAK,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,OAAO,IAAI,IAAI,CAAC,GAAG,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,KAAK,OAAO;gBAAE,SAAS;YAC1G,IAAI,IAAI,CAAC,YAAY,KAAK,UAAU;gBAAE,SAAS;YAE/C,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,gBAAgB;gBAChB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAEpF,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,kBAAkB,EAAE,CAAC;oBACnD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACvB,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,gBAAgB;4BACtB,QAAQ,EAAE,SAAS;4BACnB,IAAI,EAAE,IAAI,CAAC,YAAY;4BACvB,IAAI,EAAE,CAAC,GAAG,CAAC;4BACX,OAAO,EAAE,YAAY,IAAI,EAAE;4BAC3B,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;yBACpC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Rule } from "../types.js";
2
+ /**
3
+ * Rule: supply-chain
4
+ * Runs npm audit to detect known CVEs in dependencies.
5
+ */
6
+ export declare const supplyChainRule: Rule;