@oculum/cli 1.0.1 → 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 +343 -151
- 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,
|
|
@@ -17678,7 +17690,7 @@ var require_ai_fingerprinting = __commonJS({
|
|
|
17678
17690
|
return vulnerabilities;
|
|
17679
17691
|
}
|
|
17680
17692
|
const anyUsageByContext = categorizeAnyUsage(lines, filePath);
|
|
17681
|
-
const priorityAny = anyUsageByContext.filter((
|
|
17693
|
+
const priorityAny = anyUsageByContext.filter((usage2) => usage2.context === "api_boundary" || usage2.context === "database_layer" || usage2.context === "auth_handler");
|
|
17682
17694
|
const cappedAny = priorityAny.slice(0, 5);
|
|
17683
17695
|
if (cappedAny.length === 0) {
|
|
17684
17696
|
return vulnerabilities;
|
|
@@ -17708,21 +17720,21 @@ var require_ai_fingerprinting = __commonJS({
|
|
|
17708
17720
|
layer: 2
|
|
17709
17721
|
});
|
|
17710
17722
|
} else {
|
|
17711
|
-
for (const
|
|
17723
|
+
for (const usage2 of cappedAny) {
|
|
17712
17724
|
const contextNames = {
|
|
17713
17725
|
"api_boundary": "API request/response handler",
|
|
17714
17726
|
"database_layer": "Database query",
|
|
17715
17727
|
"auth_handler": "Authentication logic"
|
|
17716
17728
|
};
|
|
17717
17729
|
vulnerabilities.push({
|
|
17718
|
-
id: `ai-fingerprint-any-${filePath}-${
|
|
17730
|
+
id: `ai-fingerprint-any-${filePath}-${usage2.lineNumber}`,
|
|
17719
17731
|
filePath,
|
|
17720
|
-
lineNumber:
|
|
17721
|
-
lineContent:
|
|
17732
|
+
lineNumber: usage2.lineNumber,
|
|
17733
|
+
lineContent: usage2.lineContent,
|
|
17722
17734
|
severity: "low",
|
|
17723
17735
|
category: "ai_pattern",
|
|
17724
|
-
title: `[AI Pattern] TypeScript 'any' in ${contextNames[
|
|
17725
|
-
description: `Using 'any' type at a security boundary bypasses type checking and can lead to type confusion vulnerabilities. This is especially risky in ${contextNames[
|
|
17736
|
+
title: `[AI Pattern] TypeScript 'any' in ${contextNames[usage2.context] || usage2.context}`,
|
|
17737
|
+
description: `Using 'any' type at a security boundary bypasses type checking and can lead to type confusion vulnerabilities. This is especially risky in ${contextNames[usage2.context] || usage2.context}.`,
|
|
17726
17738
|
suggestedFix: 'Replace "any" with an explicit type. Use typed request schemas, ORM models, or interface definitions.',
|
|
17727
17739
|
confidence: "medium",
|
|
17728
17740
|
layer: 2
|
|
@@ -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,
|
|
@@ -29411,11 +29451,11 @@ var require_AbstractChatCompletionRunner = __commonJS({
|
|
|
29411
29451
|
prompt_tokens: 0,
|
|
29412
29452
|
total_tokens: 0
|
|
29413
29453
|
};
|
|
29414
|
-
for (const { usage } of this._chatCompletions) {
|
|
29415
|
-
if (
|
|
29416
|
-
total.completion_tokens +=
|
|
29417
|
-
total.prompt_tokens +=
|
|
29418
|
-
total.total_tokens +=
|
|
29454
|
+
for (const { usage: usage2 } of this._chatCompletions) {
|
|
29455
|
+
if (usage2) {
|
|
29456
|
+
total.completion_tokens += usage2.completion_tokens;
|
|
29457
|
+
total.prompt_tokens += usage2.prompt_tokens;
|
|
29458
|
+
total.total_tokens += usage2.total_tokens;
|
|
29419
29459
|
}
|
|
29420
29460
|
}
|
|
29421
29461
|
return total;
|
|
@@ -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) {
|
|
@@ -37001,11 +37041,11 @@ For each candidate finding, return:
|
|
|
37001
37041
|
max_completion_tokens: 4096
|
|
37002
37042
|
}));
|
|
37003
37043
|
statsLock.apiCalls++;
|
|
37004
|
-
const
|
|
37005
|
-
if (
|
|
37006
|
-
const promptTokens =
|
|
37007
|
-
const completionTokens =
|
|
37008
|
-
const cachedTokens =
|
|
37044
|
+
const usage2 = response.usage;
|
|
37045
|
+
if (usage2) {
|
|
37046
|
+
const promptTokens = usage2.prompt_tokens || 0;
|
|
37047
|
+
const completionTokens = usage2.completion_tokens || 0;
|
|
37048
|
+
const cachedTokens = usage2.prompt_tokens_details?.cached_tokens || 0;
|
|
37009
37049
|
const freshInputTokens = promptTokens - cachedTokens;
|
|
37010
37050
|
statsLock.estimatedInputTokens += freshInputTokens;
|
|
37011
37051
|
statsLock.estimatedOutputTokens += completionTokens;
|
|
@@ -37226,19 +37266,19 @@ For each candidate finding, return:
|
|
|
37226
37266
|
}));
|
|
37227
37267
|
stats.apiCalls++;
|
|
37228
37268
|
totalApiBatches++;
|
|
37229
|
-
const
|
|
37230
|
-
if (
|
|
37269
|
+
const usage2 = response.usage;
|
|
37270
|
+
if (usage2) {
|
|
37231
37271
|
console.log(`[DEBUG] Batch ${batchNum} - Full API Response Usage:`);
|
|
37232
|
-
console.log(JSON.stringify(
|
|
37272
|
+
console.log(JSON.stringify(usage2, null, 2));
|
|
37233
37273
|
console.log(`[DEBUG] Breakdown:`);
|
|
37234
|
-
console.log(` - input_tokens: ${
|
|
37235
|
-
console.log(` - output_tokens: ${
|
|
37236
|
-
console.log(` - cache_creation_input_tokens: ${
|
|
37237
|
-
console.log(` - cache_read_input_tokens: ${
|
|
37238
|
-
stats.estimatedInputTokens +=
|
|
37239
|
-
stats.estimatedOutputTokens +=
|
|
37240
|
-
const cacheCreation =
|
|
37241
|
-
const cacheRead =
|
|
37274
|
+
console.log(` - input_tokens: ${usage2.input_tokens || 0}`);
|
|
37275
|
+
console.log(` - output_tokens: ${usage2.output_tokens || 0}`);
|
|
37276
|
+
console.log(` - cache_creation_input_tokens: ${usage2.cache_creation_input_tokens || 0}`);
|
|
37277
|
+
console.log(` - cache_read_input_tokens: ${usage2.cache_read_input_tokens || 0}`);
|
|
37278
|
+
stats.estimatedInputTokens += usage2.input_tokens || 0;
|
|
37279
|
+
stats.estimatedOutputTokens += usage2.output_tokens || 0;
|
|
37280
|
+
const cacheCreation = usage2.cache_creation_input_tokens || 0;
|
|
37281
|
+
const cacheRead = usage2.cache_read_input_tokens || 0;
|
|
37242
37282
|
stats.cacheCreationTokens += cacheCreation;
|
|
37243
37283
|
stats.cacheReadTokens += cacheRead;
|
|
37244
37284
|
}
|
|
@@ -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);
|
|
@@ -42827,6 +42869,27 @@ async function pollForLogin(deviceCode) {
|
|
|
42827
42869
|
tier: result.tier
|
|
42828
42870
|
};
|
|
42829
42871
|
}
|
|
42872
|
+
async function getUsage(apiKey) {
|
|
42873
|
+
const baseUrl = getApiBaseUrl();
|
|
42874
|
+
const url = `${baseUrl}/v1/usage`;
|
|
42875
|
+
try {
|
|
42876
|
+
const response = await fetch(url, {
|
|
42877
|
+
method: "GET",
|
|
42878
|
+
headers: {
|
|
42879
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
42880
|
+
"X-Oculum-Client": "cli",
|
|
42881
|
+
"X-Oculum-Version": "1.0.0"
|
|
42882
|
+
}
|
|
42883
|
+
});
|
|
42884
|
+
const result = await response.json();
|
|
42885
|
+
return result;
|
|
42886
|
+
} catch (error) {
|
|
42887
|
+
return {
|
|
42888
|
+
success: false,
|
|
42889
|
+
error: "Failed to fetch usage data. Please check your internet connection."
|
|
42890
|
+
};
|
|
42891
|
+
}
|
|
42892
|
+
}
|
|
42830
42893
|
|
|
42831
42894
|
// src/utils/errors.ts
|
|
42832
42895
|
function enhanceError(error) {
|
|
@@ -43477,18 +43540,19 @@ async function runScanOnce(targetPath, options) {
|
|
|
43477
43540
|
}
|
|
43478
43541
|
spinner.succeed(`Found ${files.length} files to scan`);
|
|
43479
43542
|
const onProgress = (progress) => {
|
|
43543
|
+
const fileInfo = source_default.dim(`(${progress.totalFiles} files)`);
|
|
43480
43544
|
switch (progress.status) {
|
|
43481
43545
|
case "layer1":
|
|
43482
|
-
spinner.text = `
|
|
43546
|
+
spinner.text = `Layer 1: Pattern matching ${fileInfo} ${source_default.dim(`\u2192 ${progress.vulnerabilitiesFound} candidates`)}`;
|
|
43483
43547
|
break;
|
|
43484
43548
|
case "layer2":
|
|
43485
|
-
spinner.text = `
|
|
43549
|
+
spinner.text = `Layer 2: Code structure analysis ${fileInfo} ${source_default.dim(`\u2192 ${progress.vulnerabilitiesFound} findings`)}`;
|
|
43486
43550
|
break;
|
|
43487
43551
|
case "validating":
|
|
43488
|
-
spinner.text = `AI
|
|
43552
|
+
spinner.text = `AI validation ${source_default.dim(`\u2192 reviewing ${progress.vulnerabilitiesFound} candidates`)}`;
|
|
43489
43553
|
break;
|
|
43490
43554
|
case "layer3":
|
|
43491
|
-
spinner.text = `Deep AI analysis
|
|
43555
|
+
spinner.text = `Layer 3: Deep AI analysis ${source_default.dim(`\u2192 semantic security check`)}`;
|
|
43492
43556
|
break;
|
|
43493
43557
|
case "complete":
|
|
43494
43558
|
const issueText = progress.vulnerabilitiesFound === 1 ? "issue" : "issues";
|
|
@@ -43522,6 +43586,7 @@ async function runScanOnce(targetPath, options) {
|
|
|
43522
43586
|
spinner.succeed(`Backend scan complete`);
|
|
43523
43587
|
} else {
|
|
43524
43588
|
const enableAI = options.depth !== "cheap" && hasLocalAI;
|
|
43589
|
+
const shouldBeQuiet = options.quiet ?? !options.verbose;
|
|
43525
43590
|
result = await (0, import_scanner.runScan)(
|
|
43526
43591
|
files,
|
|
43527
43592
|
{
|
|
@@ -43531,7 +43596,8 @@ async function runScanOnce(targetPath, options) {
|
|
|
43531
43596
|
},
|
|
43532
43597
|
{
|
|
43533
43598
|
enableAI,
|
|
43534
|
-
scanDepth: options.depth
|
|
43599
|
+
scanDepth: options.depth,
|
|
43600
|
+
quiet: shouldBeQuiet
|
|
43535
43601
|
},
|
|
43536
43602
|
onProgress
|
|
43537
43603
|
);
|
|
@@ -43574,6 +43640,7 @@ async function runScan(targetPath, cliOptions) {
|
|
|
43574
43640
|
failOn: cliOptions.failOn ?? "high",
|
|
43575
43641
|
color: cliOptions.color,
|
|
43576
43642
|
quiet: cliOptions.quiet,
|
|
43643
|
+
verbose: cliOptions.verbose,
|
|
43577
43644
|
incremental: cliOptions.incremental,
|
|
43578
43645
|
diff: cliOptions.diff,
|
|
43579
43646
|
output: cliOptions.output
|
|
@@ -43584,6 +43651,7 @@ async function runScan(targetPath, cliOptions) {
|
|
|
43584
43651
|
format: baseOptions.format ?? projectConfig?.format ?? "terminal",
|
|
43585
43652
|
failOn: baseOptions.failOn ?? projectConfig?.failOn ?? "high",
|
|
43586
43653
|
quiet: baseOptions.quiet ?? projectConfig?.quiet,
|
|
43654
|
+
verbose: baseOptions.verbose,
|
|
43587
43655
|
output: baseOptions.output ?? projectConfig?.output
|
|
43588
43656
|
};
|
|
43589
43657
|
try {
|
|
@@ -43598,7 +43666,7 @@ async function runScan(targetPath, cliOptions) {
|
|
|
43598
43666
|
process.exit(1);
|
|
43599
43667
|
}
|
|
43600
43668
|
}
|
|
43601
|
-
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", `
|
|
43602
43670
|
Examples:
|
|
43603
43671
|
$ oculum scan . # Scan current directory (quick/free)
|
|
43604
43672
|
$ oculum scan ./src # Scan specific directory
|
|
@@ -43654,12 +43722,14 @@ async function login(options) {
|
|
|
43654
43722
|
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
43655
43723
|
console.log("");
|
|
43656
43724
|
spinner.start("Initiating login...");
|
|
43657
|
-
const { authUrl, deviceCode } = await initiateLogin();
|
|
43725
|
+
const { authUrl, deviceCode, userCode } = await initiateLogin();
|
|
43658
43726
|
spinner.stop();
|
|
43659
43727
|
console.log(source_default.white(" Open this URL in your browser to login:"));
|
|
43660
43728
|
console.log("");
|
|
43661
43729
|
console.log(source_default.cyan.bold(` ${authUrl}`));
|
|
43662
43730
|
console.log("");
|
|
43731
|
+
console.log(source_default.white(" Or enter this code manually: ") + source_default.yellow.bold(userCode));
|
|
43732
|
+
console.log("");
|
|
43663
43733
|
console.log(source_default.dim(" Waiting for browser authorization..."));
|
|
43664
43734
|
console.log(source_default.dim(" (This will timeout after 5 minutes)"));
|
|
43665
43735
|
console.log("");
|
|
@@ -46587,6 +46657,7 @@ async function showFeatureExploration() {
|
|
|
46587
46657
|
console.log(source_default.dim(" oculum watch . ") + source_default.white("Watch for changes"));
|
|
46588
46658
|
console.log(source_default.dim(" oculum ui ") + source_default.white("Interactive mode"));
|
|
46589
46659
|
console.log(source_default.dim(" oculum login ") + source_default.white("Authenticate for Pro features"));
|
|
46660
|
+
console.log(source_default.dim(" oculum usage ") + source_default.white("View credits and quota"));
|
|
46590
46661
|
console.log(source_default.dim(" oculum --help ") + source_default.white("Show all commands\n"));
|
|
46591
46662
|
await ve({
|
|
46592
46663
|
message: "Press Enter to continue",
|
|
@@ -47028,6 +47099,8 @@ async function runScanWizard() {
|
|
|
47028
47099
|
format,
|
|
47029
47100
|
failOn,
|
|
47030
47101
|
color: true,
|
|
47102
|
+
quiet: true,
|
|
47103
|
+
// Suppress scanner logs in interactive UI mode
|
|
47031
47104
|
incremental,
|
|
47032
47105
|
diff: diffRef,
|
|
47033
47106
|
output: outputPath
|
|
@@ -47055,7 +47128,9 @@ async function runQuickScan(targetPath = ".") {
|
|
|
47055
47128
|
depth: "cheap",
|
|
47056
47129
|
format: "terminal",
|
|
47057
47130
|
failOn: "high",
|
|
47058
|
-
color: true
|
|
47131
|
+
color: true,
|
|
47132
|
+
quiet: true
|
|
47133
|
+
// Suppress scanner logs in interactive UI mode
|
|
47059
47134
|
},
|
|
47060
47135
|
cancelled: false
|
|
47061
47136
|
};
|
|
@@ -47508,7 +47583,9 @@ async function runUI() {
|
|
|
47508
47583
|
depth,
|
|
47509
47584
|
format: "terminal",
|
|
47510
47585
|
failOn: "high",
|
|
47511
|
-
color: true
|
|
47586
|
+
color: true,
|
|
47587
|
+
quiet: true
|
|
47588
|
+
// Suppress scanner logs in interactive UI mode
|
|
47512
47589
|
});
|
|
47513
47590
|
console.log(output);
|
|
47514
47591
|
recordScan();
|
|
@@ -47677,6 +47754,119 @@ var uiCommand = new Command("ui").description("Interactive terminal UI").action(
|
|
|
47677
47754
|
await runUI();
|
|
47678
47755
|
});
|
|
47679
47756
|
|
|
47757
|
+
// src/commands/usage.ts
|
|
47758
|
+
function formatNumber(num) {
|
|
47759
|
+
if (num === -1) return "unlimited";
|
|
47760
|
+
return num.toLocaleString();
|
|
47761
|
+
}
|
|
47762
|
+
function createProgressBar(percentage, width = 20) {
|
|
47763
|
+
const filled = Math.round(percentage / 100 * width);
|
|
47764
|
+
const empty = width - filled;
|
|
47765
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
47766
|
+
if (percentage >= 90) return source_default.red(bar);
|
|
47767
|
+
if (percentage >= 70) return source_default.yellow(bar);
|
|
47768
|
+
return source_default.green(bar);
|
|
47769
|
+
}
|
|
47770
|
+
function formatDate(dateStr) {
|
|
47771
|
+
const date = new Date(dateStr);
|
|
47772
|
+
return date.toLocaleDateString("en-US", {
|
|
47773
|
+
month: "short",
|
|
47774
|
+
day: "numeric",
|
|
47775
|
+
year: "numeric"
|
|
47776
|
+
});
|
|
47777
|
+
}
|
|
47778
|
+
async function usage(options) {
|
|
47779
|
+
const config = getConfig();
|
|
47780
|
+
if (!isAuthenticated()) {
|
|
47781
|
+
console.log("");
|
|
47782
|
+
console.log(source_default.yellow(" \u26A0 Not logged in"));
|
|
47783
|
+
console.log("");
|
|
47784
|
+
console.log(source_default.dim(" Login to view your usage and quota:"));
|
|
47785
|
+
console.log(source_default.cyan(" oculum login"));
|
|
47786
|
+
console.log("");
|
|
47787
|
+
process.exit(1);
|
|
47788
|
+
}
|
|
47789
|
+
const spinner = ora("Fetching usage data...").start();
|
|
47790
|
+
try {
|
|
47791
|
+
const result = await getUsage(config.apiKey);
|
|
47792
|
+
if (!result.success || !result.usage || !result.plan) {
|
|
47793
|
+
spinner.fail("Failed to fetch usage data");
|
|
47794
|
+
console.log("");
|
|
47795
|
+
console.log(source_default.dim(" Error: ") + source_default.red(result.error || "Unknown error"));
|
|
47796
|
+
console.log("");
|
|
47797
|
+
process.exit(1);
|
|
47798
|
+
}
|
|
47799
|
+
spinner.stop();
|
|
47800
|
+
if (options.json) {
|
|
47801
|
+
console.log(JSON.stringify(result, null, 2));
|
|
47802
|
+
return;
|
|
47803
|
+
}
|
|
47804
|
+
const { plan, usage: usageData } = result;
|
|
47805
|
+
console.log("");
|
|
47806
|
+
console.log(source_default.bold(" \u{1F4CA} Oculum Usage"));
|
|
47807
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
47808
|
+
console.log("");
|
|
47809
|
+
const planBadge = plan.name === "pro" ? source_default.bgBlue.white(" PRO ") : plan.name === "enterprise" || plan.name === "max" ? source_default.bgMagenta.white(" MAX ") : plan.name === "starter" ? source_default.bgCyan.white(" STARTER ") : source_default.bgGray.white(" FREE ");
|
|
47810
|
+
console.log(source_default.dim(" Plan: ") + planBadge + source_default.white(` ${plan.displayName}`));
|
|
47811
|
+
console.log(source_default.dim(" Month: ") + source_default.white(usageData.month));
|
|
47812
|
+
console.log("");
|
|
47813
|
+
console.log(source_default.bold(" Credits Usage"));
|
|
47814
|
+
console.log("");
|
|
47815
|
+
const creditsDisplay = usageData.creditsLimit === -1 ? `${formatNumber(usageData.creditsUsed)} / unlimited` : `${formatNumber(usageData.creditsUsed)} / ${formatNumber(usageData.creditsLimit)}`;
|
|
47816
|
+
console.log(source_default.dim(" Used: ") + source_default.white(creditsDisplay));
|
|
47817
|
+
if (usageData.creditsLimit !== -1) {
|
|
47818
|
+
console.log(source_default.dim(" Remaining: ") + source_default.white(formatNumber(usageData.creditsRemaining)));
|
|
47819
|
+
console.log("");
|
|
47820
|
+
console.log(source_default.dim(" ") + createProgressBar(usageData.usagePercentage) + source_default.dim(` ${usageData.usagePercentage.toFixed(1)}%`));
|
|
47821
|
+
}
|
|
47822
|
+
console.log("");
|
|
47823
|
+
console.log(source_default.bold(" This Month"));
|
|
47824
|
+
console.log("");
|
|
47825
|
+
console.log(source_default.dim(" Scans: ") + source_default.white(formatNumber(usageData.totalScans)));
|
|
47826
|
+
console.log(source_default.dim(" Files: ") + source_default.white(formatNumber(usageData.totalFiles)));
|
|
47827
|
+
console.log("");
|
|
47828
|
+
console.log(source_default.dim(" Resets on: ") + source_default.white(formatDate(usageData.resetDate)));
|
|
47829
|
+
console.log("");
|
|
47830
|
+
if (result.recentScans && result.recentScans.length > 0) {
|
|
47831
|
+
console.log(source_default.bold(" Recent Scans"));
|
|
47832
|
+
console.log("");
|
|
47833
|
+
const recentToShow = result.recentScans.slice(0, 5);
|
|
47834
|
+
for (const scan of recentToShow) {
|
|
47835
|
+
const date = new Date(scan.createdAt);
|
|
47836
|
+
const timeAgo = getTimeAgo(date);
|
|
47837
|
+
console.log(source_default.dim(" \u2022 ") + source_default.white(scan.repoName || "unknown") + source_default.dim(` (${timeAgo})`));
|
|
47838
|
+
console.log(source_default.dim(" ") + source_default.dim(`${scan.filesScanned} files, ${scan.findingsCount} findings, ${scan.creditsUsed} credits`));
|
|
47839
|
+
}
|
|
47840
|
+
console.log("");
|
|
47841
|
+
}
|
|
47842
|
+
if (plan.name === "free" || plan.name === "starter") {
|
|
47843
|
+
console.log(source_default.dim(" " + "\u2500".repeat(38)));
|
|
47844
|
+
console.log("");
|
|
47845
|
+
console.log(source_default.dim(" Need more credits? ") + source_default.cyan("oculum upgrade"));
|
|
47846
|
+
console.log("");
|
|
47847
|
+
}
|
|
47848
|
+
} catch (error) {
|
|
47849
|
+
spinner.fail("Failed to fetch usage data");
|
|
47850
|
+
console.log("");
|
|
47851
|
+
console.log(source_default.dim(" Error: ") + source_default.red(String(error)));
|
|
47852
|
+
console.log("");
|
|
47853
|
+
process.exit(1);
|
|
47854
|
+
}
|
|
47855
|
+
}
|
|
47856
|
+
function getTimeAgo(date) {
|
|
47857
|
+
const now = /* @__PURE__ */ new Date();
|
|
47858
|
+
const diffMs = now.getTime() - date.getTime();
|
|
47859
|
+
const diffMins = Math.floor(diffMs / 6e4);
|
|
47860
|
+
const diffHours = Math.floor(diffMs / 36e5);
|
|
47861
|
+
const diffDays = Math.floor(diffMs / 864e5);
|
|
47862
|
+
if (diffMins < 1) return "just now";
|
|
47863
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
47864
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
47865
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
47866
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
47867
|
+
}
|
|
47868
|
+
var usageCommand = new Command("usage").description("Show current usage and quota").option("--json", "Output as JSON").action(usage);
|
|
47869
|
+
|
|
47680
47870
|
// src/index.ts
|
|
47681
47871
|
var program2 = new Command();
|
|
47682
47872
|
program2.name("oculum").description("AI-native security scanner for detecting vulnerabilities in LLM-generated code").version("1.0.0").addHelpText("after", `
|
|
@@ -47691,6 +47881,7 @@ Common Commands:
|
|
|
47691
47881
|
ui Interactive terminal UI
|
|
47692
47882
|
login Authenticate with Oculum
|
|
47693
47883
|
status Check authentication status
|
|
47884
|
+
usage View credits and quota
|
|
47694
47885
|
|
|
47695
47886
|
Learn More:
|
|
47696
47887
|
$ oculum scan --help Detailed scan options
|
|
@@ -47703,6 +47894,7 @@ program2.addCommand(authCommand);
|
|
|
47703
47894
|
program2.addCommand(loginCommand);
|
|
47704
47895
|
program2.addCommand(statusCommand);
|
|
47705
47896
|
program2.addCommand(upgradeCommand);
|
|
47897
|
+
program2.addCommand(usageCommand);
|
|
47706
47898
|
program2.addCommand(watchCommand);
|
|
47707
47899
|
program2.addCommand(uiCommand);
|
|
47708
47900
|
program2.parse();
|
package/package.json
CHANGED