toolip 1.0.7 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +189 -448
  2. package/dist/src/analyzers/ast/ast-security-analyzer.d.ts +6 -0
  3. package/dist/src/analyzers/ast/ast-security-analyzer.js +64 -0
  4. package/dist/src/analyzers/ast/ast-security-analyzer.js.map +1 -0
  5. package/dist/src/analyzers/ast/rules.d.ts +48 -0
  6. package/dist/src/analyzers/ast/rules.js +39 -0
  7. package/dist/src/analyzers/ast/rules.js.map +1 -0
  8. package/dist/src/analyzers/ast/source-analysis.d.ts +2 -0
  9. package/dist/src/analyzers/ast/source-analysis.js +225 -0
  10. package/dist/src/analyzers/ast/source-analysis.js.map +1 -0
  11. package/dist/src/analyzers/dependency-confusion/analyzer.d.ts +9 -0
  12. package/dist/src/analyzers/dependency-confusion/analyzer.js +98 -0
  13. package/dist/src/analyzers/dependency-confusion/analyzer.js.map +1 -0
  14. package/dist/src/analyzers/docker/analyzer.d.ts +6 -0
  15. package/dist/src/analyzers/docker/analyzer.js +103 -0
  16. package/dist/src/analyzers/docker/analyzer.js.map +1 -0
  17. package/dist/src/analyzers/git-history/analyzer.d.ts +8 -0
  18. package/dist/src/analyzers/git-history/analyzer.js +202 -0
  19. package/dist/src/analyzers/git-history/analyzer.js.map +1 -0
  20. package/dist/src/analyzers/git-history/secret-patterns.d.ts +7 -0
  21. package/dist/src/analyzers/git-history/secret-patterns.js +33 -0
  22. package/dist/src/analyzers/git-history/secret-patterns.js.map +1 -0
  23. package/dist/src/analyzers/install-scripts/install-script-analyzer.d.ts +6 -0
  24. package/dist/src/analyzers/install-scripts/install-script-analyzer.js +135 -0
  25. package/dist/src/analyzers/install-scripts/install-script-analyzer.js.map +1 -0
  26. package/dist/src/analyzers/reachability/import-graph.d.ts +9 -0
  27. package/dist/src/analyzers/reachability/import-graph.js +110 -0
  28. package/dist/src/analyzers/reachability/import-graph.js.map +1 -0
  29. package/dist/src/analyzers/reachability/reachability-analyzer.d.ts +16 -0
  30. package/dist/src/analyzers/reachability/reachability-analyzer.js +95 -0
  31. package/dist/src/analyzers/reachability/reachability-analyzer.js.map +1 -0
  32. package/dist/src/analyzers/vulnerability/osv-analyzer.d.ts +9 -0
  33. package/dist/src/analyzers/vulnerability/osv-analyzer.js +94 -0
  34. package/dist/src/analyzers/vulnerability/osv-analyzer.js.map +1 -0
  35. package/dist/src/analyzers/vulnerability/severity.d.ts +3 -0
  36. package/dist/src/analyzers/vulnerability/severity.js +13 -0
  37. package/dist/src/analyzers/vulnerability/severity.js.map +1 -0
  38. package/dist/src/application/analyzer-runner.d.ts +12 -0
  39. package/dist/src/application/analyzer-runner.js +45 -0
  40. package/dist/src/application/analyzer-runner.js.map +1 -0
  41. package/dist/src/commands/announce.d.ts +2 -0
  42. package/dist/src/commands/announce.js +27 -0
  43. package/dist/src/commands/announce.js.map +1 -0
  44. package/dist/src/commands/ast-scan.d.ts +2 -0
  45. package/dist/src/commands/ast-scan.js +86 -0
  46. package/dist/src/commands/ast-scan.js.map +1 -0
  47. package/dist/src/commands/audit-repo.d.ts +2 -0
  48. package/dist/src/commands/audit-repo.js +20 -0
  49. package/dist/src/commands/audit-repo.js.map +1 -0
  50. package/dist/src/commands/config.d.ts +2 -0
  51. package/dist/src/commands/config.js +69 -0
  52. package/dist/src/commands/config.js.map +1 -0
  53. package/dist/src/commands/dependency-confusion.d.ts +2 -0
  54. package/dist/src/commands/dependency-confusion.js +37 -0
  55. package/dist/src/commands/dependency-confusion.js.map +1 -0
  56. package/dist/src/commands/diff.d.ts +2 -0
  57. package/dist/src/commands/diff.js +24 -0
  58. package/dist/src/commands/diff.js.map +1 -0
  59. package/dist/src/commands/docker-scan.d.ts +2 -0
  60. package/dist/src/commands/docker-scan.js +34 -0
  61. package/dist/src/commands/docker-scan.js.map +1 -0
  62. package/dist/src/commands/git-history.d.ts +2 -0
  63. package/dist/src/commands/git-history.js +40 -0
  64. package/dist/src/commands/git-history.js.map +1 -0
  65. package/dist/src/commands/history.d.ts +2 -0
  66. package/dist/src/commands/history.js +69 -0
  67. package/dist/src/commands/history.js.map +1 -0
  68. package/dist/src/commands/install-scripts.d.ts +2 -0
  69. package/dist/src/commands/install-scripts.js +52 -0
  70. package/dist/src/commands/install-scripts.js.map +1 -0
  71. package/dist/src/commands/mcp.d.ts +2 -0
  72. package/dist/src/commands/mcp.js +10 -0
  73. package/dist/src/commands/mcp.js.map +1 -0
  74. package/dist/src/commands/monorepo.d.ts +2 -0
  75. package/dist/src/commands/monorepo.js +21 -0
  76. package/dist/src/commands/monorepo.js.map +1 -0
  77. package/dist/src/commands/package-health.d.ts +2 -0
  78. package/dist/src/commands/package-health.js +50 -0
  79. package/dist/src/commands/package-health.js.map +1 -0
  80. package/dist/src/commands/publish.d.ts +2 -0
  81. package/dist/src/commands/publish.js +25 -0
  82. package/dist/src/commands/publish.js.map +1 -0
  83. package/dist/src/commands/reachability.d.ts +2 -0
  84. package/dist/src/commands/reachability.js +47 -0
  85. package/dist/src/commands/reachability.js.map +1 -0
  86. package/dist/src/commands/sbom.d.ts +2 -0
  87. package/dist/src/commands/sbom.js +33 -0
  88. package/dist/src/commands/sbom.js.map +1 -0
  89. package/dist/src/commands/upgrade-pr.d.ts +2 -0
  90. package/dist/src/commands/upgrade-pr.js +15 -0
  91. package/dist/src/commands/upgrade-pr.js.map +1 -0
  92. package/dist/src/commands/vulnerabilities.d.ts +2 -0
  93. package/dist/src/commands/vulnerabilities.js +42 -0
  94. package/dist/src/commands/vulnerabilities.js.map +1 -0
  95. package/dist/src/commands/watch.d.ts +2 -0
  96. package/dist/src/commands/watch.js +34 -0
  97. package/dist/src/commands/watch.js.map +1 -0
  98. package/dist/src/contracts/analyzer.d.ts +23 -0
  99. package/dist/src/contracts/analyzer.js +2 -0
  100. package/dist/src/contracts/analyzer.js.map +1 -0
  101. package/dist/src/contracts/finding.d.ts +35 -0
  102. package/dist/src/contracts/finding.js +13 -0
  103. package/dist/src/contracts/finding.js.map +1 -0
  104. package/dist/src/contracts/report.d.ts +30 -0
  105. package/dist/src/contracts/report.js +2 -0
  106. package/dist/src/contracts/report.js.map +1 -0
  107. package/dist/src/contracts/rule.d.ts +12 -0
  108. package/dist/src/contracts/rule.js +7 -0
  109. package/dist/src/contracts/rule.js.map +1 -0
  110. package/dist/src/core/announce/generate.d.ts +9 -0
  111. package/dist/src/core/announce/generate.js +19 -0
  112. package/dist/src/core/announce/generate.js.map +1 -0
  113. package/dist/src/core/config/load.d.ts +3 -0
  114. package/dist/src/core/config/load.js +21 -0
  115. package/dist/src/core/config/load.js.map +1 -0
  116. package/dist/src/core/config/policy.d.ts +3 -0
  117. package/dist/src/core/config/policy.js +48 -0
  118. package/dist/src/core/config/policy.js.map +1 -0
  119. package/dist/src/core/config/schema.d.ts +172 -0
  120. package/dist/src/core/config/schema.js +84 -0
  121. package/dist/src/core/config/schema.js.map +1 -0
  122. package/dist/src/core/dependencies/inventory.d.ts +9 -0
  123. package/dist/src/core/dependencies/inventory.js +43 -0
  124. package/dist/src/core/dependencies/inventory.js.map +1 -0
  125. package/dist/src/core/diff/security-diff.d.ts +10 -0
  126. package/dist/src/core/diff/security-diff.js +20 -0
  127. package/dist/src/core/diff/security-diff.js.map +1 -0
  128. package/dist/src/core/github/remote-audit.d.ts +5 -0
  129. package/dist/src/core/github/remote-audit.js +28 -0
  130. package/dist/src/core/github/remote-audit.js.map +1 -0
  131. package/dist/src/core/github/upgrade-pr.d.ts +4 -0
  132. package/dist/src/core/github/upgrade-pr.js +41 -0
  133. package/dist/src/core/github/upgrade-pr.js.map +1 -0
  134. package/dist/src/core/history/git-metadata.d.ts +5 -0
  135. package/dist/src/core/history/git-metadata.js +37 -0
  136. package/dist/src/core/history/git-metadata.js.map +1 -0
  137. package/dist/src/core/history/store.d.ts +5 -0
  138. package/dist/src/core/history/store.js +65 -0
  139. package/dist/src/core/history/store.js.map +1 -0
  140. package/dist/src/core/history/types.d.ts +27 -0
  141. package/dist/src/core/history/types.js +2 -0
  142. package/dist/src/core/history/types.js.map +1 -0
  143. package/dist/src/core/monorepo/discover.d.ts +7 -0
  144. package/dist/src/core/monorepo/discover.js +49 -0
  145. package/dist/src/core/monorepo/discover.js.map +1 -0
  146. package/dist/src/core/publish/html.d.ts +6 -0
  147. package/dist/src/core/publish/html.js +44 -0
  148. package/dist/src/core/publish/html.js.map +1 -0
  149. package/dist/src/core/sbom/generate.d.ts +2 -0
  150. package/dist/src/core/sbom/generate.js +120 -0
  151. package/dist/src/core/sbom/generate.js.map +1 -0
  152. package/dist/src/core/security-doctor.js +19 -5
  153. package/dist/src/core/security-doctor.js.map +1 -1
  154. package/dist/src/core/watch/watch.d.ts +5 -0
  155. package/dist/src/core/watch/watch.js +49 -0
  156. package/dist/src/core/watch/watch.js.map +1 -0
  157. package/dist/src/index.js +38 -0
  158. package/dist/src/index.js.map +1 -1
  159. package/dist/src/mcp/server.d.ts +1 -0
  160. package/dist/src/mcp/server.js +64 -0
  161. package/dist/src/mcp/server.js.map +1 -0
  162. package/dist/src/providers/depsdev/client.d.ts +71 -0
  163. package/dist/src/providers/depsdev/client.js +44 -0
  164. package/dist/src/providers/depsdev/client.js.map +1 -0
  165. package/dist/src/providers/osv/client.d.ts +15 -0
  166. package/dist/src/providers/osv/client.js +51 -0
  167. package/dist/src/providers/osv/client.js.map +1 -0
  168. package/dist/src/providers/osv/types.d.ts +38 -0
  169. package/dist/src/providers/osv/types.js +2 -0
  170. package/dist/src/providers/osv/types.js.map +1 -0
  171. package/dist/src/storage/memory-cache.d.ts +7 -0
  172. package/dist/src/storage/memory-cache.js +25 -0
  173. package/dist/src/storage/memory-cache.js.map +1 -0
  174. package/package.json +5 -3
