mitnick-cli 1.0.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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +193 -0
  3. package/dist/analyzers/analyzer.interface.d.ts +32 -0
  4. package/dist/analyzers/analyzer.interface.d.ts.map +1 -0
  5. package/dist/analyzers/analyzer.interface.js +2 -0
  6. package/dist/analyzers/analyzer.interface.js.map +1 -0
  7. package/dist/analyzers/analyzer.registry.d.ts +16 -0
  8. package/dist/analyzers/analyzer.registry.d.ts.map +1 -0
  9. package/dist/analyzers/analyzer.registry.js +40 -0
  10. package/dist/analyzers/analyzer.registry.js.map +1 -0
  11. package/dist/analyzers/dependency-confusion/index.d.ts +14 -0
  12. package/dist/analyzers/dependency-confusion/index.d.ts.map +1 -0
  13. package/dist/analyzers/dependency-confusion/index.js +147 -0
  14. package/dist/analyzers/dependency-confusion/index.js.map +1 -0
  15. package/dist/analyzers/dormant-package/index.d.ts +14 -0
  16. package/dist/analyzers/dormant-package/index.d.ts.map +1 -0
  17. package/dist/analyzers/dormant-package/index.js +137 -0
  18. package/dist/analyzers/dormant-package/index.js.map +1 -0
  19. package/dist/analyzers/file-based-analyzer.d.ts +20 -0
  20. package/dist/analyzers/file-based-analyzer.d.ts.map +1 -0
  21. package/dist/analyzers/file-based-analyzer.js +35 -0
  22. package/dist/analyzers/file-based-analyzer.js.map +1 -0
  23. package/dist/analyzers/install-scripts/index.d.ts +13 -0
  24. package/dist/analyzers/install-scripts/index.d.ts.map +1 -0
  25. package/dist/analyzers/install-scripts/index.js +125 -0
  26. package/dist/analyzers/install-scripts/index.js.map +1 -0
  27. package/dist/analyzers/license/index.d.ts +12 -0
  28. package/dist/analyzers/license/index.d.ts.map +1 -0
  29. package/dist/analyzers/license/index.js +199 -0
  30. package/dist/analyzers/license/index.js.map +1 -0
  31. package/dist/analyzers/maintainer/index.d.ts +12 -0
  32. package/dist/analyzers/maintainer/index.d.ts.map +1 -0
  33. package/dist/analyzers/maintainer/index.js +93 -0
  34. package/dist/analyzers/maintainer/index.js.map +1 -0
  35. package/dist/analyzers/network-calls/index.d.ts +15 -0
  36. package/dist/analyzers/network-calls/index.d.ts.map +1 -0
  37. package/dist/analyzers/network-calls/index.js +212 -0
  38. package/dist/analyzers/network-calls/index.js.map +1 -0
  39. package/dist/analyzers/obfuscation/index.d.ts +19 -0
  40. package/dist/analyzers/obfuscation/index.d.ts.map +1 -0
  41. package/dist/analyzers/obfuscation/index.js +218 -0
  42. package/dist/analyzers/obfuscation/index.js.map +1 -0
  43. package/dist/analyzers/prototype-pollution/index.d.ts +18 -0
  44. package/dist/analyzers/prototype-pollution/index.d.ts.map +1 -0
  45. package/dist/analyzers/prototype-pollution/index.js +257 -0
  46. package/dist/analyzers/prototype-pollution/index.js.map +1 -0
  47. package/dist/analyzers/sensitive-data/index.d.ts +16 -0
  48. package/dist/analyzers/sensitive-data/index.d.ts.map +1 -0
  49. package/dist/analyzers/sensitive-data/index.js +254 -0
  50. package/dist/analyzers/sensitive-data/index.js.map +1 -0
  51. package/dist/analyzers/typosquatting/index.d.ts +14 -0
  52. package/dist/analyzers/typosquatting/index.d.ts.map +1 -0
  53. package/dist/analyzers/typosquatting/index.js +127 -0
  54. package/dist/analyzers/typosquatting/index.js.map +1 -0
  55. package/dist/analyzers/typosquatting/popular-packages.d.ts +9 -0
  56. package/dist/analyzers/typosquatting/popular-packages.d.ts.map +1 -0
  57. package/dist/analyzers/typosquatting/popular-packages.js +236 -0
  58. package/dist/analyzers/typosquatting/popular-packages.js.map +1 -0
  59. package/dist/analyzers/vulnerability/index.d.ts +12 -0
  60. package/dist/analyzers/vulnerability/index.d.ts.map +1 -0
  61. package/dist/analyzers/vulnerability/index.js +147 -0
  62. package/dist/analyzers/vulnerability/index.js.map +1 -0
  63. package/dist/cli/commands/check.d.ts +21 -0
  64. package/dist/cli/commands/check.d.ts.map +1 -0
  65. package/dist/cli/commands/check.js +204 -0
  66. package/dist/cli/commands/check.js.map +1 -0
  67. package/dist/cli/formatters/formatter.interface.d.ts +14 -0
  68. package/dist/cli/formatters/formatter.interface.d.ts.map +1 -0
  69. package/dist/cli/formatters/formatter.interface.js +2 -0
  70. package/dist/cli/formatters/formatter.interface.js.map +1 -0
  71. package/dist/cli/formatters/json.d.ts +12 -0
  72. package/dist/cli/formatters/json.d.ts.map +1 -0
  73. package/dist/cli/formatters/json.js +12 -0
  74. package/dist/cli/formatters/json.js.map +1 -0
  75. package/dist/cli/formatters/sarif.d.ts +13 -0
  76. package/dist/cli/formatters/sarif.d.ts.map +1 -0
  77. package/dist/cli/formatters/sarif.js +101 -0
  78. package/dist/cli/formatters/sarif.js.map +1 -0
  79. package/dist/cli/formatters/terminal.d.ts +13 -0
  80. package/dist/cli/formatters/terminal.d.ts.map +1 -0
  81. package/dist/cli/formatters/terminal.js +110 -0
  82. package/dist/cli/formatters/terminal.js.map +1 -0
  83. package/dist/cli/index.d.ts +9 -0
  84. package/dist/cli/index.d.ts.map +1 -0
  85. package/dist/cli/index.js +86 -0
  86. package/dist/cli/index.js.map +1 -0
  87. package/dist/core/engine.d.ts +23 -0
  88. package/dist/core/engine.d.ts.map +1 -0
  89. package/dist/core/engine.js +55 -0
  90. package/dist/core/engine.js.map +1 -0
  91. package/dist/core/scorer.d.ts +30 -0
  92. package/dist/core/scorer.d.ts.map +1 -0
  93. package/dist/core/scorer.js +88 -0
  94. package/dist/core/scorer.js.map +1 -0
  95. package/dist/core/types.d.ts +76 -0
  96. package/dist/core/types.d.ts.map +1 -0
  97. package/dist/core/types.js +30 -0
  98. package/dist/core/types.js.map +1 -0
  99. package/dist/index.d.ts +33 -0
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.js +30 -0
  102. package/dist/index.js.map +1 -0
  103. package/dist/registry/client.d.ts +27 -0
  104. package/dist/registry/client.d.ts.map +1 -0
  105. package/dist/registry/client.js +189 -0
  106. package/dist/registry/client.js.map +1 -0
  107. package/dist/registry/tarball.d.ts +34 -0
  108. package/dist/registry/tarball.d.ts.map +1 -0
  109. package/dist/registry/tarball.js +103 -0
  110. package/dist/registry/tarball.js.map +1 -0
  111. package/dist/utils/ast.d.ts +74 -0
  112. package/dist/utils/ast.d.ts.map +1 -0
  113. package/dist/utils/ast.js +150 -0
  114. package/dist/utils/ast.js.map +1 -0
  115. package/dist/utils/fs.d.ts +28 -0
  116. package/dist/utils/fs.d.ts.map +1 -0
  117. package/dist/utils/fs.js +78 -0
  118. package/dist/utils/fs.js.map +1 -0
  119. package/dist/utils/http.d.ts +40 -0
  120. package/dist/utils/http.d.ts.map +1 -0
  121. package/dist/utils/http.js +116 -0
  122. package/dist/utils/http.js.map +1 -0
  123. package/dist/utils/logger.d.ts +46 -0
  124. package/dist/utils/logger.d.ts.map +1 -0
  125. package/dist/utils/logger.js +91 -0
  126. package/dist/utils/logger.js.map +1 -0
  127. package/dist/utils/strings.d.ts +8 -0
  128. package/dist/utils/strings.d.ts.map +1 -0
  129. package/dist/utils/strings.js +12 -0
  130. package/dist/utils/strings.js.map +1 -0
  131. package/package.json +96 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Tarball download and extraction for npm packages.
