@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.
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/README.zh-CN.md +130 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +265 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +91 -0
- package/dist/config.js.map +1 -0
- package/dist/discover.d.ts +9 -0
- package/dist/discover.js +143 -0
- package/dist/discover.js.map +1 -0
- package/dist/llm/anthropic.d.ts +10 -0
- package/dist/llm/anthropic.js +67 -0
- package/dist/llm/anthropic.js.map +1 -0
- package/dist/llm/index.d.ts +10 -0
- package/dist/llm/index.js +41 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/ollama.d.ts +9 -0
- package/dist/llm/ollama.js +61 -0
- package/dist/llm/ollama.js.map +1 -0
- package/dist/llm/openai.d.ts +10 -0
- package/dist/llm/openai.js +66 -0
- package/dist/llm/openai.js.map +1 -0
- package/dist/llm/prompt.d.ts +3 -0
- package/dist/llm/prompt.js +31 -0
- package/dist/llm/prompt.js.map +1 -0
- package/dist/llm/types.d.ts +23 -0
- package/dist/llm/types.js +3 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/llm-analyzer.d.ts +13 -0
- package/dist/llm-analyzer.js +169 -0
- package/dist/llm-analyzer.js.map +1 -0
- package/dist/reporter/badge.d.ts +7 -0
- package/dist/reporter/badge.js +50 -0
- package/dist/reporter/badge.js.map +1 -0
- package/dist/reporter/json.d.ts +3 -0
- package/dist/reporter/json.js +5 -0
- package/dist/reporter/json.js.map +1 -0
- package/dist/reporter/terminal.d.ts +2 -0
- package/dist/reporter/terminal.js +64 -0
- package/dist/reporter/terminal.js.map +1 -0
- package/dist/rules/backdoor.d.ts +2 -0
- package/dist/rules/backdoor.js +57 -0
- package/dist/rules/backdoor.js.map +1 -0
- package/dist/rules/credential-hardcode.d.ts +2 -0
- package/dist/rules/credential-hardcode.js +57 -0
- package/dist/rules/credential-hardcode.js.map +1 -0
- package/dist/rules/crypto-mining.d.ts +2 -0
- package/dist/rules/crypto-mining.js +41 -0
- package/dist/rules/crypto-mining.js.map +1 -0
- package/dist/rules/data-exfil.d.ts +2 -0
- package/dist/rules/data-exfil.js +61 -0
- package/dist/rules/data-exfil.js.map +1 -0
- package/dist/rules/env-leak.d.ts +2 -0
- package/dist/rules/env-leak.js +43 -0
- package/dist/rules/env-leak.js.map +1 -0
- package/dist/rules/excessive-perms.d.ts +2 -0
- package/dist/rules/excessive-perms.js +50 -0
- package/dist/rules/excessive-perms.js.map +1 -0
- package/dist/rules/hidden-files.d.ts +2 -0
- package/dist/rules/hidden-files.js +52 -0
- package/dist/rules/hidden-files.js.map +1 -0
- package/dist/rules/index.d.ts +5 -0
- package/dist/rules/index.js +53 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/mcp-manifest.d.ts +2 -0
- package/dist/rules/mcp-manifest.js +270 -0
- package/dist/rules/mcp-manifest.js.map +1 -0
- package/dist/rules/network-ssrf.d.ts +2 -0
- package/dist/rules/network-ssrf.js +51 -0
- package/dist/rules/network-ssrf.js.map +1 -0
- package/dist/rules/obfuscation.d.ts +2 -0
- package/dist/rules/obfuscation.js +51 -0
- package/dist/rules/obfuscation.js.map +1 -0
- package/dist/rules/phone-home.d.ts +2 -0
- package/dist/rules/phone-home.js +38 -0
- package/dist/rules/phone-home.js.map +1 -0
- package/dist/rules/privilege.d.ts +2 -0
- package/dist/rules/privilege.js +111 -0
- package/dist/rules/privilege.js.map +1 -0
- package/dist/rules/prompt-injection.d.ts +2 -0
- package/dist/rules/prompt-injection.js +323 -0
- package/dist/rules/prompt-injection.js.map +1 -0
- package/dist/rules/reverse-shell.d.ts +2 -0
- package/dist/rules/reverse-shell.js +53 -0
- package/dist/rules/reverse-shell.js.map +1 -0
- package/dist/rules/sensitive-read.d.ts +2 -0
- package/dist/rules/sensitive-read.js +53 -0
- package/dist/rules/sensitive-read.js.map +1 -0
- package/dist/rules/skill-risks.d.ts +2 -0
- package/dist/rules/skill-risks.js +148 -0
- package/dist/rules/skill-risks.js.map +1 -0
- package/dist/rules/supply-chain.d.ts +6 -0
- package/dist/rules/supply-chain.js +105 -0
- package/dist/rules/supply-chain.js.map +1 -0
- package/dist/rules/tool-shadowing.d.ts +2 -0
- package/dist/rules/tool-shadowing.js +129 -0
- package/dist/rules/tool-shadowing.js.map +1 -0
- package/dist/rules/toxic-flow.d.ts +2 -0
- package/dist/rules/toxic-flow.js +160 -0
- package/dist/rules/toxic-flow.js.map +1 -0
- package/dist/rules/typosquatting.d.ts +2 -0
- package/dist/rules/typosquatting.js +56 -0
- package/dist/rules/typosquatting.js.map +1 -0
- package/dist/scanner/files.d.ts +5 -0
- package/dist/scanner/files.js +105 -0
- package/dist/scanner/files.js.map +1 -0
- package/dist/scanner/index.d.ts +6 -0
- package/dist/scanner/index.js +198 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/score.d.ts +14 -0
- package/dist/score.js +35 -0
- package/dist/score.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/yaml-simple.d.ts +6 -0
- package/dist/yaml-simple.js +98 -0
- package/dist/yaml-simple.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: typosquatting
|
|
3
|
+
* Detects potentially typosquatted npm package names.
|
|
4
|
+
*/
|
|
5
|
+
// Known popular packages and their common typos
|
|
6
|
+
const TYPOSQUAT_MAP = {
|
|
7
|
+
lodash: ["1odash", "lodsh", "lodashs", "lodahs"],
|
|
8
|
+
express: ["expresss", "expres", "exress", "exppress"],
|
|
9
|
+
axios: ["axois", "axio", "axioss", "axiso"],
|
|
10
|
+
react: ["raect", "reacct", "reactt"],
|
|
11
|
+
chalk: ["chalks", "chalkk", "chak"],
|
|
12
|
+
commander: ["comander", "commanderr", "commmander"],
|
|
13
|
+
"node-fetch": ["node-ftch", "nodefetch", "node-fetchh"],
|
|
14
|
+
request: ["reqeust", "requets", "reuqest"],
|
|
15
|
+
mongoose: ["mongose", "mongosse", "mongooose"],
|
|
16
|
+
webpack: ["webpck", "webpackk", "weback"],
|
|
17
|
+
eslint: ["eslintt", "eslnt", "elint"],
|
|
18
|
+
typescript: ["typscript", "typescipt", "typesript"],
|
|
19
|
+
};
|
|
20
|
+
export const typosquattingRule = {
|
|
21
|
+
id: "typosquatting",
|
|
22
|
+
name: "Dependency Typosquatting",
|
|
23
|
+
description: "Detects potentially typosquatted package names in dependencies",
|
|
24
|
+
run(files) {
|
|
25
|
+
const findings = [];
|
|
26
|
+
const pkgJson = files.find((f) => f.relativePath === "package.json" || f.relativePath.endsWith("/package.json"));
|
|
27
|
+
if (!pkgJson)
|
|
28
|
+
return findings;
|
|
29
|
+
try {
|
|
30
|
+
const pkg = JSON.parse(pkgJson.content);
|
|
31
|
+
const allDeps = {
|
|
32
|
+
...pkg.dependencies,
|
|
33
|
+
...pkg.devDependencies,
|
|
34
|
+
...pkg.peerDependencies,
|
|
35
|
+
...pkg.optionalDependencies,
|
|
36
|
+
};
|
|
37
|
+
for (const depName of Object.keys(allDeps)) {
|
|
38
|
+
for (const [legitimate, typos] of Object.entries(TYPOSQUAT_MAP)) {
|
|
39
|
+
if (typos.includes(depName.toLowerCase())) {
|
|
40
|
+
findings.push({
|
|
41
|
+
rule: "typosquatting",
|
|
42
|
+
severity: "critical",
|
|
43
|
+
file: pkgJson.relativePath,
|
|
44
|
+
message: `Suspicious package "${depName}" — possible typosquat of "${legitimate}"`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// ignore parse errors
|
|
52
|
+
}
|
|
53
|
+
return findings;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=typosquatting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typosquatting.js","sourceRoot":"","sources":["../../src/rules/typosquatting.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,gDAAgD;AAChD,MAAM,aAAa,GAA6B;IAC9C,MAAM,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;IAChD,OAAO,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,CAAC;IACrD,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;IAC3C,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC;IACpC,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;IACnC,SAAS,EAAE,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC;IACnD,YAAY,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,aAAa,CAAC;IACvD,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;IAC1C,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC;IAC9C,OAAO,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,CAAC;IACzC,MAAM,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC;IACrC,UAAU,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,WAAW,CAAC;CACpD,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,eAAe;IACnB,IAAI,EAAE,0BAA0B;IAChC,WAAW,EAAE,gEAAgE;IAE7E,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,cAAc,IAAI,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,eAAe,CAAC,CACrF,CAAC;QACF,IAAI,CAAC,OAAO;YAAE,OAAO,QAAQ,CAAC;QAE9B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACxC,MAAM,OAAO,GAAG;gBACd,GAAG,GAAG,CAAC,YAAY;gBACnB,GAAG,GAAG,CAAC,eAAe;gBACtB,GAAG,GAAG,CAAC,gBAAgB;gBACvB,GAAG,GAAG,CAAC,oBAAoB;aAC5B,CAAC;YAEF,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3C,KAAK,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;oBAChE,IAAI,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;wBAC1C,QAAQ,CAAC,IAAI,CAAC;4BACZ,IAAI,EAAE,eAAe;4BACrB,QAAQ,EAAE,UAAU;4BACpB,IAAI,EAAE,OAAO,CAAC,YAAY;4BAC1B,OAAO,EAAE,uBAAuB,OAAO,8BAA8B,UAAU,GAAG;yBACnF,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sBAAsB;QACxB,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ScannedFile } from "../types.js";
|
|
2
|
+
/** Recursively collect scannable files from a directory */
|
|
3
|
+
export declare function collectFiles(dir: string, base?: string): ScannedFile[];
|
|
4
|
+
/** Count total lines across files */
|
|
5
|
+
export declare function totalLines(files: ScannedFile[]): number;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { readFileSync, statSync, readdirSync } from "fs";
|
|
2
|
+
import { join, relative, extname, dirname } from "path";
|
|
3
|
+
const SKIP_DIRS = new Set([
|
|
4
|
+
"node_modules", ".git", "dist", "build", "__pycache__", ".venv", "venv",
|
|
5
|
+
]);
|
|
6
|
+
const CODE_EXTS = new Set([
|
|
7
|
+
".ts", ".js", ".mjs", ".cjs", ".tsx", ".jsx",
|
|
8
|
+
".py", ".sh", ".bash", ".zsh",
|
|
9
|
+
".json", ".yaml", ".yml", ".toml",
|
|
10
|
+
".md",
|
|
11
|
+
]);
|
|
12
|
+
const MAX_FILE_SIZE = 512 * 1024; // 512 KB
|
|
13
|
+
/** Recursively collect scannable files from a directory */
|
|
14
|
+
export function collectFiles(dir, base) {
|
|
15
|
+
const root = base ?? dir;
|
|
16
|
+
const files = [];
|
|
17
|
+
let entries;
|
|
18
|
+
try {
|
|
19
|
+
entries = readdirSync(dir);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return files;
|
|
23
|
+
}
|
|
24
|
+
for (const name of entries) {
|
|
25
|
+
if (name.startsWith(".") && name !== ".env")
|
|
26
|
+
continue;
|
|
27
|
+
if (SKIP_DIRS.has(name))
|
|
28
|
+
continue;
|
|
29
|
+
const fullPath = join(dir, name);
|
|
30
|
+
let stat;
|
|
31
|
+
try {
|
|
32
|
+
stat = statSync(fullPath);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (stat.isDirectory()) {
|
|
38
|
+
files.push(...collectFiles(fullPath, root));
|
|
39
|
+
}
|
|
40
|
+
else if (stat.isFile()) {
|
|
41
|
+
const ext = extname(name).toLowerCase();
|
|
42
|
+
if (!CODE_EXTS.has(ext) && name !== "SKILL.md")
|
|
43
|
+
continue;
|
|
44
|
+
if (stat.size > MAX_FILE_SIZE)
|
|
45
|
+
continue;
|
|
46
|
+
try {
|
|
47
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
48
|
+
const relPath = relative(root, fullPath);
|
|
49
|
+
files.push({
|
|
50
|
+
path: fullPath,
|
|
51
|
+
relativePath: relPath,
|
|
52
|
+
content,
|
|
53
|
+
lines: content.split("\n"),
|
|
54
|
+
ext,
|
|
55
|
+
context: detectFileContext(relPath, name),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// skip unreadable files
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return files;
|
|
64
|
+
}
|
|
65
|
+
/** Count total lines across files */
|
|
66
|
+
export function totalLines(files) {
|
|
67
|
+
return files.reduce((sum, f) => sum + f.lines.length, 0);
|
|
68
|
+
}
|
|
69
|
+
/** Detect file context for false positive reduction */
|
|
70
|
+
function detectFileContext(relativePath, fileName) {
|
|
71
|
+
const lowerPath = relativePath.toLowerCase();
|
|
72
|
+
const lowerName = fileName.toLowerCase();
|
|
73
|
+
const dirName = dirname(lowerPath).toLowerCase();
|
|
74
|
+
// Test files
|
|
75
|
+
if (lowerName.includes(".test.") || lowerName.includes(".spec.") ||
|
|
76
|
+
lowerName.includes("_test.") || lowerName.includes("_spec.") ||
|
|
77
|
+
lowerPath.includes("__tests__") || lowerPath.includes("/tests/") ||
|
|
78
|
+
lowerPath.startsWith("tests/") || lowerPath.startsWith("test/") ||
|
|
79
|
+
lowerName === "jest.config.js" || lowerName === "vitest.config.ts") {
|
|
80
|
+
return "test";
|
|
81
|
+
}
|
|
82
|
+
// Deploy / CI scripts
|
|
83
|
+
if (lowerPath.includes("deploy") || lowerPath.includes("ci/") ||
|
|
84
|
+
lowerPath.includes(".github/") || lowerPath.includes("scripts/") ||
|
|
85
|
+
lowerPath.includes("infra/") || lowerPath.includes("ops/") ||
|
|
86
|
+
lowerName.includes("deploy") || lowerName.includes("release") ||
|
|
87
|
+
lowerName === "dockerfile" || lowerName === "makefile") {
|
|
88
|
+
return "deploy";
|
|
89
|
+
}
|
|
90
|
+
// Config files
|
|
91
|
+
if ([".json", ".yaml", ".yml", ".toml"].includes(extname(lowerName)) &&
|
|
92
|
+
!lowerName.includes("skill")) {
|
|
93
|
+
return "config";
|
|
94
|
+
}
|
|
95
|
+
// Documentation
|
|
96
|
+
if (extname(lowerName) === ".md") {
|
|
97
|
+
return "docs";
|
|
98
|
+
}
|
|
99
|
+
// Shell scripts (standalone)
|
|
100
|
+
if ([".sh", ".bash", ".zsh"].includes(extname(lowerName))) {
|
|
101
|
+
return "script";
|
|
102
|
+
}
|
|
103
|
+
return "source";
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/scanner/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ScanResult, ScanConfig } from "../types.js";
|
|
2
|
+
import type { LlmProvider } from "../llm/types.js";
|
|
3
|
+
/** Run all rules against a target directory */
|
|
4
|
+
export declare function scan(targetDir: string, configOverride?: Partial<ScanConfig>): ScanResult;
|
|
5
|
+
/** Run all rules + optional LLM deep analysis */
|
|
6
|
+
export declare function scanWithLlm(targetDir: string, llmProvider: LlmProvider, configOverride?: Partial<ScanConfig>): Promise<ScanResult>;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { collectFiles, totalLines } from "./files.js";
|
|
2
|
+
import { rules } from "../rules/index.js";
|
|
3
|
+
import { computeScore } from "../score.js";
|
|
4
|
+
import { loadConfig, loadIgnorePatterns, isIgnored } from "../config.js";
|
|
5
|
+
/** Context types where findings are likely false positives */
|
|
6
|
+
const FP_CONTEXTS = {
|
|
7
|
+
test: "Test file — assertions and mocks commonly trigger security patterns",
|
|
8
|
+
deploy: "Deploy/CI script — HTTP requests and credential access are expected",
|
|
9
|
+
docs: "Documentation file — code examples and descriptions commonly trigger patterns",
|
|
10
|
+
config: "Config file — expected to contain URLs, paths, and credential references",
|
|
11
|
+
};
|
|
12
|
+
/** Rules most likely to false-positive in specific contexts */
|
|
13
|
+
const CONTEXT_FP_RULES = {
|
|
14
|
+
test: new Set(["data-exfil", "env-leak", "backdoor", "network-ssrf", "sensitive-read", "phone-home", "obfuscation", "crypto-mining", "reverse-shell", "credential-hardcode", "skill-risks"]),
|
|
15
|
+
deploy: new Set(["data-exfil", "env-leak", "backdoor", "network-ssrf", "credential-hardcode", "sensitive-read", "skill-risks"]),
|
|
16
|
+
docs: new Set(["data-exfil", "env-leak", "backdoor", "network-ssrf", "sensitive-read", "obfuscation", "crypto-mining", "reverse-shell", "credential-hardcode", "skill-risks"]),
|
|
17
|
+
config: new Set(["data-exfil", "env-leak", "credential-hardcode", "network-ssrf"]),
|
|
18
|
+
};
|
|
19
|
+
/** Run all rules against a target directory */
|
|
20
|
+
export function scan(targetDir, configOverride) {
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
// Load config
|
|
23
|
+
const fileConfig = loadConfig(targetDir);
|
|
24
|
+
const config = { ...fileConfig, ...configOverride };
|
|
25
|
+
// Load ignore patterns
|
|
26
|
+
const ignorePatterns = loadIgnorePatterns(targetDir);
|
|
27
|
+
if (config.ignore) {
|
|
28
|
+
ignorePatterns.push(...config.ignore);
|
|
29
|
+
}
|
|
30
|
+
// Collect and filter files
|
|
31
|
+
let files = collectFiles(targetDir);
|
|
32
|
+
if (ignorePatterns.length > 0) {
|
|
33
|
+
files = files.filter((f) => !isIgnored(f.relativePath, ignorePatterns));
|
|
34
|
+
}
|
|
35
|
+
// Filter rules based on config
|
|
36
|
+
let activeRules = [...rules];
|
|
37
|
+
if (config.rules?.enable) {
|
|
38
|
+
activeRules = activeRules.filter((r) => config.rules.enable.includes(r.id));
|
|
39
|
+
}
|
|
40
|
+
if (config.rules?.disable) {
|
|
41
|
+
activeRules = activeRules.filter((r) => !config.rules.disable.includes(r.id));
|
|
42
|
+
}
|
|
43
|
+
// Run rules
|
|
44
|
+
const findings = [];
|
|
45
|
+
for (const rule of activeRules) {
|
|
46
|
+
findings.push(...rule.run(files));
|
|
47
|
+
}
|
|
48
|
+
// Post-process: false positive detection, severity overrides, sorting
|
|
49
|
+
postProcess(findings, files, config);
|
|
50
|
+
return {
|
|
51
|
+
target: targetDir,
|
|
52
|
+
filesScanned: files.length,
|
|
53
|
+
linesScanned: totalLines(files),
|
|
54
|
+
findings,
|
|
55
|
+
score: computeScore(findings),
|
|
56
|
+
duration: Date.now() - start,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Common post-processing: false positive detection, severity overrides, sorting */
|
|
60
|
+
function postProcess(findings, files, config) {
|
|
61
|
+
for (const finding of findings) {
|
|
62
|
+
const file = files.find((f) => f.relativePath === finding.file || f.path === finding.file);
|
|
63
|
+
// Context-based FP detection
|
|
64
|
+
if (file && FP_CONTEXTS[file.context]) {
|
|
65
|
+
const fpRules = CONTEXT_FP_RULES[file.context];
|
|
66
|
+
if (fpRules?.has(finding.rule)) {
|
|
67
|
+
finding.possibleFalsePositive = true;
|
|
68
|
+
finding.falsePositiveReason = FP_CONTEXTS[file.context];
|
|
69
|
+
if (finding.severity === "critical") {
|
|
70
|
+
finding.severity = "warning";
|
|
71
|
+
}
|
|
72
|
+
else if (finding.severity === "warning") {
|
|
73
|
+
finding.severity = "info";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Security tool self-reference detection:
|
|
78
|
+
// If a file is a rule definition (contains regex patterns as data),
|
|
79
|
+
// its matches on code-analysis rules are likely false positives
|
|
80
|
+
if (file) {
|
|
81
|
+
const isRuleFile = file.relativePath.includes("rules/") && file.ext === ".ts";
|
|
82
|
+
const isSecurityToolCode = file.relativePath.includes("llm/") ||
|
|
83
|
+
file.relativePath.includes("llm-analyzer") ||
|
|
84
|
+
file.relativePath.includes("scanner/") ||
|
|
85
|
+
file.relativePath.includes("reporter/") ||
|
|
86
|
+
file.relativePath.includes("score.");
|
|
87
|
+
if (isRuleFile || isSecurityToolCode) {
|
|
88
|
+
const codeAnalysisRules = new Set([
|
|
89
|
+
"data-exfil", "backdoor", "obfuscation", "env-leak",
|
|
90
|
+
"crypto-mining", "reverse-shell", "sensitive-read",
|
|
91
|
+
"network-ssrf", "credential-hardcode", "mcp-manifest",
|
|
92
|
+
"skill-risks",
|
|
93
|
+
]);
|
|
94
|
+
if (codeAnalysisRules.has(finding.rule)) {
|
|
95
|
+
finding.possibleFalsePositive = true;
|
|
96
|
+
finding.falsePositiveReason = "Security tool source code — pattern definitions are not actual vulnerabilities";
|
|
97
|
+
if (finding.severity === "critical") {
|
|
98
|
+
finding.severity = "info";
|
|
99
|
+
}
|
|
100
|
+
else if (finding.severity === "warning") {
|
|
101
|
+
finding.severity = "info";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Evidence-based FP: regex pattern strings (common in security tools)
|
|
107
|
+
if (finding.evidence) {
|
|
108
|
+
const isRegexDef = /(?:pattern|RegExp|\/[^/]+\/[gimsuy]*|new RegExp)\s*[:=(]/.test(finding.evidence);
|
|
109
|
+
const isStringDef = /(?:const|let|var)\s+\w+\s*=\s*["'`]/.test(finding.evidence);
|
|
110
|
+
if (isRegexDef && !finding.possibleFalsePositive) {
|
|
111
|
+
finding.possibleFalsePositive = true;
|
|
112
|
+
finding.falsePositiveReason = "Pattern definition — regex/string constant, not executable code";
|
|
113
|
+
if (finding.severity !== "info")
|
|
114
|
+
finding.severity = "info";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (config.severity) {
|
|
119
|
+
for (const finding of findings) {
|
|
120
|
+
if (config.severity[finding.rule]) {
|
|
121
|
+
finding.severity = config.severity[finding.rule];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
126
|
+
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
127
|
+
}
|
|
128
|
+
/** LLM-capable file extensions for deep analysis */
|
|
129
|
+
const LLM_SCAN_EXTS = new Set([".md", ".json", ".yaml", ".yml", ".py"]);
|
|
130
|
+
/** Run all rules + optional LLM deep analysis */
|
|
131
|
+
export async function scanWithLlm(targetDir, llmProvider, configOverride) {
|
|
132
|
+
const start = Date.now();
|
|
133
|
+
const fileConfig = loadConfig(targetDir);
|
|
134
|
+
const config = { ...fileConfig, ...configOverride };
|
|
135
|
+
const ignorePatterns = loadIgnorePatterns(targetDir);
|
|
136
|
+
if (config.ignore)
|
|
137
|
+
ignorePatterns.push(...config.ignore);
|
|
138
|
+
let files = collectFiles(targetDir);
|
|
139
|
+
if (ignorePatterns.length > 0) {
|
|
140
|
+
files = files.filter((f) => !isIgnored(f.relativePath, ignorePatterns));
|
|
141
|
+
}
|
|
142
|
+
let activeRules = [...rules];
|
|
143
|
+
if (config.rules?.enable) {
|
|
144
|
+
activeRules = activeRules.filter((r) => config.rules.enable.includes(r.id));
|
|
145
|
+
}
|
|
146
|
+
if (config.rules?.disable) {
|
|
147
|
+
activeRules = activeRules.filter((r) => !config.rules.disable.includes(r.id));
|
|
148
|
+
}
|
|
149
|
+
// Phase 1: static regex rules
|
|
150
|
+
const findings = [];
|
|
151
|
+
for (const rule of activeRules) {
|
|
152
|
+
findings.push(...rule.run(files));
|
|
153
|
+
}
|
|
154
|
+
// Phase 2: LLM deep analysis on markdown, config, and Python files
|
|
155
|
+
const llmTargets = files.filter((f) => LLM_SCAN_EXTS.has(f.ext));
|
|
156
|
+
let totalTokens = 0;
|
|
157
|
+
for (const file of llmTargets) {
|
|
158
|
+
try {
|
|
159
|
+
const result = await llmProvider.analyze(file.content, file.relativePath);
|
|
160
|
+
totalTokens += result.tokensUsed || 0;
|
|
161
|
+
for (const llmFinding of result.findings) {
|
|
162
|
+
// Deduplicate: skip if regex already found a similar issue on the same line
|
|
163
|
+
const isDuplicate = findings.some((f) => f.file === file.relativePath &&
|
|
164
|
+
f.line === llmFinding.line &&
|
|
165
|
+
f.rule === "prompt-injection");
|
|
166
|
+
if (!isDuplicate) {
|
|
167
|
+
findings.push({
|
|
168
|
+
rule: "prompt-injection-llm",
|
|
169
|
+
severity: llmFinding.severity,
|
|
170
|
+
file: file.relativePath,
|
|
171
|
+
line: llmFinding.line,
|
|
172
|
+
message: `[LLM] ${llmFinding.description}`,
|
|
173
|
+
evidence: llmFinding.evidence,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
// LLM failure is non-fatal — fallback to regex-only
|
|
180
|
+
findings.push({
|
|
181
|
+
rule: "prompt-injection-llm",
|
|
182
|
+
severity: "info",
|
|
183
|
+
file: file.relativePath,
|
|
184
|
+
message: `LLM analysis skipped: ${err.message?.slice(0, 80) || "unknown error"}`,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
postProcess(findings, files, config);
|
|
189
|
+
return {
|
|
190
|
+
target: targetDir,
|
|
191
|
+
filesScanned: files.length,
|
|
192
|
+
linesScanned: totalLines(files),
|
|
193
|
+
findings,
|
|
194
|
+
score: computeScore(findings),
|
|
195
|
+
duration: Date.now() - start,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"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"}
|
package/dist/score.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Finding } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Compute a security score from 0-100.
|
|
4
|
+
*
|
|
5
|
+
* Starts at 100, deducts points per finding:
|
|
6
|
+
* critical: -25
|
|
7
|
+
* warning: -10
|
|
8
|
+
* info: -0
|
|
9
|
+
*
|
|
10
|
+
* Minimum score is 0.
|
|
11
|
+
*/
|
|
12
|
+
export declare function computeScore(findings: Finding[]): number;
|
|
13
|
+
/** Human-readable risk label */
|
|
14
|
+
export declare function riskLabel(score: number): string;
|
package/dist/score.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute a security score from 0-100.
|
|
3
|
+
*
|
|
4
|
+
* Starts at 100, deducts points per finding:
|
|
5
|
+
* critical: -25
|
|
6
|
+
* warning: -10
|
|
7
|
+
* info: -0
|
|
8
|
+
*
|
|
9
|
+
* Minimum score is 0.
|
|
10
|
+
*/
|
|
11
|
+
export function computeScore(findings) {
|
|
12
|
+
let score = 100;
|
|
13
|
+
for (const f of findings) {
|
|
14
|
+
switch (f.severity) {
|
|
15
|
+
case "critical":
|
|
16
|
+
score -= 25;
|
|
17
|
+
break;
|
|
18
|
+
case "warning":
|
|
19
|
+
score -= 10;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return Math.max(0, score);
|
|
24
|
+
}
|
|
25
|
+
/** Human-readable risk label */
|
|
26
|
+
export function riskLabel(score) {
|
|
27
|
+
if (score >= 90)
|
|
28
|
+
return "Low Risk";
|
|
29
|
+
if (score >= 70)
|
|
30
|
+
return "Moderate Risk";
|
|
31
|
+
if (score >= 40)
|
|
32
|
+
return "High Risk";
|
|
33
|
+
return "Critical Risk";
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.js","sourceRoot":"","sources":["../src/score.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,QAAmB;IAC9C,IAAI,KAAK,GAAG,GAAG,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,QAAQ,CAAC,CAAC,QAAQ,EAAE,CAAC;YACnB,KAAK,UAAU;gBACb,KAAK,IAAI,EAAE,CAAC;gBACZ,MAAM;YACR,KAAK,SAAS;gBACZ,KAAK,IAAI,EAAE,CAAC;gBACZ,MAAM;QACV,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,UAAU,CAAC;IACnC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,eAAe,CAAC;IACxC,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,WAAW,CAAC;IACpC,OAAO,eAAe,CAAC;AACzB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Severity levels for findings */
|
|
2
|
+
export type Severity = "critical" | "warning" | "info";
|
|
3
|
+
/** File context hints for reducing false positives */
|
|
4
|
+
export type FileContext = "test" | "deploy" | "config" | "docs" | "script" | "source";
|
|
5
|
+
/** A single security finding */
|
|
6
|
+
export interface Finding {
|
|
7
|
+
rule: string;
|
|
8
|
+
severity: Severity;
|
|
9
|
+
file: string;
|
|
10
|
+
line?: number;
|
|
11
|
+
message: string;
|
|
12
|
+
evidence?: string;
|
|
13
|
+
/** If true, the finding is likely a false positive due to file context */
|
|
14
|
+
possibleFalsePositive?: boolean;
|
|
15
|
+
/** Why it might be a false positive */
|
|
16
|
+
falsePositiveReason?: string;
|
|
17
|
+
}
|
|
18
|
+
/** Scan result for a directory */
|
|
19
|
+
export interface ScanResult {
|
|
20
|
+
target: string;
|
|
21
|
+
filesScanned: number;
|
|
22
|
+
linesScanned: number;
|
|
23
|
+
findings: Finding[];
|
|
24
|
+
score: number;
|
|
25
|
+
duration: number;
|
|
26
|
+
}
|
|
27
|
+
/** A scanner rule */
|
|
28
|
+
export interface Rule {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
run(files: ScannedFile[]): Finding[];
|
|
33
|
+
}
|
|
34
|
+
/** A file loaded for scanning */
|
|
35
|
+
export interface ScannedFile {
|
|
36
|
+
path: string;
|
|
37
|
+
relativePath: string;
|
|
38
|
+
content: string;
|
|
39
|
+
lines: string[];
|
|
40
|
+
ext: string;
|
|
41
|
+
/** Detected file context for false positive reduction */
|
|
42
|
+
context: FileContext;
|
|
43
|
+
}
|
|
44
|
+
/** Parsed SKILL.md metadata */
|
|
45
|
+
export interface SkillMetadata {
|
|
46
|
+
name?: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
permissions?: string[];
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
/** Scan configuration from .agentshield.yml */
|
|
52
|
+
export interface ScanConfig {
|
|
53
|
+
rules?: {
|
|
54
|
+
enable?: string[];
|
|
55
|
+
disable?: string[];
|
|
56
|
+
};
|
|
57
|
+
severity?: Record<string, "critical" | "warning" | "info">;
|
|
58
|
+
failUnder?: number;
|
|
59
|
+
ignore?: string[];
|
|
60
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal YAML parser for simple config files.
|
|
3
|
+
* Supports: scalars, lists, nested objects (2 levels deep).
|
|
4
|
+
* For complex YAML, use the 'yaml' package.
|
|
5
|
+
*/
|
|
6
|
+
export function parse(input) {
|
|
7
|
+
const result = {};
|
|
8
|
+
const lines = input.split("\n");
|
|
9
|
+
let currentKey = "";
|
|
10
|
+
let currentSubKey = "";
|
|
11
|
+
let currentList = null;
|
|
12
|
+
for (const rawLine of lines) {
|
|
13
|
+
// Skip comments and empty lines
|
|
14
|
+
const line = rawLine.replace(/#.*$/, "");
|
|
15
|
+
if (!line.trim())
|
|
16
|
+
continue;
|
|
17
|
+
const indent = rawLine.search(/\S/);
|
|
18
|
+
// List item
|
|
19
|
+
if (line.trim().startsWith("- ")) {
|
|
20
|
+
const value = line.trim().slice(2).trim().replace(/^["']|["']$/g, "");
|
|
21
|
+
if (currentList) {
|
|
22
|
+
currentList.push(value);
|
|
23
|
+
}
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// Key: value
|
|
27
|
+
const match = line.match(/^(\s*)(\w+)\s*:\s*(.*)/);
|
|
28
|
+
if (!match)
|
|
29
|
+
continue;
|
|
30
|
+
const [, , key, rawValue] = match;
|
|
31
|
+
const value = rawValue.trim().replace(/^["']|["']$/g, "");
|
|
32
|
+
if (indent === 0) {
|
|
33
|
+
// Top-level key
|
|
34
|
+
if (currentList && currentKey) {
|
|
35
|
+
// Save previous list
|
|
36
|
+
if (currentSubKey) {
|
|
37
|
+
result[currentKey][currentSubKey] = currentList;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
result[currentKey] = currentList;
|
|
41
|
+
}
|
|
42
|
+
currentList = null;
|
|
43
|
+
currentSubKey = "";
|
|
44
|
+
}
|
|
45
|
+
currentKey = key;
|
|
46
|
+
if (value) {
|
|
47
|
+
// Scalar value
|
|
48
|
+
result[currentKey] = parseScalar(value);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Object or list follows
|
|
52
|
+
if (!result[currentKey] || typeof result[currentKey] !== "object") {
|
|
53
|
+
result[currentKey] = {};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else if (indent === 2 && currentKey) {
|
|
58
|
+
// Sub-key
|
|
59
|
+
if (currentList && currentSubKey) {
|
|
60
|
+
result[currentKey][currentSubKey] = currentList;
|
|
61
|
+
currentList = null;
|
|
62
|
+
}
|
|
63
|
+
currentSubKey = key;
|
|
64
|
+
if (value) {
|
|
65
|
+
if (typeof result[currentKey] !== "object")
|
|
66
|
+
result[currentKey] = {};
|
|
67
|
+
result[currentKey][currentSubKey] = parseScalar(value);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// List follows
|
|
71
|
+
currentList = [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Save trailing list
|
|
76
|
+
if (currentList) {
|
|
77
|
+
if (currentSubKey && currentKey) {
|
|
78
|
+
if (typeof result[currentKey] !== "object")
|
|
79
|
+
result[currentKey] = {};
|
|
80
|
+
result[currentKey][currentSubKey] = currentList;
|
|
81
|
+
}
|
|
82
|
+
else if (currentKey) {
|
|
83
|
+
result[currentKey] = currentList;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function parseScalar(value) {
|
|
89
|
+
if (value === "true")
|
|
90
|
+
return true;
|
|
91
|
+
if (value === "false")
|
|
92
|
+
return false;
|
|
93
|
+
const num = Number(value);
|
|
94
|
+
if (!isNaN(num) && value !== "")
|
|
95
|
+
return num;
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=yaml-simple.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"yaml-simple.js","sourceRoot":"","sources":["../src/yaml-simple.ts"],"names":[],"mappings":"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"}
|