@veraxhq/verax 0.2.1 → 0.4.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.
- package/README.md +10 -6
- package/bin/verax.js +11 -11
- package/package.json +29 -8
- package/src/cli/commands/baseline.js +103 -0
- package/src/cli/commands/default.js +51 -6
- package/src/cli/commands/doctor.js +29 -0
- package/src/cli/commands/ga.js +246 -0
- package/src/cli/commands/gates.js +95 -0
- package/src/cli/commands/inspect.js +4 -2
- package/src/cli/commands/release-check.js +215 -0
- package/src/cli/commands/run.js +45 -6
- package/src/cli/commands/security-check.js +212 -0
- package/src/cli/commands/truth.js +113 -0
- package/src/cli/entry.js +30 -20
- package/src/cli/util/angular-component-extractor.js +179 -0
- package/src/cli/util/angular-navigation-detector.js +141 -0
- package/src/cli/util/angular-network-detector.js +161 -0
- package/src/cli/util/angular-state-detector.js +162 -0
- package/src/cli/util/ast-interactive-detector.js +544 -0
- package/src/cli/util/ast-network-detector.js +603 -0
- package/src/cli/util/ast-promise-extractor.js +581 -0
- package/src/cli/util/ast-usestate-detector.js +602 -0
- package/src/cli/util/atomic-write.js +12 -1
- package/src/cli/util/bootstrap-guard.js +86 -0
- package/src/cli/util/console-reporter.js +72 -0
- package/src/cli/util/detection-engine.js +105 -41
- package/src/cli/util/determinism-runner.js +124 -0
- package/src/cli/util/determinism-writer.js +129 -0
- package/src/cli/util/digest-engine.js +359 -0
- package/src/cli/util/dom-diff.js +226 -0
- package/src/cli/util/evidence-engine.js +287 -0
- package/src/cli/util/expectation-extractor.js +151 -5
- package/src/cli/util/findings-writer.js +3 -0
- package/src/cli/util/framework-detector.js +572 -0
- package/src/cli/util/idgen.js +1 -1
- package/src/cli/util/interaction-planner.js +529 -0
- package/src/cli/util/learn-writer.js +2 -0
- package/src/cli/util/ledger-writer.js +110 -0
- package/src/cli/util/monorepo-resolver.js +162 -0
- package/src/cli/util/observation-engine.js +127 -278
- package/src/cli/util/observe-writer.js +2 -0
- package/src/cli/util/project-discovery.js +284 -0
- package/src/cli/util/project-writer.js +2 -0
- package/src/cli/util/run-id.js +23 -27
- package/src/cli/util/run-resolver.js +64 -0
- package/src/cli/util/run-result.js +778 -0
- package/src/cli/util/selector-resolver.js +235 -0
- package/src/cli/util/source-requirement.js +55 -0
- package/src/cli/util/summary-writer.js +2 -0
- package/src/cli/util/svelte-navigation-detector.js +163 -0
- package/src/cli/util/svelte-network-detector.js +80 -0
- package/src/cli/util/svelte-sfc-extractor.js +146 -0
- package/src/cli/util/svelte-state-detector.js +242 -0
- package/src/cli/util/trust-activation-integration.js +496 -0
- package/src/cli/util/trust-activation-wrapper.js +85 -0
- package/src/cli/util/trust-integration-hooks.js +164 -0
- package/src/cli/util/types.js +153 -0
- package/src/cli/util/url-validation.js +40 -0
- package/src/cli/util/vue-navigation-detector.js +178 -0
- package/src/cli/util/vue-sfc-extractor.js +161 -0
- package/src/cli/util/vue-state-detector.js +215 -0
- package/src/types/fs-augment.d.ts +23 -0
- package/src/types/global.d.ts +137 -0
- package/src/types/internal-types.d.ts +35 -0
- package/src/verax/cli/init.js +4 -18
- package/src/verax/core/action-classifier.js +4 -3
- package/src/verax/core/artifacts/registry.js +139 -0
- package/src/verax/core/artifacts/verifier.js +990 -0
- package/src/verax/core/baseline/baseline.enforcer.js +137 -0
- package/src/verax/core/baseline/baseline.snapshot.js +233 -0
- package/src/verax/core/capabilities/gates.js +505 -0
- package/src/verax/core/capabilities/registry.js +475 -0
- package/src/verax/core/confidence/confidence-compute.js +144 -0
- package/src/verax/core/confidence/confidence-invariants.js +234 -0
- package/src/verax/core/confidence/confidence-report-writer.js +112 -0
- package/src/verax/core/confidence/confidence-weights.js +44 -0
- package/src/verax/core/confidence/confidence.defaults.js +65 -0
- package/src/verax/core/confidence/confidence.loader.js +80 -0
- package/src/verax/core/confidence/confidence.schema.js +94 -0
- package/src/verax/core/confidence-engine-refactor.js +489 -0
- package/src/verax/core/confidence-engine.js +625 -0
- package/src/verax/core/contracts/index.js +29 -0
- package/src/verax/core/contracts/types.js +186 -0
- package/src/verax/core/contracts/validators.js +456 -0
- package/src/verax/core/decisions/decision.trace.js +278 -0
- package/src/verax/core/determinism/contract-writer.js +89 -0
- package/src/verax/core/determinism/contract.js +139 -0
- package/src/verax/core/determinism/diff.js +405 -0
- package/src/verax/core/determinism/engine.js +222 -0
- package/src/verax/core/determinism/finding-identity.js +149 -0
- package/src/verax/core/determinism/normalize.js +466 -0
- package/src/verax/core/determinism/report-writer.js +93 -0
- package/src/verax/core/determinism/run-fingerprint.js +123 -0
- package/src/verax/core/dynamic-route-intelligence.js +529 -0
- package/src/verax/core/evidence/evidence-capture-service.js +308 -0
- package/src/verax/core/evidence/evidence-intent-ledger.js +166 -0
- package/src/verax/core/evidence-builder.js +487 -0
- package/src/verax/core/execution-mode-context.js +77 -0
- package/src/verax/core/execution-mode-detector.js +192 -0
- package/src/verax/core/failures/exit-codes.js +88 -0
- package/src/verax/core/failures/failure-summary.js +76 -0
- package/src/verax/core/failures/failure.factory.js +225 -0
- package/src/verax/core/failures/failure.ledger.js +133 -0
- package/src/verax/core/failures/failure.types.js +196 -0
- package/src/verax/core/failures/index.js +10 -0
- package/src/verax/core/ga/ga-report-writer.js +43 -0
- package/src/verax/core/ga/ga.artifact.js +49 -0
- package/src/verax/core/ga/ga.contract.js +435 -0
- package/src/verax/core/ga/ga.enforcer.js +87 -0
- package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
- package/src/verax/core/guardrails/policy.defaults.js +210 -0
- package/src/verax/core/guardrails/policy.loader.js +84 -0
- package/src/verax/core/guardrails/policy.schema.js +110 -0
- package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
- package/src/verax/core/guardrails-engine.js +505 -0
- package/src/verax/core/incremental-store.js +1 -0
- package/src/verax/core/integrity/budget.js +138 -0
- package/src/verax/core/integrity/determinism.js +342 -0
- package/src/verax/core/integrity/integrity.js +208 -0
- package/src/verax/core/integrity/poisoning.js +108 -0
- package/src/verax/core/integrity/transaction.js +140 -0
- package/src/verax/core/observe/run-timeline.js +318 -0
- package/src/verax/core/perf/perf.contract.js +186 -0
- package/src/verax/core/perf/perf.display.js +65 -0
- package/src/verax/core/perf/perf.enforcer.js +91 -0
- package/src/verax/core/perf/perf.monitor.js +209 -0
- package/src/verax/core/perf/perf.report.js +200 -0
- package/src/verax/core/pipeline-tracker.js +243 -0
- package/src/verax/core/product-definition.js +127 -0
- package/src/verax/core/release/provenance.builder.js +130 -0
- package/src/verax/core/release/release-report-writer.js +40 -0
- package/src/verax/core/release/release.enforcer.js +164 -0
- package/src/verax/core/release/reproducibility.check.js +222 -0
- package/src/verax/core/release/sbom.builder.js +292 -0
- package/src/verax/core/replay-validator.js +2 -0
- package/src/verax/core/replay.js +4 -0
- package/src/verax/core/report/cross-index.js +195 -0
- package/src/verax/core/report/human-summary.js +362 -0
- package/src/verax/core/route-intelligence.js +420 -0
- package/src/verax/core/run-id.js +6 -3
- package/src/verax/core/run-manifest.js +4 -3
- package/src/verax/core/security/secrets.scan.js +329 -0
- package/src/verax/core/security/security-report.js +50 -0
- package/src/verax/core/security/security.enforcer.js +128 -0
- package/src/verax/core/security/supplychain.defaults.json +38 -0
- package/src/verax/core/security/supplychain.policy.js +334 -0
- package/src/verax/core/security/vuln.scan.js +265 -0
- package/src/verax/core/truth/truth.certificate.js +252 -0
- package/src/verax/core/ui-feedback-intelligence.js +481 -0
- package/src/verax/detect/conditional-ui-silent-failure.js +84 -0
- package/src/verax/detect/confidence-engine.js +62 -34
- package/src/verax/detect/confidence-helper.js +34 -0
- package/src/verax/detect/dynamic-route-findings.js +338 -0
- package/src/verax/detect/expectation-chain-detector.js +417 -0
- package/src/verax/detect/expectation-model.js +2 -2
- package/src/verax/detect/failure-cause-inference.js +293 -0
- package/src/verax/detect/findings-writer.js +131 -35
- package/src/verax/detect/flow-detector.js +2 -2
- package/src/verax/detect/form-silent-failure.js +98 -0
- package/src/verax/detect/index.js +46 -5
- package/src/verax/detect/invariants-enforcer.js +147 -0
- package/src/verax/detect/journey-stall-detector.js +558 -0
- package/src/verax/detect/navigation-silent-failure.js +82 -0
- package/src/verax/detect/problem-aggregator.js +361 -0
- package/src/verax/detect/route-findings.js +219 -0
- package/src/verax/detect/summary-writer.js +477 -0
- package/src/verax/detect/test-failure-cause-inference.js +314 -0
- package/src/verax/detect/ui-feedback-findings.js +207 -0
- package/src/verax/detect/view-switch-correlator.js +242 -0
- package/src/verax/flow/flow-engine.js +2 -1
- package/src/verax/flow/flow-spec.js +0 -6
- package/src/verax/index.js +4 -0
- package/src/verax/intel/ts-program.js +1 -0
- package/src/verax/intel/vue-navigation-extractor.js +3 -0
- package/src/verax/learn/action-contract-extractor.js +3 -0
- package/src/verax/learn/ast-contract-extractor.js +1 -1
- package/src/verax/learn/flow-extractor.js +1 -0
- package/src/verax/learn/project-detector.js +5 -0
- package/src/verax/learn/react-router-extractor.js +2 -0
- package/src/verax/learn/source-instrumenter.js +1 -0
- package/src/verax/learn/state-extractor.js +2 -1
- package/src/verax/learn/static-extractor.js +1 -0
- package/src/verax/observe/coverage-gaps.js +132 -0
- package/src/verax/observe/expectation-handler.js +126 -0
- package/src/verax/observe/incremental-skip.js +46 -0
- package/src/verax/observe/index.js +51 -155
- package/src/verax/observe/interaction-executor.js +192 -0
- package/src/verax/observe/interaction-runner.js +782 -513
- package/src/verax/observe/network-firewall.js +86 -0
- package/src/verax/observe/observation-builder.js +169 -0
- package/src/verax/observe/observe-context.js +205 -0
- package/src/verax/observe/observe-helpers.js +192 -0
- package/src/verax/observe/observe-runner.js +230 -0
- package/src/verax/observe/observers/budget-observer.js +185 -0
- package/src/verax/observe/observers/console-observer.js +102 -0
- package/src/verax/observe/observers/coverage-observer.js +107 -0
- package/src/verax/observe/observers/interaction-observer.js +471 -0
- package/src/verax/observe/observers/navigation-observer.js +132 -0
- package/src/verax/observe/observers/network-observer.js +87 -0
- package/src/verax/observe/observers/safety-observer.js +82 -0
- package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
- package/src/verax/observe/page-traversal.js +138 -0
- package/src/verax/observe/snapshot-ops.js +94 -0
- package/src/verax/observe/ui-feedback-detector.js +742 -0
- package/src/verax/scan-summary-writer.js +2 -0
- package/src/verax/shared/artifact-manager.js +25 -5
- package/src/verax/shared/caching.js +1 -0
- package/src/verax/shared/css-spinner-rules.js +204 -0
- package/src/verax/shared/expectation-tracker.js +1 -0
- package/src/verax/shared/view-switch-rules.js +208 -0
- package/src/verax/shared/zip-artifacts.js +6 -0
- package/src/verax/shared/config-loader.js +0 -169
- /package/src/verax/shared/{expectation-proof.js → expectation-validation.js} +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.8 — Supply-Chain Policy
|
|
3
|
+
*
|
|
4
|
+
* Enforces supply-chain security policies:
|
|
5
|
+
* - License allowlist/denylist
|
|
6
|
+
* - Integrity hash requirements
|
|
7
|
+
* - Postinstall script restrictions
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
11
|
+
import { resolve } from 'path';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_POLICY = JSON.parse(
|
|
14
|
+
readFileSync(new URL('./supplychain.defaults.json', import.meta.url), 'utf-8')
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load supply-chain policy
|
|
19
|
+
*
|
|
20
|
+
* @param {string} projectDir - Project directory
|
|
21
|
+
* @returns {Object} Policy object
|
|
22
|
+
*/
|
|
23
|
+
export function loadSupplyChainPolicy(projectDir) {
|
|
24
|
+
const customPath = resolve(projectDir, 'supplychain.policy.json');
|
|
25
|
+
|
|
26
|
+
if (existsSync(customPath)) {
|
|
27
|
+
try {
|
|
28
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
29
|
+
const custom = JSON.parse(readFileSync(customPath, 'utf-8'));
|
|
30
|
+
// Merge with defaults (custom overrides)
|
|
31
|
+
return {
|
|
32
|
+
...DEFAULT_POLICY,
|
|
33
|
+
...custom,
|
|
34
|
+
licensePolicy: {
|
|
35
|
+
...DEFAULT_POLICY.licensePolicy,
|
|
36
|
+
...(custom.licensePolicy || {})
|
|
37
|
+
},
|
|
38
|
+
integrityPolicy: {
|
|
39
|
+
...DEFAULT_POLICY.integrityPolicy,
|
|
40
|
+
...(custom.integrityPolicy || {})
|
|
41
|
+
},
|
|
42
|
+
scriptPolicy: {
|
|
43
|
+
...DEFAULT_POLICY.scriptPolicy,
|
|
44
|
+
...(custom.scriptPolicy || {})
|
|
45
|
+
},
|
|
46
|
+
sourcePolicy: {
|
|
47
|
+
...DEFAULT_POLICY.sourcePolicy,
|
|
48
|
+
...(custom.sourcePolicy || {})
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
} catch {
|
|
52
|
+
// Invalid custom policy, use defaults
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return DEFAULT_POLICY;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get package.json dependencies
|
|
61
|
+
*
|
|
62
|
+
* @param {string} projectDir - Project directory
|
|
63
|
+
* @returns {Object} Dependencies
|
|
64
|
+
*/
|
|
65
|
+
function getDependencies(projectDir) {
|
|
66
|
+
try {
|
|
67
|
+
const pkgPath = resolve(projectDir, 'package.json');
|
|
68
|
+
if (!existsSync(pkgPath)) {
|
|
69
|
+
return { dependencies: {}, devDependencies: {} };
|
|
70
|
+
}
|
|
71
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
72
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
73
|
+
return {
|
|
74
|
+
dependencies: pkg.dependencies || {},
|
|
75
|
+
devDependencies: pkg.devDependencies || {}
|
|
76
|
+
};
|
|
77
|
+
} catch {
|
|
78
|
+
return { dependencies: {}, devDependencies: {} };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get package license from node_modules
|
|
84
|
+
*
|
|
85
|
+
* @param {string} projectDir - Project directory
|
|
86
|
+
* @param {string} packageName - Package name
|
|
87
|
+
* @returns {string|null} License or null
|
|
88
|
+
*/
|
|
89
|
+
function getPackageLicense(projectDir, packageName) {
|
|
90
|
+
try {
|
|
91
|
+
const pkgPath = resolve(projectDir, 'node_modules', packageName, 'package.json');
|
|
92
|
+
if (!existsSync(pkgPath)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
96
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
97
|
+
|
|
98
|
+
if (typeof pkg.license === 'string') {
|
|
99
|
+
return pkg.license;
|
|
100
|
+
} else if (pkg.license && pkg.license.type) {
|
|
101
|
+
return pkg.license.type;
|
|
102
|
+
} else if (Array.isArray(pkg.licenses) && pkg.licenses.length > 0) {
|
|
103
|
+
return pkg.licenses[0].type || pkg.licenses[0];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return null;
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check package-lock.json for integrity hashes
|
|
114
|
+
*
|
|
115
|
+
* @param {string} projectDir - Project directory
|
|
116
|
+
* @returns {Object} Integrity check results
|
|
117
|
+
*/
|
|
118
|
+
function checkIntegrityHashes(projectDir) {
|
|
119
|
+
const lockPath = resolve(projectDir, 'package-lock.json');
|
|
120
|
+
const missing = [];
|
|
121
|
+
|
|
122
|
+
if (!existsSync(lockPath)) {
|
|
123
|
+
return { missing: [], total: 0 };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
128
|
+
const lock = JSON.parse(readFileSync(lockPath, 'utf-8'));
|
|
129
|
+
const packages = lock.packages || {};
|
|
130
|
+
let total = 0;
|
|
131
|
+
|
|
132
|
+
for (const [path, pkg] of Object.entries(packages)) {
|
|
133
|
+
if (path && pkg && pkg.version) {
|
|
134
|
+
total++;
|
|
135
|
+
if (!pkg.integrity && !pkg.resolved?.includes('file:')) {
|
|
136
|
+
missing.push({
|
|
137
|
+
package: pkg.name || path,
|
|
138
|
+
path: path,
|
|
139
|
+
version: pkg.version
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { missing, total };
|
|
146
|
+
} catch {
|
|
147
|
+
return { missing: [], total: 0 };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Check for postinstall scripts
|
|
153
|
+
*
|
|
154
|
+
* @param {string} projectDir - Project directory
|
|
155
|
+
* @returns {Array} Packages with postinstall scripts
|
|
156
|
+
*/
|
|
157
|
+
function checkPostinstallScripts(projectDir) {
|
|
158
|
+
const packages = [];
|
|
159
|
+
const nodeModulesPath = resolve(projectDir, 'node_modules');
|
|
160
|
+
|
|
161
|
+
if (!existsSync(nodeModulesPath)) {
|
|
162
|
+
return packages;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const { readdirSync } = require('fs');
|
|
167
|
+
const entries = readdirSync(nodeModulesPath, { withFileTypes: true });
|
|
168
|
+
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
171
|
+
const pkgPath = resolve(nodeModulesPath, entry.name, 'package.json');
|
|
172
|
+
if (existsSync(pkgPath)) {
|
|
173
|
+
try {
|
|
174
|
+
// @ts-expect-error - readFileSync with encoding returns string
|
|
175
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
176
|
+
if (pkg.scripts) {
|
|
177
|
+
if (pkg.scripts.postinstall || pkg.scripts.preinstall || pkg.scripts.install) {
|
|
178
|
+
packages.push({
|
|
179
|
+
name: pkg.name || entry.name,
|
|
180
|
+
version: pkg.version || 'unknown',
|
|
181
|
+
scripts: Object.keys(pkg.scripts).filter(s =>
|
|
182
|
+
['postinstall', 'preinstall', 'install'].includes(s)
|
|
183
|
+
)
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
// Skip invalid packages
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
// If scanning fails, return empty
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return packages;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Evaluate supply-chain policy
|
|
202
|
+
*
|
|
203
|
+
* @param {string} projectDir - Project directory
|
|
204
|
+
* @returns {Promise<Object>} Evaluation results
|
|
205
|
+
*/
|
|
206
|
+
export async function evaluateSupplyChainPolicy(projectDir) {
|
|
207
|
+
const policy = loadSupplyChainPolicy(projectDir);
|
|
208
|
+
const deps = getDependencies(projectDir);
|
|
209
|
+
const violations = [];
|
|
210
|
+
const warnings = [];
|
|
211
|
+
|
|
212
|
+
// Check licenses
|
|
213
|
+
const allDeps = { ...deps.dependencies, ...deps.devDependencies };
|
|
214
|
+
for (const [packageName] of Object.entries(allDeps)) {
|
|
215
|
+
const license = getPackageLicense(projectDir, packageName);
|
|
216
|
+
|
|
217
|
+
if (license) {
|
|
218
|
+
// Check denylist
|
|
219
|
+
if (policy.licensePolicy.denylist.includes(license)) {
|
|
220
|
+
violations.push({
|
|
221
|
+
type: 'FORBIDDEN_LICENSE',
|
|
222
|
+
package: packageName,
|
|
223
|
+
license: license,
|
|
224
|
+
severity: 'BLOCKING',
|
|
225
|
+
message: `Package ${packageName} uses forbidden license: ${license}`
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Check allowlist (if strict mode)
|
|
230
|
+
if (policy.licensePolicy.strictMode && !policy.licensePolicy.allowlist.includes(license)) {
|
|
231
|
+
violations.push({
|
|
232
|
+
type: 'UNALLOWED_LICENSE',
|
|
233
|
+
package: packageName,
|
|
234
|
+
license: license,
|
|
235
|
+
severity: 'BLOCKING',
|
|
236
|
+
message: `Package ${packageName} uses unallowed license: ${license}`
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
warnings.push({
|
|
241
|
+
type: 'MISSING_LICENSE',
|
|
242
|
+
package: packageName,
|
|
243
|
+
message: `Package ${packageName} has no license information`
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check integrity hashes
|
|
249
|
+
if (policy.integrityPolicy.requireIntegrityHash) {
|
|
250
|
+
const integrityCheck = checkIntegrityHashes(projectDir);
|
|
251
|
+
for (const missing of integrityCheck.missing) {
|
|
252
|
+
if (!policy.integrityPolicy.allowedMissingIntegrity.includes(missing.package)) {
|
|
253
|
+
violations.push({
|
|
254
|
+
type: 'MISSING_INTEGRITY',
|
|
255
|
+
package: missing.package,
|
|
256
|
+
version: missing.version,
|
|
257
|
+
severity: 'BLOCKING',
|
|
258
|
+
message: `Package ${missing.package}@${missing.version} missing integrity hash`
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check postinstall scripts
|
|
265
|
+
if (policy.scriptPolicy.forbidPostinstall ||
|
|
266
|
+
policy.scriptPolicy.forbidPreinstall ||
|
|
267
|
+
policy.scriptPolicy.forbidInstall) {
|
|
268
|
+
const scripts = checkPostinstallScripts(projectDir);
|
|
269
|
+
for (const pkg of scripts) {
|
|
270
|
+
const forbiddenScripts = pkg.scripts.filter(s => {
|
|
271
|
+
if (s === 'postinstall' && policy.scriptPolicy.forbidPostinstall) return true;
|
|
272
|
+
if (s === 'preinstall' && policy.scriptPolicy.forbidPreinstall) return true;
|
|
273
|
+
if (s === 'install' && policy.scriptPolicy.forbidInstall) return true;
|
|
274
|
+
return false;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
if (forbiddenScripts.length > 0 && !policy.scriptPolicy.allowlist.includes(pkg.name)) {
|
|
278
|
+
violations.push({
|
|
279
|
+
type: 'FORBIDDEN_SCRIPT',
|
|
280
|
+
package: pkg.name,
|
|
281
|
+
version: pkg.version,
|
|
282
|
+
scripts: forbiddenScripts,
|
|
283
|
+
severity: 'BLOCKING',
|
|
284
|
+
message: `Package ${pkg.name} has forbidden scripts: ${forbiddenScripts.join(', ')}`
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const ok = violations.length === 0;
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
ok,
|
|
294
|
+
violations,
|
|
295
|
+
warnings,
|
|
296
|
+
summary: {
|
|
297
|
+
totalViolations: violations.length,
|
|
298
|
+
totalWarnings: warnings.length,
|
|
299
|
+
byType: violations.reduce((acc, v) => {
|
|
300
|
+
acc[v.type] = (acc[v.type] || 0) + 1;
|
|
301
|
+
return acc;
|
|
302
|
+
}, {}),
|
|
303
|
+
evaluatedAt: new Date().toISOString()
|
|
304
|
+
},
|
|
305
|
+
policy: {
|
|
306
|
+
version: policy.version,
|
|
307
|
+
licensePolicy: {
|
|
308
|
+
allowlist: policy.licensePolicy.allowlist,
|
|
309
|
+
denylist: policy.licensePolicy.denylist,
|
|
310
|
+
strictMode: policy.licensePolicy.strictMode
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Write supply-chain report
|
|
318
|
+
*
|
|
319
|
+
* @param {string} projectDir - Project directory
|
|
320
|
+
* @param {Object} report - Evaluation results
|
|
321
|
+
* @returns {string} Path to written file
|
|
322
|
+
*/
|
|
323
|
+
export function writeSupplyChainReport(projectDir, report) {
|
|
324
|
+
const outputDir = resolve(projectDir, 'release');
|
|
325
|
+
if (!existsSync(outputDir)) {
|
|
326
|
+
mkdirSync(outputDir, { recursive: true });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const outputPath = resolve(outputDir, 'security.supplychain.report.json');
|
|
330
|
+
writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
331
|
+
|
|
332
|
+
return outputPath;
|
|
333
|
+
}
|
|
334
|
+
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PHASE 21.8 — Vulnerability Scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans dependencies for vulnerabilities using npm audit.
|
|
5
|
+
* HIGH/CRITICAL = BLOCKING, MEDIUM = WARNING (configurable).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync as _readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
9
|
+
import { resolve } from 'path';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if OSV scanner is available
|
|
14
|
+
*
|
|
15
|
+
* @returns {boolean} Whether osv-scanner is available
|
|
16
|
+
*/
|
|
17
|
+
function checkOSVAvailable() {
|
|
18
|
+
try {
|
|
19
|
+
execSync('osv-scanner --version', {
|
|
20
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
21
|
+
timeout: 5000
|
|
22
|
+
});
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run npm audit
|
|
31
|
+
*
|
|
32
|
+
* @param {string} projectDir - Project directory
|
|
33
|
+
* @returns {Object|null} Audit results or null
|
|
34
|
+
*/
|
|
35
|
+
function runNpmAudit(projectDir) {
|
|
36
|
+
try {
|
|
37
|
+
const result = execSync('npm audit --json', {
|
|
38
|
+
cwd: projectDir,
|
|
39
|
+
encoding: 'utf-8',
|
|
40
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return JSON.parse(result);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// npm audit exits with non-zero on vulnerabilities
|
|
46
|
+
try {
|
|
47
|
+
const stderr = error.stderr?.toString() || '';
|
|
48
|
+
const stdout = error.stdout?.toString() || '';
|
|
49
|
+
const output = stdout || stderr;
|
|
50
|
+
|
|
51
|
+
if (output) {
|
|
52
|
+
return JSON.parse(output);
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Failed to parse
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse vulnerabilities from audit results
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} auditResults - npm audit JSON output
|
|
66
|
+
* @returns {Array} Array of vulnerabilities
|
|
67
|
+
*/
|
|
68
|
+
function parseVulnerabilities(auditResults) {
|
|
69
|
+
const vulnerabilities = [];
|
|
70
|
+
|
|
71
|
+
if (!auditResults || !auditResults.vulnerabilities) {
|
|
72
|
+
return vulnerabilities;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const [packageName, vulnData] of Object.entries(auditResults.vulnerabilities)) {
|
|
76
|
+
if (Array.isArray(vulnData)) {
|
|
77
|
+
for (const vuln of vulnData) {
|
|
78
|
+
vulnerabilities.push({
|
|
79
|
+
package: packageName,
|
|
80
|
+
severity: vuln.severity?.toUpperCase() || 'UNKNOWN',
|
|
81
|
+
title: vuln.title || vuln.name || 'Unknown vulnerability',
|
|
82
|
+
url: vuln.url || null,
|
|
83
|
+
dependencyOf: vuln.dependencyOf || null,
|
|
84
|
+
via: vuln.via || null
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
} else if (vulnData.vulnerabilities) {
|
|
88
|
+
for (const vuln of vulnData.vulnerabilities) {
|
|
89
|
+
vulnerabilities.push({
|
|
90
|
+
package: packageName,
|
|
91
|
+
severity: vuln.severity?.toUpperCase() || 'UNKNOWN',
|
|
92
|
+
title: vuln.title || vuln.name || 'Unknown vulnerability',
|
|
93
|
+
url: vuln.url || null,
|
|
94
|
+
dependencyOf: vulnData.dependencyOf || null,
|
|
95
|
+
via: vuln.via || null
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return vulnerabilities;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Scan for vulnerabilities
|
|
106
|
+
*
|
|
107
|
+
* @param {string} projectDir - Project directory
|
|
108
|
+
* @param {Object} options - Options
|
|
109
|
+
* @param {boolean} options.blockMedium - Block MEDIUM severity (default: false)
|
|
110
|
+
* @param {boolean} options.requireOSV - Require OSV scanner (default: false)
|
|
111
|
+
* @returns {Promise<Object>} Scan results
|
|
112
|
+
*/
|
|
113
|
+
export async function scanVulnerabilities(projectDir, options = { blockMedium: false, requireOSV: false }) {
|
|
114
|
+
const { blockMedium = false, requireOSV = false } = options;
|
|
115
|
+
|
|
116
|
+
// Check OSV availability
|
|
117
|
+
const osvAvailable = checkOSVAvailable();
|
|
118
|
+
let osvResults = null;
|
|
119
|
+
|
|
120
|
+
if (osvAvailable) {
|
|
121
|
+
try {
|
|
122
|
+
// Run OSV scanner
|
|
123
|
+
const osvOutput = execSync('osv-scanner --format=json .', {
|
|
124
|
+
cwd: projectDir,
|
|
125
|
+
encoding: 'utf-8',
|
|
126
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
127
|
+
timeout: 60000
|
|
128
|
+
});
|
|
129
|
+
try {
|
|
130
|
+
osvResults = JSON.parse(osvOutput);
|
|
131
|
+
} catch {
|
|
132
|
+
// OSV scanner may output non-JSON on errors
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// OSV scanner failed, fall back to npm audit
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Always try npm audit as fallback
|
|
140
|
+
const auditResults = runNpmAudit(projectDir);
|
|
141
|
+
|
|
142
|
+
if (!auditResults && !osvResults) {
|
|
143
|
+
if (requireOSV && !osvAvailable) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
error: 'OSV scanner not available',
|
|
147
|
+
availability: 'NOT_AVAILABLE',
|
|
148
|
+
tool: null,
|
|
149
|
+
vulnerabilities: [],
|
|
150
|
+
summary: {
|
|
151
|
+
total: 0,
|
|
152
|
+
critical: 0,
|
|
153
|
+
high: 0,
|
|
154
|
+
medium: 0,
|
|
155
|
+
low: 0,
|
|
156
|
+
scannedAt: new Date().toISOString()
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
error: 'Failed to run npm audit',
|
|
164
|
+
availability: 'FAILED',
|
|
165
|
+
tool: 'NPM_AUDIT',
|
|
166
|
+
vulnerabilities: [],
|
|
167
|
+
summary: {
|
|
168
|
+
total: 0,
|
|
169
|
+
critical: 0,
|
|
170
|
+
high: 0,
|
|
171
|
+
medium: 0,
|
|
172
|
+
low: 0,
|
|
173
|
+
scannedAt: new Date().toISOString()
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Parse vulnerabilities from npm audit (primary source)
|
|
179
|
+
let vulnerabilities = [];
|
|
180
|
+
if (auditResults) {
|
|
181
|
+
vulnerabilities = parseVulnerabilities(auditResults);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Merge OSV results if available
|
|
185
|
+
if (osvResults && osvResults.results) {
|
|
186
|
+
for (const result of osvResults.results) {
|
|
187
|
+
if (result.packages && Array.isArray(result.packages)) {
|
|
188
|
+
for (const pkg of result.packages) {
|
|
189
|
+
if (result.vulnerabilities && Array.isArray(result.vulnerabilities)) {
|
|
190
|
+
for (const vuln of result.vulnerabilities) {
|
|
191
|
+
// Avoid duplicates (merge by package + ID)
|
|
192
|
+
const existing = vulnerabilities.find(v =>
|
|
193
|
+
v.package === pkg.package?.name &&
|
|
194
|
+
v.osvId === vuln.id
|
|
195
|
+
);
|
|
196
|
+
if (!existing) {
|
|
197
|
+
vulnerabilities.push({
|
|
198
|
+
package: pkg.package?.name || 'unknown',
|
|
199
|
+
severity: vuln.severity?.toUpperCase() || 'UNKNOWN',
|
|
200
|
+
title: vuln.summary || vuln.id || 'Unknown vulnerability',
|
|
201
|
+
url: vuln.database_specific?.url || vuln.id ? `https://osv.dev/vulnerability/${vuln.id}` : null,
|
|
202
|
+
osvId: vuln.id,
|
|
203
|
+
source: 'OSV'
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const critical = vulnerabilities.filter(v => v.severity === 'CRITICAL');
|
|
214
|
+
const high = vulnerabilities.filter(v => v.severity === 'HIGH');
|
|
215
|
+
const medium = vulnerabilities.filter(v => v.severity === 'MEDIUM');
|
|
216
|
+
const low = vulnerabilities.filter(v => v.severity === 'LOW');
|
|
217
|
+
|
|
218
|
+
// BLOCKING: CRITICAL or HIGH
|
|
219
|
+
// WARNING: MEDIUM (blocking if blockMedium=true)
|
|
220
|
+
const blocking = critical.length > 0 || high.length > 0 || (blockMedium && medium.length > 0);
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
ok: !blocking,
|
|
224
|
+
blocking,
|
|
225
|
+
availability: osvAvailable ? 'AVAILABLE' : 'NOT_AVAILABLE',
|
|
226
|
+
tool: osvAvailable ? 'OSV_SCANNER' : (auditResults ? 'NPM_AUDIT' : null),
|
|
227
|
+
osvAvailable,
|
|
228
|
+
vulnerabilities,
|
|
229
|
+
summary: {
|
|
230
|
+
total: vulnerabilities.length,
|
|
231
|
+
critical: critical.length,
|
|
232
|
+
high: high.length,
|
|
233
|
+
medium: medium.length,
|
|
234
|
+
low: low.length,
|
|
235
|
+
blocking,
|
|
236
|
+
warnings: blockMedium ? 0 : medium.length,
|
|
237
|
+
scannedAt: new Date().toISOString()
|
|
238
|
+
},
|
|
239
|
+
metadata: {
|
|
240
|
+
auditVersion: auditResults?.auditReportVersion || null,
|
|
241
|
+
npmVersion: auditResults?.npmVersion || null,
|
|
242
|
+
osvVersion: osvAvailable ? 'detected' : null
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Write vulnerability report
|
|
249
|
+
*
|
|
250
|
+
* @param {string} projectDir - Project directory
|
|
251
|
+
* @param {Object} report - Scan results
|
|
252
|
+
* @returns {string} Path to written file
|
|
253
|
+
*/
|
|
254
|
+
export function writeVulnReport(projectDir, report) {
|
|
255
|
+
const outputDir = resolve(projectDir, 'release');
|
|
256
|
+
if (!existsSync(outputDir)) {
|
|
257
|
+
mkdirSync(outputDir, { recursive: true });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const outputPath = resolve(outputDir, 'security.vuln.report.json');
|
|
261
|
+
writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
|
|
262
|
+
|
|
263
|
+
return outputPath;
|
|
264
|
+
}
|
|
265
|
+
|