@oculum/cli 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +127 -106
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -12404,6 +12404,29 @@ var require_entropy = __commonJS({
12404
12404
  ];
12405
12405
  return debugPatterns.some((pattern) => pattern.test(lineContent));
12406
12406
  }
12407
+ function isCommandLineSnippet(value, lineContent) {
12408
+ const commandContextPatterns = [
12409
+ /\b(quickFix|command|example|usage|cli|help|hint)\b/i,
12410
+ /\b(run|exec|execute|install)\b\s*:/i
12411
+ ];
12412
+ const envAssignmentPatterns = [
12413
+ /^\s*(export\s+)?[A-Z_][A-Z0-9_]*=/,
12414
+ // export VAR=... or VAR=...
12415
+ /\bNODE_OPTIONS=/i
12416
+ ];
12417
+ const commandPatterns = [
12418
+ /^\s*\$\s+/,
12419
+ // shell prompt
12420
+ /\s--[a-z0-9][a-z0-9-]*/i,
12421
+ // CLI flags
12422
+ /\b(npm|pnpm|yarn|npx|node|bun|deno|git|curl|wget|oculum|python|pip|brew)\b/i
12423
+ ];
12424
+ const hasCommandContext = commandContextPatterns.some((p2) => p2.test(lineContent));
12425
+ const looksLikeEnvAssignment = envAssignmentPatterns.some((p2) => p2.test(value));
12426
+ const looksLikeCommand = commandPatterns.some((p2) => p2.test(value));
12427
+ const hasMultipleTokens = value.trim().split(/\s+/).length >= 2;
12428
+ return (hasCommandContext || looksLikeEnvAssignment || looksLikeCommand) && hasMultipleTokens;
12429
+ }
12407
12430
  function isInlineStyle(lineContent) {
12408
12431
  const jsxStylePatterns = [
12409
12432
  /style\s*=\s*\{\{/,
@@ -12556,6 +12579,8 @@ var require_entropy = __commonJS({
12556
12579
  continue;
12557
12580
  if (isDebugLogContent(lineContent))
12558
12581
  continue;
12582
+ if (isCommandLineSnippet(value, lineContent))
12583
+ continue;
12559
12584
  if (isRegexPattern(value))
12560
12585
  continue;
12561
12586
  if (isTemplateWithCode(value, lineContent))
@@ -14317,7 +14342,11 @@ var require_path_exclusions = __commonJS({
14317
14342
  "**/demo/**",
14318
14343
  "**/sample/**",
14319
14344
  "**/samples/**",
14320
- "**/playground/**"
14345
+ "**/playground/**",
14346
+ "**/oculum.json",
14347
+ // Scanner output files
14348
+ "**/*.scan.json"
14349
+ // Any scan result files
14321
14350
  ],
14322
14351
  fixturePatterns: [
14323
14352
  "**/fixtures/**",
@@ -15892,8 +15921,7 @@ var require_dangerous_functions = __commonJS({
15892
15921
  const contextEnd = Math.min(lines.length, lineNumber + 5);
15893
15922
  const context = lines.slice(contextStart, contextEnd).join("\n");
15894
15923
  const cosmeticContextPatterns = [
15895
- // UI component files
15896
- /\/(components?|ui|widgets?|animations?|contexts?)\//i,
15924
+ // UI component files - REMOVED, let severity classification handle these
15897
15925
  // Style-related variables/functions
15898
15926
  /\b(style|styles|css|className|animation|transition)/i,
15899
15927
  /\b(width|height|opacity|color|transform|rotate|scale|translate)/i,
@@ -15906,13 +15934,9 @@ var require_dangerous_functions = __commonJS({
15906
15934
  /delay.*Math\.random/i,
15907
15935
  /duration.*Math\.random/i,
15908
15936
  // UI state variations
15909
- /\b(variant|theme|layout|position).*Math\.random/i,
15910
- // UI identifier variable names (toast, notification, element, component IDs)
15911
- /\b(toast|notification|element|component|widget|modal|dialog|popup).*id\b/i,
15912
- /\bid\s*=.*Math\.random/i,
15913
- /\bkey\s*=.*Math\.random/i,
15914
- // React keys
15915
- /\btempId|temporaryId|uniqueId\b/i
15937
+ /\b(variant|theme|layout|position).*Math\.random/i
15938
+ // NOTE: Removed UI identifier patterns (key, id, tempId, etc.) - these should be
15939
+ // classified with info/low severity by the severity classification logic, not skipped entirely
15916
15940
  ];
15917
15941
  if (cosmeticContextPatterns.some((p2) => p2.test(context))) {
15918
15942
  return true;
@@ -15936,10 +15960,14 @@ var require_dangerous_functions = __commonJS({
15936
15960
  }
15937
15961
  function extractFunctionContext(content, lineNumber) {
15938
15962
  const lines = content.split("\n");
15939
- const start = Math.max(0, lineNumber - 10);
15963
+ const start = Math.max(0, lineNumber - 20);
15940
15964
  for (let i = lineNumber; i >= start; i--) {
15941
15965
  const line = lines[i];
15942
- const funcDeclMatch = line.match(/(?:export\s+)?function\s+(\w+)/i);
15966
+ const hasMethodCallWithArrowCallback = /\.\w+\(.*\([^)]*\)\s*=>/.test(line);
15967
+ if (hasMethodCallWithArrowCallback) {
15968
+ continue;
15969
+ }
15970
+ const funcDeclMatch = line.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)/i);
15943
15971
  if (funcDeclMatch) {
15944
15972
  return funcDeclMatch[1].toLowerCase();
15945
15973
  }
@@ -16077,7 +16105,24 @@ var require_dangerous_functions = __commonJS({
16077
16105
  "dialog",
16078
16106
  "popup",
16079
16107
  "unique",
16080
- "react"
16108
+ "react",
16109
+ // Non-security randomness usage (backoff/sampling/experiments)
16110
+ "jitter",
16111
+ "retry",
16112
+ "backoff",
16113
+ "delay",
16114
+ "timeout",
16115
+ "latency",
16116
+ "sample",
16117
+ "sampling",
16118
+ "probability",
16119
+ "chance",
16120
+ "rollout",
16121
+ "experiment",
16122
+ "abtest",
16123
+ "cohort",
16124
+ "bucket",
16125
+ "variant"
16081
16126
  ];
16082
16127
  if (lowRiskPatterns.some((p2) => lower.includes(p2))) {
16083
16128
  return "low";
@@ -16109,7 +16154,9 @@ var require_dangerous_functions = __commonJS({
16109
16154
  const inUIContext = isCosmeticMathRandom(lineContent, content, lineNumber);
16110
16155
  const businessLogicPatterns = [
16111
16156
  /\b(business|order|invoice|customer|product|transaction)Id\b/i,
16112
- /\b(reference|tracking|confirmation)Number\b/i
16157
+ /\b(reference|tracking|confirmation)Number\b/i,
16158
+ /\b(backoff|retry|jitter|delay|timeout|latency)\b/i,
16159
+ /\b(sample|sampling|probability|chance|rollout|experiment|abtest|cohort|bucket|variant)\b/i
16113
16160
  ];
16114
16161
  const inBusinessLogicContext = businessLogicPatterns.some((p2) => p2.test(context)) && !inSecurityContext;
16115
16162
  let contextDescription = "unknown context";
@@ -16120,7 +16167,7 @@ var require_dangerous_functions = __commonJS({
16120
16167
  } else if (inUIContext) {
16121
16168
  contextDescription = "UI/cosmetic usage";
16122
16169
  } else if (inBusinessLogicContext) {
16123
- contextDescription = "business identifier generation";
16170
+ contextDescription = "non-security usage";
16124
16171
  }
16125
16172
  return {
16126
16173
  inSecurityContext,
@@ -16388,24 +16435,24 @@ var require_dangerous_functions = __commonJS({
16388
16435
  explanation = " (seed/demo data generation)";
16389
16436
  description = "Math.random() used for generating fixture/seed data. Not security-critical in development contexts.";
16390
16437
  suggestedFix = "Acceptable for seed data. Use crypto.randomUUID() if uniqueness guarantees needed.";
16391
- } else if (nameRisk === "high" || context.inSecurityContext || functionIntent === "security") {
16392
- severity2 = "high";
16393
- confidence2 = "high";
16394
- explanation = " (security-sensitive context)";
16395
- description = "Math.random() is NOT cryptographically secure and MUST NOT be used for tokens, keys, passwords, or session IDs. This can lead to predictable values that attackers can exploit.";
16396
- suggestedFix = "Replace with crypto.randomBytes() or crypto.randomUUID() for security-sensitive operations";
16397
16438
  } else if (toStringPattern.intent === "short-ui-id") {
16398
16439
  severity2 = "info";
16399
16440
  confidence2 = "low";
16400
16441
  explanation = " (UI correlation ID)";
16401
16442
  description = "Math.random() used for short UI correlation IDs. Not security-critical, but collisions possible in high-volume scenarios.";
16402
16443
  suggestedFix = "For UI correlation, crypto.randomUUID() provides better uniqueness guarantees";
16444
+ } else if (nameRisk === "high" || context.inSecurityContext || functionIntent === "security") {
16445
+ severity2 = "high";
16446
+ confidence2 = "high";
16447
+ explanation = " (security-sensitive context)";
16448
+ description = "Math.random() is NOT cryptographically secure and MUST NOT be used for tokens, keys, passwords, or session IDs. This can lead to predictable values that attackers can exploit.";
16449
+ suggestedFix = "Replace with crypto.randomBytes() or crypto.randomUUID() for security-sensitive operations";
16403
16450
  } else if (nameRisk === "low" || context.inBusinessLogicContext || toStringPattern.intent === "business-id") {
16404
16451
  severity2 = "low";
16405
16452
  confidence2 = "low";
16406
- explanation = " (business identifier)";
16407
- description = "Math.random() is being used for non-security purposes (business IDs, tracking numbers). While not critical, Math.random() can produce collisions in high-volume scenarios.";
16408
- suggestedFix = "Consider crypto.randomUUID() for better uniqueness guarantees and collision resistance";
16453
+ explanation = " (non-security usage)";
16454
+ description = "Math.random() is being used for non-security purposes (business IDs, sampling, jitter/backoff, experiments). While not critical, Math.random() can produce collisions or bias in high-volume scenarios.";
16455
+ suggestedFix = "Use crypto.randomUUID() for uniqueness-sensitive IDs. For sampling/backoff, consider a seeded PRNG if determinism is needed.";
16409
16456
  } else {
16410
16457
  severity2 = "medium";
16411
16458
  confidence2 = "medium";
@@ -37475,7 +37522,7 @@ Example response format (OPTIMIZED):
37475
37522
  keep: { type: "boolean" }
37476
37523
  },
37477
37524
  required: ["index", "keep"],
37478
- additionalProperties: true
37525
+ additionalProperties: false
37479
37526
  }
37480
37527
  }
37481
37528
  },
@@ -44649,18 +44696,19 @@ async function status() {
44649
44696
  spinner.succeed(" Authenticated");
44650
44697
  console.log("");
44651
44698
  const email = result.email || config.email || "unknown";
44652
- const tier = result.tier || "free";
44653
- if (tier !== config.tier || email !== config.email) {
44654
- setAuthCredentials(config.apiKey, email, tier);
44699
+ const rawTier = String(result.tier || "free");
44700
+ const normalizedTier = rawTier === "enterprise" ? "max" : rawTier;
44701
+ if (normalizedTier !== config.tier || email !== config.email) {
44702
+ setAuthCredentials(config.apiKey, email, normalizedTier);
44655
44703
  }
44656
- const tierBadge = tier === "pro" ? source_default.bgBlue.white(" PRO ") : tier === "enterprise" ? source_default.bgMagenta.white(" ENTERPRISE ") : source_default.bgGray.white(" FREE ");
44704
+ const tierBadge = normalizedTier === "pro" ? source_default.bgBlue.white(" PRO ") : normalizedTier === "max" ? source_default.bgMagenta.white(" MAX ") : normalizedTier === "starter" ? source_default.bgCyan.white(" STARTER ") : source_default.bgGray.white(" FREE ");
44657
44705
  console.log(source_default.dim(" Email: ") + source_default.white(email));
44658
44706
  console.log(source_default.dim(" Plan: ") + tierBadge);
44659
44707
  console.log("");
44660
44708
  console.log(source_default.bold(" Available Scan Depths:"));
44661
44709
  console.log("");
44662
44710
  console.log(source_default.green(" \u2713 ") + source_default.white("cheap") + source_default.dim(" Fast pattern matching (always free)"));
44663
- if (tier === "pro" || tier === "enterprise") {
44711
+ if (normalizedTier === "pro" || normalizedTier === "max") {
44664
44712
  console.log(source_default.green(" \u2713 ") + source_default.white("validated") + source_default.dim(" AI validation (~70% fewer false positives)"));
44665
44713
  console.log(source_default.dim(" \u{1F512} ") + source_default.white("deep") + source_default.dim(" Multi-agent analysis (coming soon)"));
44666
44714
  } else {
@@ -46394,6 +46442,48 @@ var esm_default = { watch, FSWatcher };
46394
46442
  // src/commands/watch.ts
46395
46443
  var import_scanner2 = __toESM(require_dist());
46396
46444
  var import_formatters2 = __toESM(require_formatters());
46445
+ var WATCH_IGNORE_PATTERNS = [
46446
+ "**/node_modules/**",
46447
+ "**/dist/**",
46448
+ "**/build/**",
46449
+ "**/.git/**",
46450
+ "**/vendor/**",
46451
+ "**/__pycache__/**",
46452
+ "**/venv/**",
46453
+ "**/.venv/**",
46454
+ "**/coverage/**",
46455
+ "**/.next/**",
46456
+ "**/.nuxt/**",
46457
+ "**/.turbo/**",
46458
+ "**/out/**",
46459
+ "**/.cache/**",
46460
+ "**/.vercel/**",
46461
+ "**/.netlify/**",
46462
+ "**/target/**",
46463
+ "**/bin/**",
46464
+ "**/obj/**",
46465
+ "**/.gradle/**",
46466
+ "**/.mvn/**",
46467
+ "**/bower_components/**",
46468
+ "**/jspm_packages/**",
46469
+ "**/.yarn/**",
46470
+ "**/.pnp.*",
46471
+ "**/package-lock.json",
46472
+ "**/yarn.lock",
46473
+ "**/pnpm-lock.yaml",
46474
+ "**/*.min.js",
46475
+ "**/*.min.css",
46476
+ "**/*.bundle.js",
46477
+ "**/validation-test/**",
46478
+ "**/validation-repos/**",
46479
+ "**/validation-results/**",
46480
+ "**/test-files/**",
46481
+ "**/.env",
46482
+ "**/.env.*",
46483
+ "**/.env.local",
46484
+ "**/.env.*.local",
46485
+ "**/**.env"
46486
+ ];
46397
46487
  function isScannableFile2(filePath) {
46398
46488
  const ext2 = (0, import_path5.extname)(filePath).toLowerCase();
46399
46489
  const fileName = (0, import_path5.basename)(filePath);
@@ -46563,17 +46653,9 @@ async function watch2(targetPath, options) {
46563
46653
  "**/*.cs",
46564
46654
  "**/package.json"
46565
46655
  ];
46566
- const ignorePatterns2 = [
46567
- "**/node_modules/**",
46568
- "**/dist/**",
46569
- "**/build/**",
46570
- "**/.git/**",
46571
- "**/vendor/**",
46572
- "**/__pycache__/**"
46573
- ];
46574
46656
  const allFiles2 = await glob3(patterns2, {
46575
46657
  cwd: absolutePath,
46576
- ignore: ignorePatterns2,
46658
+ ignore: WATCH_IGNORE_PATTERNS,
46577
46659
  nodir: true,
46578
46660
  absolute: true
46579
46661
  });
@@ -46584,35 +46666,7 @@ async function watch2(targetPath, options) {
46584
46666
  isScanning = false;
46585
46667
  runScanOnChanges();
46586
46668
  };
46587
- const watcherIgnore = [
46588
- "**/node_modules/**",
46589
- "**/dist/**",
46590
- "**/build/**",
46591
- "**/.git/**",
46592
- "**/vendor/**",
46593
- "**/__pycache__/**",
46594
- "**/venv/**",
46595
- "**/.venv/**",
46596
- "**/coverage/**",
46597
- "**/.next/**",
46598
- "**/.nuxt/**",
46599
- "**/.turbo/**",
46600
- "**/out/**",
46601
- "**/.cache/**",
46602
- "**/.vercel/**",
46603
- "**/.netlify/**",
46604
- "**/target/**",
46605
- "**/.gradle/**",
46606
- "**/.mvn/**",
46607
- "**/bower_components/**",
46608
- "**/.yarn/**",
46609
- "**/package-lock.json",
46610
- "**/yarn.lock",
46611
- "**/pnpm-lock.yaml",
46612
- "**/*.min.js",
46613
- "**/*.min.css",
46614
- "**/*.bundle.js"
46615
- ];
46669
+ const watcherIgnore = WATCH_IGNORE_PATTERNS;
46616
46670
  const watcher = esm_default.watch(absolutePath, {
46617
46671
  ignored: watcherIgnore,
46618
46672
  persistent: true,
@@ -46696,42 +46750,9 @@ async function watch2(targetPath, options) {
46696
46750
  "**/*.cs",
46697
46751
  "**/package.json"
46698
46752
  ];
46699
- const ignorePatterns = [
46700
- "**/node_modules/**",
46701
- "**/dist/**",
46702
- "**/build/**",
46703
- "**/.git/**",
46704
- "**/vendor/**",
46705
- "**/__pycache__/**",
46706
- "**/venv/**",
46707
- "**/.venv/**",
46708
- "**/coverage/**",
46709
- "**/.next/**",
46710
- "**/.nuxt/**",
46711
- "**/.turbo/**",
46712
- "**/out/**",
46713
- "**/.cache/**",
46714
- "**/.vercel/**",
46715
- "**/.netlify/**",
46716
- "**/target/**",
46717
- "**/bin/**",
46718
- "**/obj/**",
46719
- "**/.gradle/**",
46720
- "**/.mvn/**",
46721
- "**/bower_components/**",
46722
- "**/jspm_packages/**",
46723
- "**/.yarn/**",
46724
- "**/.pnp.*",
46725
- "**/package-lock.json",
46726
- "**/yarn.lock",
46727
- "**/pnpm-lock.yaml",
46728
- "**/*.min.js",
46729
- "**/*.min.css",
46730
- "**/*.bundle.js"
46731
- ];
46732
46753
  const allFiles = await glob2(patterns, {
46733
46754
  cwd: absolutePath,
46734
- ignore: ignorePatterns,
46755
+ ignore: WATCH_IGNORE_PATTERNS,
46735
46756
  nodir: true,
46736
46757
  absolute: true
46737
46758
  });
@@ -47453,7 +47474,7 @@ function showLogo() {
47453
47474
  async function showWelcomeScreen() {
47454
47475
  console.clear();
47455
47476
  showLogo();
47456
- console.log(source_default.bold.white(" AI-Native api key Security Scanner for Modern Codebases\n"));
47477
+ console.log(source_default.bold.white(" AI-Native Security Scanner for Modern Codebases\n"));
47457
47478
  console.log(source_default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
47458
47479
  console.log(source_default.white(" Oculum detects security vulnerabilities in AI-generated"));
47459
47480
  console.log(source_default.white(" code and LLM-powered applications, including:\n"));
@@ -48586,7 +48607,7 @@ async function runUsageFlow() {
48586
48607
  console.log(source_default.bold(" \u{1F4CA} Oculum Usage"));
48587
48608
  console.log(source_default.dim(" " + "\u2500".repeat(38)));
48588
48609
  console.log("");
48589
- 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 ");
48610
+ const planBadge = plan.name === "pro" ? source_default.bgBlue.white(" PRO ") : plan.name === "max" ? source_default.bgMagenta.white(" MAX ") : plan.name === "starter" ? source_default.bgCyan.white(" STARTER ") : source_default.bgGray.white(" FREE ");
48590
48611
  console.log(source_default.dim(" Plan: ") + planBadge + source_default.white(` ${plan.displayName}`));
48591
48612
  console.log(source_default.dim(" Month: ") + source_default.white(usageData.month));
48592
48613
  console.log("");
@@ -48878,7 +48899,7 @@ async function usage(options) {
48878
48899
  console.log(source_default.bold(" \u{1F4CA} Oculum Usage"));
48879
48900
  console.log(source_default.dim(" " + "\u2500".repeat(38)));
48880
48901
  console.log("");
48881
- 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 ");
48902
+ const planBadge = plan.name === "pro" ? source_default.bgBlue.white(" PRO ") : plan.name === "max" ? source_default.bgMagenta.white(" MAX ") : plan.name === "starter" ? source_default.bgCyan.white(" STARTER ") : source_default.bgGray.white(" FREE ");
48882
48903
  console.log(source_default.dim(" Plan: ") + planBadge + source_default.white(` ${plan.displayName}`));
48883
48904
  console.log(source_default.dim(" Month: ") + source_default.white(usageData.month));
48884
48905
  console.log("");
@@ -49377,7 +49398,7 @@ function shouldRunUI() {
49377
49398
  return isOcAlias || isUICommand;
49378
49399
  }
49379
49400
  var program2 = new Command();
49380
- program2.name("oculum").description("AI-native security scanner for detecting vulnerabilities in LLM-generated code").version("1.0.9").addHelpText("after", `
49401
+ program2.name("oculum").description("AI-native security scanner for detecting vulnerabilities in LLM-generated code").version("1.0.15").addHelpText("after", `
49381
49402
  Quick Start:
49382
49403
  $ oculum scan . Scan current directory (free)
49383
49404
  $ oculum ui Interactive mode with guided setup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oculum/cli",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "AI-native security scanner CLI for detecting vulnerabilities in AI-generated code, BYOK patterns, and modern web applications",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -19,14 +19,14 @@
19
19
  "url": "https://github.com/flexipie/oculum/issues"
20
20
  },
21
21
  "scripts": {
22
- "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\" --define:process.env.OCULUM_API_URL='undefined' --define:VERSION='\"1.0.9\"'",
22
+ "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --banner:js=\"#!/usr/bin/env node\" --define:process.env.OCULUM_API_URL='undefined' --define:VERSION='\"'$npm_package_version'\"'",
23
23
  "dev": "npm run build -- --watch",
24
24
  "test": "echo \"No tests configured yet\"",
25
25
  "lint": "eslint src/"
26
26
  },
27
27
  "dependencies": {
28
- "@oculum/scanner": "^1.0.3",
29
- "@oculum/shared": "^1.0.2",
28
+ "@oculum/scanner": "^1.0.5",
29
+ "@oculum/shared": "^1.0.5",
30
30
  "commander": "^12.1.0",
31
31
  "chalk": "^5.3.0",
32
32
  "ora": "^8.1.1",