@guardbee/mcp-reverse-shield 0.0.0-stage → 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 (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -2
  3. package/TR.md +114 -0
  4. package/ZH.md +61 -0
  5. package/dist/__tests__/android.test.d.ts +2 -0
  6. package/dist/__tests__/android.test.d.ts.map +1 -0
  7. package/dist/__tests__/android.test.js +114 -0
  8. package/dist/__tests__/android.test.js.map +1 -0
  9. package/dist/__tests__/electron.test.d.ts +2 -0
  10. package/dist/__tests__/electron.test.d.ts.map +1 -0
  11. package/dist/__tests__/electron.test.js +128 -0
  12. package/dist/__tests__/electron.test.js.map +1 -0
  13. package/dist/__tests__/live.test.d.ts +2 -0
  14. package/dist/__tests__/live.test.d.ts.map +1 -0
  15. package/dist/__tests__/live.test.js +67 -0
  16. package/dist/__tests__/live.test.js.map +1 -0
  17. package/dist/__tests__/native.test.d.ts +2 -0
  18. package/dist/__tests__/native.test.d.ts.map +1 -0
  19. package/dist/__tests__/native.test.js +111 -0
  20. package/dist/__tests__/native.test.js.map +1 -0
  21. package/dist/__tests__/scanner.test.d.ts +2 -0
  22. package/dist/__tests__/scanner.test.d.ts.map +1 -0
  23. package/dist/__tests__/scanner.test.js +127 -0
  24. package/dist/__tests__/scanner.test.js.map +1 -0
  25. package/dist/__tests__/testFixtures.d.ts +61 -0
  26. package/dist/__tests__/testFixtures.d.ts.map +1 -0
  27. package/dist/__tests__/testFixtures.js +416 -0
  28. package/dist/__tests__/testFixtures.js.map +1 -0
  29. package/dist/__tests__/web.test.d.ts +2 -0
  30. package/dist/__tests__/web.test.d.ts.map +1 -0
  31. package/dist/__tests__/web.test.js +156 -0
  32. package/dist/__tests__/web.test.js.map +1 -0
  33. package/dist/apk.d.ts +9 -0
  34. package/dist/apk.d.ts.map +1 -0
  35. package/dist/apk.js +132 -0
  36. package/dist/apk.js.map +1 -0
  37. package/dist/asar.d.ts +10 -0
  38. package/dist/asar.d.ts.map +1 -0
  39. package/dist/asar.js +73 -0
  40. package/dist/asar.js.map +1 -0
  41. package/dist/axml.d.ts +20 -0
  42. package/dist/axml.d.ts.map +1 -0
  43. package/dist/axml.js +104 -0
  44. package/dist/axml.js.map +1 -0
  45. package/dist/cli.d.ts +3 -0
  46. package/dist/cli.d.ts.map +1 -0
  47. package/dist/cli.js +172 -0
  48. package/dist/cli.js.map +1 -0
  49. package/dist/config.d.ts +8 -0
  50. package/dist/config.d.ts.map +1 -0
  51. package/dist/config.js +102 -0
  52. package/dist/config.js.map +1 -0
  53. package/dist/dex.d.ts +16 -0
  54. package/dist/dex.d.ts.map +1 -0
  55. package/dist/dex.js +63 -0
  56. package/dist/dex.js.map +1 -0
  57. package/dist/electron.d.ts +26 -0
  58. package/dist/electron.d.ts.map +1 -0
  59. package/dist/electron.js +146 -0
  60. package/dist/electron.js.map +1 -0
  61. package/dist/finding.d.ts +30 -0
  62. package/dist/finding.d.ts.map +1 -0
  63. package/dist/finding.js +38 -0
  64. package/dist/finding.js.map +1 -0
  65. package/dist/fsutil.d.ts +20 -0
  66. package/dist/fsutil.d.ts.map +1 -0
  67. package/dist/fsutil.js +80 -0
  68. package/dist/fsutil.js.map +1 -0
  69. package/dist/index.d.ts +16 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +12 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/live.d.ts +14 -0
  74. package/dist/live.d.ts.map +1 -0
  75. package/dist/live.js +144 -0
  76. package/dist/live.js.map +1 -0
  77. package/dist/native.d.ts +18 -0
  78. package/dist/native.d.ts.map +1 -0
  79. package/dist/native.js +411 -0
  80. package/dist/native.js.map +1 -0
  81. package/dist/rules.d.ts +22 -0
  82. package/dist/rules.d.ts.map +1 -0
  83. package/dist/rules.js +354 -0
  84. package/dist/rules.js.map +1 -0
  85. package/dist/sarif.d.ts +3 -0
  86. package/dist/sarif.d.ts.map +1 -0
  87. package/dist/sarif.js +60 -0
  88. package/dist/sarif.js.map +1 -0
  89. package/dist/scanner.d.ts +27 -0
  90. package/dist/scanner.d.ts.map +1 -0
  91. package/dist/scanner.js +169 -0
  92. package/dist/scanner.js.map +1 -0
  93. package/dist/server.d.ts +7 -0
  94. package/dist/server.d.ts.map +1 -0
  95. package/dist/server.js +88 -0
  96. package/dist/server.js.map +1 -0
  97. package/dist/web.d.ts +56 -0
  98. package/dist/web.d.ts.map +1 -0
  99. package/dist/web.js +301 -0
  100. package/dist/web.js.map +1 -0
  101. package/dist/zip.d.ts +21 -0
  102. package/dist/zip.d.ts.map +1 -0
  103. package/dist/zip.js +112 -0
  104. package/dist/zip.js.map +1 -0
  105. package/package.json +52 -3
@@ -0,0 +1,169 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
2
+ import { basename, join, relative, sep } from "path";
3
+ import { analyzeApk, isApk } from "./apk.js";
4
+ import { readAsar } from "./asar.js";
5
+ import { analyzeElectron, detectElectron } from "./electron.js";
6
+ import { bySeverity, grade } from "./finding.js";
7
+ import { readPrefix, walkFiles } from "./fsutil.js";
8
+ import { analyzeNative, detectNativeFormat } from "./native.js";
9
+ import { analyzeWebFiles, isWebAsset } from "./web.js";
10
+ const DEFAULT_MAX_FILES = 5000;
11
+ const MAX_NATIVE_BYTES = 256 * 1024 * 1024;
12
+ // Build output (dist/, build/, out/) is exactly what this scanner is for, so
13
+ // unlike the source scanners only VCS, dependency and cache folders are skipped.
14
+ const SKIP_DIRS = new Set(["node_modules", ".git", ".svn", ".hg", "__pycache__", ".venv", "venv", ".gradle", ".idea", ".turbo", ".cache"]);
15
+ class Collector {
16
+ findings = [];
17
+ notes = [];
18
+ kinds = new Set();
19
+ scannedFiles = 0;
20
+ truncated = false;
21
+ add(findings, kind) {
22
+ this.findings.push(...findings);
23
+ if (kind)
24
+ this.kinds.add(kind);
25
+ for (const f of findings)
26
+ this.kinds.add(f.target);
27
+ }
28
+ }
29
+ function analyzeNativeFile(path, c) {
30
+ const size = statSync(path).size;
31
+ if (size < 64)
32
+ return false;
33
+ const format = detectNativeFormat(readPrefix(path, 8));
34
+ if (!format)
35
+ return false;
36
+ if (size > MAX_NATIVE_BYTES) {
37
+ c.notes.push(`${path}: ${format} binary larger than ${MAX_NATIVE_BYTES / 1024 / 1024} MiB, not parsed`);
38
+ return true;
39
+ }
40
+ const result = analyzeNative(readFileSync(path), path);
41
+ if (!result)
42
+ return false;
43
+ c.scannedFiles++;
44
+ c.notes.push(...result.notes);
45
+ c.add(result.findings, result.flavor === "dotnet" ? "dotnet" : "native");
46
+ return true;
47
+ }
48
+ function macMainExecutables(appDir) {
49
+ const dir = join(appDir, "Contents", "MacOS");
50
+ try {
51
+ return readdirSync(dir).map((n) => join(dir, n)).filter((p) => statSync(p).isFile());
52
+ }
53
+ catch {
54
+ return [];
55
+ }
56
+ }
57
+ function scanAppBundle(dir, c, maxFiles) {
58
+ const electron = detectElectron(dir);
59
+ if (electron) {
60
+ const result = analyzeElectron(electron, maxFiles);
61
+ c.scannedFiles += result.scannedFiles;
62
+ c.notes.push(...result.notes);
63
+ c.add(result.findings, "electron");
64
+ }
65
+ // The app's own executable; bundled frameworks (Electron, Sparkle, ...) are third-party builds.
66
+ for (const exe of macMainExecutables(dir))
67
+ analyzeNativeFile(exe, c);
68
+ }
69
+ function scanFile(path, c) {
70
+ if (isApk(path)) {
71
+ const result = analyzeApk(path);
72
+ if (!result) {
73
+ c.notes.push(`${path}: no AndroidManifest.xml, not an APK`);
74
+ return;
75
+ }
76
+ c.scannedFiles += result.scannedFiles;
77
+ c.notes.push(...result.notes);
78
+ c.add(result.findings, "android");
79
+ return;
80
+ }
81
+ if (/\.asar$/i.test(path)) {
82
+ const entries = readAsar(path);
83
+ if (!entries) {
84
+ c.notes.push(`${path}: not a readable ASAR archive`);
85
+ return;
86
+ }
87
+ const web = entries.filter((e) => isWebAsset(e.key));
88
+ c.scannedFiles += web.length;
89
+ c.add(analyzeWebFiles(web), "electron");
90
+ return;
91
+ }
92
+ if (isWebAsset(basename(path))) {
93
+ c.scannedFiles++;
94
+ c.add(analyzeWebFiles([{ key: basename(path), display: path, size: statSync(path).size, read: () => readFileSync(path) }]), "web");
95
+ return;
96
+ }
97
+ if (!analyzeNativeFile(path, c))
98
+ c.notes.push(`${path}: not a recognised artifact (APK, ASAR, JavaScript, source map, ELF, Mach-O or PE)`);
99
+ }
100
+ function scanDir(root, c, options, maxFiles) {
101
+ if (root.endsWith(".app") || detectElectron(root)) {
102
+ scanAppBundle(root, c, maxFiles);
103
+ return;
104
+ }
105
+ const web = [];
106
+ const { truncated } = walkFiles(root, {
107
+ maxFiles,
108
+ include: options.include,
109
+ exclude: options.exclude,
110
+ skipDirs: SKIP_DIRS,
111
+ claimDir: (dir) => {
112
+ if (!dir.endsWith(".app") && !detectElectron(dir))
113
+ return false;
114
+ scanAppBundle(dir, c, maxFiles);
115
+ return true;
116
+ },
117
+ }, (full) => {
118
+ const key = relative(root, full).split(sep).join("/");
119
+ if (isWebAsset(key)) {
120
+ web.push({ key, display: full, size: statSync(full).size, read: () => readFileSync(full) });
121
+ }
122
+ else if (isApk(full) || /\.asar$/i.test(full)) {
123
+ scanFile(full, c);
124
+ }
125
+ else {
126
+ analyzeNativeFile(full, c);
127
+ }
128
+ });
129
+ c.truncated = truncated;
130
+ if (web.length > 0) {
131
+ c.scannedFiles += web.length;
132
+ c.add(analyzeWebFiles(web), "web");
133
+ }
134
+ }
135
+ /**
136
+ * Scans a build artifact for what it gives away to a reverse engineer: a file
137
+ * (APK, ASAR, bundle, source map, ELF/Mach-O/PE/.NET binary), a macOS .app,
138
+ * an Electron app folder, or a build output directory containing any of them.
139
+ */
140
+ export function scanPath(target, options = {}) {
141
+ const start = Date.now();
142
+ const c = new Collector();
143
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
144
+ if (!existsSync(target))
145
+ throw new Error(`Path not found: ${target}`);
146
+ // "Foo.app/" from shell completion must still be recognised as a bundle.
147
+ let path = target;
148
+ while (path.length > 1 && (path.endsWith("/") || path.endsWith("\\")))
149
+ path = path.slice(0, -1);
150
+ if (statSync(path).isDirectory())
151
+ scanDir(path, c, options, maxFiles);
152
+ else
153
+ scanFile(path, c);
154
+ if (c.truncated)
155
+ c.notes.push(`Stopped after ${maxFiles} files; raise maxFiles to scan the rest.`);
156
+ const findings = c.findings.sort(bySeverity);
157
+ return {
158
+ target,
159
+ kinds: [...c.kinds],
160
+ grade: grade(findings),
161
+ scannedFiles: c.scannedFiles,
162
+ totalFindings: findings.length,
163
+ findings,
164
+ notes: c.notes,
165
+ truncated: c.truncated,
166
+ durationMs: Date.now() - start,
167
+ };
168
+ }
169
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ScanResult } from "./scanner.js";
3
+ export declare function formatResult(result: ScanResult): string;
4
+ export declare function startServer(): Promise<void>;
5
+ /** The package's tools, also served by @guardbee/security-suite under its own prefix. */
6
+ export declare function registerTools(server: McpServer): void;
7
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAyBvD;AAED,wBAAsB,WAAW,kBAShC;AAED,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAoDrD"}
package/dist/server.js ADDED
@@ -0,0 +1,88 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { instrumentServer } from "@guardbee/mcp-telemetry";
4
+ import { z } from "zod";
5
+ import { scanUrl } from "./live.js";
6
+ import { RULES } from "./rules.js";
7
+ import { scanPath } from "./scanner.js";
8
+ export function formatResult(result) {
9
+ const lines = [];
10
+ const kinds = result.kinds.length > 0 ? result.kinds.join(", ") : "nothing recognised";
11
+ if (result.findings.length === 0) {
12
+ lines.push(`✅ Grade ${result.grade} — nothing handed to a reverse engineer for free. ${result.scannedFiles} file(s) checked (${kinds}) in ${result.durationMs}ms.`);
13
+ }
14
+ else {
15
+ const count = { critical: 0, high: 0, medium: 0, low: 0 };
16
+ for (const f of result.findings)
17
+ count[f.severity]++;
18
+ lines.push(`🛡️ Reverse-engineering resistance: grade ${result.grade} — ${result.findings.length} finding(s) in ${result.scannedFiles} file(s) (${kinds}, ${result.durationMs}ms)`);
19
+ lines.push(` Critical: ${count.critical} High: ${count.high} Medium: ${count.medium} Low: ${count.low}`);
20
+ lines.push("");
21
+ for (const f of result.findings) {
22
+ lines.push(`[${f.severity.toUpperCase()}] ${f.patternName} (${f.target}/${f.category})`);
23
+ if (f.file)
24
+ lines.push(` Where : ${f.file}${f.line > 1 ? `:${f.line}:${f.column}` : ""}`);
25
+ if (f.match)
26
+ lines.push(` Evidence : ${f.match}`);
27
+ lines.push(` Gives away : ${f.context}`);
28
+ lines.push(` Fix : ${f.recommendation}`);
29
+ lines.push("");
30
+ }
31
+ }
32
+ if (result.notes.length > 0) {
33
+ lines.push("Notes:");
34
+ for (const n of result.notes)
35
+ lines.push(` • ${n}`);
36
+ }
37
+ return lines.join("\n");
38
+ }
39
+ export async function startServer() {
40
+ const server = new McpServer({
41
+ name: "guardbee-reverse-shield",
42
+ version: "0.1.0",
43
+ });
44
+ instrumentServer(server, "reverse-shield");
45
+ registerTools(server);
46
+ const transport = new StdioServerTransport();
47
+ await server.connect(transport);
48
+ }
49
+ /** The package's tools, also served by @guardbee/security-suite under its own prefix. */
50
+ export function registerTools(server) {
51
+ server.tool("scan_artifact", "Audit a shipped build artifact for how easily it can be reverse engineered — the defensive counterpart to reverse-engineering MCPs. Accepts an APK, an Electron app (.app bundle, app folder or app.asar), a macOS/Linux/Windows binary (Mach-O, ELF, PE, .NET assembly), a JavaScript bundle or source map, or a build output directory containing any of these. Reports shipped source maps and source code, missing minification/R8/.NET obfuscation, debug info and unstripped symbols, debuggable flags and entitlements, Electron fuses that allow Node/inspector/ASAR tampering, missing code signing, embedded credentials and internal hosts, with an A–F grade.", {
52
+ path: z.string().describe("Absolute or relative path to the artifact or build output directory"),
53
+ maxFiles: z.number().int().positive().optional().describe("Maximum number of files to check in a directory (default: 5000)"),
54
+ include: z.array(z.string()).optional().describe("Only check files whose path contains one of these strings"),
55
+ exclude: z.array(z.string()).optional().describe("Skip files/dirs whose path contains one of these strings"),
56
+ }, async ({ path, maxFiles, include, exclude }) => {
57
+ try {
58
+ return { content: [{ type: "text", text: formatResult(scanPath(path, { maxFiles, include, exclude })) }] };
59
+ }
60
+ catch (err) {
61
+ return { isError: true, content: [{ type: "text", text: err.message }] };
62
+ }
63
+ });
64
+ server.tool("scan_url", "Check a live website the way an analyst starts: fetch the page, read its same-site JavaScript bundles, follow sourceMappingURL comments and try <bundle>.map even when the comment was stripped. Reports publicly downloadable source maps (and whether they embed the original sources), unminified or development bundles, embedded credentials and internal hosts. Third-party scripts are not fetched.", {
65
+ url: z.string().url().describe("Page URL (http or https)"),
66
+ maxScripts: z.number().int().positive().max(100).optional().describe("Maximum number of same-site scripts to fetch (default: 25)"),
67
+ }, async ({ url, maxScripts }) => {
68
+ try {
69
+ return { content: [{ type: "text", text: formatResult(await scanUrl(url, { maxScripts })) }] };
70
+ }
71
+ catch (err) {
72
+ return { isError: true, content: [{ type: "text", text: err.message }] };
73
+ }
74
+ });
75
+ server.tool("list_checks", "List every reverse-engineering resistance check, grouped by target (web, electron, android, native, dotnet), with what each finding gives away and how to fix it", {}, async () => {
76
+ const lines = ["Reverse-engineering resistance checks:\n"];
77
+ for (const target of ["web", "electron", "android", "native", "dotnet"]) {
78
+ lines.push(`[${target}]`);
79
+ for (const r of RULES.filter((rule) => rule.target === target)) {
80
+ lines.push(` • ${r.id} (${r.severity}, ${r.category}) — ${r.name}`);
81
+ lines.push(` gives away: ${r.exposes}`);
82
+ }
83
+ lines.push("");
84
+ }
85
+ return { content: [{ type: "text", text: lines.join("\n") }] };
86
+ });
87
+ }
88
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/web.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ import type { Finding } from "./finding.js";
2
+ /**
3
+ * The URL of the last `//# sourceMappingURL=` comment that ends its line.
4
+ * Scanned by hand rather than with a regex: bundles are untrusted input and
5
+ * inline maps make the comment megabytes long. Only the last occurrence on
6
+ * each line can end it, so every line is looked at once and the scan stays
7
+ * linear even for a line made of nothing but the key.
8
+ */
9
+ export declare function sourceMappingUrl(text: string): string | null;
10
+ export interface SourceMapInfo {
11
+ sources: number;
12
+ embedded: number;
13
+ samplePaths: string[];
14
+ }
15
+ /** Returns null when the text is not a source map (version + mappings). */
16
+ export declare function parseSourceMap(text: string): SourceMapInfo | null;
17
+ export declare function sourceMapFinding(info: SourceMapInfo, file: string, id: "sourcemap_shipped" | "sourcemap_inline" | "sourcemap_publicly_reachable"): Finding;
18
+ export declare function looksUnminified(text: string): {
19
+ unminified: boolean;
20
+ avgLineLength: number;
21
+ indentedRatio: number;
22
+ };
23
+ export interface JsCheckOptions {
24
+ /** Skip the minification check (e.g. for small inline snippets). */
25
+ skipMinifyCheck?: boolean;
26
+ }
27
+ /**
28
+ * Checks one shipped JavaScript file for what it gives away: source maps
29
+ * (inline or referenced), eval-wrapped dev bundles, missing minification,
30
+ * framework development builds, embedded credentials and internal hosts.
31
+ * Source maps that sit next to the file are handled by the caller, which can
32
+ * see the filesystem or the network.
33
+ */
34
+ export declare function analyzeJavaScript(text: string, file: string, options?: JsCheckOptions): Finding[];
35
+ export declare const MAX_TEXT_BYTES: number;
36
+ /** A file in a build directory, an ASAR or an APK, keyed by a "/"-separated path. */
37
+ export interface ArtifactFile {
38
+ key: string;
39
+ display: string;
40
+ size: number;
41
+ read(): Buffer | null;
42
+ }
43
+ export declare function isWebAsset(key: string): boolean;
44
+ /**
45
+ * Runs the JavaScript and source map checks over a set of shipped files. A
46
+ * bundle whose sourceMappingURL resolves to a map in the same set is reported
47
+ * once, through the map, rather than again as a bare reference.
48
+ */
49
+ export declare function analyzeWebFiles(files: ArtifactFile[]): Finding[];
50
+ /**
51
+ * A packaged app repeats the same key, host list or build setting across
52
+ * dozens of chunks. Report each distinct thing once and say how far it
53
+ * spreads, so the real number of problems stays readable.
54
+ */
55
+ export declare function collapseRepeats(findings: Finding[]): Finding[];
56
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAuE5C;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB5D;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAcjE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,mBAAmB,GAAG,kBAAkB,GAAG,8BAA8B,GAAG,OAAO,CAQ1J;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAanH;AAED,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,EAAE,CA8DrG;AAID,eAAO,MAAM,cAAc,QAAmB,CAAC;AAE/C,qFAAqF;AACrF,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAWD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,OAAO,EAAE,CAwBhE;AAID;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAwC9D"}
package/dist/web.js ADDED
@@ -0,0 +1,301 @@
1
+ import { SECRET_RULES } from "@guardbee/guard-core";
2
+ import { finding } from "./finding.js";
3
+ const MIN_BYTES_FOR_MINIFY_CHECK = 4096;
4
+ const INLINE_MAP_PREFIX = /^data:application\/json;(?:charset=[^;,]+;)?base64,/;
5
+ const EVAL_SOURCE_URL = /sourceURL=webpack(?:-internal)?:\/\//;
6
+ const DEV_BUILD_MARKERS = [
7
+ { framework: "React", pattern: /react(?:-dom)?\.development\.js/ },
8
+ { framework: "Vue", pattern: /You are running (?:a development build of Vue|Vue in development mode)/ },
9
+ { framework: "Angular", pattern: /Angular is running in development mode/ },
10
+ { framework: "webpack dev server / HMR", pattern: /\[HMR\] (?:connected|Waiting for update signal)/ },
11
+ ];
12
+ const INTERNAL_URL = /\bhttps?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:internal|local|corp|lan)|(?:[a-z0-9-]+\.)*(?:staging|stage|preprod|uat|qa|dev)(?:-[a-z0-9]+)?\.[a-z0-9-]+(?:\.[a-z0-9-]+)*)(?::\d+)?/gi;
13
+ // Fixed-format credentials only: minified code is full of 40-character base64
14
+ // runs and quoted strings next to the word "key", which the broad rules would
15
+ // report by the hundred.
16
+ const BUNDLE_SECRET_RULES = SECRET_RULES.filter((r) => !r.broad);
17
+ // The private-key rules match the PEM header alone. Crypto libraries bundled
18
+ // into an app carry those headers as string constants for their parsers, so a
19
+ // bundle only holds a key when base64 key material follows the header.
20
+ const PEM_HEADER_RULE = /private_key/;
21
+ // Placeholder shapes that never occur in a real credential (same heuristic as
22
+ // secret-scanner): a repeated or sequential run, or an "example" word.
23
+ const PLACEHOLDER_WORDS = /example|placeholder|sample|dummy|fake[_-]?key|change[-_]?me|testkey|yourkey|redacted/i;
24
+ function isPlaceholder(value) {
25
+ if (/(.)\1{7,}/.test(value) || PLACEHOLDER_WORDS.test(value))
26
+ return true;
27
+ let run = 1;
28
+ for (let i = 1; i < value.length; i++) {
29
+ run = value.charCodeAt(i) === value.charCodeAt(i - 1) + 1 ? run + 1 : 1;
30
+ if (run >= 8)
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+ // Connection-string rules skip the generic check (a real URL can sit on an
36
+ // example.com host), so judge the parts instead: UI hints and docs use a
37
+ // literal "password" and a literal "host", which a deployed database never has.
38
+ const CONNECTION_RULE = /^db_url_/;
39
+ const PLACEHOLDER_PASSWORD = /^(?:password|passw(?:or)?d|pass|pwd|secret|sifre|şifre|parola|changeme|your[-_]?password|<[^>]*>|\*+|x+|\.{3})$/i;
40
+ const PLACEHOLDER_HOST = /^(?:host|hostname|your[-_]?host|server|db-host)$/i;
41
+ function isPlaceholderConnection(url) {
42
+ const m = /^[a-z+]+:\/\/[^:/@]*:([^@]*)@([^:/?#]+)/i.exec(url);
43
+ return m !== null && (PLACEHOLDER_PASSWORD.test(m[1]) || PLACEHOLDER_HOST.test(m[2]));
44
+ }
45
+ const PEM_BODY = /^(?:\s|\\[rn]|["'`+,])*(?:[A-Za-z-]+:[^\n\\]*(?:\\n|\n)(?:\s|\\[rn])*)*[A-Za-z0-9+/]{60,}/;
46
+ function lineCol(text, index) {
47
+ let line = 1;
48
+ let last = -1;
49
+ for (let i = text.indexOf("\n"); i !== -1 && i < index; i = text.indexOf("\n", i + 1)) {
50
+ line++;
51
+ last = i;
52
+ }
53
+ return { line, column: index - last };
54
+ }
55
+ function mask(value) {
56
+ return value.length <= 8 ? "****" : `${value.slice(0, 4)}…${value.slice(-2)} (${value.length} chars)`;
57
+ }
58
+ const MAPPING_KEY = "sourceMappingURL=";
59
+ const isSpace = (ch) => ch === " " || ch === "\t" || ch === "\r" || ch === "\n" || ch === "\f" || ch === "\v";
60
+ /**
61
+ * The URL of the last `//# sourceMappingURL=` comment that ends its line.
62
+ * Scanned by hand rather than with a regex: bundles are untrusted input and
63
+ * inline maps make the comment megabytes long. Only the last occurrence on
64
+ * each line can end it, so every line is looked at once and the scan stays
65
+ * linear even for a line made of nothing but the key.
66
+ */
67
+ export function sourceMappingUrl(text) {
68
+ let i = text.lastIndexOf(MAPPING_KEY);
69
+ while (i !== -1) {
70
+ const lineStart = text.lastIndexOf("\n", i) + 1;
71
+ const nl = text.indexOf("\n", i);
72
+ const lineEnd = nl === -1 ? text.length : nl;
73
+ let j = i - 1;
74
+ while (j >= lineStart && isSpace(text[j]))
75
+ j--;
76
+ if (j - 2 >= lineStart && (text[j] === "#" || text[j] === "@") && text[j - 1] === "/" && text[j - 2] === "/") {
77
+ const start = i + MAPPING_KEY.length;
78
+ let end = start;
79
+ while (end < lineEnd && !isSpace(text[end]) && text[end] !== "'" && text[end] !== '"')
80
+ end++;
81
+ let k = end;
82
+ while (k < lineEnd && isSpace(text[k]))
83
+ k++;
84
+ if (end > start && k === lineEnd)
85
+ return text.slice(start, end);
86
+ }
87
+ i = lineStart >= 2 ? text.lastIndexOf(MAPPING_KEY, lineStart - 2) : -1;
88
+ }
89
+ return null;
90
+ }
91
+ /** Returns null when the text is not a source map (version + mappings). */
92
+ export function parseSourceMap(text) {
93
+ let map;
94
+ try {
95
+ map = JSON.parse(text);
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ if (!map || typeof map !== "object")
101
+ return null;
102
+ const m = map;
103
+ if (m.version === undefined || (typeof m.mappings !== "string" && !Array.isArray(m.sections)))
104
+ return null;
105
+ const sources = Array.isArray(m.sources) ? m.sources.filter((s) => typeof s === "string") : [];
106
+ const embedded = Array.isArray(m.sourcesContent) ? m.sourcesContent.filter((c) => typeof c === "string" && c.length > 0).length : 0;
107
+ const own = sources.filter((s) => !s.includes("node_modules"));
108
+ return { sources: sources.length, embedded, samplePaths: (own.length > 0 ? own : sources).slice(0, 3) };
109
+ }
110
+ export function sourceMapFinding(info, file, id) {
111
+ const paths = info.samplePaths.length > 0 ? ` e.g. ${info.samplePaths.join(", ")}` : "";
112
+ if (info.embedded > 0) {
113
+ return finding("sourcemap_embeds_source", file, {
114
+ match: `${info.embedded} of ${info.sources} source file(s) embedded in sourcesContent${paths}`,
115
+ });
116
+ }
117
+ return finding(id, file, { match: `${info.sources} original source path(s) mapped${paths}` });
118
+ }
119
+ export function looksUnminified(text) {
120
+ let nonEmpty = 0;
121
+ let chars = 0;
122
+ let indented = 0;
123
+ for (const line of text.split("\n")) {
124
+ if (line.trim() === "")
125
+ continue;
126
+ nonEmpty++;
127
+ chars += line.length;
128
+ if (/^(?: {2,}|\t)\S/.test(line))
129
+ indented++;
130
+ }
131
+ const avgLineLength = nonEmpty === 0 ? 0 : chars / nonEmpty;
132
+ const indentedRatio = nonEmpty === 0 ? 0 : indented / nonEmpty;
133
+ return { unminified: nonEmpty >= 40 && avgLineLength < 100 && indentedRatio > 0.3, avgLineLength, indentedRatio };
134
+ }
135
+ /**
136
+ * Checks one shipped JavaScript file for what it gives away: source maps
137
+ * (inline or referenced), eval-wrapped dev bundles, missing minification,
138
+ * framework development builds, embedded credentials and internal hosts.
139
+ * Source maps that sit next to the file are handled by the caller, which can
140
+ * see the filesystem or the network.
141
+ */
142
+ export function analyzeJavaScript(text, file, options = {}) {
143
+ const findings = [];
144
+ const mapUrl = sourceMappingUrl(text);
145
+ if (mapUrl && INLINE_MAP_PREFIX.test(mapUrl)) {
146
+ const info = parseSourceMap(Buffer.from(mapUrl.replace(INLINE_MAP_PREFIX, ""), "base64").toString("utf8"));
147
+ findings.push(info ? sourceMapFinding(info, file, "sourcemap_inline") : finding("sourcemap_inline", file, { match: "inline data: source map (not parseable)" }));
148
+ }
149
+ else if (mapUrl) {
150
+ findings.push(finding("sourcemap_reference", file, { match: `//# sourceMappingURL=${mapUrl}` }));
151
+ }
152
+ const isEvalBundle = text.includes("eval(") && EVAL_SOURCE_URL.test(text);
153
+ if (isEvalBundle) {
154
+ const idx = text.search(EVAL_SOURCE_URL);
155
+ findings.push(finding("eval_devtool_bundle", file, { match: text.slice(idx, idx + 80).split("\\n")[0], ...lineCol(text, idx) }));
156
+ }
157
+ if (!options.skipMinifyCheck && !isEvalBundle && Buffer.byteLength(text) >= MIN_BYTES_FOR_MINIFY_CHECK) {
158
+ const m = looksUnminified(text);
159
+ if (m.unminified) {
160
+ findings.push(finding("unminified_bundle", file, {
161
+ match: `average line ${m.avgLineLength.toFixed(0)} chars, ${(m.indentedRatio * 100).toFixed(0)}% indented lines`,
162
+ }));
163
+ }
164
+ }
165
+ for (const marker of DEV_BUILD_MARKERS) {
166
+ const idx = text.search(marker.pattern);
167
+ if (idx !== -1) {
168
+ findings.push(finding("dev_build_framework", file, { match: `${marker.framework}: ${text.slice(idx, idx + 60).trim()}`, ...lineCol(text, idx) }));
169
+ }
170
+ }
171
+ for (const rule of BUNDLE_SECRET_RULES) {
172
+ rule.pattern.lastIndex = 0;
173
+ const re = new RegExp(rule.pattern.source, rule.pattern.flags.includes("g") ? rule.pattern.flags : `${rule.pattern.flags}g`);
174
+ for (const m of text.matchAll(re)) {
175
+ const value = m[0];
176
+ if (rule.allowlist?.some((a) => a.test(value)))
177
+ continue;
178
+ if (CONNECTION_RULE.test(rule.id) ? isPlaceholderConnection(value) : !rule.skipPlaceholderCheck && isPlaceholder(value))
179
+ continue;
180
+ if (PEM_HEADER_RULE.test(rule.id) && !PEM_BODY.test(text.slice((m.index ?? 0) + value.length, (m.index ?? 0) + value.length + 160)))
181
+ continue;
182
+ findings.push(finding("bundle_secret", file, {
183
+ severity: rule.severity === "low" ? "medium" : rule.severity,
184
+ match: `${rule.name}: ${mask(value)}`,
185
+ ...lineCol(text, m.index ?? 0),
186
+ }));
187
+ break; // one per rule per file is enough to act on
188
+ }
189
+ }
190
+ const hosts = new Set();
191
+ let firstHostIndex = -1;
192
+ for (const m of text.matchAll(INTERNAL_URL)) {
193
+ hosts.add(m[0]);
194
+ if (firstHostIndex === -1)
195
+ firstHostIndex = m.index ?? 0;
196
+ if (hosts.size >= 5)
197
+ break;
198
+ }
199
+ if (hosts.size > 0) {
200
+ findings.push(finding("internal_endpoint", file, { match: [...hosts].join(", "), ...lineCol(text, firstHostIndex) }));
201
+ }
202
+ return findings;
203
+ }
204
+ const JS_EXT = /\.(?:m|c)?js$/i;
205
+ const MAP_EXT = /\.map$/i;
206
+ export const MAX_TEXT_BYTES = 20 * 1024 * 1024;
207
+ export function isWebAsset(key) {
208
+ return (JS_EXT.test(key) || MAP_EXT.test(key)) && !key.split("/").includes("node_modules");
209
+ }
210
+ function resolveKey(fromKey, ref) {
211
+ const parts = fromKey.split("/").slice(0, -1);
212
+ for (const seg of ref.split(/[?#]/)[0].split("/")) {
213
+ if (seg === "..")
214
+ parts.pop();
215
+ else if (seg !== "." && seg !== "")
216
+ parts.push(seg);
217
+ }
218
+ return parts.join("/");
219
+ }
220
+ /**
221
+ * Runs the JavaScript and source map checks over a set of shipped files. A
222
+ * bundle whose sourceMappingURL resolves to a map in the same set is reported
223
+ * once, through the map, rather than again as a bare reference.
224
+ */
225
+ export function analyzeWebFiles(files) {
226
+ const findings = [];
227
+ const keys = new Set(files.map((f) => f.key));
228
+ for (const file of files) {
229
+ if (file.size > MAX_TEXT_BYTES)
230
+ continue;
231
+ if (MAP_EXT.test(file.key)) {
232
+ const buf = file.read();
233
+ const info = buf ? parseSourceMap(buf.toString("utf8")) : null;
234
+ if (info)
235
+ findings.push(sourceMapFinding(info, file.display, "sourcemap_shipped"));
236
+ continue;
237
+ }
238
+ if (!JS_EXT.test(file.key))
239
+ continue;
240
+ const buf = file.read();
241
+ if (!buf)
242
+ continue;
243
+ const text = buf.toString("utf8");
244
+ const mapUrl = sourceMappingUrl(text);
245
+ const mapShipped = mapUrl !== null && !INLINE_MAP_PREFIX.test(mapUrl) && keys.has(resolveKey(file.key, mapUrl));
246
+ for (const f of analyzeJavaScript(text, file.display)) {
247
+ if (mapShipped && f.patternId === "sourcemap_reference")
248
+ continue;
249
+ findings.push(f);
250
+ }
251
+ }
252
+ return collapseRepeats(findings);
253
+ }
254
+ const COLLAPSE_AFTER = 5;
255
+ /**
256
+ * A packaged app repeats the same key, host list or build setting across
257
+ * dozens of chunks. Report each distinct thing once and say how far it
258
+ * spreads, so the real number of problems stays readable.
259
+ */
260
+ export function collapseRepeats(findings) {
261
+ const out = [];
262
+ const secrets = new Map();
263
+ const hosts = [];
264
+ const perFile = new Map();
265
+ for (const f of findings) {
266
+ if (f.patternId === "bundle_secret") {
267
+ const seen = secrets.get(f.match);
268
+ if (seen)
269
+ seen.files++;
270
+ else
271
+ secrets.set(f.match, { first: f, files: 1 });
272
+ }
273
+ else if (f.patternId === "internal_endpoint") {
274
+ hosts.push(f);
275
+ }
276
+ else if (f.patternId === "unminified_bundle" || f.patternId === "sourcemap_reference") {
277
+ perFile.set(f.patternId, [...(perFile.get(f.patternId) ?? []), f]);
278
+ }
279
+ else {
280
+ out.push(f);
281
+ }
282
+ }
283
+ for (const { first, files } of secrets.values()) {
284
+ out.push(files > 1 ? { ...first, match: `${first.match} — also in ${files - 1} other file(s)` } : first);
285
+ }
286
+ if (hosts.length > 0) {
287
+ const all = new Set(hosts.flatMap((h) => h.match.split(", ")));
288
+ const list = [...all].slice(0, 8).join(", ") + (all.size > 8 ? `, … (${all.size} hosts)` : "");
289
+ out.push({ ...hosts[0], match: hosts.length > 1 ? `${list} — in ${hosts.length} files` : list });
290
+ }
291
+ for (const group of perFile.values()) {
292
+ if (group.length <= COLLAPSE_AFTER) {
293
+ out.push(...group);
294
+ continue;
295
+ }
296
+ const names = group.slice(0, 3).map((g) => (g.file ?? "").split(/[/\\]/).pop());
297
+ out.push({ ...group[0], line: 1, column: 1, match: `${group.length} files, e.g. ${names.join(", ")}` });
298
+ }
299
+ return out;
300
+ }
301
+ //# sourceMappingURL=web.js.map