3
+ *
4
+ * Downloads a .tgz from the npm registry, extracts it to a temp directory,
5
+ * and provides cleanup. All npm tarballs contain a top-level `package/`
6
+ * directory which is stripped during extraction.
7
+ */
8
+ export interface TarballSuccess {
9
+ readonly ok: true;
10
+ /** Absolute path to the directory containing extracted package files. */
11
+ readonly extractedPath: string;
12
+ /** Call this to clean up the temp directory when analysis is done. */
13
+ readonly cleanup: () => Promise<void>;
14
+ }
15
+ export interface TarballError {
16
+ readonly ok: false;
17
+ readonly error: TarballErrorKind;
18
+ readonly message: string;
19
+ }
20
+ export type TarballResult = TarballSuccess | TarballError;
21
+ export type TarballErrorKind = 'download_failed' | 'extraction_failed' | 'io_error' | 'size_exceeded';
22
+ /**
23
+ * Download a tarball from the given URL and extract it to a temp directory.
24
+ *
25
+ * npm tarballs contain a `package/` top-level directory. We strip one level
26
+ * so the extracted path directly contains the package files.
27
+ *
28
+ * @param tarballUrl - Full URL to the .tgz file on the npm registry
29
+ * @param packageName - Package name (for logging)
30
+ * @returns A discriminated union with the extracted path and cleanup function,
31
+ * or an error description.
32
+ */
33
+ export declare function downloadAndExtract(tarballUrl: string, packageName: string): Promise<TarballResult>;
34
+ //# sourceMappingURL=tarball.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tarball.d.ts","sourceRoot":"","sources":["../../src/registry/tarball.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,YAAY,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GACxB,iBAAiB,GACjB,mBAAmB,GACnB,UAAU,GACV,eAAe,CAAC;AAMpB;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,CAAC,CAkFxB"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Tarball download and extraction for npm packages.
3
+ *
4
+ * Downloads a .tgz from the npm registry, extracts it to a temp directory,
5
+ * and provides cleanup. All npm tarballs contain a top-level `package/`
6
+ * directory which is stripped during extraction.
7
+ */
8
+ import { writeFile, mkdir } from 'node:fs/promises';
9
+ import { join } from 'node:path';
10
+ import * as tar from 'tar';
11
+ import { createTempDir, cleanupTempDir } from '../utils/fs.js';
12
+ import { fetchBuffer } from '../utils/http.js';
13
+ import { logger } from '../utils/logger.js';
14
+ const MAX_TARBALL_SIZE = 100 * 1024 * 1024; // 100MB
15
+ // ─── Download & Extract ───────────────────────────────────
16
+ /**
17
+ * Download a tarball from the given URL and extract it to a temp directory.
18
+ *
19
+ * npm tarballs contain a `package/` top-level directory. We strip one level
20
+ * so the extracted path directly contains the package files.
21
+ *
22
+ * @param tarballUrl - Full URL to the .tgz file on the npm registry
23
+ * @param packageName - Package name (for logging)
24
+ * @returns A discriminated union with the extracted path and cleanup function,
25
+ * or an error description.
26
+ */
27
+ export async function downloadAndExtract(tarballUrl, packageName) {
28
+ let tempDir;
29
+ try {
30
+ // Create temp directory
31
+ tempDir = await createTempDir();
32
+ logger.debug(`Created temp directory: ${tempDir}`);
33
+ // Download tarball
34
+ logger.debug(`Downloading tarball for ${packageName}`, { url: tarballUrl });
35
+ const downloadResult = await fetchBuffer(tarballUrl, { timeout: 60_000 });
36
+ if (!downloadResult.ok) {
37
+ await cleanupTempDir(tempDir);
38
+ return {
39
+ ok: false,
40
+ error: 'download_failed',
41
+ message: `Failed to download tarball for ${packageName}: ${downloadResult.message}`,
42
+ };
43
+ }
44
+ // Check tarball size before writing to disk
45
+ if (downloadResult.data.byteLength > MAX_TARBALL_SIZE) {
46
+ await cleanupTempDir(tempDir);
47
+ return {
48
+ ok: false,
49
+ error: 'size_exceeded',
50
+ message: `Tarball for ${packageName} exceeds maximum allowed size of ${MAX_TARBALL_SIZE} bytes`,
51
+ };
52
+ }
53
+ const tgzPath = join(tempDir, 'package.tgz');
54
+ await writeFile(tgzPath, downloadResult.data);
55
+ // Extract tarball
56
+ const extractPath = join(tempDir, 'extracted');
57
+ await mkdir(extractPath, { recursive: true });
58
+ logger.debug(`Extracting tarball to ${extractPath}`);
59
+ try {
60
+ await tar.extract({
61
+ file: tgzPath,
62
+ cwd: extractPath,
63
+ strip: 1, // Remove the top-level `package/` directory
64
+ // Prevent path traversal
65
+ filter: (path) => {
66
+ const normalized = path.replace(/\\/g, '/');
67
+ return !normalized.includes('..') && !normalized.startsWith('/');
68
+ },
69
+ });
70
+ }
71
+ catch (extractError) {
72
+ await cleanupTempDir(tempDir);
73
+ const message = extractError instanceof Error ? extractError.message : String(extractError);
74
+ return {
75
+ ok: false,
76
+ error: 'extraction_failed',
77
+ message: `Failed to extract tarball for ${packageName}: ${message}`,
78
+ };
79
+ }
80
+ logger.debug(`Successfully extracted ${packageName} to ${extractPath}`);
81
+ const capturedTempDir = tempDir;
82
+ return {
83
+ ok: true,
84
+ extractedPath: extractPath,
85
+ cleanup: async () => {
86
+ logger.debug(`Cleaning up temp directory: ${capturedTempDir}`);
87
+ await cleanupTempDir(capturedTempDir);
88
+ },
89
+ };
90
+ }
91
+ catch (error) {
92
+ if (tempDir !== undefined) {
93
+ await cleanupTempDir(tempDir);
94
+ }
95
+ const message = error instanceof Error ? error.message : String(error);
96
+ return {
97
+ ok: false,
98
+ error: 'io_error',
99
+ message: `Unexpected error processing tarball for ${packageName}: ${message}`,
100
+ };
101
+ }
102
+ }
103
+ //# sourceMappingURL=tarball.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tarball.js","sourceRoot":"","sources":["../../src/registry/tarball.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * AST parsing and traversal helpers using @typescript-eslint/typescript-estree.
3
+ *
4
+ * All functions handle parse errors gracefully — they return empty results
5
+ * rather than throwing, since malformed files should not crash analysis.
6
+ */
7
+ import { type AST } from '@typescript-eslint/typescript-estree';
8
+ type TSESTreeNode = AST<{
9
+ range: true;
10
+ loc: true;
11
+ }>;
12
+ /** A generic AST node — we use Record to avoid `any`. */
13
+ type ASTNode = Record<string, unknown> & {
14
+ readonly type: string;
15
+ readonly loc?: {
16
+ readonly start: {
17
+ readonly line: number;
18
+ readonly column: number;
19
+ };
20
+ readonly end: {
21
+ readonly line: number;
22
+ readonly column: number;
23
+ };
24
+ };
25
+ };
26
+ /** Callback for the AST walker. Return `false` to skip children. */
27
+ type WalkCallback = (node: ASTNode) => boolean | void;
28
+ interface ParseResult {
29
+ readonly ok: true;
30
+ readonly ast: TSESTreeNode;
31
+ }
32
+ interface ParseFailure {
33
+ readonly ok: false;
34
+ readonly error: string;
35
+ }
36
+ type ParseOutcome = ParseResult | ParseFailure;
37
+ /**
38
+ * Parse JavaScript or TypeScript source code into an AST.
39
+ * Returns a discriminated union so callers can check `ok` before using the AST.
40
+ */
41
+ export declare function parseSource(source: string, filePath?: string): ParseOutcome;
42
+ /**
43
+ * Recursively walk all nodes in an AST, calling the visitor for each.
44
+ * If the visitor returns `false`, children of that node are skipped.
45
+ */
46
+ export declare function walkAST(node: unknown, visitor: WalkCallback): void;
47
+ /**
48
+ * Extract all string literal values from source code.
49
+ * Includes regular string literals and template literal quasis.
50
+ * Returns an empty array on parse failure.
51
+ */
52
+ export declare function extractStringLiterals(source: string, filePath?: string): readonly string[];
53
+ /**
54
+ * Get the line number of an AST node, or undefined if location info is missing.
55
+ */
56
+ export declare function getNodeLine(node: ASTNode): number | undefined;
57
+ /**
58
+ * Check if a node is a call expression calling a specific function name.
59
+ * Handles both simple calls (`eval(...)`) and member calls (`process.exit(...)`).
60
+ */
61
+ export declare function isCallTo(node: ASTNode, name: string): boolean;
62
+ /**
63
+ * Check if a node is a member expression accessing a specific property.
64
+ * E.g., `process.env` where property name is "env".
65
+ */
66
+ /**
67
+ * Build an optional line property for a Finding, omitting it when undefined.
68
+ */
69
+ export declare function optionalLine(line: number | undefined): {
70
+ readonly line: number;
71
+ } | Record<string, never>;
72
+ export declare function isMemberAccess(node: ASTNode, objectName: string, propertyName: string): boolean;
73
+ export {};
74
+ //# sourceMappingURL=ast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../src/utils/ast.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAS,KAAK,GAAG,EAAE,MAAM,sCAAsC,CAAC;AAIvE,KAAK,YAAY,GAAG,GAAG,CAAC;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAEpD,yDAAyD;AACzD,KAAK,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE;QACb,QAAQ,CAAC,KAAK,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QACnE,QAAQ,CAAC,GAAG,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KAClE,CAAC;CACH,CAAC;AAEF,oEAAoE;AAEpE,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,GAAG,IAAI,CAAC;AAItD,UAAU,WAAW;IACnB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;CAC5B;AAED,UAAU,YAAY;IACpB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,KAAK,YAAY,GAAG,WAAW,GAAG,YAAY,CAAC;AAE/C;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAkB3E;AAID;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAgBlE;AAID;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CA8B1F;AAgBD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE7D;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAoB7D;AAED;;;GAGG;AACH;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAEnD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAc/F"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * AST parsing and traversal helpers using @typescript-eslint/typescript-estree.
3
+ *
4
+ * All functions handle parse errors gracefully — they return empty results
5
+ * rather than throwing, since malformed files should not crash analysis.
6
+ */
7
+ import { parse } from '@typescript-eslint/typescript-estree';
8
+ /**
9
+ * Parse JavaScript or TypeScript source code into an AST.
10
+ * Returns a discriminated union so callers can check `ok` before using the AST.
11
+ */
12
+ export function parseSource(source, filePath) {
13
+ try {
14
+ const ast = parse(source, {
15
+ range: true,
16
+ loc: true,
17
+ jsx: true,
18
+ // Allow any syntax — we're analyzing, not compiling
19
+ allowInvalidAST: true,
20
+ suppressDeprecatedPropertyWarnings: true,
21
+ });
22
+ return { ok: true, ast };
23
+ }
24
+ catch (error) {
25
+ const message = error instanceof Error ? error.message : String(error);
26
+ return {
27
+ ok: false,
28
+ error: `Parse error${filePath !== undefined ? ` in ${filePath}` : ''}: ${message}`,
29
+ };
30
+ }
31
+ }
32
+ // ─── Walk ─────────────────────────────────────────────────
33
+ /**
34
+ * Recursively walk all nodes in an AST, calling the visitor for each.
35
+ * If the visitor returns `false`, children of that node are skipped.
36
+ */
37
+ export function walkAST(node, visitor) {
38
+ if (!isASTNode(node))
39
+ return;
40
+ const shouldDescend = visitor(node);
41
+ if (shouldDescend === false)
42
+ return;
43
+ for (const key of Object.keys(node)) {
44
+ const value = node[key];
45
+ if (Array.isArray(value)) {
46
+ for (const child of value) {
47
+ walkAST(child, visitor);
48
+ }
49
+ }
50
+ else if (isASTNode(value)) {
51
+ walkAST(value, visitor);
52
+ }
53
+ }
54
+ }
55
+ // ─── String Extraction ───────────────────────────────────
56
+ /**
57
+ * Extract all string literal values from source code.
58
+ * Includes regular string literals and template literal quasis.
59
+ * Returns an empty array on parse failure.
60
+ */
61
+ export function extractStringLiterals(source, filePath) {
62
+ const result = parseSource(source, filePath);
63
+ if (!result.ok)
64
+ return [];
65
+ const strings = [];
66
+ walkAST(result.ast, (node) => {
67
+ if (node.type === 'Literal' && typeof node['value'] === 'string') {
68
+ strings.push(node['value']);
69
+ }
70
+ if (node.type === 'TemplateLiteral') {
71
+ const quasis = node['quasis'];
72
+ if (Array.isArray(quasis)) {
73
+ for (const quasi of quasis) {
74
+ if (isASTNode(quasi)) {
75
+ const value = quasi['value'];
76
+ if (value !== null && typeof value === 'object') {
77
+ const raw = value['raw'];
78
+ if (typeof raw === 'string' && raw.length > 0) {
79
+ strings.push(raw);
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ });
87
+ return strings;
88
+ }
89
+ // ─── Utilities ────────────────────────────────────────────
90
+ /**
91
+ * Check whether a value looks like an AST node (has a `type` string property).
92
+ */
93
+ function isASTNode(value) {
94
+ return (value !== null &&
95
+ typeof value === 'object' &&
96
+ 'type' in value &&
97
+ typeof value['type'] === 'string');
98
+ }
99
+ /**
100
+ * Get the line number of an AST node, or undefined if location info is missing.
101
+ */
102
+ export function getNodeLine(node) {
103
+ return node.loc?.start.line;
104
+ }
105
+ /**
106
+ * Check if a node is a call expression calling a specific function name.
107
+ * Handles both simple calls (`eval(...)`) and member calls (`process.exit(...)`).
108
+ */
109
+ export function isCallTo(node, name) {
110
+ if (node.type !== 'CallExpression')
111
+ return false;
112
+ const callee = node['callee'];
113
+ if (!isASTNode(callee))
114
+ return false;
115
+ // Simple identifier: eval(...)
116
+ if (callee.type === 'Identifier' && callee['name'] === name) {
117
+ return true;
118
+ }
119
+ // Member expression: obj.method(...)
120
+ if (callee.type === 'MemberExpression') {
121
+ const property = callee['property'];
122
+ if (isASTNode(property) && property.type === 'Identifier' && property['name'] === name) {
123
+ return true;
124
+ }
125
+ }
126
+ return false;
127
+ }
128
+ /**
129
+ * Check if a node is a member expression accessing a specific property.
130
+ * E.g., `process.env` where property name is "env".
131
+ */
132
+ /**
133
+ * Build an optional line property for a Finding, omitting it when undefined.
134
+ */
135
+ export function optionalLine(line) {
136
+ return line !== undefined ? { line } : {};
137
+ }
138
+ export function isMemberAccess(node, objectName, propertyName) {
139
+ if (node.type !== 'MemberExpression')
140
+ return false;
141
+ const object = node['object'];
142
+ const property = node['property'];
143
+ if (!isASTNode(object) || !isASTNode(property))
144
+ return false;
145
+ return (object.type === 'Identifier' &&
146
+ object['name'] === objectName &&
147
+ property.type === 'Identifier' &&
148
+ property['name'] === propertyName);
149
+ }
150
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/utils/ast.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Filesystem utilities for temp directory management and file operations.
3
+ */
4
+ /**
5
+ * Check whether a file path has an analyzable JavaScript/TypeScript extension.
6
+ */
7
+ export declare function isAnalyzableFile(filePath: string): boolean;
8
+ /**
9
+ * Create a temporary directory for package extraction.
10
+ * Returns the absolute path to the created directory.
11
+ */
12
+ export declare function createTempDir(): Promise<string>;
13
+ /**
14
+ * Remove a temporary directory and all its contents.
15
+ * Silently ignores errors (directory may already be cleaned up).
16
+ */
17
+ export declare function cleanupTempDir(dirPath: string): Promise<void>;
18
+ /**
19
+ * Recursively walk a directory, yielding absolute file paths.
20
+ * Skips `node_modules` and hidden directories (starting with `.`).
21
+ */
22
+ export declare function walkDirectory(dirPath: string): AsyncGenerator<string>;
23
+ /**
24
+ * Read a file's contents as UTF-8 text.
25
+ * Returns null if the file cannot be read (missing, permission denied, etc.).
26
+ */
27
+ export declare function readFileSafe(filePath: string): Promise<string | null>;
28
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAI1D;AAMD;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAErD;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnE;AAID;;;GAGG;AACH,wBAAuB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAoB5E;AAID;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAM3E"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Filesystem utilities for temp directory management and file operations.
3
+ */
4
+ import { mkdtemp, rm, readFile, readdir } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+ // ─── Analyzable File Extensions ───────────────────────────
8
+ const ANALYZABLE_EXTENSIONS = new Set(['.js', '.ts', '.mjs', '.cjs', '.mts', '.cts']);
9
+ /**
10
+ * Check whether a file path has an analyzable JavaScript/TypeScript extension.
11
+ */
12
+ export function isAnalyzableFile(filePath) {
13
+ const dotIdx = filePath.lastIndexOf('.');
14
+ if (dotIdx === -1)
15
+ return false;
16
+ return ANALYZABLE_EXTENSIONS.has(filePath.slice(dotIdx));
17
+ }
18
+ // ─── Temp Directory ───────────────────────────────────────
19
+ const TEMP_PREFIX = 'mitnick-';
20
+ /**
21
+ * Create a temporary directory for package extraction.
22
+ * Returns the absolute path to the created directory.
23
+ */
24
+ export async function createTempDir() {
25
+ return mkdtemp(join(tmpdir(), TEMP_PREFIX));
26
+ }
27
+ /**
28
+ * Remove a temporary directory and all its contents.
29
+ * Silently ignores errors (directory may already be cleaned up).
30
+ */
31
+ export async function cleanupTempDir(dirPath) {
32
+ try {
33
+ await rm(dirPath, { recursive: true, force: true });
34
+ }
35
+ catch {
36
+ // Best-effort cleanup — ignore errors
37
+ }
38
+ }
39
+ // ─── Directory Walking ────────────────────────────────────
40
+ /**
41
+ * Recursively walk a directory, yielding absolute file paths.
42
+ * Skips `node_modules` and hidden directories (starting with `.`).
43
+ */
44
+ export async function* walkDirectory(dirPath) {
45
+ let entries;
46
+ try {
47
+ entries = await readdir(dirPath, { withFileTypes: true });
48
+ }
49
+ catch {
50
+ return;
51
+ }
52
+ for (const entry of entries) {
53
+ const fullPath = join(dirPath, entry.name);
54
+ if (entry.isDirectory()) {
55
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
56
+ continue;
57
+ }
58
+ yield* walkDirectory(fullPath);
59
+ }
60
+ else if (entry.isFile()) {
61
+ yield fullPath;
62
+ }
63
+ }
64
+ }
65
+ // ─── Safe File Read ───────────────────────────────────────
66
+ /**
67
+ * Read a file's contents as UTF-8 text.
68
+ * Returns null if the file cannot be read (missing, permission denied, etc.).
69
+ */
70
+ export async function readFileSafe(filePath) {
71
+ try {
72
+ return await readFile(filePath, 'utf-8');
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,6DAA6D;AAE7D,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEtF;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAChC,OAAO,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,6DAA6D;AAE7D,MAAM,WAAW,GAAG,UAAU,CAAC;AAE/B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAe;IAClD,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;AACH,CAAC;AAED,6DAA6D;AAE7D;;;GAGG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,aAAa,CAAC,OAAe;IAClD,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAE3C,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChE,SAAS;YACX,CAAC;YACD,KAAK,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1B,MAAM,QAAQ,CAAC;QACjB,CAAC;IACH,CAAC;AACH,CAAC;AAED,6DAA6D;AAE7D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * HTTP client wrapper using native fetch (Node 18+).
3
+ *
4
+ * Provides typed fetch helpers with timeout support and
5
+ * structured error handling via discriminated unions.
6
+ */
7
+ interface HttpSuccess<T> {
8
+ readonly ok: true;
9
+ readonly data: T;
10
+ readonly status: number;
11
+ }
12
+ interface HttpError {
13
+ readonly ok: false;
14
+ readonly error: HttpErrorKind;
15
+ readonly message: string;
16
+ readonly status?: number;
17
+ }
18
+ export type HttpResult<T> = HttpSuccess<T> | HttpError;
19
+ type HttpErrorKind = 'network' | 'timeout' | 'not_found' | 'rate_limited' | 'server_error' | 'parse_error';
20
+ interface FetchOptions {
21
+ readonly timeout?: number;
22
+ readonly headers?: Readonly<Record<string, string>>;
23
+ readonly method?: string;
24
+ readonly body?: string;
25
+ }
26
+ /**
27
+ * Fetch JSON from a URL with timeout support and typed response.
28
+ * Returns a discriminated union so callers handle errors explicitly.
29
+ *
30
+ * **Note:** The generic type parameter `T` is applied via an `as T` cast on the
31
+ * parsed JSON. Callers are responsible for validating the returned `data`
32
+ * (e.g., with zod) before relying on its shape.
33
+ */
34
+ export declare function fetchJson<T>(url: string, options?: FetchOptions): Promise<HttpResult<T>>;
35
+ /**
36
+ * Fetch raw bytes from a URL. Used for tarball downloads.
37
+ */
38
+ export declare function fetchBuffer(url: string, options?: FetchOptions): Promise<HttpResult<Buffer>>;
39
+ export {};
40
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/utils/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,UAAU,WAAW,CAAC,CAAC;IACrB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,SAAS;IACjB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;AAEvD,KAAK,aAAa,GACd,SAAS,GACT,SAAS,GACT,WAAW,GACX,cAAc,GACd,cAAc,GACd,aAAa,CAAC;AAIlB,UAAU,YAAY;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAwBD;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CA+CxB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CA+B7B"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * HTTP client wrapper using native fetch (Node 18+).
3
+ *
4
+ * Provides typed fetch helpers with timeout support and
5
+ * structured error handling via discriminated unions.
6
+ */
7
+ const DEFAULT_TIMEOUT = 30_000;
8
+ // ─── Helpers ──────────────────────────────────────────────
9
+ function classifyStatus(status) {
10
+ if (status >= 200 && status < 300)
11
+ return null;
12
+ if (status === 404)
13
+ return 'not_found';
14
+ if (status === 429)
15
+ return 'rate_limited';
16
+ if (status >= 500)
17
+ return 'server_error';
18
+ return 'network';
19
+ }
20
+ function classifyError(error) {
21
+ if (error instanceof DOMException && error.name === 'AbortError') {
22
+ return { ok: false, error: 'timeout', message: 'Request timed out' };
23
+ }
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ return { ok: false, error: 'network', message };
26
+ }
27
+ // ─── Typed Fetch ──────────────────────────────────────────
28
+ /**
29
+ * Fetch JSON from a URL with timeout support and typed response.
30
+ * Returns a discriminated union so callers handle errors explicitly.
31
+ *
32
+ * **Note:** The generic type parameter `T` is applied via an `as T` cast on the
33
+ * parsed JSON. Callers are responsible for validating the returned `data`
34
+ * (e.g., with zod) before relying on its shape.
35
+ */
36
+ export async function fetchJson(url, options = {}) {
37
+ const { timeout = DEFAULT_TIMEOUT, headers, method, body } = options;
38
+ const controller = new AbortController();
39
+ const timer = setTimeout(() => controller.abort(), timeout);
40
+ try {
41
+ const init = {
42
+ method: method ?? 'GET',
43
+ headers: {
44
+ Accept: 'application/json',
45
+ ...headers,
46
+ },
47
+ signal: controller.signal,
48
+ };
49
+ if (body !== undefined) {
50
+ init.body = body;
51
+ }
52
+ const response = await fetch(url, init);
53
+ const errorKind = classifyStatus(response.status);
54
+ if (errorKind !== null) {
55
+ const text = await response.text().catch(() => '');
56
+ return {
57
+ ok: false,
58
+ error: errorKind,
59
+ message: text !== '' ? text : `HTTP ${response.status}`,
60
+ status: response.status,
61
+ };
62
+ }
63
+ try {
64
+ const data = (await response.json());
65
+ return { ok: true, data, status: response.status };
66
+ }
67
+ catch {
68
+ return {
69
+ ok: false,
70
+ error: 'parse_error',
71
+ message: 'Failed to parse JSON response',
72
+ status: response.status,
73
+ };
74
+ }
75
+ }
76
+ catch (error) {
77
+ return classifyError(error);
78
+ }
79
+ finally {
80
+ clearTimeout(timer);
81
+ }
82
+ }
83
+ /**
84
+ * Fetch raw bytes from a URL. Used for tarball downloads.
85
+ */
86
+ export async function fetchBuffer(url, options = {}) {
87
+ const { timeout = DEFAULT_TIMEOUT, headers, method } = options;
88
+ const controller = new AbortController();
89
+ const timer = setTimeout(() => controller.abort(), timeout);
90
+ try {
91
+ const response = await fetch(url, {
92
+ method: method ?? 'GET',
93
+ headers: { ...headers },
94
+ signal: controller.signal,
95
+ });
96
+ const errorKind = classifyStatus(response.status);
97
+ if (errorKind !== null) {
98
+ const text = await response.text().catch(() => '');
99
+ return {
100
+ ok: false,
101
+ error: errorKind,
102
+ message: text !== '' ? text : `HTTP ${response.status}`,
103
+ status: response.status,
104
+ };
105
+ }
106
+ const arrayBuffer = await response.arrayBuffer();
107
+ return { ok: true, data: Buffer.from(arrayBuffer), status: response.status };
108
+ }
109
+ catch (error) {
110
+ return classifyError(error);
111
+ }
112
+ finally {
113
+ clearTimeout(timer);
114
+ }
115
+ }
116
+ //# sourceMappingURL=http.js.map