@@ -0,0 +1,202 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { historicalSecretPatterns } from './secret-patterns.js';
4
+ function redact(value) {
5
+ if (value.length <= 12) {
6
+ return '[redacted]';
7
+ }
8
+ return (`${value.slice(0, 6)}` +
9
+ '...[redacted]...' +
10
+ `${value.slice(-4)}`);
11
+ }
12
+ function fingerprint(value) {
13
+ return createHash('sha256')
14
+ .update(value)
15
+ .digest('hex');
16
+ }
17
+ function isTestFile(file) {
18
+ if (!file) {
19
+ return false;
20
+ }
21
+ return (file.startsWith('tests/') ||
22
+ file.includes('/tests/') ||
23
+ file.includes('/__tests__/') ||
24
+ /\.(test|spec)\.[cm]?[jt]sx?$/.test(file));
25
+ }
26
+ async function gitLog(root, maxCommits, signal) {
27
+ return new Promise((resolve, reject) => {
28
+ const child = spawn('git', [
29
+ 'log',
30
+ `--max-count=${maxCommits}`,
31
+ '--all',
32
+ '--no-renames',
33
+ '--format=__TOOLIP_COMMIT__%H%x09%an%x09%aI',
34
+ '--patch',
35
+ '--unified=0',
36
+ '--no-color'
37
+ ], {
38
+ cwd: root,
39
+ stdio: [
40
+ 'ignore',
41
+ 'pipe',
42
+ 'pipe'
43
+ ]
44
+ });
45
+ let stdout = '';
46
+ let stderr = '';
47
+ child.stdout.setEncoding('utf8');
48
+ child.stderr.setEncoding('utf8');
49
+ child.stdout.on('data', (chunk) => {
50
+ stdout += chunk;
51
+ });
52
+ child.stderr.on('data', (chunk) => {
53
+ stderr += chunk;
54
+ });
55
+ const abort = () => {
56
+ child.kill('SIGTERM');
57
+ };
58
+ signal?.addEventListener('abort', abort, {
59
+ once: true
60
+ });
61
+ child.on('error', reject);
62
+ child.on('close', (code) => {
63
+ signal?.removeEventListener('abort', abort);
64
+ if (code !== 0) {
65
+ reject(new Error(stderr.trim() ||
66
+ `git log exited with ${code}.`));
67
+ return;
68
+ }
69
+ resolve(stdout);
70
+ });
71
+ });
72
+ }
73
+ function sections(output) {
74
+ return output
75
+ .split('__TOOLIP_COMMIT__')
76
+ .filter(Boolean)
77
+ .map((section) => {
78
+ const newline = section.indexOf('\n');
79
+ const header = newline === -1
80
+ ? section
81
+ : section.slice(0, newline);
82
+ const body = newline === -1
83
+ ? ''
84
+ : section.slice(newline + 1);
85
+ const [commit, author, date] = header.split('\t');
86
+ return {
87
+ commit: commit ?? 'unknown',
88
+ author: author ?? 'unknown',
89
+ date: date ?? 'unknown',
90
+ body
91
+ };
92
+ });
93
+ }
94
+ function addedLines(body) {
95
+ const output = [];
96
+ let currentFile;
97
+ for (const line of body.split('\n')) {
98
+ if (line.startsWith('+++ b/')) {
99
+ currentFile = line.slice('+++ b/'.length);
100
+ continue;
101
+ }
102
+ if (line.startsWith('+++ /dev/null')) {
103
+ currentFile = undefined;
104
+ continue;
105
+ }
106
+ if (line.startsWith('+') &&
107
+ !line.startsWith('+++')) {
108
+ output.push({
109
+ file: currentFile,
110
+ content: line.slice(1)
111
+ });
112
+ }
113
+ }
114
+ return output;
115
+ }
116
+ export class GitHistorySecretAnalyzer {
117
+ maxCommits;
118
+ id = 'git-history-secrets';
119
+ version = '1.0.1';
120
+ constructor(maxCommits = 1000) {
121
+ this.maxCommits = maxCommits;
122
+ }
123
+ async analyze(context) {
124
+ const startedAt = performance.now();
125
+ const output = await gitLog(context.root, this.maxCommits, context.signal);
126
+ const findings = [];
127
+ const seen = new Set();
128
+ const commitSections = sections(output);
129
+ for (const section of commitSections) {
130
+ for (const line of addedLines(section.body)) {
131
+ for (const pattern of historicalSecretPatterns) {
132
+ pattern.regex.lastIndex = 0;
133
+ for (const match of line.content.matchAll(pattern.regex)) {
134
+ const value = match[0];
135
+ const secretFingerprint = fingerprint(value);
136
+ const key = `${pattern.id}:${section.commit}:` +
137
+ `${line.file ?? 'unknown'}:` +
138
+ secretFingerprint;
139
+ if (seen.has(key)) {
140
+ continue;
141
+ }
142
+ seen.add(key);
143
+ const testFixture = isTestFile(line.file);
144
+ findings.push({
145
+ id: key,
146
+ ruleId: pattern.id,
147
+ title: testFixture
148
+ ? `Potential historical test fixture: ${pattern.title}`
149
+ : pattern.title,
150
+ category: 'git-history-secret',
151
+ severity: testFixture
152
+ ? 'low'
153
+ : pattern.severity,
154
+ confidence: testFixture
155
+ ? 'medium'
156
+ : 'high',
157
+ message: testFixture
158
+ ? `A secret-like value was introduced in test file ${line.file ?? 'unknown'} in commit ${section.commit}. Confirm that it is synthetic fixture data.`
159
+ : `A secret-like value was introduced in commit ${section.commit}.`,
160
+ source: 'git-history',
161
+ location: line.file
162
+ ? {
163
+ file: line.file
164
+ }
165
+ : undefined,
166
+ evidence: [
167
+ {
168
+ summary: redact(value),
169
+ fingerprint: secretFingerprint
170
+ }
171
+ ],
172
+ remediation: {
173
+ summary: testFixture
174
+ ? 'Confirm that the value is synthetic test data. Replace realistic credential fixtures with clearly fake placeholders where possible.'
175
+ : 'Revoke or rotate the credential immediately, then remove it from repository history using an approved history-rewrite process.'
176
+ },
177
+ metadata: {
178
+ commit: section.commit,
179
+ author: section.author,
180
+ date: section.date,
181
+ file: line.file,
182
+ testFixture
183
+ }
184
+ });
185
+ }
186
+ }
187
+ }
188
+ }
189
+ return {
190
+ analyzer: this.id,
191
+ durationMs: Math.round(performance.now() - startedAt),
192
+ findings,
193
+ metadata: {
194
+ commitsScanned: commitSections.length,
195
+ findings: findings.length,
196
+ testFixtures: findings.filter((finding) => finding.metadata?.testFixture === true).length,
197
+ maxCommits: this.maxCommits
198
+ }
199
+ };
200
+ }
201
+ }
202
+ //# sourceMappingURL=analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../../../src/analyzers/git-history/analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type HistoricalSecretPattern = {
2
+ id: string;
3
+ title: string;
4
+ severity: 'critical' | 'high' | 'medium';
5
+ regex: RegExp;
6
+ };
7
+ export declare const historicalSecretPatterns: HistoricalSecretPattern[];
@@ -0,0 +1,33 @@
1
+ export const historicalSecretPatterns = [
2
+ {
3
+ id: 'TLP-GIT-101',
4
+ title: 'GitHub token in Git history',
5
+ severity: 'critical',
6
+ regex: /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g
7
+ },
8
+ {
9
+ id: 'TLP-GIT-102',
10
+ title: 'npm token in Git history',
11
+ severity: 'critical',
12
+ regex: /\bnpm_[A-Za-z0-9]{20,}\b/g
13
+ },
14
+ {
15
+ id: 'TLP-GIT-103',
16
+ title: 'AWS access key in Git history',
17
+ severity: 'critical',
18
+ regex: /\bAKIA[0-9A-Z]{16}\b/g
19
+ },
20
+ {
21
+ id: 'TLP-GIT-104',
22
+ title: 'Private key in Git history',
23
+ severity: 'critical',
24
+ regex: /-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/g
25
+ },
26
+ {
27
+ id: 'TLP-GIT-105',
28
+ title: 'Password assignment in Git history',
29
+ severity: 'high',
30
+ regex: /\b(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]/gi
31
+ }
32
+ ];
33
+ //# sourceMappingURL=secret-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-patterns.js","sourceRoot":"","sources":["../../../../src/analyzers/git-history/secret-patterns.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,MAAM,wBAAwB,GACP;IAC1B;QACE,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,6BAA6B;QACpC,QAAQ,EAAE,UAAU;QACpB,KAAK,EACH,kCAAkC;KACrC;IACD;QACE,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,0BAA0B;QACjC,QAAQ,EAAE,UAAU;QACpB,KAAK,EACH,2BAA2B;KAC9B;IACD;QACE,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,+BAA+B;QACtC,QAAQ,EAAE,UAAU;QACpB,KAAK,EACH,uBAAuB;KAC1B;IACD;QACE,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,4BAA4B;QACnC,QAAQ,EAAE,UAAU;QACpB,KAAK,EACH,yDAAyD;KAC5D;IACD;QACE,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,oCAAoC;QAC3C,QAAQ,EAAE,MAAM;QAChB,KAAK,EACH,wDAAwD;KAC3D;CACF,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { Analyzer, AnalyzerContext, AnalyzerResult } from '../../contracts/analyzer.js';
2
+ export declare class InstallScriptAnalyzer implements Analyzer {
3
+ readonly id = "install-script-behavior";
4
+ readonly version = "1.0.0";
5
+ analyze(context: AnalyzerContext): Promise<AnalyzerResult>;
6
+ }
@@ -0,0 +1,135 @@
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { readNpmDependencyInventory } from '../../core/dependencies/inventory.js';
4
+ const lifecycleNames = [
5
+ 'preinstall',
6
+ 'install',
7
+ 'postinstall'
8
+ ];
9
+ const signals = [
10
+ {
11
+ id: 'TLP-INSTALL-001',
12
+ title: 'Network access in lifecycle script',
13
+ severity: 'high',
14
+ regex: /\b(curl|wget|Invoke-WebRequest|fetch|axios|http\.get|https\.get)\b/i,
15
+ message: 'The lifecycle script appears to perform network access.',
16
+ remediation: 'Review the remote destination and downloaded content before installation.'
17
+ },
18
+ {
19
+ id: 'TLP-INSTALL-002',
20
+ title: 'Shell execution in lifecycle script',
21
+ severity: 'high',
22
+ regex: /\b(sh|bash|zsh|cmd|powershell|pwsh)\b|child_process|execSync?\s*\(/i,
23
+ message: 'The lifecycle script invokes a shell or child process.',
24
+ remediation: 'Confirm every command and argument is necessary and does not execute untrusted input.'
25
+ },
26
+ {
27
+ id: 'TLP-INSTALL-003',
28
+ title: 'Filesystem write or deletion in lifecycle script',
29
+ severity: 'medium',
30
+ regex: /\b(rm\s+-rf|del\s+\/|rmdir|chmod|chown|writeFile|appendFile|unlink|rename)\b/i,
31
+ message: 'The lifecycle script appears to modify or delete filesystem content.',
32
+ remediation: 'Confirm writes remain within the package boundary and cannot alter sensitive files.'
33
+ },
34
+ {
35
+ id: 'TLP-INSTALL-004',
36
+ title: 'Encoded or obfuscated lifecycle script',
37
+ severity: 'high',
38
+ regex: /\b(base64|atob|fromCharCode|Buffer\.from\([^)]*,\s*['"]base64['"]|eval\s*\()/i,
39
+ message: 'The lifecycle script contains encoding or dynamic execution indicators.',
40
+ remediation: 'Decode and review the complete payload before allowing the script to run.'
41
+ },
42
+ {
43
+ id: 'TLP-INSTALL-005',
44
+ title: 'Credential or environment inspection in lifecycle script',
45
+ severity: 'medium',
46
+ regex: /\b(process\.env|AWS_|GITHUB_TOKEN|NPM_TOKEN|HOME|USERPROFILE|SSH_AUTH_SOCK)\b/i,
47
+ message: 'The lifecycle script reads environment or credential-related values.',
48
+ remediation: 'Verify that the package does not exfiltrate or persist sensitive environment data.'
49
+ }
50
+ ];
51
+ async function exists(filePath) {
52
+ try {
53
+ await access(filePath);
54
+ return true;
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ }
60
+ export class InstallScriptAnalyzer {
61
+ id = 'install-script-behavior';
62
+ version = '1.0.0';
63
+ async analyze(context) {
64
+ const startedAt = performance.now();
65
+ const dependencies = await readNpmDependencyInventory(context.root);
66
+ const findings = [];
67
+ let packagesInspected = 0;
68
+ let lifecycleScripts = 0;
69
+ for (const dependency of dependencies) {
70
+ if (context.signal?.aborted) {
71
+ throw new Error('Install-script analysis was cancelled.');
72
+ }
73
+ const manifestPath = path.join(context.root, 'node_modules', ...dependency.name.split('/'), 'package.json');
74
+ if (!(await exists(manifestPath))) {
75
+ continue;
76
+ }
77
+ const manifest = JSON.parse(await readFile(manifestPath, 'utf8'));
78
+ packagesInspected += 1;
79
+ for (const lifecycleName of lifecycleNames) {
80
+ const command = manifest.scripts?.[lifecycleName];
81
+ if (!command) {
82
+ continue;
83
+ }
84
+ lifecycleScripts += 1;
85
+ for (const signal of signals) {
86
+ signal.regex.lastIndex = 0;
87
+ if (!signal.regex.test(command)) {
88
+ continue;
89
+ }
90
+ findings.push({
91
+ id: `${signal.id}:${dependency.name}:` +
92
+ `${dependency.version}:${lifecycleName}`,
93
+ ruleId: signal.id,
94
+ title: signal.title,
95
+ category: 'install-script',
96
+ severity: signal.severity,
97
+ confidence: 'medium',
98
+ message: `${dependency.name}@${dependency.version} ` +
99
+ `defines ${lifecycleName}: ${signal.message}`,
100
+ source: 'package-manifest',
101
+ evidence: [
102
+ {
103
+ summary: `${lifecycleName}: ${command.slice(0, 240)}`,
104
+ fingerprint: `${dependency.name}@${dependency.version}:` +
105
+ `${lifecycleName}:${signal.id}`
106
+ }
107
+ ],
108
+ remediation: {
109
+ summary: signal.remediation
110
+ },
111
+ metadata: {
112
+ package: dependency.name,
113
+ version: dependency.version,
114
+ direct: dependency.direct,
115
+ development: dependency.development,
116
+ lifecycle: lifecycleName,
117
+ command
118
+ }
119
+ });
120
+ }
121
+ }
122
+ }
123
+ return {
124
+ analyzer: this.id,
125
+ durationMs: Math.round(performance.now() - startedAt),
126
+ findings,
127
+ metadata: {
128
+ packagesInspected,
129
+ lifecycleScripts,
130
+ findings: findings.length
131
+ }
132
+ };
133
+ }
134
+ }
135
+ //# sourceMappingURL=install-script-analyzer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-script-analyzer.js","sourceRoot":"","sources":["../../../../src/analyzers/install-scripts/install-script-analyzer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type ImportReference = {
2
+ packageName: string;
3
+ importedSymbols: string[];
4
+ localSymbols: string[];
5
+ kind: 'default' | 'named' | 'namespace' | 'side-effect' | 'require' | 'dynamic-import';
6
+ line: number;
7
+ column: number;
8
+ };
9
+ export declare function extractPackageImports(filePath: string, content: string): ImportReference[];
@@ -0,0 +1,110 @@
1
+ import ts from 'typescript';
2
+ function packageRoot(specifier) {
3
+ if (specifier.startsWith('.') ||
4
+ specifier.startsWith('/') ||
5
+ specifier.startsWith('node:')) {
6
+ return undefined;
7
+ }
8
+ if (specifier.startsWith('@')) {
9
+ const [scope, name] = specifier.split('/');
10
+ return scope && name ? `${scope}/${name}` : undefined;
11
+ }
12
+ return specifier.split('/')[0];
13
+ }
14
+ function location(sourceFile, node) {
15
+ const value = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
16
+ return {
17
+ line: value.line + 1,
18
+ column: value.character + 1
19
+ };
20
+ }
21
+ export function extractPackageImports(filePath, content) {
22
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx')
23
+ ? ts.ScriptKind.TSX
24
+ : filePath.endsWith('.jsx')
25
+ ? ts.ScriptKind.JSX
26
+ : filePath.endsWith('.js') ||
27
+ filePath.endsWith('.mjs') ||
28
+ filePath.endsWith('.cjs')
29
+ ? ts.ScriptKind.JS
30
+ : ts.ScriptKind.TS);
31
+ const references = [];
32
+ const visit = (node) => {
33
+ if (ts.isImportDeclaration(node) &&
34
+ ts.isStringLiteral(node.moduleSpecifier)) {
35
+ const root = packageRoot(node.moduleSpecifier.text);
36
+ if (root) {
37
+ const importedSymbols = [];
38
+ const localSymbols = [];
39
+ let kind = 'side-effect';
40
+ const clause = node.importClause;
41
+ if (clause?.name) {
42
+ kind = 'default';
43
+ importedSymbols.push('default');
44
+ localSymbols.push(clause.name.text);
45
+ }
46
+ if (clause?.namedBindings) {
47
+ if (ts.isNamespaceImport(clause.namedBindings)) {
48
+ kind = 'namespace';
49
+ importedSymbols.push('*');
50
+ localSymbols.push(clause.namedBindings.name.text);
51
+ }
52
+ else {
53
+ kind = 'named';
54
+ for (const element of clause.namedBindings.elements) {
55
+ importedSymbols.push(element.propertyName?.text ??
56
+ element.name.text);
57
+ localSymbols.push(element.name.text);
58
+ }
59
+ }
60
+ }
61
+ references.push({
62
+ packageName: root,
63
+ importedSymbols,
64
+ localSymbols,
65
+ kind,
66
+ ...location(sourceFile, node)
67
+ });
68
+ }
69
+ }
70
+ if (ts.isCallExpression(node) &&
71
+ ts.isIdentifier(node.expression) &&
72
+ node.expression.text === 'require' &&
73
+ node.arguments.length === 1) {
74
+ const [argument] = node.arguments;
75
+ if (argument && ts.isStringLiteral(argument)) {
76
+ const root = packageRoot(argument.text);
77
+ if (root) {
78
+ references.push({
79
+ packageName: root,
80
+ importedSymbols: ['*'],
81
+ localSymbols: [],
82
+ kind: 'require',
83
+ ...location(sourceFile, node)
84
+ });
85
+ }
86
+ }
87
+ }
88
+ if (ts.isCallExpression(node) &&
89
+ node.expression.kind === ts.SyntaxKind.ImportKeyword &&
90
+ node.arguments.length === 1) {
91
+ const [argument] = node.arguments;
92
+ if (argument && ts.isStringLiteral(argument)) {
93
+ const root = packageRoot(argument.text);
94
+ if (root) {
95
+ references.push({
96
+ packageName: root,
97
+ importedSymbols: ['*'],
98
+ localSymbols: [],
99
+ kind: 'dynamic-import',
100
+ ...location(sourceFile, node)
101
+ });
102
+ }
103
+ }
104
+ }
105
+ ts.forEachChild(node, visit);
106
+ };
107
+ visit(sourceFile);
108
+ return references;
109
+ }
110
+ //# sourceMappingURL=import-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-graph.js","sourceRoot":"","sources":["../../../../src/analyzers/reachability/import-graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Analyzer, AnalyzerContext, AnalyzerResult } from '../../contracts/analyzer.js';
2
+ import { type ImportReference } from './import-graph.js';
3
+ export type PackageReachability = {
4
+ packageName: string;
5
+ state: 'reachable' | 'possibly-reachable' | 'not-observed';
6
+ direct: boolean;
7
+ development: boolean;
8
+ references: Array<ImportReference & {
9
+ file: string;
10
+ }>;
11
+ };
12
+ export declare class ReachabilityAnalyzer implements Analyzer {
13
+ readonly id = "package-reachability";
14
+ readonly version = "1.0.0";
15
+ analyze(context: AnalyzerContext): Promise<AnalyzerResult>;
16
+ }
@@ -0,0 +1,95 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { createScannerContext } from '../../core/scanner-context.js';
3
+ import { readNpmDependencyInventory } from '../../core/dependencies/inventory.js';
4
+ import { extractPackageImports } from './import-graph.js';
5
+ const codeExtensions = new Set([
6
+ 'js',
7
+ 'jsx',
8
+ 'ts',
9
+ 'tsx',
10
+ 'mjs',
11
+ 'cjs'
12
+ ]);
13
+ export class ReachabilityAnalyzer {
14
+ id = 'package-reachability';
15
+ version = '1.0.0';
16
+ async analyze(context) {
17
+ const startedAt = performance.now();
18
+ const dependencies = await readNpmDependencyInventory(context.root);
19
+ const scannerContext = await createScannerContext(context.root);
20
+ const references = new Map();
21
+ for (const file of scannerContext.files) {
22
+ if (!codeExtensions.has(file.extension) ||
23
+ file.relativePath.endsWith('.d.ts') ||
24
+ file.relativePath.startsWith('dist/')) {
25
+ continue;
26
+ }
27
+ if (context.signal?.aborted) {
28
+ throw new Error('Reachability analysis was cancelled.');
29
+ }
30
+ const content = await readFile(file.absolutePath, 'utf8');
31
+ for (const reference of extractPackageImports(file.relativePath, content)) {
32
+ const current = references.get(reference.packageName) ?? [];
33
+ current.push({
34
+ ...reference,
35
+ file: file.relativePath
36
+ });
37
+ references.set(reference.packageName, current);
38
+ }
39
+ }
40
+ const packageStates = dependencies.map((dependency) => {
41
+ const packageReferences = references.get(dependency.name) ?? [];
42
+ return {
43
+ packageName: dependency.name,
44
+ state: packageReferences.length > 0
45
+ ? 'reachable'
46
+ : dependency.direct
47
+ ? 'possibly-reachable'
48
+ : 'not-observed',
49
+ direct: dependency.direct,
50
+ development: dependency.development,
51
+ references: packageReferences
52
+ };
53
+ });
54
+ const findings = packageStates
55
+ .filter((item) => item.state === 'reachable')
56
+ .map((item) => ({
57
+ id: `TLP-REACH-${item.packageName
58
+ .toUpperCase()
59
+ .replaceAll(/[^A-Z0-9]/g, '-')}`,
60
+ ruleId: 'TLP-REACH-001',
61
+ title: `Package usage observed: ${item.packageName}`,
62
+ category: 'reachability',
63
+ severity: 'info',
64
+ confidence: 'high',
65
+ message: `${item.packageName} is imported or required by project source files.`,
66
+ source: 'typescript-ast',
67
+ evidence: item.references.slice(0, 20).map((reference) => ({
68
+ summary: `${reference.file}:${reference.line}:${reference.column} ` +
69
+ `${reference.kind} import`,
70
+ fingerprint: `${item.packageName}:${reference.file}:` +
71
+ `${reference.line}:${reference.column}`
72
+ })),
73
+ metadata: {
74
+ package: item.packageName,
75
+ state: item.state,
76
+ direct: item.direct,
77
+ development: item.development,
78
+ references: item.references
79
+ }
80
+ }));
81
+ return {
82
+ analyzer: this.id,
83
+ durationMs: Math.round(performance.now() - startedAt),
84
+ findings,
85
+ metadata: {
86
+ dependencies: packageStates.length,
87
+ reachable: packageStates.filter((item) => item.state === 'reachable').length,
88
+ possiblyReachable: packageStates.filter((item) => item.state === 'possibly-reachable').length,
89
+ notObserved: packageStates.filter((item) => item.state === 'not-observed').length,
90
+ packages: packageStates
91
+ }
92
+ };
93
+ }
94
+ }
95
+ //# sourceMappingURL=reachability-analyzer.js.map