@hongmaple0820/scale-engine 0.23.0 → 0.24.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 (64) hide show
  1. package/dist/api/cli.js +192 -1
  2. package/dist/api/cli.js.map +1 -1
  3. package/dist/dashboard/MetricsAggregator.d.ts +38 -0
  4. package/dist/dashboard/MetricsAggregator.js +99 -0
  5. package/dist/dashboard/MetricsAggregator.js.map +1 -0
  6. package/dist/dashboard/index.d.ts +2 -0
  7. package/dist/dashboard/index.js +1 -0
  8. package/dist/dashboard/index.js.map +1 -1
  9. package/dist/dashboard/server.js +1 -1
  10. package/dist/dashboard/server.js.map +1 -1
  11. package/dist/evolution/AutoDefectCreator.d.ts +11 -2
  12. package/dist/evolution/AutoDefectCreator.js +46 -2
  13. package/dist/evolution/AutoDefectCreator.js.map +1 -1
  14. package/dist/evolution/EvolutionEngine.d.ts +3 -0
  15. package/dist/evolution/EvolutionEngine.js +18 -2
  16. package/dist/evolution/EvolutionEngine.js.map +1 -1
  17. package/dist/evolution/RuleMaturity.d.ts +39 -0
  18. package/dist/evolution/RuleMaturity.js +70 -0
  19. package/dist/evolution/RuleMaturity.js.map +1 -0
  20. package/dist/guardrails/ActiveRedTeam.d.ts +46 -0
  21. package/dist/guardrails/ActiveRedTeam.js +203 -0
  22. package/dist/guardrails/ActiveRedTeam.js.map +1 -0
  23. package/dist/guardrails/DependencyAuditor.d.ts +68 -0
  24. package/dist/guardrails/DependencyAuditor.js +331 -0
  25. package/dist/guardrails/DependencyAuditor.js.map +1 -0
  26. package/dist/hooks/HookGeneratorEnhanced.js +18 -18
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.js +5 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/output/GovernanceDashboard.d.ts +2 -0
  31. package/dist/output/GovernanceDashboard.js +31 -0
  32. package/dist/output/GovernanceDashboard.js.map +1 -1
  33. package/dist/routing/PromptCachePolicy.d.ts +37 -0
  34. package/dist/routing/PromptCachePolicy.js +97 -0
  35. package/dist/routing/PromptCachePolicy.js.map +1 -0
  36. package/dist/runtime/ModelUsageLedger.d.ts +50 -0
  37. package/dist/runtime/ModelUsageLedger.js +92 -0
  38. package/dist/runtime/ModelUsageLedger.js.map +1 -0
  39. package/dist/runtime/index.d.ts +1 -0
  40. package/dist/runtime/index.js +1 -0
  41. package/dist/runtime/index.js.map +1 -1
  42. package/dist/workflow/autonomous/BackgroundHunter.d.ts +74 -0
  43. package/dist/workflow/autonomous/BackgroundHunter.js +220 -0
  44. package/dist/workflow/autonomous/BackgroundHunter.js.map +1 -0
  45. package/dist/workflow/autonomous/index.d.ts +1 -0
  46. package/dist/workflow/autonomous/index.js +1 -0
  47. package/dist/workflow/autonomous/index.js.map +1 -1
  48. package/dist/workflow/gates/GateSystem.d.ts +10 -0
  49. package/dist/workflow/gates/GateSystem.js +62 -0
  50. package/dist/workflow/gates/GateSystem.js.map +1 -1
  51. package/dist/workflow/gates/VisualGate.d.ts +41 -0
  52. package/dist/workflow/gates/VisualGate.js +174 -0
  53. package/dist/workflow/gates/VisualGate.js.map +1 -0
  54. package/dist/workflow/index.d.ts +1 -0
  55. package/dist/workflow/index.js +1 -0
  56. package/dist/workflow/index.js.map +1 -1
  57. package/docs/ACTIVE_SECURITY_VISUAL_GATES.md +87 -0
  58. package/docs/BACKGROUND_HUNTER.md +62 -0
  59. package/docs/CONTEXT_BUDGET.md +32 -6
  60. package/docs/DEPENDENCY_AUDIT.md +89 -0
  61. package/docs/EVOLUTION_SHADOW_MODE.md +63 -0
  62. package/docs/GOVERNANCE_DASHBOARD.md +21 -5
  63. package/docs/README.md +22 -12
  64. package/package.json +13 -9
