@oculum/cli 1.0.2 → 1.0.3
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/dist/index.js +175 -123
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14406,6 +14406,44 @@ var require_layer1 = __commonJS({
|
|
|
14406
14406
|
var weak_crypto_1 = require_weak_crypto();
|
|
14407
14407
|
var tiers_1 = require_tiers();
|
|
14408
14408
|
var path_exclusions_1 = require_path_exclusions();
|
|
14409
|
+
function processFileLayer1(file) {
|
|
14410
|
+
const stats = {
|
|
14411
|
+
known_secrets: 0,
|
|
14412
|
+
weak_crypto: 0,
|
|
14413
|
+
sensitive_urls: 0,
|
|
14414
|
+
entropy: 0,
|
|
14415
|
+
config_audit: 0,
|
|
14416
|
+
file_flags: 0,
|
|
14417
|
+
ai_comments: 0
|
|
14418
|
+
};
|
|
14419
|
+
const entropyFindings = (0, entropy_1.detectHighEntropyStrings)(file.content, file.path);
|
|
14420
|
+
const patternFindings = (0, patterns_1.detectKnownPatterns)(file.content, file.path);
|
|
14421
|
+
const configFindings = (0, config_audit_1.auditConfiguration)(file.content, file.path);
|
|
14422
|
+
const fileFlags = (0, file_flags_1.detectDangerousFiles)(file.content, file.path);
|
|
14423
|
+
const commentFindings = (0, comments_1.detectAICommentPatterns)(file.content, file.path);
|
|
14424
|
+
const urlFindings = (0, urls_1.detectSensitiveURLs)(file.content, file.path);
|
|
14425
|
+
const cryptoFindings = (0, weak_crypto_1.detectWeakCrypto)(file.content, file.path);
|
|
14426
|
+
stats.entropy = entropyFindings.length;
|
|
14427
|
+
stats.known_secrets = patternFindings.length;
|
|
14428
|
+
stats.config_audit = configFindings.length;
|
|
14429
|
+
stats.file_flags = fileFlags.length;
|
|
14430
|
+
stats.ai_comments = commentFindings.length;
|
|
14431
|
+
stats.sensitive_urls = urlFindings.length;
|
|
14432
|
+
stats.weak_crypto = cryptoFindings.length;
|
|
14433
|
+
return {
|
|
14434
|
+
findings: [
|
|
14435
|
+
...entropyFindings,
|
|
14436
|
+
...patternFindings,
|
|
14437
|
+
...configFindings,
|
|
14438
|
+
...fileFlags,
|
|
14439
|
+
...commentFindings,
|
|
14440
|
+
...urlFindings,
|
|
14441
|
+
...cryptoFindings
|
|
14442
|
+
],
|
|
14443
|
+
stats
|
|
14444
|
+
};
|
|
14445
|
+
}
|
|
14446
|
+
var LAYER1_PARALLEL_BATCH_SIZE = 50;
|
|
14409
14447
|
async function runLayer1Scan(files) {
|
|
14410
14448
|
const startTime = Date.now();
|
|
14411
14449
|
const vulnerabilities = [];
|
|
@@ -14418,50 +14456,24 @@ var require_layer1 = __commonJS({
|
|
|
14418
14456
|
file_flags: 0,
|
|
14419
14457
|
ai_comments: 0
|
|
14420
14458
|
};
|
|
14421
|
-
for (
|
|
14422
|
-
const
|
|
14423
|
-
const
|
|
14424
|
-
const
|
|
14425
|
-
|
|
14426
|
-
|
|
14427
|
-
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
rawStats.known_secrets += patternFindings.length;
|
|
14431
|
-
rawStats.config_audit += configFindings.length;
|
|
14432
|
-
rawStats.file_flags += fileFlags.length;
|
|
14433
|
-
rawStats.ai_comments += commentFindings.length;
|
|
14434
|
-
rawStats.sensitive_urls += urlFindings.length;
|
|
14435
|
-
rawStats.weak_crypto += cryptoFindings.length;
|
|
14436
|
-
vulnerabilities.push(...entropyFindings, ...patternFindings, ...configFindings, ...fileFlags, ...commentFindings, ...urlFindings, ...cryptoFindings);
|
|
14459
|
+
for (let i = 0; i < files.length; i += LAYER1_PARALLEL_BATCH_SIZE) {
|
|
14460
|
+
const batch = files.slice(i, i + LAYER1_PARALLEL_BATCH_SIZE);
|
|
14461
|
+
const results = await Promise.all(batch.map((file) => Promise.resolve(processFileLayer1(file))));
|
|
14462
|
+
for (const result of results) {
|
|
14463
|
+
vulnerabilities.push(...result.findings);
|
|
14464
|
+
for (const [key, value] of Object.entries(result.stats)) {
|
|
14465
|
+
rawStats[key] += value;
|
|
14466
|
+
}
|
|
14467
|
+
}
|
|
14437
14468
|
}
|
|
14438
14469
|
const dedupedVulnerabilities = deduplicateFindings(vulnerabilities);
|
|
14439
14470
|
const { kept: uniqueVulnerabilities, suppressed } = (0, path_exclusions_1.filterFindingsByPath)(dedupedVulnerabilities);
|
|
14440
|
-
if (suppressed.length > 0) {
|
|
14441
|
-
const byReason = {};
|
|
14442
|
-
for (const s of suppressed) {
|
|
14443
|
-
const reason = s.reason || "unknown";
|
|
14444
|
-
byReason[reason] = (byReason[reason] || 0) + 1;
|
|
14445
|
-
}
|
|
14446
|
-
console.log(`[Layer 1] Suppressed ${suppressed.length} findings in test/seed/example files:`);
|
|
14447
|
-
for (const [reason, count] of Object.entries(byReason)) {
|
|
14448
|
-
console.log(` - ${reason}: ${count}`);
|
|
14449
|
-
}
|
|
14450
|
-
}
|
|
14451
14471
|
const dedupedStats = {};
|
|
14452
14472
|
for (const vuln of uniqueVulnerabilities) {
|
|
14453
14473
|
const cat = vuln.category;
|
|
14454
14474
|
dedupedStats[cat] = (dedupedStats[cat] || 0) + 1;
|
|
14455
14475
|
}
|
|
14456
14476
|
const tierStats = (0, tiers_1.computeTierStats)(uniqueVulnerabilities.map((v2) => ({ category: v2.category, layer: 1 })));
|
|
14457
|
-
console.log("[Layer 1] Heuristic breakdown (raw findings before dedupe):");
|
|
14458
|
-
for (const [name, count] of Object.entries(rawStats)) {
|
|
14459
|
-
if (count > 0) {
|
|
14460
|
-
const tier = (0, tiers_1.getLayer1DetectorTier)(name);
|
|
14461
|
-
console.log(` - ${name}: ${count} (${tier})`);
|
|
14462
|
-
}
|
|
14463
|
-
}
|
|
14464
|
-
console.log(`[Layer 1] Tier breakdown (after dedupe): ${(0, tiers_1.formatTierStats)(tierStats)}`);
|
|
14465
14477
|
return {
|
|
14466
14478
|
vulnerabilities: uniqueVulnerabilities,
|
|
14467
14479
|
filesScanned: files.length,
|
|
@@ -20677,6 +20689,85 @@ var require_layer2 = __commonJS({
|
|
|
20677
20689
|
var ai_endpoint_protection_1 = require_ai_endpoint_protection();
|
|
20678
20690
|
var ai_schema_validation_1 = require_ai_schema_validation();
|
|
20679
20691
|
var tiers_1 = require_tiers();
|
|
20692
|
+
function processFileLayer2(file, options, authHelperContext) {
|
|
20693
|
+
const stats = {
|
|
20694
|
+
variables: 0,
|
|
20695
|
+
logicGates: 0,
|
|
20696
|
+
dangerousFunctions: 0,
|
|
20697
|
+
riskyImports: 0,
|
|
20698
|
+
authAntipatterns: 0,
|
|
20699
|
+
frameworkIssues: 0,
|
|
20700
|
+
aiFingerprints: 0,
|
|
20701
|
+
dataExposure: 0,
|
|
20702
|
+
byokPatterns: 0,
|
|
20703
|
+
promptHygiene: 0,
|
|
20704
|
+
executionSinks: 0,
|
|
20705
|
+
agentTools: 0,
|
|
20706
|
+
ragSafety: 0,
|
|
20707
|
+
endpointProtection: 0,
|
|
20708
|
+
schemaValidation: 0
|
|
20709
|
+
};
|
|
20710
|
+
if (!isCodeFile(file.path)) {
|
|
20711
|
+
return { findings: [], stats };
|
|
20712
|
+
}
|
|
20713
|
+
const variableFindings = (0, variables_1.detectSensitiveVariables)(file.content, file.path);
|
|
20714
|
+
const logicFindings = (0, logic_gates_1.detectLogicGates)(file.content, file.path);
|
|
20715
|
+
const dangerousFuncFindings = (0, dangerous_functions_1.detectDangerousFunctions)(file.content, file.path);
|
|
20716
|
+
const riskyImportFindings = (0, risky_imports_1.detectRiskyImports)(file.content, file.path);
|
|
20717
|
+
const authFindings = (0, auth_antipatterns_1.detectAuthAntipatterns)(file.content, file.path, {
|
|
20718
|
+
middlewareConfig: options.middlewareConfig,
|
|
20719
|
+
authHelpers: authHelperContext,
|
|
20720
|
+
fileAuthImports: options.fileAuthImports
|
|
20721
|
+
});
|
|
20722
|
+
const frameworkFindings = (0, framework_checks_1.detectFrameworkIssues)(file.content, file.path);
|
|
20723
|
+
const aiFindings = (0, ai_fingerprinting_1.detectAIFingerprints)(file.content, file.path);
|
|
20724
|
+
const dataExposureFindings = (0, data_exposure_1.detectDataExposure)(file.content, file.path);
|
|
20725
|
+
const byokFindings = (0, byok_patterns_1.detectBYOKPatterns)(file.content, file.path, options.middlewareConfig);
|
|
20726
|
+
const promptHygieneFindings = (0, ai_prompt_hygiene_1.detectAIPromptHygiene)(file.content, file.path);
|
|
20727
|
+
const executionSinkFindings = (0, ai_execution_sinks_1.detectAIExecutionSinks)(file.content, file.path);
|
|
20728
|
+
const agentToolFindings = (0, ai_agent_tools_1.detectAIAgentTools)(file.content, file.path);
|
|
20729
|
+
const ragSafetyFindings = (0, ai_rag_safety_1.detectRAGSafetyIssues)(file.content, file.path);
|
|
20730
|
+
const endpointProtectionFindings = (0, ai_endpoint_protection_1.detectAIEndpointProtection)(file.content, file.path, {
|
|
20731
|
+
middlewareConfig: options.middlewareConfig
|
|
20732
|
+
});
|
|
20733
|
+
const schemaValidationFindings = (0, ai_schema_validation_1.detectAISchemaValidation)(file.content, file.path);
|
|
20734
|
+
stats.variables = variableFindings.length;
|
|
20735
|
+
stats.logicGates = logicFindings.length;
|
|
20736
|
+
stats.dangerousFunctions = dangerousFuncFindings.length;
|
|
20737
|
+
stats.riskyImports = riskyImportFindings.length;
|
|
20738
|
+
stats.authAntipatterns = authFindings.length;
|
|
20739
|
+
stats.frameworkIssues = frameworkFindings.length;
|
|
20740
|
+
stats.aiFingerprints = aiFindings.length;
|
|
20741
|
+
stats.dataExposure = dataExposureFindings.length;
|
|
20742
|
+
stats.byokPatterns = byokFindings.length;
|
|
20743
|
+
stats.promptHygiene = promptHygieneFindings.length;
|
|
20744
|
+
stats.executionSinks = executionSinkFindings.length;
|
|
20745
|
+
stats.agentTools = agentToolFindings.length;
|
|
20746
|
+
stats.ragSafety = ragSafetyFindings.length;
|
|
20747
|
+
stats.endpointProtection = endpointProtectionFindings.length;
|
|
20748
|
+
stats.schemaValidation = schemaValidationFindings.length;
|
|
20749
|
+
return {
|
|
20750
|
+
findings: [
|
|
20751
|
+
...variableFindings,
|
|
20752
|
+
...logicFindings,
|
|
20753
|
+
...dangerousFuncFindings,
|
|
20754
|
+
...riskyImportFindings,
|
|
20755
|
+
...authFindings,
|
|
20756
|
+
...frameworkFindings,
|
|
20757
|
+
...aiFindings,
|
|
20758
|
+
...dataExposureFindings,
|
|
20759
|
+
...byokFindings,
|
|
20760
|
+
...promptHygieneFindings,
|
|
20761
|
+
...executionSinkFindings,
|
|
20762
|
+
...agentToolFindings,
|
|
20763
|
+
...ragSafetyFindings,
|
|
20764
|
+
...endpointProtectionFindings,
|
|
20765
|
+
...schemaValidationFindings
|
|
20766
|
+
],
|
|
20767
|
+
stats
|
|
20768
|
+
};
|
|
20769
|
+
}
|
|
20770
|
+
var LAYER2_PARALLEL_BATCH_SIZE = 50;
|
|
20680
20771
|
async function runLayer2Scan(files, options = {}) {
|
|
20681
20772
|
const startTime = Date.now();
|
|
20682
20773
|
const vulnerabilities = [];
|
|
@@ -20693,51 +20784,19 @@ var require_layer2 = __commonJS({
|
|
|
20693
20784
|
promptHygiene: 0,
|
|
20694
20785
|
executionSinks: 0,
|
|
20695
20786
|
agentTools: 0,
|
|
20696
|
-
// M5: New AI-era detectors
|
|
20697
20787
|
ragSafety: 0,
|
|
20698
20788
|
endpointProtection: 0,
|
|
20699
20789
|
schemaValidation: 0
|
|
20700
20790
|
};
|
|
20701
20791
|
const authHelperContext = options.authHelperContext || (0, auth_helper_detector_1.detectAuthHelpers)(files);
|
|
20702
|
-
for (
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
const
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
authHelpers: authHelperContext,
|
|
20711
|
-
fileAuthImports: options.fileAuthImports
|
|
20712
|
-
});
|
|
20713
|
-
const frameworkFindings = (0, framework_checks_1.detectFrameworkIssues)(file.content, file.path);
|
|
20714
|
-
const aiFindings = (0, ai_fingerprinting_1.detectAIFingerprints)(file.content, file.path);
|
|
20715
|
-
const dataExposureFindings = (0, data_exposure_1.detectDataExposure)(file.content, file.path);
|
|
20716
|
-
const byokFindings = (0, byok_patterns_1.detectBYOKPatterns)(file.content, file.path, options.middlewareConfig);
|
|
20717
|
-
const promptHygieneFindings = (0, ai_prompt_hygiene_1.detectAIPromptHygiene)(file.content, file.path);
|
|
20718
|
-
const executionSinkFindings = (0, ai_execution_sinks_1.detectAIExecutionSinks)(file.content, file.path);
|
|
20719
|
-
const agentToolFindings = (0, ai_agent_tools_1.detectAIAgentTools)(file.content, file.path);
|
|
20720
|
-
const ragSafetyFindings = (0, ai_rag_safety_1.detectRAGSafetyIssues)(file.content, file.path);
|
|
20721
|
-
const endpointProtectionFindings = (0, ai_endpoint_protection_1.detectAIEndpointProtection)(file.content, file.path, {
|
|
20722
|
-
middlewareConfig: options.middlewareConfig
|
|
20723
|
-
});
|
|
20724
|
-
const schemaValidationFindings = (0, ai_schema_validation_1.detectAISchemaValidation)(file.content, file.path);
|
|
20725
|
-
stats.variables += variableFindings.length;
|
|
20726
|
-
stats.logicGates += logicFindings.length;
|
|
20727
|
-
stats.dangerousFunctions += dangerousFuncFindings.length;
|
|
20728
|
-
stats.riskyImports += riskyImportFindings.length;
|
|
20729
|
-
stats.authAntipatterns += authFindings.length;
|
|
20730
|
-
stats.frameworkIssues += frameworkFindings.length;
|
|
20731
|
-
stats.aiFingerprints += aiFindings.length;
|
|
20732
|
-
stats.dataExposure += dataExposureFindings.length;
|
|
20733
|
-
stats.byokPatterns += byokFindings.length;
|
|
20734
|
-
stats.promptHygiene += promptHygieneFindings.length;
|
|
20735
|
-
stats.executionSinks += executionSinkFindings.length;
|
|
20736
|
-
stats.agentTools += agentToolFindings.length;
|
|
20737
|
-
stats.ragSafety += ragSafetyFindings.length;
|
|
20738
|
-
stats.endpointProtection += endpointProtectionFindings.length;
|
|
20739
|
-
stats.schemaValidation += schemaValidationFindings.length;
|
|
20740
|
-
vulnerabilities.push(...variableFindings, ...logicFindings, ...dangerousFuncFindings, ...riskyImportFindings, ...authFindings, ...frameworkFindings, ...aiFindings, ...dataExposureFindings, ...byokFindings, ...promptHygieneFindings, ...executionSinkFindings, ...agentToolFindings, ...ragSafetyFindings, ...endpointProtectionFindings, ...schemaValidationFindings);
|
|
20792
|
+
for (let i = 0; i < files.length; i += LAYER2_PARALLEL_BATCH_SIZE) {
|
|
20793
|
+
const batch = files.slice(i, i + LAYER2_PARALLEL_BATCH_SIZE);
|
|
20794
|
+
const results = await Promise.all(batch.map((file) => Promise.resolve(processFileLayer2(file, options, authHelperContext))));
|
|
20795
|
+
for (const result of results) {
|
|
20796
|
+
vulnerabilities.push(...result.findings);
|
|
20797
|
+
for (const [key, value] of Object.entries(result.stats)) {
|
|
20798
|
+
stats[key] += value;
|
|
20799
|
+
}
|
|
20741
20800
|
}
|
|
20742
20801
|
}
|
|
20743
20802
|
const dedupedVulnerabilities = deduplicateFindings(vulnerabilities);
|
|
@@ -20757,16 +20816,6 @@ var require_layer2 = __commonJS({
|
|
|
20757
20816
|
];
|
|
20758
20817
|
}
|
|
20759
20818
|
const { kept: uniqueVulnerabilities, suppressed } = (0, path_exclusions_1.filterFindingsByPath)(dedupedVulnerabilities, Object.keys(exclusionConfig).length > 0 ? exclusionConfig : void 0);
|
|
20760
|
-
if (suppressed.length > 0) {
|
|
20761
|
-
console.log(`[Layer 2] Suppressed ${suppressed.length} findings in test/seed/example files:`);
|
|
20762
|
-
const byReason = /* @__PURE__ */ new Map();
|
|
20763
|
-
for (const { reason } of suppressed) {
|
|
20764
|
-
byReason.set(reason || "unknown", (byReason.get(reason || "unknown") || 0) + 1);
|
|
20765
|
-
}
|
|
20766
|
-
for (const [reason, count] of byReason) {
|
|
20767
|
-
console.log(` - ${reason}: ${count}`);
|
|
20768
|
-
}
|
|
20769
|
-
}
|
|
20770
20819
|
const rawStats = {
|
|
20771
20820
|
sensitive_variables: stats.variables,
|
|
20772
20821
|
logic_gates: stats.logicGates,
|
|
@@ -20813,15 +20862,6 @@ var require_layer2 = __commonJS({
|
|
|
20813
20862
|
ai_endpoint_protection: "ai_endpoint_protection",
|
|
20814
20863
|
ai_schema_validation: "ai_schema_validation"
|
|
20815
20864
|
};
|
|
20816
|
-
console.log("[Layer 2] Heuristic breakdown (raw findings before dedupe):");
|
|
20817
|
-
for (const [name, count] of Object.entries(rawStats)) {
|
|
20818
|
-
if (count > 0) {
|
|
20819
|
-
const detectorName = detectorNameMap[name];
|
|
20820
|
-
const tier = detectorName ? (0, tiers_1.getLayer2DetectorTier)(detectorName) : "unknown";
|
|
20821
|
-
console.log(` - ${name}: ${count} (${tier})`);
|
|
20822
|
-
}
|
|
20823
|
-
}
|
|
20824
|
-
console.log(`[Layer 2] Tier breakdown (after dedupe): ${(0, tiers_1.formatTierStats)(tierStats)}`);
|
|
20825
20865
|
return {
|
|
20826
20866
|
vulnerabilities: uniqueVulnerabilities,
|
|
20827
20867
|
filesScanned: files.filter((f) => isCodeFile(f.path)).length,
|
|
@@ -36127,8 +36167,8 @@ var require_anthropic = __commonJS({
|
|
|
36127
36167
|
var context_helpers_1 = require_context_helpers();
|
|
36128
36168
|
var project_context_builder_1 = require_project_context_builder();
|
|
36129
36169
|
var tiers_1 = require_tiers();
|
|
36130
|
-
var FILES_PER_API_BATCH =
|
|
36131
|
-
var PARALLEL_API_BATCHES =
|
|
36170
|
+
var FILES_PER_API_BATCH = 8;
|
|
36171
|
+
var PARALLEL_API_BATCHES = 6;
|
|
36132
36172
|
function getAnthropicClient() {
|
|
36133
36173
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
36134
36174
|
if (!apiKey) {
|
|
@@ -38758,9 +38798,15 @@ var require_dist = __commonJS({
|
|
|
38758
38798
|
const scanModeConfig = resolveScanModeConfig(options);
|
|
38759
38799
|
const isIncremental = scanModeConfig.mode === "incremental";
|
|
38760
38800
|
const depth = scanModeConfig.scanDepth || "cheap";
|
|
38761
|
-
|
|
38801
|
+
const quiet = options.quiet ?? false;
|
|
38802
|
+
const log = (message) => {
|
|
38803
|
+
if (!quiet) {
|
|
38804
|
+
console.log(message);
|
|
38805
|
+
}
|
|
38806
|
+
};
|
|
38807
|
+
log(`[Scanner] repo=${repoInfo.name} mode=${scanModeConfig.mode} depth=${depth} files=${files.length}`);
|
|
38762
38808
|
if (isIncremental && scanModeConfig.changedFiles) {
|
|
38763
|
-
|
|
38809
|
+
log(`[Scanner] repo=${repoInfo.name} incremental_files=${scanModeConfig.changedFiles.length}`);
|
|
38764
38810
|
}
|
|
38765
38811
|
const reportProgress = (status2, message, vulnerabilitiesFound = allVulnerabilities.length) => {
|
|
38766
38812
|
if (onProgress) {
|
|
@@ -38777,12 +38823,12 @@ var require_dist = __commonJS({
|
|
|
38777
38823
|
try {
|
|
38778
38824
|
const middlewareConfig = (0, middleware_detector_1.detectGlobalAuthMiddleware)(files);
|
|
38779
38825
|
if (middlewareConfig.hasAuthMiddleware) {
|
|
38780
|
-
|
|
38826
|
+
log(`[Scanner] repo=${repoInfo.name} auth_middleware=${middlewareConfig.authType || "unknown"} file=${middlewareConfig.middlewareFile}`);
|
|
38781
38827
|
}
|
|
38782
38828
|
const fileAuthImports = (0, imported_auth_detector_1.buildFileAuthImports)(files);
|
|
38783
38829
|
const filesWithImportedAuth = Array.from(fileAuthImports.values()).filter((f) => f.usesImportedAuth).length;
|
|
38784
38830
|
if (filesWithImportedAuth > 0) {
|
|
38785
|
-
|
|
38831
|
+
log(`[Scanner] repo=${repoInfo.name} files_with_imported_auth=${filesWithImportedAuth}`);
|
|
38786
38832
|
}
|
|
38787
38833
|
reportProgress("layer1", "Running surface scan (patterns, entropy, config)...");
|
|
38788
38834
|
let layer1Result = await (0, layer1_1.runLayer1Scan)(files);
|
|
@@ -38791,18 +38837,18 @@ var require_dist = __commonJS({
|
|
|
38791
38837
|
...layer1Result,
|
|
38792
38838
|
vulnerabilities: (0, urls_1.aggregateLocalhostFindings)(layer1Result.vulnerabilities)
|
|
38793
38839
|
};
|
|
38794
|
-
|
|
38840
|
+
log(`[Layer1] repo=${repoInfo.name} findings_raw=${layer1RawCount} findings_deduped=${layer1Result.vulnerabilities.length}`);
|
|
38795
38841
|
reportProgress("layer2", "Running structural scan (variables, logic gates)...", layer1Result.vulnerabilities.length);
|
|
38796
38842
|
const layer2Result = await (0, layer2_1.runLayer2Scan)(files, { middlewareConfig, fileAuthImports });
|
|
38797
38843
|
const heuristicBreakdown = Object.entries(layer2Result.stats.raw).filter(([, count]) => count > 0).map(([name, count]) => `${name}:${count}`).join(",");
|
|
38798
|
-
|
|
38844
|
+
log(`[Layer2] repo=${repoInfo.name} findings_raw=${Object.values(layer2Result.stats.raw).reduce((a, b3) => a + b3, 0)} findings_deduped=${layer2Result.vulnerabilities.length} heuristic_breakdown={${heuristicBreakdown}}`);
|
|
38799
38845
|
const layer12Findings = [...layer1Result.vulnerabilities, ...layer2Result.vulnerabilities];
|
|
38800
38846
|
const beforeAggregationCount = layer12Findings.length;
|
|
38801
38847
|
const aggregatedFindings = aggregateNoisyFindings(layer12Findings);
|
|
38802
38848
|
const aggregatedCount = beforeAggregationCount - aggregatedFindings.length;
|
|
38803
38849
|
const tierFiltered = filterByTierAndDepth(aggregatedFindings, depth);
|
|
38804
|
-
|
|
38805
|
-
|
|
38850
|
+
log(`[Scanner] repo=${repoInfo.name} tier_breakdown=${(0, tiers_1.formatTierStats)(tierFiltered.tierStats)}`);
|
|
38851
|
+
log(`[Scanner] repo=${repoInfo.name} depth=${depth} tier_routing: surface=${tierFiltered.toSurface.length} validate=${tierFiltered.toValidate.length} hidden=${tierFiltered.hidden.length}`);
|
|
38806
38852
|
const additionalValidation = tierFiltered.toSurface.filter(
|
|
38807
38853
|
(v2) => v2.requiresAIValidation || v2.category === "ai_prompt_injection" || // Story B1: Prompt hygiene
|
|
38808
38854
|
v2.category === "ai_unsafe_execution" || // Story B2: LLM output execution
|
|
@@ -38817,7 +38863,7 @@ var require_dist = __commonJS({
|
|
|
38817
38863
|
autoDismissBySeverity[d3.finding.severity] = (autoDismissBySeverity[d3.finding.severity] || 0) + 1;
|
|
38818
38864
|
}
|
|
38819
38865
|
if (autoDismissed.length > 0) {
|
|
38820
|
-
|
|
38866
|
+
log(`[Layer2] repo=${repoInfo.name} auto_dismissed_total=${autoDismissed.length} by_severity={info:${autoDismissBySeverity.info},low:${autoDismissBySeverity.low},medium:${autoDismissBySeverity.medium},high:${autoDismissBySeverity.high}}`);
|
|
38821
38867
|
}
|
|
38822
38868
|
const maxValidationFiles = scanModeConfig.maxAIValidationFiles || MAX_VALIDATION_CANDIDATES_PER_FILE;
|
|
38823
38869
|
const cappedValidation = capValidationCandidatesPerFile(afterAutoDismiss, maxValidationFiles);
|
|
@@ -38832,13 +38878,13 @@ var require_dist = __commonJS({
|
|
|
38832
38878
|
validatedFindings = validationResult.vulnerabilities;
|
|
38833
38879
|
const { stats: validationStats } = validationResult;
|
|
38834
38880
|
capturedValidationStats = validationStats;
|
|
38835
|
-
|
|
38836
|
-
|
|
38881
|
+
log(`[AI Validation] repo=${repoInfo.name} depth=${depth} candidates=${findingsToValidate.length} capped_from=${requiresValidation.length} auto_dismissed=${autoDismissed.length} kept=${validationStats.confirmedFindings} rejected=${validationStats.dismissedFindings} downgraded=${validationStats.downgradedFindings}`);
|
|
38882
|
+
log(`[AI Validation] cost_estimate: input_tokens=${validationStats.estimatedInputTokens} output_tokens=${validationStats.estimatedOutputTokens} cost=$${validationStats.estimatedCost.toFixed(4)} api_calls=${validationStats.apiCalls}`);
|
|
38837
38883
|
const notValidated = cappedValidation.filter((v2) => !findingsToValidate.includes(v2));
|
|
38838
38884
|
validatedFindings.push(...notValidated);
|
|
38839
38885
|
}
|
|
38840
38886
|
} else if (scanModeConfig.skipAIValidation) {
|
|
38841
|
-
|
|
38887
|
+
log(`[AI Validation] repo=${repoInfo.name} depth=${depth} skipped=true reason=scan_mode_config`);
|
|
38842
38888
|
}
|
|
38843
38889
|
allVulnerabilities.push(...validatedFindings, ...noValidationNeeded);
|
|
38844
38890
|
const shouldRunLayer3 = options.enableAI !== false && !scanModeConfig.skipLayer3;
|
|
@@ -38863,9 +38909,9 @@ var require_dist = __commonJS({
|
|
|
38863
38909
|
}
|
|
38864
38910
|
});
|
|
38865
38911
|
allVulnerabilities.push(...layer3Result.vulnerabilities);
|
|
38866
|
-
|
|
38912
|
+
log(`[Layer3] repo=${repoInfo.name} depth=${depth} files_analyzed=${layer3Result.aiAnalyzed} findings=${layer3Result.vulnerabilities.length}`);
|
|
38867
38913
|
} else if (scanModeConfig.skipLayer3) {
|
|
38868
|
-
|
|
38914
|
+
log(`[Layer3] repo=${repoInfo.name} depth=${depth} skipped=true reason=scan_mode_config`);
|
|
38869
38915
|
}
|
|
38870
38916
|
const uniqueVulnerabilities = deduplicateVulnerabilities(allVulnerabilities);
|
|
38871
38917
|
const resolvedVulnerabilities = resolveContradictions(uniqueVulnerabilities, middlewareConfig);
|
|
@@ -38976,9 +39022,6 @@ Found ${group.length} occurrences at lines: ${lineDisplay}`,
|
|
|
38976
39022
|
});
|
|
38977
39023
|
const capped = sorted.slice(0, maxPerFile);
|
|
38978
39024
|
result.push(...capped);
|
|
38979
|
-
if (sorted.length > maxPerFile) {
|
|
38980
|
-
console.log(`[Scanner] Capped ${filePath}: ${sorted.length} \u2192 ${maxPerFile} validation candidates`);
|
|
38981
|
-
}
|
|
38982
39025
|
}
|
|
38983
39026
|
return result;
|
|
38984
39027
|
}
|
|
@@ -39031,7 +39074,6 @@ Found ${group.length} occurrences at lines: ${lineDisplay}`,
|
|
|
39031
39074
|
for (const vuln of otherAuthFindings) {
|
|
39032
39075
|
if (vuln.severity === "critical" || vuln.severity === "high") {
|
|
39033
39076
|
const reason = isAPIRouteProtected ? "API route protected by middleware" : isClientCallingProtectedAPI ? "client component calling protected API" : "route is protected";
|
|
39034
|
-
console.log(`[Contradiction] Dropping "${vuln.title}" (${vuln.severity}) - ${reason}`);
|
|
39035
39077
|
continue;
|
|
39036
39078
|
}
|
|
39037
39079
|
result.push(vuln);
|
|
@@ -43498,18 +43540,19 @@ async function runScanOnce(targetPath, options) {
|
|
|
43498
43540
|
}
|
|
43499
43541
|
spinner.succeed(`Found ${files.length} files to scan`);
|
|
43500
43542
|
const onProgress = (progress) => {
|
|
43543
|
+
const fileInfo = source_default.dim(`(${progress.totalFiles} files)`);
|
|
43501
43544
|
switch (progress.status) {
|
|
43502
43545
|
case "layer1":
|
|
43503
|
-
spinner.text = `
|
|
43546
|
+
spinner.text = `Layer 1: Pattern matching ${fileInfo} ${source_default.dim(`\u2192 ${progress.vulnerabilitiesFound} candidates`)}`;
|
|
43504
43547
|
break;
|
|
43505
43548
|
case "layer2":
|
|
43506
|
-
spinner.text = `
|
|
43549
|
+
spinner.text = `Layer 2: Code structure analysis ${fileInfo} ${source_default.dim(`\u2192 ${progress.vulnerabilitiesFound} findings`)}`;
|
|
43507
43550
|
break;
|
|
43508
43551
|
case "validating":
|
|
43509
|
-
spinner.text = `AI
|
|
43552
|
+
spinner.text = `AI validation ${source_default.dim(`\u2192 reviewing ${progress.vulnerabilitiesFound} candidates`)}`;
|
|
43510
43553
|
break;
|
|
43511
43554
|
case "layer3":
|
|
43512
|
-
spinner.text = `Deep AI analysis
|
|
43555
|
+
spinner.text = `Layer 3: Deep AI analysis ${source_default.dim(`\u2192 semantic security check`)}`;
|
|
43513
43556
|
break;
|
|
43514
43557
|
case "complete":
|
|
43515
43558
|
const issueText = progress.vulnerabilitiesFound === 1 ? "issue" : "issues";
|
|
@@ -43543,6 +43586,7 @@ async function runScanOnce(targetPath, options) {
|
|
|
43543
43586
|
spinner.succeed(`Backend scan complete`);
|
|
43544
43587
|
} else {
|
|
43545
43588
|
const enableAI = options.depth !== "cheap" && hasLocalAI;
|
|
43589
|
+
const shouldBeQuiet = options.quiet ?? !options.verbose;
|
|
43546
43590
|
result = await (0, import_scanner.runScan)(
|
|
43547
43591
|
files,
|
|
43548
43592
|
{
|
|
@@ -43552,7 +43596,8 @@ async function runScanOnce(targetPath, options) {
|
|
|
43552
43596
|
},
|
|
43553
43597
|
{
|
|
43554
43598
|
enableAI,
|
|
43555
|
-
scanDepth: options.depth
|
|
43599
|
+
scanDepth: options.depth,
|
|
43600
|
+
quiet: shouldBeQuiet
|
|
43556
43601
|
},
|
|
43557
43602
|
onProgress
|
|
43558
43603
|
);
|
|
@@ -43595,6 +43640,7 @@ async function runScan(targetPath, cliOptions) {
|
|
|
43595
43640
|
failOn: cliOptions.failOn ?? "high",
|
|
43596
43641
|
color: cliOptions.color,
|
|
43597
43642
|
quiet: cliOptions.quiet,
|
|
43643
|
+
verbose: cliOptions.verbose,
|
|
43598
43644
|
incremental: cliOptions.incremental,
|
|
43599
43645
|
diff: cliOptions.diff,
|
|
43600
43646
|
output: cliOptions.output
|
|
@@ -43605,6 +43651,7 @@ async function runScan(targetPath, cliOptions) {
|
|
|
43605
43651
|
format: baseOptions.format ?? projectConfig?.format ?? "terminal",
|
|
43606
43652
|
failOn: baseOptions.failOn ?? projectConfig?.failOn ?? "high",
|
|
43607
43653
|
quiet: baseOptions.quiet ?? projectConfig?.quiet,
|
|
43654
|
+
verbose: baseOptions.verbose,
|
|
43608
43655
|
output: baseOptions.output ?? projectConfig?.output
|
|
43609
43656
|
};
|
|
43610
43657
|
try {
|
|
@@ -43619,7 +43666,7 @@ async function runScan(targetPath, cliOptions) {
|
|
|
43619
43666
|
process.exit(1);
|
|
43620
43667
|
}
|
|
43621
43668
|
}
|
|
43622
|
-
var scanCommand = new Command("scan").description("Scan a directory or file for security vulnerabilities").argument("[path]", "path to scan", ".").option("-d, --depth <depth>", "scan depth: cheap (free), validated, deep", "cheap").option("-m, --mode <mode>", "alias for --depth (quick, validated, deep)").option("-f, --format <format>", "output format: terminal, json, sarif, markdown", "terminal").option("--fail-on <severity>", "exit with error code if findings at severity", "high").option("--no-color", "disable colored output").option("-q, --quiet", "minimal output for CI (suppress spinners and decorations)").option("-o, --output <file>", "write output to file").option("--incremental", "only scan changed files (requires git)").option("--diff <ref>", "diff against branch/commit for incremental scan").addHelpText("after", `
|
|
43669
|
+
var scanCommand = new Command("scan").description("Scan a directory or file for security vulnerabilities").argument("[path]", "path to scan", ".").option("-d, --depth <depth>", "scan depth: cheap (free), validated, deep", "cheap").option("-m, --mode <mode>", "alias for --depth (quick, validated, deep)").option("-f, --format <format>", "output format: terminal, json, sarif, markdown", "terminal").option("--fail-on <severity>", "exit with error code if findings at severity", "high").option("--no-color", "disable colored output").option("-q, --quiet", "minimal output for CI (suppress spinners and decorations)").option("-v, --verbose", "show detailed scanner logs (debug mode)").option("-o, --output <file>", "write output to file").option("--incremental", "only scan changed files (requires git)").option("--diff <ref>", "diff against branch/commit for incremental scan").addHelpText("after", `
|
|
43623
43670
|
Examples:
|
|
43624
43671
|
$ oculum scan . # Scan current directory (quick/free)
|
|
43625
43672
|
$ oculum scan ./src # Scan specific directory
|
|
@@ -47052,6 +47099,8 @@ async function runScanWizard() {
|
|
|
47052
47099
|
format,
|
|
47053
47100
|
failOn,
|
|
47054
47101
|
color: true,
|
|
47102
|
+
quiet: true,
|
|
47103
|
+
// Suppress scanner logs in interactive UI mode
|
|
47055
47104
|
incremental,
|
|
47056
47105
|
diff: diffRef,
|
|
47057
47106
|
output: outputPath
|
|
@@ -47079,7 +47128,9 @@ async function runQuickScan(targetPath = ".") {
|
|
|
47079
47128
|
depth: "cheap",
|
|
47080
47129
|
format: "terminal",
|
|
47081
47130
|
failOn: "high",
|
|
47082
|
-
color: true
|
|
47131
|
+
color: true,
|
|
47132
|
+
quiet: true
|
|
47133
|
+
// Suppress scanner logs in interactive UI mode
|
|
47083
47134
|
},
|
|
47084
47135
|
cancelled: false
|
|
47085
47136
|
};
|
|
@@ -47532,7 +47583,9 @@ async function runUI() {
|
|
|
47532
47583
|
depth,
|
|
47533
47584
|
format: "terminal",
|
|
47534
47585
|
failOn: "high",
|
|
47535
|
-
color: true
|
|
47586
|
+
color: true,
|
|
47587
|
+
quiet: true
|
|
47588
|
+
// Suppress scanner logs in interactive UI mode
|
|
47536
47589
|
});
|
|
47537
47590
|
console.log(output);
|
|
47538
47591
|
recordScan();
|
|
@@ -47771,7 +47824,6 @@ async function usage(options) {
|
|
|
47771
47824
|
console.log("");
|
|
47772
47825
|
console.log(source_default.dim(" Scans: ") + source_default.white(formatNumber(usageData.totalScans)));
|
|
47773
47826
|
console.log(source_default.dim(" Files: ") + source_default.white(formatNumber(usageData.totalFiles)));
|
|
47774
|
-
console.log(source_default.dim(" Cost: ") + source_default.white(`$${usageData.totalCostDollars.toFixed(4)}`));
|
|
47775
47827
|
console.log("");
|
|
47776
47828
|
console.log(source_default.dim(" Resets on: ") + source_default.white(formatDate(usageData.resetDate)));
|
|
47777
47829
|
console.log("");
|
package/package.json
CHANGED