@@ -0,0 +1,46 @@
1
+ export type ActiveRedTeamSeverity = 'HIGH' | 'MEDIUM' | 'LOW';
2
+ export type ActiveRedTeamStatus = 'SKIPPED' | 'PASSED' | 'FAILED';
3
+ export interface ActiveRedTeamConfig {
4
+ enabled?: boolean;
5
+ baseUrl?: string;
6
+ startCommand?: string;
7
+ targets?: string[];
8
+ payloads?: string[];
9
+ timeoutMs?: number;
10
+ maxRequests?: number;
11
+ }
12
+ export interface ActiveRedTeamFinding {
13
+ ruleId: string;
14
+ severity: ActiveRedTeamSeverity;
15
+ target: string;
16
+ message: string;
17
+ evidence?: string;
18
+ }
19
+ export interface ActiveRedTeamProbe {
20
+ target: string;
21
+ url: string;
22
+ status?: number;
23
+ passed: boolean;
24
+ findingIds?: string[];
25
+ error?: string;
26
+ }
27
+ export interface ActiveRedTeamReport {
28
+ ok: boolean;
29
+ status: ActiveRedTeamStatus;
30
+ evidence: string;
31
+ findings: ActiveRedTeamFinding[];
32
+ probes: ActiveRedTeamProbe[];
33
+ blockers: string[];
34
+ summary: {
35
+ targets: number;
36
+ requests: number;
37
+ findings: number;
38
+ };
39
+ }
40
+ export interface ActiveRedTeamRuntime {
41
+ fetch?: (url: string, init?: RequestInit) => Promise<{
42
+ status: number;
43
+ text(): Promise<string>;
44
+ }>;
45
+ }
46
+ export declare function runActiveRedTeam(config?: ActiveRedTeamConfig, runtime?: ActiveRedTeamRuntime): Promise<ActiveRedTeamReport>;
@@ -0,0 +1,203 @@
1
+ const DEFAULT_PAYLOADS = [
2
+ '<scale-probe>',
3
+ '"\'<scale-probe>',
4
+ ];
5
+ const DEFAULT_TIMEOUT_MS = 5000;
6
+ const DEFAULT_MAX_REQUESTS = 20;
7
+ export async function runActiveRedTeam(config, runtime = {}) {
8
+ if (!config?.enabled) {
9
+ return createReport({
10
+ ok: true,
11
+ status: 'SKIPPED',
12
+ evidence: 'Active security not enabled or not configured.',
13
+ });
14
+ }
15
+ const resolved = resolveConfig(config);
16
+ if (resolved.blockers.length > 0 || !resolved.config) {
17
+ return createReport({
18
+ ok: false,
19
+ status: 'FAILED',
20
+ evidence: `Active security configuration invalid: ${resolved.blockers.join('; ')}`,
21
+ blockers: resolved.blockers,
22
+ });
23
+ }
24
+ const fetchImpl = runtime.fetch ?? globalThis.fetch;
25
+ if (!fetchImpl) {
26
+ return createReport({
27
+ ok: false,
28
+ status: 'FAILED',
29
+ evidence: 'Active security requires fetch support in the current runtime.',
30
+ blockers: ['fetch runtime is unavailable'],
31
+ summary: {
32
+ targets: resolved.config.targets.length,
33
+ requests: 0,
34
+ findings: 0,
35
+ },
36
+ });
37
+ }
38
+ const probes = [];
39
+ const findings = [];
40
+ const requests = buildRequests(resolved.config);
41
+ for (const request of requests) {
42
+ const probe = {
43
+ target: request.target,
44
+ url: request.url,
45
+ passed: true,
46
+ findingIds: [],
47
+ };
48
+ try {
49
+ const response = await fetchWithTimeout(fetchImpl, request.url, resolved.config.timeoutMs);
50
+ const text = await response.text();
51
+ probe.status = response.status;
52
+ if (text.includes(request.payload)) {
53
+ const finding = {
54
+ ruleId: 'active.reflected-payload',
55
+ severity: 'HIGH',
56
+ target: request.target,
57
+ message: `Probe payload was reflected by ${request.target}.`,
58
+ evidence: request.payload,
59
+ };
60
+ findings.push(finding);
61
+ probe.passed = false;
62
+ probe.findingIds?.push(finding.ruleId);
63
+ }
64
+ if (response.status >= 500) {
65
+ const finding = {
66
+ ruleId: 'active.server-error',
67
+ severity: 'MEDIUM',
68
+ target: request.target,
69
+ message: `Probe returned HTTP ${response.status} for ${request.target}.`,
70
+ evidence: `status=${response.status}`,
71
+ };
72
+ findings.push(finding);
73
+ probe.passed = false;
74
+ probe.findingIds?.push(finding.ruleId);
75
+ }
76
+ }
77
+ catch (error) {
78
+ probe.passed = false;
79
+ probe.error = error instanceof Error ? error.message : String(error);
80
+ findings.push({
81
+ ruleId: 'active.probe-error',
82
+ severity: 'MEDIUM',
83
+ target: request.target,
84
+ message: `Active security probe failed for ${request.target}.`,
85
+ evidence: probe.error,
86
+ });
87
+ probe.findingIds?.push('active.probe-error');
88
+ }
89
+ probes.push(probe);
90
+ }
91
+ const blockers = findings
92
+ .filter(finding => finding.severity === 'HIGH')
93
+ .map(finding => `${finding.ruleId}: ${finding.message}`);
94
+ const ok = blockers.length === 0;
95
+ const evidence = ok
96
+ ? `Active security probes passed (${probes.length} requests, ${findings.length} findings).`
97
+ : `Active security found blockers: ${blockers.join('; ')}`;
98
+ return createReport({
99
+ ok,
100
+ status: ok ? 'PASSED' : 'FAILED',
101
+ evidence,
102
+ findings,
103
+ probes,
104
+ blockers,
105
+ summary: {
106
+ targets: resolved.config.targets.length,
107
+ requests: probes.length,
108
+ findings: findings.length,
109
+ },
110
+ });
111
+ }
112
+ function resolveConfig(input) {
113
+ const blockers = [];
114
+ if (!input.baseUrl) {
115
+ blockers.push('security.active.baseUrl is required');
116
+ }
117
+ else {
118
+ try {
119
+ const url = new URL(input.baseUrl);
120
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
121
+ blockers.push('security.active.baseUrl must use http or https');
122
+ }
123
+ }
124
+ catch {
125
+ blockers.push('security.active.baseUrl must be a valid URL');
126
+ }
127
+ }
128
+ const targets = input.targets?.filter(Boolean) ?? [];
129
+ if (targets.length === 0) {
130
+ blockers.push('security.active.targets must contain at least one target');
131
+ }
132
+ const payloads = input.payloads?.filter(Boolean) ?? DEFAULT_PAYLOADS;
133
+ if (payloads.length === 0) {
134
+ blockers.push('security.active.payloads must contain at least one payload');
135
+ }
136
+ const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
137
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
138
+ blockers.push('security.active.timeoutMs must be greater than zero');
139
+ }
140
+ const maxRequests = Math.min(input.maxRequests ?? DEFAULT_MAX_REQUESTS, DEFAULT_MAX_REQUESTS);
141
+ if (!Number.isFinite(maxRequests) || maxRequests <= 0) {
142
+ blockers.push('security.active.maxRequests must be greater than zero');
143
+ }
144
+ if (blockers.length > 0 || !input.baseUrl) {
145
+ return { blockers };
146
+ }
147
+ return {
148
+ blockers,
149
+ config: {
150
+ baseUrl: input.baseUrl,
151
+ targets,
152
+ payloads,
153
+ timeoutMs,
154
+ maxRequests,
155
+ },
156
+ };
157
+ }
158
+ function buildRequests(config) {
159
+ const requests = [];
160
+ for (const target of config.targets) {
161
+ for (const payload of config.payloads) {
162
+ const url = new URL(target, config.baseUrl);
163
+ url.searchParams.set('scale_probe', payload);
164
+ requests.push({
165
+ target,
166
+ payload,
167
+ url: url.toString(),
168
+ });
169
+ if (requests.length >= config.maxRequests) {
170
+ return requests;
171
+ }
172
+ }
173
+ }
174
+ return requests;
175
+ }
176
+ async function fetchWithTimeout(fetchImpl, url, timeoutMs) {
177
+ const controller = new AbortController();
178
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
179
+ try {
180
+ return await fetchImpl(url, { signal: controller.signal });
181
+ }
182
+ finally {
183
+ clearTimeout(timeout);
184
+ }
185
+ }
186
+ function createReport(input) {
187
+ const findings = input.findings ?? [];
188
+ const probes = input.probes ?? [];
189
+ return {
190
+ ok: input.ok,
191
+ status: input.status,
192
+ evidence: input.evidence,
193
+ findings,
194
+ probes,
195
+ blockers: input.blockers ?? [],
196
+ summary: input.summary ?? {
197
+ targets: 0,
198
+ requests: probes.length,
199
+ findings: findings.length,
200
+ },
201
+ };
202
+ }
203
+ //# sourceMappingURL=ActiveRedTeam.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActiveRedTeam.js","sourceRoot":"","sources":["../../src/guardrails/ActiveRedTeam.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ export type DependencyAuditSeverity = 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
2
+ export type DependencyAuditMode = 'compatibility' | 'strict' | 'offline';
3
+ export interface DependencyAuditPolicyFile {
4
+ version?: number;
5
+ mode?: DependencyAuditMode;
6
+ maxPackages?: number;
7
+ maxPackageFiles?: number;
8
+ allowPackages?: string[];
9
+ baselineFindings?: Array<{
10
+ packageName: string;
11
+ version?: string;
12
+ ruleId: string;
13
+ reason?: string;
14
+ }>;
15
+ }
16
+ export interface ResolvedDependencyAuditPolicy {
17
+ version: number;
18
+ mode: DependencyAuditMode;
19
+ maxPackages: number;
20
+ maxPackageFiles: number;
21
+ allowPackages: string[];
22
+ baselineFindings: Array<{
23
+ packageName: string;
24
+ version?: string;
25
+ ruleId: string;
26
+ reason?: string;
27
+ }>;
28
+ warnings: string[];
29
+ }
30
+ export interface DependencyAuditFinding {
31
+ packageName: string;
32
+ version?: string;
33
+ ruleId: string;
34
+ severity: DependencyAuditSeverity;
35
+ path?: string;
36
+ message: string;
37
+ evidence?: string;
38
+ fix?: string;
39
+ }
40
+ export interface DependencyAuditSummary {
41
+ packagesAudited: number;
42
+ totalFindings: number;
43
+ bySeverity: Record<DependencyAuditSeverity, number>;
44
+ }
45
+ export interface DependencyAuditReport {
46
+ ok: boolean;
47
+ projectDir: string;
48
+ lockfilePath?: string;
49
+ policyPath: string;
50
+ mode: DependencyAuditMode;
51
+ packagesAudited: string[];
52
+ findings: DependencyAuditFinding[];
53
+ blockers: string[];
54
+ summary: DependencyAuditSummary;
55
+ warnings: string[];
56
+ }
57
+ export interface DependencyAuditOptions {
58
+ projectDir?: string;
59
+ scaleDir?: string;
60
+ mode?: DependencyAuditMode;
61
+ changedPackages?: string[];
62
+ maxPackages?: number;
63
+ maxPackageFiles?: number;
64
+ }
65
+ export declare function dependencyAuditPolicyTemplate(): string;
66
+ export declare function dependencyAuditPolicyPath(projectDir?: string, scaleDir?: string): string;
67
+ export declare function loadDependencyAuditPolicy(projectDir?: string, scaleDir?: string): ResolvedDependencyAuditPolicy;
68
+ export declare function auditDependencies(options?: DependencyAuditOptions): DependencyAuditReport;
@@ -0,0 +1,331 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { basename, join, relative, sep } from 'node:path';
3
+ const SOURCE_EXTENSIONS = new Set(['.js', '.cjs', '.mjs']);
4
+ const DEFAULT_MAX_PACKAGES = 50;
5
+ const DEFAULT_MAX_PACKAGE_FILES = 25;
6
+ export function dependencyAuditPolicyTemplate() {
7
+ return JSON.stringify({
8
+ version: 1,
9
+ mode: 'compatibility',
10
+ maxPackages: DEFAULT_MAX_PACKAGES,
11
+ maxPackageFiles: DEFAULT_MAX_PACKAGE_FILES,
12
+ allowPackages: [],
13
+ baselineFindings: [],
14
+ }, null, 2) + '\n';
15
+ }
16
+ export function dependencyAuditPolicyPath(projectDir = process.cwd(), scaleDir = '.scale') {
17
+ const scaleRoot = scaleDir.includes(':') || scaleDir.startsWith('/') || scaleDir.startsWith('\\\\')
18
+ ? scaleDir
19
+ : join(projectDir, scaleDir);
20
+ return join(scaleRoot, 'security', 'dependency-policy.json');
21
+ }
22
+ export function loadDependencyAuditPolicy(projectDir = process.cwd(), scaleDir = '.scale') {
23
+ const path = dependencyAuditPolicyPath(projectDir, scaleDir);
24
+ const warnings = [];
25
+ let parsed = {};
26
+ if (existsSync(path)) {
27
+ try {
28
+ parsed = JSON.parse(readFileSync(path, 'utf-8'));
29
+ }
30
+ catch (error) {
31
+ warnings.push(`Failed to read ${path}: ${error instanceof Error ? error.message : String(error)}; using built-in defaults.`);
32
+ }
33
+ }
34
+ return {
35
+ version: typeof parsed.version === 'number' ? parsed.version : 1,
36
+ mode: normalizeMode(parsed.mode) ?? 'compatibility',
37
+ maxPackages: positiveNumber(parsed.maxPackages) ?? DEFAULT_MAX_PACKAGES,
38
+ maxPackageFiles: positiveNumber(parsed.maxPackageFiles) ?? DEFAULT_MAX_PACKAGE_FILES,
39
+ allowPackages: Array.isArray(parsed.allowPackages)
40
+ ? parsed.allowPackages.filter(item => typeof item === 'string' && item.length > 0)
41
+ : [],
42
+ baselineFindings: Array.isArray(parsed.baselineFindings)
43
+ ? parsed.baselineFindings.filter(item => typeof item.packageName === 'string' &&
44
+ typeof item.ruleId === 'string')
45
+ : [],
46
+ warnings,
47
+ };
48
+ }
49
+ export function auditDependencies(options = {}) {
50
+ const projectDir = options.projectDir ?? process.cwd();
51
+ const scaleDir = options.scaleDir ?? '.scale';
52
+ const policy = loadDependencyAuditPolicy(projectDir, scaleDir);
53
+ const mode = options.mode ?? policy.mode;
54
+ const lockfilePath = join(projectDir, 'package-lock.json');
55
+ const warnings = [...policy.warnings];
56
+ if (!existsSync(lockfilePath)) {
57
+ warnings.push('No package-lock.json found; dependency audit skipped.');
58
+ return createReport({
59
+ projectDir,
60
+ policyPath: dependencyAuditPolicyPath(projectDir, scaleDir),
61
+ mode,
62
+ packagesAudited: [],
63
+ findings: [],
64
+ warnings,
65
+ });
66
+ }
67
+ let lockfile;
68
+ try {
69
+ lockfile = JSON.parse(readFileSync(lockfilePath, 'utf-8'));
70
+ }
71
+ catch (error) {
72
+ warnings.push(`Failed to parse package-lock.json: ${error instanceof Error ? error.message : String(error)}`);
73
+ return createReport({
74
+ projectDir,
75
+ lockfilePath,
76
+ policyPath: dependencyAuditPolicyPath(projectDir, scaleDir),
77
+ mode,
78
+ packagesAudited: [],
79
+ findings: [{
80
+ packageName: 'package-lock.json',
81
+ ruleId: 'dependency.lockfile-invalid',
82
+ severity: mode === 'strict' ? 'HIGH' : 'LOW',
83
+ path: 'package-lock.json',
84
+ message: 'package-lock.json could not be parsed.',
85
+ }],
86
+ warnings,
87
+ });
88
+ }
89
+ const packages = selectPackages(lockfile, {
90
+ changedPackages: options.changedPackages,
91
+ maxPackages: options.maxPackages ?? policy.maxPackages,
92
+ }).filter(pkg => !policy.allowPackages.includes(pkg.name));
93
+ const rawFindings = packages.flatMap(pkg => scanPackage(projectDir, pkg, options.maxPackageFiles ?? policy.maxPackageFiles));
94
+ const findings = rawFindings.filter(finding => !isBaselineFinding(finding, policy));
95
+ return createReport({
96
+ projectDir,
97
+ lockfilePath,
98
+ policyPath: dependencyAuditPolicyPath(projectDir, scaleDir),
99
+ mode,
100
+ packagesAudited: packages.map(pkg => pkg.name),
101
+ findings,
102
+ warnings,
103
+ });
104
+ }
105
+ function createReport(input) {
106
+ const blockers = input.findings
107
+ .filter(finding => shouldBlock(finding.severity, input.mode))
108
+ .map(finding => `${finding.severity} ${finding.ruleId} in ${finding.packageName}${finding.version ? `@${finding.version}` : ''} - ${finding.message}`);
109
+ return {
110
+ ok: blockers.length === 0,
111
+ projectDir: input.projectDir,
112
+ lockfilePath: input.lockfilePath,
113
+ policyPath: input.policyPath,
114
+ mode: input.mode,
115
+ packagesAudited: input.packagesAudited,
116
+ findings: input.findings,
117
+ blockers,
118
+ summary: summarize(input.packagesAudited.length, input.findings),
119
+ warnings: input.warnings,
120
+ };
121
+ }
122
+ function selectPackages(lockfile, options) {
123
+ const packages = lockfile.packages ?? {};
124
+ const root = packages[''] ?? {};
125
+ const direct = new Set([
126
+ ...Object.keys(root.dependencies ?? {}),
127
+ ...Object.keys(root.devDependencies ?? {}),
128
+ ...Object.keys(root.optionalDependencies ?? {}),
129
+ ]);
130
+ const requested = new Set(options.changedPackages?.length ? options.changedPackages : [...direct]);
131
+ return Object.entries(packages)
132
+ .filter(([path]) => isTopLevelNodeModulePath(path))
133
+ .map(([path, metadata]) => ({ name: packageNameFromLockPath(path), path, metadata }))
134
+ .filter(pkg => requested.has(pkg.name))
135
+ .sort((a, b) => a.name.localeCompare(b.name))
136
+ .slice(0, Math.max(0, options.maxPackages));
137
+ }
138
+ function scanPackage(projectDir, pkg, maxPackageFiles) {
139
+ const findings = [];
140
+ if (pkg.metadata.hasInstallScript) {
141
+ findings.push({
142
+ packageName: pkg.name,
143
+ version: pkg.metadata.version,
144
+ ruleId: 'dependency.install-script',
145
+ severity: 'HIGH',
146
+ path: pkg.path,
147
+ message: 'Dependency declares an install lifecycle script.',
148
+ evidence: 'hasInstallScript=true',
149
+ fix: 'Review package provenance, pin the version, or replace the dependency.',
150
+ });
151
+ }
152
+ if (pkg.metadata.bin) {
153
+ findings.push({
154
+ packageName: pkg.name,
155
+ version: pkg.metadata.version,
156
+ ruleId: 'dependency.bin-script',
157
+ severity: 'MEDIUM',
158
+ path: pkg.path,
159
+ message: 'Dependency exposes executable bin scripts.',
160
+ evidence: typeof pkg.metadata.bin === 'string' ? pkg.metadata.bin : Object.keys(pkg.metadata.bin).join(', '),
161
+ fix: 'Verify the command is intentionally used and comes from a trusted package.',
162
+ });
163
+ }
164
+ if (pkg.metadata.deprecated) {
165
+ findings.push({
166
+ packageName: pkg.name,
167
+ version: pkg.metadata.version,
168
+ ruleId: 'dependency.deprecated',
169
+ severity: 'HIGH',
170
+ path: pkg.path,
171
+ message: 'Dependency is marked deprecated.',
172
+ evidence: pkg.metadata.deprecated,
173
+ fix: 'Upgrade or replace the deprecated dependency.',
174
+ });
175
+ }
176
+ findings.push(...scanPackageSource(projectDir, pkg, maxPackageFiles));
177
+ return findings;
178
+ }
179
+ function scanPackageSource(projectDir, pkg, maxPackageFiles) {
180
+ const root = join(projectDir, ...pkg.path.split('/'));
181
+ if (!existsSync(root))
182
+ return [];
183
+ const files = [];
184
+ walkPackage(root, files, maxPackageFiles);
185
+ const findings = [];
186
+ for (const file of files) {
187
+ let content = '';
188
+ try {
189
+ content = readFileSync(file, 'utf-8');
190
+ }
191
+ catch {
192
+ continue;
193
+ }
194
+ if (content.includes('\u0000'))
195
+ continue;
196
+ const relativePath = normalizePath(relative(projectDir, file));
197
+ const lines = content.split(/\r?\n/);
198
+ for (let index = 0; index < lines.length; index += 1) {
199
+ const line = lines[index];
200
+ const finding = sourceFindingForLine(pkg, relativePath, index + 1, line);
201
+ if (finding)
202
+ findings.push(finding);
203
+ }
204
+ }
205
+ return findings;
206
+ }
207
+ function sourceFindingForLine(pkg, path, lineNumber, line) {
208
+ const trimmed = line.trim();
209
+ if (trimmed === '' || trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'))
210
+ return null;
211
+ if (/\beval\s*\(|new\s+Function\s*\(/.test(trimmed)) {
212
+ return {
213
+ packageName: pkg.name,
214
+ version: pkg.metadata.version,
215
+ ruleId: 'dependency.eval',
216
+ severity: 'CRITICAL',
217
+ path,
218
+ message: 'Dependency source uses dynamic code execution.',
219
+ evidence: `line ${lineNumber}: ${trimmed.slice(0, 180)}`,
220
+ fix: 'Replace the dependency or pin and review a safe version.',
221
+ };
222
+ }
223
+ if (/\bexecSync\s*\(|\bchild_process\.exec\s*\(|\bshell\s*:\s*true\b|\bspawn\s*\([^,\n]+,\s*[^,\n]+,\s*\{[^}]*shell\s*:\s*true/.test(trimmed)) {
224
+ return {
225
+ packageName: pkg.name,
226
+ version: pkg.metadata.version,
227
+ ruleId: 'dependency.shell-exec',
228
+ severity: 'HIGH',
229
+ path,
230
+ message: 'Dependency source performs shell execution.',
231
+ evidence: `line ${lineNumber}: ${trimmed.slice(0, 180)}`,
232
+ fix: 'Review whether the dependency can execute user-controlled input.',
233
+ };
234
+ }
235
+ if (/\b(?:fetch|axios|request|http\.request|https\.request)\s*\(/.test(trimmed)) {
236
+ return {
237
+ packageName: pkg.name,
238
+ version: pkg.metadata.version,
239
+ ruleId: 'dependency.network-access',
240
+ severity: 'MEDIUM',
241
+ path,
242
+ message: 'Dependency source performs network access.',
243
+ evidence: `line ${lineNumber}: ${trimmed.slice(0, 180)}`,
244
+ fix: 'Confirm the network behavior is expected and documented.',
245
+ };
246
+ }
247
+ return null;
248
+ }
249
+ function walkPackage(dir, files, maxFiles) {
250
+ if (files.length >= maxFiles)
251
+ return;
252
+ let entries;
253
+ try {
254
+ entries = readdirSync(dir, { withFileTypes: true });
255
+ }
256
+ catch {
257
+ return;
258
+ }
259
+ for (const entry of entries) {
260
+ if (files.length >= maxFiles)
261
+ return;
262
+ const fullPath = join(dir, entry.name);
263
+ if (entry.isDirectory()) {
264
+ if (['node_modules', 'test', 'tests', '__tests__', 'coverage', 'dist', 'docs', 'example', 'examples', 'benchmark', 'benchmarks'].includes(entry.name))
265
+ continue;
266
+ walkPackage(fullPath, files, maxFiles);
267
+ }
268
+ else if (entry.isFile() && SOURCE_EXTENSIONS.has(extension(entry.name))) {
269
+ try {
270
+ if (statSync(fullPath).size <= 300_000)
271
+ files.push(fullPath);
272
+ }
273
+ catch {
274
+ // Ignore files that disappear during scanning.
275
+ }
276
+ }
277
+ }
278
+ }
279
+ function packageNameFromLockPath(path) {
280
+ const parts = path.split('/');
281
+ if (parts[1]?.startsWith('@'))
282
+ return `${parts[1]}/${parts[2] ?? ''}`;
283
+ return parts[1] ?? basename(path);
284
+ }
285
+ function isTopLevelNodeModulePath(path) {
286
+ const parts = path.split('/');
287
+ if (parts[0] !== 'node_modules')
288
+ return false;
289
+ if (parts[1]?.startsWith('@'))
290
+ return parts.length === 3;
291
+ return parts.length === 2;
292
+ }
293
+ function isBaselineFinding(finding, policy) {
294
+ return policy.baselineFindings.some(item => item.packageName === finding.packageName &&
295
+ item.ruleId === finding.ruleId &&
296
+ (item.version === undefined || item.version === finding.version));
297
+ }
298
+ function shouldBlock(severity, mode) {
299
+ if (severity === 'CRITICAL')
300
+ return true;
301
+ return mode === 'strict' && severity === 'HIGH';
302
+ }
303
+ function summarize(packagesAudited, findings) {
304
+ const bySeverity = {
305
+ CRITICAL: 0,
306
+ HIGH: 0,
307
+ MEDIUM: 0,
308
+ LOW: 0,
309
+ };
310
+ for (const finding of findings)
311
+ bySeverity[finding.severity] += 1;
312
+ return {
313
+ packagesAudited,
314
+ totalFindings: findings.length,
315
+ bySeverity,
316
+ };
317
+ }
318
+ function normalizeMode(value) {
319
+ return value === 'compatibility' || value === 'strict' || value === 'offline' ? value : undefined;
320
+ }
321
+ function positiveNumber(value) {
322
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined;
323
+ }
324
+ function extension(path) {
325
+ const index = path.lastIndexOf('.');
326
+ return index >= 0 ? path.slice(index).toLowerCase() : '';
327
+ }
328
+ function normalizePath(path) {
329
+ return path.split(sep).join('/');
330
+ }
331
+ //# sourceMappingURL=DependencyAuditor.js.map