@vibgrate/cli 1.0.64 → 1.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -179,6 +179,7 @@ Vibgrate now supports explicit privacy controls:
179
179
 
180
180
  - `--no-local-artifacts` prevents writing `.vibgrate/*.json` files to disk.
181
181
  - `--max-privacy` enables a hardened profile (suppresses local artifact writes and disables high-context scanners such as UI-purpose evidence and architecture/code-quality enrichment).
182
+ - Additional commercial scanner families are available and independently toggleable in `vibgrate.config.*`: `runtimeConfiguration`, `dataStores`, `apiSurface`, `operationalResilience`, `assetBranding`, and `ossGovernance` (see `docs/commercial-grade-scanners.md`).
182
183
  - `--offline` disables live registry/network lookups and never uploads scan results.
183
184
  - `--package-manifest <file>` accepts a local JSON or ZIP package-version manifest so drift can still be calculated offline. Download the latest bundle at `https://github.com/vibgrate/manifests/latest-packages.zip`.
184
185
 
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  baselineCommand,
3
3
  runBaseline
4
- } from "./chunk-GCMUJKM7.js";
5
- import "./chunk-IQ4T2HLG.js";
4
+ } from "./chunk-IHDUX5MC.js";
5
+ import "./chunk-DMYMJUQP.js";
6
6
  import "./chunk-TBE6NQ5Z.js";
7
7
  export {
8
8
  baselineCommand,
@@ -1169,6 +1169,260 @@ import * as crypto3 from "crypto";
1169
1169
  import * as path2 from "path";
1170
1170
  import { Command as Command2 } from "commander";
1171
1171
  import chalk3 from "chalk";
1172
+
1173
+ // src/utils/compact-artifact.ts
1174
+ import * as zlib from "zlib";
1175
+ import { promisify } from "util";
1176
+
1177
+ // src/utils/compact-evidence.ts
1178
+ var CATEGORY_PATTERNS = [
1179
+ { category: "pricing", pattern: /price|pricing|billing|subscri|trial|credit|plan|tier|upgrade|premium|pro|enterprise/i },
1180
+ { category: "auth", pattern: /sign[- ]?in|sign[- ]?up|log[- ]?in|log[- ]?out|auth|sso|oauth|password|register|invite|onboard/i },
1181
+ { category: "dashboard", pattern: /dashboard|overview|home|main|summary|stats/i },
1182
+ { category: "settings", pattern: /setting|config|preference|option|profile|account/i },
1183
+ { category: "users", pattern: /user|member|team|role|permission|access|admin|owner/i },
1184
+ { category: "integrations", pattern: /integrat|connect|webhook|api[- ]?key|sync|import|export/i },
1185
+ { category: "reports", pattern: /report|analy|metric|chart|graph|insight|track/i },
1186
+ { category: "workflows", pattern: /workflow|automat|schedule|trigger|action|job|task|pipeline/i },
1187
+ { category: "projects", pattern: /project|workspace|organization|folder|repo/i },
1188
+ { category: "navigation", pattern: /menu|nav|sidebar|header|footer|breadcrumb/i }
1189
+ ];
1190
+ function compactUiPurpose(result, maxSamplesPerCategory = 3) {
1191
+ const evidence = result.topEvidence;
1192
+ const dependencies = evidence.filter((e) => e.kind === "dependency").map((e) => e.value).slice(0, 10);
1193
+ const routes = dedupeRoutes(
1194
+ evidence.filter((e) => e.kind === "route").map((e) => e.value)
1195
+ ).slice(0, 15);
1196
+ const textEvidence = evidence.filter(
1197
+ (e) => e.kind !== "dependency" && e.kind !== "route" && e.kind !== "feature_flag"
1198
+ );
1199
+ const byCategory = /* @__PURE__ */ new Map();
1200
+ const categoryCounts = {};
1201
+ for (const item of textEvidence) {
1202
+ const category = categorize(item.value);
1203
+ if (!byCategory.has(category)) {
1204
+ byCategory.set(category, /* @__PURE__ */ new Set());
1205
+ }
1206
+ const normalized = normalizeValue(item.value);
1207
+ if (normalized.length >= 3) {
1208
+ byCategory.get(category).add(normalized);
1209
+ }
1210
+ }
1211
+ const samples = [];
1212
+ for (const [category, values] of byCategory) {
1213
+ const deduped = dedupeStrings([...values]);
1214
+ categoryCounts[category] = deduped.length;
1215
+ for (const value of deduped.slice(0, maxSamplesPerCategory)) {
1216
+ samples.push({ kind: "text", value, category });
1217
+ }
1218
+ }
1219
+ const featureFlags = evidence.filter((e) => e.kind === "feature_flag");
1220
+ if (featureFlags.length > 0) {
1221
+ categoryCounts["feature_flags"] = featureFlags.length;
1222
+ samples.push({ kind: "feature_flag", value: "feature flags detected", category: "feature_flags" });
1223
+ }
1224
+ return {
1225
+ samples,
1226
+ categoryCounts,
1227
+ originalCount: evidence.length,
1228
+ dependencies,
1229
+ routes,
1230
+ detectedFrameworks: result.detectedFrameworks
1231
+ };
1232
+ }
1233
+ function categorize(value) {
1234
+ for (const { category, pattern } of CATEGORY_PATTERNS) {
1235
+ if (pattern.test(value)) return category;
1236
+ }
1237
+ return "general";
1238
+ }
1239
+ function normalizeValue(value) {
1240
+ return value.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim().slice(0, 60);
1241
+ }
1242
+ function dedupeStrings(values) {
1243
+ const sorted = values.sort((a, b) => b.length - a.length);
1244
+ const kept = [];
1245
+ for (const value of sorted) {
1246
+ const isDupe = kept.some((k) => {
1247
+ const stem = value.slice(0, 6);
1248
+ return k.startsWith(stem) || k.includes(value) || value.includes(k);
1249
+ });
1250
+ if (!isDupe) {
1251
+ kept.push(value);
1252
+ }
1253
+ }
1254
+ return kept;
1255
+ }
1256
+ function dedupeRoutes(routes) {
1257
+ const seen = /* @__PURE__ */ new Set();
1258
+ const result = [];
1259
+ for (const route of routes) {
1260
+ const normalized = route.replace(/:[a-z_]+/gi, ":param").replace(/\[\[*\.*\.*[a-z_]+\]*\]/gi, ":param").replace(/\/+$/, "").toLowerCase();
1261
+ if (!seen.has(normalized)) {
1262
+ seen.add(normalized);
1263
+ result.push(route);
1264
+ }
1265
+ }
1266
+ return result;
1267
+ }
1268
+
1269
+ // src/utils/compact-artifact.ts
1270
+ var gzip2 = promisify(zlib.gzip);
1271
+ var MAX_ITEMS = 50;
1272
+ function extractName(entry) {
1273
+ const match = entry.match(/^(.+?)\s*\(/);
1274
+ return match ? match[1].trim() : entry.trim();
1275
+ }
1276
+ function compactDataStores(result) {
1277
+ return {
1278
+ databaseTechnologies: result.databaseTechnologies.slice(0, 10),
1279
+ connectionStrings: [],
1280
+ // Don't include connection strings in upload
1281
+ connectionPoolSettings: result.connectionPoolSettings.slice(0, MAX_ITEMS),
1282
+ replicationSettings: result.replicationSettings.slice(0, 20),
1283
+ readReplicaSettings: result.readReplicaSettings.slice(0, 20),
1284
+ failoverSettings: result.failoverSettings.slice(0, 20),
1285
+ collationAndEncoding: result.collationAndEncoding.slice(0, 20),
1286
+ queryTimeoutDefaults: result.queryTimeoutDefaults.slice(0, 20),
1287
+ manualIndexes: result.manualIndexes.map(extractName).slice(0, MAX_ITEMS),
1288
+ tables: result.tables.map(extractName).slice(0, MAX_ITEMS),
1289
+ views: result.views.map(extractName).slice(0, MAX_ITEMS),
1290
+ storedProcedures: result.storedProcedures.map(extractName).slice(0, MAX_ITEMS),
1291
+ triggers: result.triggers.map(extractName).slice(0, MAX_ITEMS),
1292
+ rowLevelSecurityPolicies: result.rowLevelSecurityPolicies.slice(0, 20),
1293
+ otherServices: result.otherServices.slice(0, 20)
1294
+ };
1295
+ }
1296
+ function compactApiSurface(result) {
1297
+ const seenProviders = /* @__PURE__ */ new Set();
1298
+ const uniqueIntegrations = result.integrations.filter((i) => {
1299
+ const domain = i.provider.split(":")[0];
1300
+ if (seenProviders.has(domain)) return false;
1301
+ seenProviders.add(domain);
1302
+ return true;
1303
+ }).slice(0, MAX_ITEMS).map((i) => ({
1304
+ provider: i.provider,
1305
+ endpoint: "",
1306
+ // Don't include full endpoints
1307
+ version: i.version,
1308
+ parameters: [],
1309
+ // Don't include params
1310
+ configOptions: [],
1311
+ authHints: []
1312
+ }));
1313
+ return {
1314
+ integrations: uniqueIntegrations,
1315
+ openApiSpecifications: result.openApiSpecifications.slice(0, 10),
1316
+ webhookUrls: result.webhookUrls.slice(0, 20),
1317
+ callbackEndpoints: result.callbackEndpoints.slice(0, 20),
1318
+ apiVersionPins: result.apiVersionPins.slice(0, 20),
1319
+ tokenExpirationPolicies: result.tokenExpirationPolicies.slice(0, 20),
1320
+ rateLimitOverrides: result.rateLimitOverrides.slice(0, 20),
1321
+ customHeaders: result.customHeaders.slice(0, 20),
1322
+ corsPolicies: result.corsPolicies.slice(0, 20),
1323
+ oauthScopes: result.oauthScopes.slice(0, 20),
1324
+ apiTokens: []
1325
+ // Don't include token references
1326
+ };
1327
+ }
1328
+ function compactAssetBranding(result) {
1329
+ return {
1330
+ faviconFiles: result.faviconFiles.slice(0, 1),
1331
+ productLogos: []
1332
+ // Don't include logos
1333
+ };
1334
+ }
1335
+ function prepareArtifactForUpload(artifact) {
1336
+ const compacted = { ...artifact };
1337
+ if (compacted.extended) {
1338
+ const ext = { ...compacted.extended };
1339
+ if (ext.dataStores) {
1340
+ ext.dataStores = compactDataStores(ext.dataStores);
1341
+ }
1342
+ if (ext.apiSurface) {
1343
+ ext.apiSurface = compactApiSurface(ext.apiSurface);
1344
+ }
1345
+ if (ext.assetBranding) {
1346
+ ext.assetBranding = compactAssetBranding(ext.assetBranding);
1347
+ }
1348
+ if (ext.uiPurpose) {
1349
+ const compactedUi = compactUiPurpose(ext.uiPurpose);
1350
+ ext.uiPurpose = {
1351
+ enabled: ext.uiPurpose.enabled,
1352
+ detectedFrameworks: compactedUi.detectedFrameworks,
1353
+ evidenceCount: compactedUi.originalCount,
1354
+ capped: ext.uiPurpose.capped,
1355
+ topEvidence: [],
1356
+ // Clear full evidence
1357
+ unknownSignals: [],
1358
+ // Add compacted data under extended properties
1359
+ ...{ compacted: compactedUi }
1360
+ };
1361
+ }
1362
+ if (ext.runtimeConfiguration) {
1363
+ ext.runtimeConfiguration = {
1364
+ ...ext.runtimeConfiguration,
1365
+ environmentVariables: ext.runtimeConfiguration.environmentVariables.slice(0, 100),
1366
+ hiddenConfigFiles: ext.runtimeConfiguration.hiddenConfigFiles.slice(0, MAX_ITEMS),
1367
+ startupArguments: ext.runtimeConfiguration.startupArguments.slice(0, 100)
1368
+ };
1369
+ }
1370
+ if (ext.operationalResilience) {
1371
+ const ops = ext.operationalResilience;
1372
+ ext.operationalResilience = {
1373
+ implicitTimeouts: ops.implicitTimeouts.slice(0, 30),
1374
+ defaultPaginationSize: ops.defaultPaginationSize.slice(0, 30),
1375
+ implicitRetryLogic: ops.implicitRetryLogic.slice(0, 30),
1376
+ defaultLocale: ops.defaultLocale.slice(0, 20),
1377
+ defaultCurrency: ops.defaultCurrency.slice(0, 20),
1378
+ implicitTimezone: ops.implicitTimezone.slice(0, 20),
1379
+ defaultCharacterEncoding: ops.defaultCharacterEncoding.slice(0, 20),
1380
+ sessionStores: ops.sessionStores.slice(0, 20),
1381
+ distributedLocks: ops.distributedLocks.slice(0, 20),
1382
+ jobSchedulers: ops.jobSchedulers.slice(0, 30),
1383
+ idempotencyKeys: ops.idempotencyKeys.slice(0, 20),
1384
+ rateLimitingCounters: ops.rateLimitingCounters.slice(0, 20),
1385
+ circuitBreakerState: ops.circuitBreakerState.slice(0, 20),
1386
+ abTestToggles: ops.abTestToggles.slice(0, 20),
1387
+ regionalEnablementRules: ops.regionalEnablementRules.slice(0, 20),
1388
+ betaAccessGroups: ops.betaAccessGroups.slice(0, 20),
1389
+ licensingEnforcementLogic: ops.licensingEnforcementLogic.slice(0, 20),
1390
+ killSwitches: ops.killSwitches.slice(0, 20),
1391
+ connectorRetryLogic: ops.connectorRetryLogic.slice(0, 20),
1392
+ apiPollingIntervals: ops.apiPollingIntervals.slice(0, 20),
1393
+ fieldMappings: ops.fieldMappings.slice(0, 20),
1394
+ schemaRegistryRules: ops.schemaRegistryRules.slice(0, 20),
1395
+ deadLetterQueueBehavior: ops.deadLetterQueueBehavior.slice(0, 20),
1396
+ dataMaskingRules: ops.dataMaskingRules.slice(0, 20),
1397
+ transformationLogic: ops.transformationLogic.slice(0, 20),
1398
+ timezoneHandling: ops.timezoneHandling.slice(0, 20),
1399
+ encryptionSettings: ops.encryptionSettings.slice(0, 30),
1400
+ hardcodedSecretSignals: ops.hardcodedSecretSignals.slice(0, 20)
1401
+ };
1402
+ }
1403
+ if (ext.dependencyGraph) {
1404
+ ext.dependencyGraph = {
1405
+ ...ext.dependencyGraph,
1406
+ phantomDependencies: ext.dependencyGraph.phantomDependencies.slice(0, MAX_ITEMS),
1407
+ phantomDependencyDetails: ext.dependencyGraph.phantomDependencyDetails?.slice(0, MAX_ITEMS),
1408
+ duplicatedPackages: ext.dependencyGraph.duplicatedPackages.slice(0, MAX_ITEMS)
1409
+ };
1410
+ }
1411
+ compacted.extended = ext;
1412
+ }
1413
+ return compacted;
1414
+ }
1415
+ async function compressArtifact(artifact) {
1416
+ const json = JSON.stringify(artifact);
1417
+ return gzip2(json, { level: 9 });
1418
+ }
1419
+ async function prepareCompressedUpload(artifact) {
1420
+ const compacted = prepareArtifactForUpload(artifact);
1421
+ const compressed = await compressArtifact(compacted);
1422
+ return { body: compressed, contentEncoding: "gzip" };
1423
+ }
1424
+
1425
+ // src/commands/push.ts
1172
1426
  function parseDsn(dsn) {
1173
1427
  const cleaned = dsn.replace(/[\x00-\x1F\x7F\uFEFF\u200B-\u200D\u2060]/g, "").trim();
1174
1428
  const match = cleaned.match(/^vibgrate\+(https?):?\/\/([^:]+):([^@]+)@([^/]+)\/(.+)$/);
@@ -1203,7 +1457,8 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
1203
1457
  if (opts.strict) process.exit(1);
1204
1458
  return;
1205
1459
  }
1206
- const body = await readTextFile(filePath);
1460
+ const artifact = await readJsonFile(filePath);
1461
+ const { body, contentEncoding } = await prepareCompressedUpload(artifact);
1207
1462
  const timestamp = String(Date.now());
1208
1463
  let host = parsed.host;
1209
1464
  if (opts.region) {
@@ -1216,12 +1471,16 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
1216
1471
  }
1217
1472
  }
1218
1473
  const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
1219
- console.log(chalk3.dim(`Uploading to ${host}...`));
1474
+ const originalSize = JSON.stringify(artifact).length;
1475
+ const compressedSize = body.length;
1476
+ const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
1477
+ console.log(chalk3.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
1220
1478
  try {
1221
1479
  const response = await fetch(url, {
1222
1480
  method: "POST",
1223
1481
  headers: {
1224
1482
  "Content-Type": "application/json",
1483
+ "Content-Encoding": contentEncoding,
1225
1484
  "X-Vibgrate-Timestamp": timestamp,
1226
1485
  "Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
1227
1486
  "Connection": "close"
@@ -1251,7 +1510,7 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
1251
1510
  });
1252
1511
 
1253
1512
  // src/commands/scan.ts
1254
- import * as path23 from "path";
1513
+ import * as path24 from "path";
1255
1514
  import { Command as Command3 } from "commander";
1256
1515
  import chalk6 from "chalk";
1257
1516
 
@@ -7446,96 +7705,353 @@ function isUsefulString(s) {
7446
7705
  return true;
7447
7706
  }
7448
7707
 
7449
- // src/utils/compact-evidence.ts
7450
- var CATEGORY_PATTERNS = [
7451
- { category: "pricing", pattern: /price|pricing|billing|subscri|trial|credit|plan|tier|upgrade|premium|pro|enterprise/i },
7452
- { category: "auth", pattern: /sign[- ]?in|sign[- ]?up|log[- ]?in|log[- ]?out|auth|sso|oauth|password|register|invite|onboard/i },
7453
- { category: "dashboard", pattern: /dashboard|overview|home|main|summary|stats/i },
7454
- { category: "settings", pattern: /setting|config|preference|option|profile|account/i },
7455
- { category: "users", pattern: /user|member|team|role|permission|access|admin|owner/i },
7456
- { category: "integrations", pattern: /integrat|connect|webhook|api[- ]?key|sync|import|export/i },
7457
- { category: "reports", pattern: /report|analy|metric|chart|graph|insight|track/i },
7458
- { category: "workflows", pattern: /workflow|automat|schedule|trigger|action|job|task|pipeline/i },
7459
- { category: "projects", pattern: /project|workspace|organization|folder|repo/i },
7460
- { category: "navigation", pattern: /menu|nav|sidebar|header|footer|breadcrumb/i }
7461
- ];
7462
- function compactUiPurpose(result, maxSamplesPerCategory = 3) {
7463
- const evidence = result.topEvidence;
7464
- const dependencies = evidence.filter((e) => e.kind === "dependency").map((e) => e.value).slice(0, 10);
7465
- const routes = dedupeRoutes(
7466
- evidence.filter((e) => e.kind === "route").map((e) => e.value)
7467
- ).slice(0, 15);
7468
- const textEvidence = evidence.filter(
7469
- (e) => e.kind !== "dependency" && e.kind !== "route" && e.kind !== "feature_flag"
7470
- );
7471
- const byCategory = /* @__PURE__ */ new Map();
7472
- const categoryCounts = {};
7473
- for (const item of textEvidence) {
7474
- const category = categorize(item.value);
7475
- if (!byCategory.has(category)) {
7476
- byCategory.set(category, /* @__PURE__ */ new Set());
7477
- }
7478
- const normalized = normalizeValue(item.value);
7479
- if (normalized.length >= 3) {
7480
- byCategory.get(category).add(normalized);
7481
- }
7708
+ // src/scanners/requirements-scanners.ts
7709
+ import * as path23 from "path";
7710
+ var MAX_SCAN_BYTES = 75e4;
7711
+ function uniq(values) {
7712
+ return [...new Set(values)].sort((a, b) => a.localeCompare(b));
7713
+ }
7714
+ function pushMatch(matches, value, sourceFile) {
7715
+ if (!value) return;
7716
+ const normalized = value.trim();
7717
+ if (!normalized) return;
7718
+ matches.push(`${normalized} (${sourceFile})`);
7719
+ }
7720
+ function extract(content, pattern, sourceFile) {
7721
+ const out = [];
7722
+ let match;
7723
+ while ((match = pattern.exec(content)) !== null) {
7724
+ const captured = match.slice(1).find((v) => typeof v === "string" && v.length > 0);
7725
+ pushMatch(out, captured ?? match[0], sourceFile);
7482
7726
  }
7483
- const samples = [];
7484
- for (const [category, values] of byCategory) {
7485
- const deduped = dedupeStrings([...values]);
7486
- categoryCounts[category] = deduped.length;
7487
- for (const value of deduped.slice(0, maxSamplesPerCategory)) {
7488
- samples.push({ kind: "text", value, category });
7727
+ return out;
7728
+ }
7729
+ function detectDbBrand(raw) {
7730
+ const value = raw.toLowerCase();
7731
+ if (value.includes("postgres")) return { kind: "sql", brand: "PostgreSQL", version: null, evidence: raw };
7732
+ if (value.includes("mysql") || value.includes("mariadb")) return { kind: "sql", brand: "MySQL/MariaDB", version: null, evidence: raw };
7733
+ if (value.includes("mssql") || value.includes("sqlserver")) return { kind: "sql", brand: "SQL Server", version: null, evidence: raw };
7734
+ if (value.includes("oracle")) return { kind: "sql", brand: "Oracle", version: null, evidence: raw };
7735
+ if (value.includes("sqlite")) return { kind: "sql", brand: "SQLite", version: null, evidence: raw };
7736
+ if (value.includes("mongodb")) return { kind: "nosql", brand: "MongoDB", version: null, evidence: raw };
7737
+ if (value.includes("redis")) return { kind: "nosql", brand: "Redis", version: null, evidence: raw };
7738
+ if (value.includes("cassandra")) return { kind: "nosql", brand: "Cassandra", version: null, evidence: raw };
7739
+ if (value.includes("dynamodb")) return { kind: "nosql", brand: "DynamoDB", version: null, evidence: raw };
7740
+ if (value.includes("neo4j")) return { kind: "nosql", brand: "Neo4j", version: null, evidence: raw };
7741
+ return null;
7742
+ }
7743
+ async function scanTextFiles(rootDir, fileCache) {
7744
+ const entries = await fileCache.walkDir(rootDir);
7745
+ const files = entries.filter((entry) => entry.isFile).map((entry) => entry.absPath);
7746
+ const out = [];
7747
+ for (const file of files) {
7748
+ try {
7749
+ const relPath = path23.relative(rootDir, file).replace(/\\/g, "/");
7750
+ const content = await fileCache.readTextFile(file);
7751
+ if (!content || content.length > MAX_SCAN_BYTES) continue;
7752
+ out.push({ relPath, content });
7753
+ } catch {
7489
7754
  }
7490
7755
  }
7491
- const featureFlags = evidence.filter((e) => e.kind === "feature_flag");
7492
- if (featureFlags.length > 0) {
7493
- categoryCounts["feature_flags"] = featureFlags.length;
7494
- samples.push({ kind: "feature_flag", value: "feature flags detected", category: "feature_flags" });
7495
- }
7496
- return {
7497
- samples,
7498
- categoryCounts,
7499
- originalCount: evidence.length,
7500
- dependencies,
7501
- routes,
7502
- detectedFrameworks: result.detectedFrameworks
7756
+ return out;
7757
+ }
7758
+ async function scanRuntimeConfiguration(rootDir, fileCache) {
7759
+ const files = await scanTextFiles(rootDir, fileCache);
7760
+ const result = {
7761
+ environmentVariables: [],
7762
+ featureFlags: [],
7763
+ hiddenConfigFiles: [],
7764
+ dotEnvFiles: [],
7765
+ secretsInjectionPaths: [],
7766
+ containerEntrypoints: [],
7767
+ startupArguments: [],
7768
+ jvmFlags: [],
7769
+ threadPoolSettings: []
7503
7770
  };
7771
+ for (const file of files) {
7772
+ if (file.relPath.split("/").some((segment) => segment.startsWith("."))) {
7773
+ result.hiddenConfigFiles.push(file.relPath);
7774
+ }
7775
+ if (/\.env(\.|$)/i.test(file.relPath)) {
7776
+ result.dotEnvFiles.push(file.relPath);
7777
+ }
7778
+ result.environmentVariables.push(...extract(file.content, /(?:process\.env\.([A-Z0-9_]{3,})|process\.env\[['"]([A-Z0-9_]{3,})['"]\]|System\.getenv\(['"]([A-Z0-9_]{3,})['"]\)|os\.environ\[['"]([A-Z0-9_]{3,})['"]\]|getenv\(['"]([A-Z0-9_]{3,})['"]\))/g, file.relPath));
7779
+ result.featureFlags.push(...extract(file.content, /\b(?:feature[_-]?flag|ff_|is[A-Z][A-Za-z0-9]+Enabled|launchdarkly|unleash)\b/gi, file.relPath));
7780
+ result.secretsInjectionPaths.push(...extract(file.content, /\b(?:vault|secrets?\/(?:mount|inject|path)|aws\/secretsmanager|gcp\/secretmanager)\b/gi, file.relPath));
7781
+ result.containerEntrypoints.push(...extract(file.content, /\b(?:ENTRYPOINT|CMD)\s+([^\n]+)/g, file.relPath));
7782
+ result.startupArguments.push(...extract(file.content, /\b(?:--[a-z0-9-]+=?[\w:/.-]*)/gi, file.relPath));
7783
+ result.jvmFlags.push(...extract(file.content, /\b(-Xmx\S+|-Xms\S+|-XX:[^\s'"\]]+)/g, file.relPath));
7784
+ result.threadPoolSettings.push(...extract(file.content, /\b(?:thread[_-]?pool|max[_-]?threads|min[_-]?threads|worker[_-]?threads)\b[^\n]*/gi, file.relPath));
7785
+ }
7786
+ result.environmentVariables = uniq(result.environmentVariables);
7787
+ result.featureFlags = uniq(result.featureFlags);
7788
+ result.hiddenConfigFiles = uniq(result.hiddenConfigFiles);
7789
+ result.dotEnvFiles = uniq(result.dotEnvFiles);
7790
+ result.secretsInjectionPaths = uniq(result.secretsInjectionPaths);
7791
+ result.containerEntrypoints = uniq(result.containerEntrypoints);
7792
+ result.startupArguments = uniq(result.startupArguments).slice(0, 250);
7793
+ result.jvmFlags = uniq(result.jvmFlags);
7794
+ result.threadPoolSettings = uniq(result.threadPoolSettings);
7795
+ return result;
7504
7796
  }
7505
- function categorize(value) {
7506
- for (const { category, pattern } of CATEGORY_PATTERNS) {
7507
- if (pattern.test(value)) return category;
7797
+ async function scanDataStores(rootDir, fileCache) {
7798
+ const files = await scanTextFiles(rootDir, fileCache);
7799
+ const dbTechnologies = [];
7800
+ const result = {
7801
+ databaseTechnologies: [],
7802
+ connectionStrings: [],
7803
+ connectionPoolSettings: [],
7804
+ replicationSettings: [],
7805
+ readReplicaSettings: [],
7806
+ failoverSettings: [],
7807
+ collationAndEncoding: [],
7808
+ queryTimeoutDefaults: [],
7809
+ manualIndexes: [],
7810
+ tables: [],
7811
+ views: [],
7812
+ storedProcedures: [],
7813
+ triggers: [],
7814
+ rowLevelSecurityPolicies: [],
7815
+ otherServices: []
7816
+ };
7817
+ for (const file of files) {
7818
+ const connStrings = extract(file.content, /\b(?:postgres(?:ql)?:\/\/[^\s'"`]+|mysql:\/\/[^\s'"`]+|mongodb(?:\+srv)?:\/\/[^\s'"`]+|redis:\/\/[^\s'"`]+)\b/gi, file.relPath);
7819
+ result.connectionStrings.push(...connStrings);
7820
+ const dbEvidence = [
7821
+ ...extract(file.content, /\b(?:postgres|postgresql|mysql|mariadb|mssql|sqlserver|oracle|sqlite|mongodb|redis|cassandra|dynamodb|neo4j)\b/gi, file.relPath),
7822
+ ...connStrings
7823
+ ];
7824
+ for (const evidence of dbEvidence) {
7825
+ const detected = detectDbBrand(evidence);
7826
+ if (detected) dbTechnologies.push(detected);
7827
+ }
7828
+ result.connectionPoolSettings.push(...extract(file.content, /\b(?:pool(?:Size|_size)?|maxPoolSize|minPoolSize|connectionLimit)\b[^\n]*/gi, file.relPath));
7829
+ result.replicationSettings.push(...extract(file.content, /\b(?:replication|cluster|replicaSet)\b[^\n]*/gi, file.relPath));
7830
+ result.readReplicaSettings.push(...extract(file.content, /\b(?:read[_-]?replica|reader[_-]?endpoint)\b[^\n]*/gi, file.relPath));
7831
+ result.failoverSettings.push(...extract(file.content, /\b(?:failover|multi[_-]?az|secondary[_-]?host)\b[^\n]*/gi, file.relPath));
7832
+ result.collationAndEncoding.push(...extract(file.content, /\b(?:collation|charset|encoding|utf-?8)\b[^\n]*/gi, file.relPath));
7833
+ result.queryTimeoutDefaults.push(...extract(file.content, /\b(?:statement_timeout|query[_-]?timeout|socketTimeout|commandTimeout)\b[^\n]*/gi, file.relPath));
7834
+ result.manualIndexes.push(...extract(file.content, /\bcreate\s+(?:unique\s+)?index\s+[^;\n]+/gi, file.relPath));
7835
+ result.tables.push(...extract(file.content, /\bcreate\s+table\s+([a-zA-Z0-9_."]+)/gi, file.relPath));
7836
+ result.views.push(...extract(file.content, /\bcreate\s+view\s+([a-zA-Z0-9_."]+)/gi, file.relPath));
7837
+ result.storedProcedures.push(...extract(file.content, /\bcreate\s+(?:or\s+replace\s+)?procedure\s+([a-zA-Z0-9_."]+)/gi, file.relPath));
7838
+ result.triggers.push(...extract(file.content, /\bcreate\s+trigger\s+([a-zA-Z0-9_."]+)/gi, file.relPath));
7839
+ result.rowLevelSecurityPolicies.push(...extract(file.content, /\b(?:row\s+level\s+security|create\s+policy|alter\s+table\s+.*\s+enable\s+row\s+level\s+security)\b[^;\n]*/gi, file.relPath));
7840
+ result.otherServices.push(...extract(file.content, /\b(?:redis:\/\/[^\s'"`]+|amqp:\/\/[^\s'"`]+|kafka:\/\/[^\s'"`]+|elasticsearch:\/\/[^\s'"`]+)\b/gi, file.relPath));
7841
+ }
7842
+ const dedupDb = /* @__PURE__ */ new Map();
7843
+ for (const db of dbTechnologies) dedupDb.set(`${db.kind}:${db.brand}:${db.evidence}`, db);
7844
+ result.databaseTechnologies = [...dedupDb.values()].sort((a, b) => a.brand.localeCompare(b.brand));
7845
+ result.connectionStrings = uniq(result.connectionStrings);
7846
+ result.connectionPoolSettings = uniq(result.connectionPoolSettings);
7847
+ result.replicationSettings = uniq(result.replicationSettings);
7848
+ result.readReplicaSettings = uniq(result.readReplicaSettings);
7849
+ result.failoverSettings = uniq(result.failoverSettings);
7850
+ result.collationAndEncoding = uniq(result.collationAndEncoding);
7851
+ result.queryTimeoutDefaults = uniq(result.queryTimeoutDefaults);
7852
+ result.manualIndexes = uniq(result.manualIndexes);
7853
+ result.tables = uniq(result.tables);
7854
+ result.views = uniq(result.views);
7855
+ result.storedProcedures = uniq(result.storedProcedures);
7856
+ result.triggers = uniq(result.triggers);
7857
+ result.rowLevelSecurityPolicies = uniq(result.rowLevelSecurityPolicies);
7858
+ result.otherServices = uniq(result.otherServices);
7859
+ return result;
7860
+ }
7861
+ function detectOpenApiSpecification(relPath, content) {
7862
+ const lowerPath = relPath.toLowerCase();
7863
+ const format = lowerPath.endsWith(".json") ? "json" : lowerPath.endsWith(".yaml") ? "yaml" : lowerPath.endsWith(".yml") ? "yml" : null;
7864
+ if (!format) return null;
7865
+ const hasOpenApi = /\bopenapi\s*[:=]\s*['"]?3\./i.test(content) || /\bswagger\s*[:=]\s*['"]?2\.0/i.test(content) || /"openapi"\s*:\s*"3\./i.test(content);
7866
+ const likelyName = /(openapi|swagger).*(\.ya?ml|\.json)$/i.test(lowerPath) || /\/api-docs\b/i.test(lowerPath);
7867
+ if (!hasOpenApi && !likelyName) return null;
7868
+ const version = content.match(/\bopenapi\s*[:=]\s*['"]?([^'"\s,]+)/i)?.[1] ?? content.match(/\bswagger\s*[:=]\s*['"]?([^'"\s,]+)/i)?.[1] ?? null;
7869
+ const title = content.match(/\btitle\s*[:=]\s*['"]([^'"]+)['"]/i)?.[1] ?? content.match(/\btitle\s*[:=]\s*([^\n#]+)/i)?.[1]?.trim() ?? content.match(/"title"\s*:\s*"([^"]+)"/i)?.[1] ?? null;
7870
+ const endpointCount = content.match(/(^|\n)\s*\/[^\n:]+:\s*(get|post|put|patch|delete|options|head)/gim)?.length ?? content.match(/"\/[^"]+"\s*:\s*\{/g)?.length ?? null;
7871
+ return { path: relPath, format, version, title, endpointCount };
7872
+ }
7873
+ async function scanApiSurface(rootDir, fileCache) {
7874
+ const files = await scanTextFiles(rootDir, fileCache);
7875
+ const integrations = [];
7876
+ const result = {
7877
+ integrations: [],
7878
+ openApiSpecifications: [],
7879
+ webhookUrls: [],
7880
+ callbackEndpoints: [],
7881
+ apiVersionPins: [],
7882
+ tokenExpirationPolicies: [],
7883
+ rateLimitOverrides: [],
7884
+ customHeaders: [],
7885
+ corsPolicies: [],
7886
+ oauthScopes: [],
7887
+ apiTokens: []
7888
+ };
7889
+ for (const file of files) {
7890
+ const openApiSpec = detectOpenApiSpecification(file.relPath, file.content);
7891
+ if (openApiSpec) result.openApiSpecifications.push(openApiSpec);
7892
+ const endpoints = extract(file.content, /\bhttps?:\/\/[^\s'"`]+/gi, file.relPath);
7893
+ for (const endpoint of endpoints) {
7894
+ const provider = endpoint.replace(/^https?:\/\//, "").split("/")[0] ?? "unknown";
7895
+ const versionMatch = endpoint.match(/\/(v\d+(?:\.\d+)?)\b/i);
7896
+ const params = endpoint.match(/[?&]([a-zA-Z0-9_.-]+)=/g)?.map((p) => p.replace(/[?&=]/g, "")) ?? [];
7897
+ integrations.push({
7898
+ provider,
7899
+ endpoint,
7900
+ version: versionMatch ? versionMatch[1] : null,
7901
+ parameters: params,
7902
+ configOptions: extract(file.content, /\b(?:baseUrl|apiUrl|endpoint|timeout|retries|apiVersion)\b[^\n]*/gi, file.relPath),
7903
+ authHints: extract(file.content, /\b(?:api[_-]?key|bearer\s+[a-z0-9_.-]+|client[_-]?secret|oauth)\b[^\n]*/gi, file.relPath)
7904
+ });
7905
+ }
7906
+ result.webhookUrls.push(...extract(file.content, /\bhttps?:\/\/[^\s'"`]*(?:webhook|hooks?)[^\s'"`]*/gi, file.relPath));
7907
+ result.callbackEndpoints.push(...extract(file.content, /\bhttps?:\/\/[^\s'"`]*(?:callback|redirect_uri)[^\s'"`]*/gi, file.relPath));
7908
+ result.apiVersionPins.push(...extract(file.content, /\bapi[_-]?version\b[^\n]*/gi, file.relPath));
7909
+ result.tokenExpirationPolicies.push(...extract(file.content, /\b(?:token[_-]?expiry|expires[_-]?in|refresh[_-]?token[_-]?ttl)\b[^\n]*/gi, file.relPath));
7910
+ result.rateLimitOverrides.push(...extract(file.content, /\b(?:rate[_-]?limit|max[_-]?requests|throttle)\b[^\n]*/gi, file.relPath));
7911
+ result.customHeaders.push(...extract(file.content, /\b(?:x-[a-z0-9-]+|authorization|user-agent)\b[^\n]*/gi, file.relPath));
7912
+ result.corsPolicies.push(...extract(file.content, /\b(?:cors|access-control-allow-origin|allowedOrigins)\b[^\n]*/gi, file.relPath));
7913
+ result.oauthScopes.push(...extract(file.content, /\b(?:scope|scopes)\b[^\n]*(?:openid|profile|email|read|write)/gi, file.relPath));
7914
+ result.apiTokens.push(...extract(file.content, /\b(?:api[_-]?token|access[_-]?token|bearer[_-]?token)\b[^\n]*/gi, file.relPath));
7915
+ }
7916
+ const integrationMap = /* @__PURE__ */ new Map();
7917
+ for (const integration of integrations) {
7918
+ const key = `${integration.provider}:${integration.endpoint}`;
7919
+ integrationMap.set(key, {
7920
+ ...integration,
7921
+ parameters: uniq(integration.parameters),
7922
+ configOptions: uniq(integration.configOptions),
7923
+ authHints: uniq(integration.authHints)
7924
+ });
7508
7925
  }
7509
- return "general";
7926
+ result.integrations = [...integrationMap.values()].sort((a, b) => a.provider.localeCompare(b.provider));
7927
+ result.openApiSpecifications = [...new Map(result.openApiSpecifications.map((spec) => [spec.path, spec])).values()].sort((a, b) => a.path.localeCompare(b.path));
7928
+ result.webhookUrls = uniq(result.webhookUrls);
7929
+ result.callbackEndpoints = uniq(result.callbackEndpoints);
7930
+ result.apiVersionPins = uniq(result.apiVersionPins);
7931
+ result.tokenExpirationPolicies = uniq(result.tokenExpirationPolicies);
7932
+ result.rateLimitOverrides = uniq(result.rateLimitOverrides);
7933
+ result.customHeaders = uniq(result.customHeaders);
7934
+ result.corsPolicies = uniq(result.corsPolicies);
7935
+ result.oauthScopes = uniq(result.oauthScopes);
7936
+ result.apiTokens = uniq(result.apiTokens);
7937
+ return result;
7510
7938
  }
7511
- function normalizeValue(value) {
7512
- return value.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim().slice(0, 60);
7939
+ async function scanOperationalResilience(rootDir, fileCache) {
7940
+ const files = await scanTextFiles(rootDir, fileCache);
7941
+ const result = {
7942
+ implicitTimeouts: [],
7943
+ defaultPaginationSize: [],
7944
+ implicitRetryLogic: [],
7945
+ defaultLocale: [],
7946
+ defaultCurrency: [],
7947
+ implicitTimezone: [],
7948
+ defaultCharacterEncoding: [],
7949
+ sessionStores: [],
7950
+ distributedLocks: [],
7951
+ jobSchedulers: [],
7952
+ idempotencyKeys: [],
7953
+ rateLimitingCounters: [],
7954
+ circuitBreakerState: [],
7955
+ abTestToggles: [],
7956
+ regionalEnablementRules: [],
7957
+ betaAccessGroups: [],
7958
+ licensingEnforcementLogic: [],
7959
+ killSwitches: [],
7960
+ connectorRetryLogic: [],
7961
+ apiPollingIntervals: [],
7962
+ fieldMappings: [],
7963
+ schemaRegistryRules: [],
7964
+ deadLetterQueueBehavior: [],
7965
+ dataMaskingRules: [],
7966
+ transformationLogic: [],
7967
+ timezoneHandling: [],
7968
+ encryptionSettings: [],
7969
+ hardcodedSecretSignals: []
7970
+ };
7971
+ for (const file of files) {
7972
+ result.implicitTimeouts.push(...extract(file.content, /\b(?:timeout|requestTimeout|connectTimeout|readTimeout)\b[^\n]*/gi, file.relPath));
7973
+ result.defaultPaginationSize.push(...extract(file.content, /\b(?:pageSize|limit|perPage|defaultPageSize)\b[^\n]*/gi, file.relPath));
7974
+ result.implicitRetryLogic.push(...extract(file.content, /\b(?:retry|backoff|maxRetries)\b[^\n]*/gi, file.relPath));
7975
+ result.defaultLocale.push(...extract(file.content, /\b(?:defaultLocale|locale\s*[:=]|en-US|en_GB)\b[^\n]*/gi, file.relPath));
7976
+ result.defaultCurrency.push(...extract(file.content, /\b(?:defaultCurrency|currency\s*[:=]|USD|EUR|GBP)\b[^\n]*/gi, file.relPath));
7977
+ result.implicitTimezone.push(...extract(file.content, /\b(?:timezone|timeZone|UTC|GMT)\b[^\n]*/gi, file.relPath));
7978
+ result.defaultCharacterEncoding.push(...extract(file.content, /\b(?:charset|encoding|UTF-?8|ISO-8859-1)\b[^\n]*/gi, file.relPath));
7979
+ result.sessionStores.push(...extract(file.content, /\b(?:sessionStore|redisStore|memoryStore)\b[^\n]*/gi, file.relPath));
7980
+ result.distributedLocks.push(...extract(file.content, /\b(?:distributed[_-]?lock|redlock|mutex)\b[^\n]*/gi, file.relPath));
7981
+ result.jobSchedulers.push(...extract(file.content, /\b(?:cron|schedule|bullmq|agenda|job[_-]?scheduler)\b[^\n]*/gi, file.relPath));
7982
+ result.idempotencyKeys.push(...extract(file.content, /\b(?:idempotency[_-]?key|Idempotency-Key)\b[^\n]*/gi, file.relPath));
7983
+ result.rateLimitingCounters.push(...extract(file.content, /\b(?:rate[_-]?limit|throttle|quota)\b[^\n]*/gi, file.relPath));
7984
+ result.circuitBreakerState.push(...extract(file.content, /\b(?:circuit[_-]?breaker|half[_-]?open|open[_-]?state)\b[^\n]*/gi, file.relPath));
7985
+ result.abTestToggles.push(...extract(file.content, /\b(?:a\/b|ab[_-]?test|experiment[_-]?flag)\b[^\n]*/gi, file.relPath));
7986
+ result.regionalEnablementRules.push(...extract(file.content, /\b(?:region[_-]?enabled|geo[_-]?fence|country[_-]?allow)\b[^\n]*/gi, file.relPath));
7987
+ result.betaAccessGroups.push(...extract(file.content, /\b(?:beta[_-]?users?|early[_-]?access|allowlist)\b[^\n]*/gi, file.relPath));
7988
+ result.licensingEnforcementLogic.push(...extract(file.content, /\b(?:license[_-]?key|entitlement|plan[_-]?check)\b[^\n]*/gi, file.relPath));
7989
+ result.killSwitches.push(...extract(file.content, /\b(?:kill[_-]?switch|disable[_-]?all|emergency[_-]?stop)\b[^\n]*/gi, file.relPath));
7990
+ result.connectorRetryLogic.push(...extract(file.content, /\b(?:connector|integration)\b[^\n]*(?:retry|backoff)/gi, file.relPath));
7991
+ result.apiPollingIntervals.push(...extract(file.content, /\b(?:poll(?:ing)?[_-]?interval|sync[_-]?interval)\b[^\n]*/gi, file.relPath));
7992
+ result.fieldMappings.push(...extract(file.content, /\b(?:field[_-]?mapping|mapFields?|column[_-]?map)\b[^\n]*/gi, file.relPath));
7993
+ result.schemaRegistryRules.push(...extract(file.content, /\b(?:schema[_-]?registry|avro|protobuf)\b[^\n]*/gi, file.relPath));
7994
+ result.deadLetterQueueBehavior.push(...extract(file.content, /\b(?:dead[_-]?letter|dlq)\b[^\n]*/gi, file.relPath));
7995
+ result.dataMaskingRules.push(...extract(file.content, /\b(?:data[_-]?mask|redact|pii[_-]?mask)\b[^\n]*/gi, file.relPath));
7996
+ result.transformationLogic.push(...extract(file.content, /\b(?:transform|mapper|normaliz(?:e|ation))\b[^\n]*/gi, file.relPath));
7997
+ result.timezoneHandling.push(...extract(file.content, /\b(?:convertTimezone|tz\(|moment\.tz|DateTimeZone)\b[^\n]*/gi, file.relPath));
7998
+ result.encryptionSettings.push(...extract(file.content, /\b(?:kms|encrypt(?:ion)?|cipher|tls|minTlsVersion)\b[^\n]*/gi, file.relPath));
7999
+ result.hardcodedSecretSignals.push(...extract(file.content, /\b(?:password|passwd|connectionString|api[_-]?key|secret)\b\s*[:=]\s*['"][^'"]{4,}['"]/gi, file.relPath));
8000
+ }
8001
+ Object.keys(result).forEach((key) => {
8002
+ result[key] = uniq(result[key]);
8003
+ });
8004
+ return result;
7513
8005
  }
7514
- function dedupeStrings(values) {
7515
- const sorted = values.sort((a, b) => b.length - a.length);
7516
- const kept = [];
7517
- for (const value of sorted) {
7518
- const isDupe = kept.some((k) => {
7519
- const stem = value.slice(0, 6);
7520
- return k.startsWith(stem) || k.includes(value) || value.includes(k);
7521
- });
7522
- if (!isDupe) {
7523
- kept.push(value);
8006
+ async function scanAssetBranding(rootDir, fileCache) {
8007
+ const entries = await fileCache.walkDir(rootDir);
8008
+ const faviconCandidates = entries.filter((entry) => entry.isFile && /favicon/i.test(entry.name));
8009
+ const logoCandidates = entries.filter((entry) => entry.isFile && /logo/i.test(entry.name));
8010
+ const faviconFiles = [];
8011
+ for (const entry of faviconCandidates.slice(0, 10)) {
8012
+ try {
8013
+ const content = await fileCache.readTextFile(entry.absPath);
8014
+ if (!content) continue;
8015
+ const encoded = Buffer.from(content).toString("base64");
8016
+ faviconFiles.push({
8017
+ path: entry.relPath,
8018
+ base64: encoded.slice(0, 1e4)
8019
+ });
8020
+ } catch {
7524
8021
  }
7525
8022
  }
7526
- return kept;
8023
+ return {
8024
+ faviconFiles,
8025
+ productLogos: uniq(logoCandidates.map((entry) => entry.relPath))
8026
+ };
7527
8027
  }
7528
- function dedupeRoutes(routes) {
7529
- const seen = /* @__PURE__ */ new Set();
7530
- const result = [];
7531
- for (const route of routes) {
7532
- const normalized = route.replace(/:[a-z_]+/gi, ":param").replace(/\[\[*\.*\.*[a-z_]+\]*\]/gi, ":param").replace(/\/+$/, "").toLowerCase();
7533
- if (!seen.has(normalized)) {
7534
- seen.add(normalized);
7535
- result.push(route);
8028
+ async function scanOssGovernance(rootDir, fileCache) {
8029
+ const files = await scanTextFiles(rootDir, fileCache);
8030
+ const directDeps = /* @__PURE__ */ new Set();
8031
+ const transitiveDeps = /* @__PURE__ */ new Set();
8032
+ const vulns = [];
8033
+ const licenseRisks = [];
8034
+ for (const file of files) {
8035
+ if (/package(-lock)?\.json$|pnpm-lock\.yaml$|poetry\.lock$|pom\.xml$/i.test(file.relPath)) {
8036
+ const depMatches = file.content.match(/"([@a-zA-Z0-9_.\/-]+)"\s*:/g) ?? [];
8037
+ depMatches.forEach((match) => transitiveDeps.add(match.replace(/["\s:]/g, "")));
8038
+ }
8039
+ if (/package\.json$/i.test(file.relPath)) {
8040
+ const depMatches = file.content.match(/"([@a-zA-Z0-9_.\/-]+)"\s*:\s*"[~^0-9*><=.-]+"/g) ?? [];
8041
+ depMatches.forEach((match) => {
8042
+ const pkg2 = match.split(":")[0]?.replace(/"/g, "").trim();
8043
+ if (pkg2) directDeps.add(pkg2);
8044
+ });
7536
8045
  }
8046
+ vulns.push(...extract(file.content, /\b(?:CVE-\d{4}-\d{4,}|critical vulnerability|high severity)\b[^\n]*/gi, file.relPath));
8047
+ licenseRisks.push(...extract(file.content, /\b(?:GPL-3\.0|AGPL|SSPL|BUSL|source[- ]available)\b[^\n]*/gi, file.relPath));
7537
8048
  }
7538
- return result;
8049
+ return {
8050
+ directDependencies: directDeps.size,
8051
+ transitiveDependencies: Math.max(transitiveDeps.size, directDeps.size),
8052
+ knownVulnerabilities: uniq(vulns),
8053
+ licenseRisks: uniq(licenseRisks)
8054
+ };
7539
8055
  }
7540
8056
 
7541
8057
  // src/utils/tool-installer.ts
@@ -7639,21 +8155,27 @@ function escapeLabel(input) {
7639
8155
  }
7640
8156
  function scoreClass(score) {
7641
8157
  if (score === void 0 || Number.isNaN(score)) return "scoreUnknown";
7642
- if (score >= 70) return "scoreHigh";
7643
- if (score >= 40) return "scoreModerate";
8158
+ if (score >= 80) return "scoreHigh";
8159
+ if (score >= 50) return "scoreModerate";
7644
8160
  return "scoreLow";
7645
8161
  }
7646
8162
  function nodeLabel(project) {
7647
8163
  const score = project.drift?.score;
7648
- const scoreText = typeof score === "number" ? ` (${score})` : " (n/a)";
7649
- return `${project.name}${scoreText}`;
8164
+ if (typeof score === "number") {
8165
+ return `${project.name} (${score})`;
8166
+ }
8167
+ return project.name;
7650
8168
  }
7651
8169
  function buildDefs() {
7652
8170
  return [
7653
- "classDef scoreHigh fill:#064e3b,stroke:#10b981,color:#d1fae5,stroke-width:2px",
7654
- "classDef scoreModerate fill:#78350f,stroke:#f59e0b,color:#fef3c7,stroke-width:2px",
7655
- "classDef scoreLow fill:#7f1d1d,stroke:#ef4444,color:#fee2e2,stroke-width:2px",
7656
- "classDef scoreUnknown fill:#334155,stroke:#94a3b8,color:#e2e8f0,stroke-width:2px"
8171
+ // Emerald border for high scores (>= 80) - border-emerald-500
8172
+ "classDef scoreHigh fill:#1e293b,stroke:#10b981,color:#f1f5f9,stroke-width:2px",
8173
+ // Amber border for moderate scores (50-79) - border-amber-500
8174
+ "classDef scoreModerate fill:#1e293b,stroke:#f59e0b,color:#f1f5f9,stroke-width:2px",
8175
+ // Red border for low scores (< 50) - border-red-500
8176
+ "classDef scoreLow fill:#1e293b,stroke:#ef4444,color:#f1f5f9,stroke-width:2px",
8177
+ // Slate border for unknown scores
8178
+ "classDef scoreUnknown fill:#1e293b,stroke:#64748b,color:#94a3b8,stroke-width:2px"
7657
8179
  ];
7658
8180
  }
7659
8181
  function generateWorkspaceRelationshipMermaid(projects) {
@@ -7739,17 +8261,17 @@ async function discoverSolutions(rootDir, fileCache) {
7739
8261
  for (const solutionFile of solutionFiles) {
7740
8262
  try {
7741
8263
  const content = await fileCache.readTextFile(solutionFile);
7742
- const dir = path23.dirname(solutionFile);
7743
- const relSolutionPath = path23.relative(rootDir, solutionFile).replace(/\\/g, "/");
8264
+ const dir = path24.dirname(solutionFile);
8265
+ const relSolutionPath = path24.relative(rootDir, solutionFile).replace(/\\/g, "/");
7744
8266
  const projectPaths = /* @__PURE__ */ new Set();
7745
8267
  const projectRegex = /Project\("[^"]*"\)\s*=\s*"([^"]*)",\s*"([^"]+\.csproj)"/g;
7746
8268
  let match;
7747
8269
  while ((match = projectRegex.exec(content)) !== null) {
7748
8270
  const projectRelative = match[2];
7749
- const absProjectPath = path23.resolve(dir, projectRelative.replace(/\\/g, "/"));
7750
- projectPaths.add(path23.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
8271
+ const absProjectPath = path24.resolve(dir, projectRelative.replace(/\\/g, "/"));
8272
+ projectPaths.add(path24.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
7751
8273
  }
7752
- const solutionName = path23.basename(solutionFile, path23.extname(solutionFile));
8274
+ const solutionName = path24.basename(solutionFile, path24.extname(solutionFile));
7753
8275
  parsed.push({
7754
8276
  path: relSolutionPath,
7755
8277
  name: solutionName,
@@ -7792,7 +8314,13 @@ async function runScan(rootDir, opts) {
7792
8314
  architecture: !maxPrivacyMode,
7793
8315
  codeQuality: !maxPrivacyMode,
7794
8316
  owaspCategoryMapping: !maxPrivacyMode,
7795
- uiPurpose: !maxPrivacyMode
8317
+ uiPurpose: !maxPrivacyMode,
8318
+ runtimeConfiguration: !maxPrivacyMode,
8319
+ dataStores: true,
8320
+ apiSurface: !maxPrivacyMode,
8321
+ operationalResilience: !maxPrivacyMode,
8322
+ assetBranding: true,
8323
+ ossGovernance: true
7796
8324
  };
7797
8325
  let filesScanned = 0;
7798
8326
  const progress = new ScanProgress(rootDir);
@@ -7821,7 +8349,13 @@ async function runScan(rootDir, opts) {
7821
8349
  ...scannerPolicy.architecture && scanners?.architecture?.enabled !== false ? [{ id: "architecture", label: "Architecture layers" }] : [],
7822
8350
  ...scannerPolicy.codeQuality && scanners?.codeQuality?.enabled !== false ? [{ id: "codequality", label: "Code quality metrics" }] : [],
7823
8351
  ...scannerPolicy.owaspCategoryMapping && scanners?.owaspCategoryMapping?.enabled !== false ? [{ id: "owasp", label: "OWASP category mapping" }] : [],
7824
- ...!maxPrivacyMode && (opts.uiPurpose || scanners?.uiPurpose?.enabled === true) ? [{ id: "uipurpose", label: "UI purpose evidence" }] : []
8352
+ ...!maxPrivacyMode && (opts.uiPurpose || scanners?.uiPurpose?.enabled === true) ? [{ id: "uipurpose", label: "UI purpose evidence" }] : [],
8353
+ ...scannerPolicy.runtimeConfiguration && scanners?.runtimeConfiguration?.enabled !== false ? [{ id: "runtimecfg", label: "Runtime configuration" }] : [],
8354
+ ...scannerPolicy.dataStores && scanners?.dataStores?.enabled !== false ? [{ id: "datastores", label: "Data stores & schema" }] : [],
8355
+ ...scannerPolicy.apiSurface && scanners?.apiSurface?.enabled !== false ? [{ id: "apisurface", label: "API integrations" }] : [],
8356
+ ...scannerPolicy.operationalResilience && scanners?.operationalResilience?.enabled !== false ? [{ id: "resilience", label: "Operational resilience" }] : [],
8357
+ ...scannerPolicy.assetBranding && scanners?.assetBranding?.enabled !== false ? [{ id: "branding", label: "Branding assets" }] : [],
8358
+ ...scannerPolicy.ossGovernance && scanners?.ossGovernance?.enabled !== false ? [{ id: "ossgov", label: "OSS governance" }] : []
7825
8359
  ] : [],
7826
8360
  { id: "drift", label: "Computing drift score" },
7827
8361
  { id: "findings", label: "Generating findings" }
@@ -7947,7 +8481,7 @@ async function runScan(rootDir, opts) {
7947
8481
  project.drift = computeDriftScore([project]);
7948
8482
  project.projectId = computeProjectId(project.path, project.name, workspaceId);
7949
8483
  }
7950
- const solutionsManifestPath = path23.join(rootDir, ".vibgrate", "solutions.json");
8484
+ const solutionsManifestPath = path24.join(rootDir, ".vibgrate", "solutions.json");
7951
8485
  const persistedSolutionIds = /* @__PURE__ */ new Map();
7952
8486
  if (await pathExists(solutionsManifestPath)) {
7953
8487
  try {
@@ -8124,6 +8658,66 @@ async function runScan(rootDir, opts) {
8124
8658
  })
8125
8659
  );
8126
8660
  }
8661
+ if (scannerPolicy.runtimeConfiguration && scanners?.runtimeConfiguration?.enabled !== false) {
8662
+ progress.startStep("runtimecfg");
8663
+ scannerTasks.push(
8664
+ scanRuntimeConfiguration(rootDir, fileCache).then((result) => {
8665
+ extended.runtimeConfiguration = result;
8666
+ const count = result.environmentVariables.length + result.featureFlags.length + result.dotEnvFiles.length;
8667
+ progress.completeStep("runtimecfg", `${count} runtime signals`, count);
8668
+ })
8669
+ );
8670
+ }
8671
+ if (scannerPolicy.dataStores && scanners?.dataStores?.enabled !== false) {
8672
+ progress.startStep("datastores");
8673
+ scannerTasks.push(
8674
+ scanDataStores(rootDir, fileCache).then((result) => {
8675
+ extended.dataStores = result;
8676
+ const count = result.databaseTechnologies.length + result.tables.length + result.views.length;
8677
+ progress.completeStep("datastores", `${result.databaseTechnologies.length} engines \xB7 ${result.tables.length} tables`, count);
8678
+ })
8679
+ );
8680
+ }
8681
+ if (scannerPolicy.apiSurface && scanners?.apiSurface?.enabled !== false) {
8682
+ progress.startStep("apisurface");
8683
+ scannerTasks.push(
8684
+ scanApiSurface(rootDir, fileCache).then((result) => {
8685
+ extended.apiSurface = result;
8686
+ const count = result.integrations.length + result.webhookUrls.length;
8687
+ progress.completeStep("apisurface", `${result.integrations.length} integrations`, count);
8688
+ })
8689
+ );
8690
+ }
8691
+ if (scannerPolicy.operationalResilience && scanners?.operationalResilience?.enabled !== false) {
8692
+ progress.startStep("resilience");
8693
+ scannerTasks.push(
8694
+ scanOperationalResilience(rootDir, fileCache).then((result) => {
8695
+ extended.operationalResilience = result;
8696
+ const count = result.implicitTimeouts.length + result.implicitRetryLogic.length + result.killSwitches.length;
8697
+ progress.completeStep("resilience", `${count} resilience signals`, count);
8698
+ })
8699
+ );
8700
+ }
8701
+ if (scannerPolicy.assetBranding && scanners?.assetBranding?.enabled !== false) {
8702
+ progress.startStep("branding");
8703
+ scannerTasks.push(
8704
+ scanAssetBranding(rootDir, fileCache).then((result) => {
8705
+ extended.assetBranding = result;
8706
+ const count = result.faviconFiles.length + result.productLogos.length;
8707
+ progress.completeStep("branding", `${result.faviconFiles.length} favicons \xB7 ${result.productLogos.length} logos`, count);
8708
+ })
8709
+ );
8710
+ }
8711
+ if (scannerPolicy.ossGovernance && scanners?.ossGovernance?.enabled !== false) {
8712
+ progress.startStep("ossgov");
8713
+ scannerTasks.push(
8714
+ scanOssGovernance(rootDir, fileCache).then((result) => {
8715
+ extended.ossGovernance = result;
8716
+ const count = result.directDependencies + result.knownVulnerabilities.length + result.licenseRisks.length;
8717
+ progress.completeStep("ossgov", `${result.directDependencies} direct deps`, count);
8718
+ })
8719
+ );
8720
+ }
8127
8721
  if (scannerPolicy.dependencyRisk && scanners?.dependencyRisk?.enabled !== false) {
8128
8722
  progress.startStep("deprisk");
8129
8723
  scannerTasks.push(
@@ -8164,7 +8758,7 @@ async function runScan(rootDir, opts) {
8164
8758
  const summary = [`${up.topEvidence.length} evidence`, ...up.capped ? ["capped"] : []].join(" \xB7 ");
8165
8759
  progress.completeStep("uipurpose", summary, up.topEvidence.length);
8166
8760
  await Promise.all(allProjects.map(async (project) => {
8167
- const projectDir = path23.join(rootDir, project.path);
8761
+ const projectDir = path24.join(rootDir, project.path);
8168
8762
  const projectResult = await scanUiPurpose(projectDir, fileCache, 150);
8169
8763
  if (projectResult.topEvidence.length > 0) {
8170
8764
  project.uiPurpose = compactUiPurpose(projectResult);
@@ -8258,13 +8852,19 @@ async function runScan(rootDir, opts) {
8258
8852
  if (extended.codeQuality) filesScanned += extended.codeQuality.filesAnalyzed;
8259
8853
  if (extended.owaspCategoryMapping) filesScanned += extended.owaspCategoryMapping.scannedFiles;
8260
8854
  if (extended.uiPurpose) filesScanned += extended.uiPurpose.topEvidence.length;
8855
+ if (extended.runtimeConfiguration) filesScanned += extended.runtimeConfiguration.hiddenConfigFiles.length;
8856
+ if (extended.dataStores) filesScanned += extended.dataStores.databaseTechnologies.length + extended.dataStores.tables.length;
8857
+ if (extended.apiSurface) filesScanned += extended.apiSurface.integrations.length;
8858
+ if (extended.operationalResilience) filesScanned += extended.operationalResilience.implicitTimeouts.length;
8859
+ if (extended.assetBranding) filesScanned += extended.assetBranding.faviconFiles.length + extended.assetBranding.productLogos.length;
8860
+ if (extended.ossGovernance) filesScanned += extended.ossGovernance.directDependencies;
8261
8861
  const durationMs = Date.now() - scanStart;
8262
8862
  const repository = await buildRepositoryInfo(rootDir, vcs.remoteUrl, extended.buildDeploy?.ci);
8263
8863
  const artifact = {
8264
8864
  schemaVersion: "1.0",
8265
8865
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
8266
8866
  vibgrateVersion: VERSION,
8267
- rootPath: path23.basename(rootDir),
8867
+ rootPath: path24.basename(rootDir),
8268
8868
  ...vcs.type !== "unknown" ? { vcs } : {},
8269
8869
  repository,
8270
8870
  projects: allProjects,
@@ -8278,7 +8878,7 @@ async function runScan(rootDir, opts) {
8278
8878
  relationshipDiagram
8279
8879
  };
8280
8880
  if (opts.baseline) {
8281
- const baselinePath = path23.resolve(opts.baseline);
8881
+ const baselinePath = path24.resolve(opts.baseline);
8282
8882
  if (await pathExists(baselinePath)) {
8283
8883
  try {
8284
8884
  const baseline = await readJsonFile(baselinePath);
@@ -8290,10 +8890,10 @@ async function runScan(rootDir, opts) {
8290
8890
  }
8291
8891
  }
8292
8892
  if (!opts.noLocalArtifacts && !maxPrivacyMode) {
8293
- const vibgrateDir = path23.join(rootDir, ".vibgrate");
8893
+ const vibgrateDir = path24.join(rootDir, ".vibgrate");
8294
8894
  await ensureDir(vibgrateDir);
8295
- await writeJsonFile(path23.join(vibgrateDir, "scan_result.json"), artifact);
8296
- await writeJsonFile(path23.join(vibgrateDir, "solutions.json"), {
8895
+ await writeJsonFile(path24.join(vibgrateDir, "scan_result.json"), artifact);
8896
+ await writeJsonFile(path24.join(vibgrateDir, "solutions.json"), {
8297
8897
  scannedAt: artifact.timestamp,
8298
8898
  solutions: solutions.map((solution) => ({
8299
8899
  solutionId: solution.solutionId,
@@ -8314,10 +8914,10 @@ async function runScan(rootDir, opts) {
8314
8914
  if (!opts.noLocalArtifacts && !maxPrivacyMode) {
8315
8915
  for (const project of allProjects) {
8316
8916
  if (project.drift && project.path) {
8317
- const projectDir = path23.resolve(rootDir, project.path);
8318
- const projectVibgrateDir = path23.join(projectDir, ".vibgrate");
8917
+ const projectDir = path24.resolve(rootDir, project.path);
8918
+ const projectVibgrateDir = path24.join(projectDir, ".vibgrate");
8319
8919
  await ensureDir(projectVibgrateDir);
8320
- await writeJsonFile(path23.join(projectVibgrateDir, "project_score.json"), {
8920
+ await writeJsonFile(path24.join(projectVibgrateDir, "project_score.json"), {
8321
8921
  projectId: project.projectId,
8322
8922
  name: project.name,
8323
8923
  type: project.type,
@@ -8337,7 +8937,7 @@ async function runScan(rootDir, opts) {
8337
8937
  if (opts.format === "json") {
8338
8938
  const jsonStr = JSON.stringify(artifact, null, 2);
8339
8939
  if (opts.out) {
8340
- await writeTextFile(path23.resolve(opts.out), jsonStr);
8940
+ await writeTextFile(path24.resolve(opts.out), jsonStr);
8341
8941
  console.log(chalk6.green("\u2714") + ` JSON written to ${opts.out}`);
8342
8942
  } else {
8343
8943
  console.log(jsonStr);
@@ -8346,7 +8946,7 @@ async function runScan(rootDir, opts) {
8346
8946
  const sarif = formatSarif(artifact);
8347
8947
  const sarifStr = JSON.stringify(sarif, null, 2);
8348
8948
  if (opts.out) {
8349
- await writeTextFile(path23.resolve(opts.out), sarifStr);
8949
+ await writeTextFile(path24.resolve(opts.out), sarifStr);
8350
8950
  console.log(chalk6.green("\u2714") + ` SARIF written to ${opts.out}`);
8351
8951
  } else {
8352
8952
  console.log(sarifStr);
@@ -8355,20 +8955,20 @@ async function runScan(rootDir, opts) {
8355
8955
  const markdown = formatMarkdown(artifact);
8356
8956
  console.log(markdown);
8357
8957
  if (opts.out) {
8358
- await writeTextFile(path23.resolve(opts.out), markdown);
8958
+ await writeTextFile(path24.resolve(opts.out), markdown);
8359
8959
  }
8360
8960
  } else {
8361
8961
  const text = formatText(artifact);
8362
8962
  console.log(text);
8363
8963
  if (opts.out) {
8364
- await writeTextFile(path23.resolve(opts.out), text);
8964
+ await writeTextFile(path24.resolve(opts.out), text);
8365
8965
  }
8366
8966
  }
8367
8967
  return artifact;
8368
8968
  }
8369
8969
  async function buildRepositoryInfo(rootDir, remoteUrl, ciSystems) {
8370
- const packageJsonPath = path23.join(rootDir, "package.json");
8371
- let name = path23.basename(rootDir);
8970
+ const packageJsonPath = path24.join(rootDir, "package.json");
8971
+ let name = path24.basename(rootDir);
8372
8972
  let version;
8373
8973
  if (await pathExists(packageJsonPath)) {
8374
8974
  try {
@@ -8403,7 +9003,7 @@ async function autoPush(artifact, rootDir, opts) {
8403
9003
  if (opts.strict) process.exit(1);
8404
9004
  return;
8405
9005
  }
8406
- const body = JSON.stringify(artifact);
9006
+ const { body, contentEncoding } = await prepareCompressedUpload(artifact);
8407
9007
  const timestamp = String(Date.now());
8408
9008
  let host = parsed.host;
8409
9009
  if (opts.region) {
@@ -8416,12 +9016,16 @@ async function autoPush(artifact, rootDir, opts) {
8416
9016
  }
8417
9017
  }
8418
9018
  const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
8419
- console.log(chalk6.dim(`Uploading to ${host}...`));
9019
+ const originalSize = JSON.stringify(artifact).length;
9020
+ const compressedSize = body.length;
9021
+ const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
9022
+ console.log(chalk6.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
8420
9023
  try {
8421
9024
  const response = await fetch(url, {
8422
9025
  method: "POST",
8423
9026
  headers: {
8424
9027
  "Content-Type": "application/json",
9028
+ "Content-Encoding": contentEncoding,
8425
9029
  "X-Vibgrate-Timestamp": timestamp,
8426
9030
  "Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
8427
9031
  "Connection": "close"
@@ -8463,7 +9067,7 @@ function parseNonNegativeNumber(value, label) {
8463
9067
  return parsed;
8464
9068
  }
8465
9069
  var scanCommand = new Command3("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif|md)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--install-tools", "Auto-install missing security scanners via Homebrew").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").action(async (targetPath, opts) => {
8466
- const rootDir = path23.resolve(targetPath);
9070
+ const rootDir = path24.resolve(targetPath);
8467
9071
  if (!await pathExists(rootDir)) {
8468
9072
  console.error(chalk6.red(`Path does not exist: ${rootDir}`));
8469
9073
  process.exit(1);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runScan
3
- } from "./chunk-IQ4T2HLG.js";
3
+ } from "./chunk-DMYMJUQP.js";
4
4
  import {
5
5
  writeJsonFile
6
6
  } from "./chunk-TBE6NQ5Z.js";
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  baselineCommand
4
- } from "./chunk-GCMUJKM7.js";
4
+ } from "./chunk-IHDUX5MC.js";
5
5
  import {
6
6
  VERSION,
7
7
  dsnCommand,
@@ -10,7 +10,7 @@ import {
10
10
  pushCommand,
11
11
  scanCommand,
12
12
  writeDefaultConfig
13
- } from "./chunk-IQ4T2HLG.js";
13
+ } from "./chunk-DMYMJUQP.js";
14
14
  import {
15
15
  ensureDir,
16
16
  pathExists,
@@ -39,7 +39,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
39
39
  console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
40
40
  }
41
41
  if (opts.baseline) {
42
- const { runBaseline } = await import("./baseline-HGBNL6CN.js");
42
+ const { runBaseline } = await import("./baseline-IEG2JITU.js");
43
43
  await runBaseline(rootDir);
44
44
  }
45
45
  console.log("");
package/dist/index.d.ts CHANGED
@@ -196,6 +196,12 @@ interface ScannersConfig {
196
196
  codeQuality?: ScannerToggle;
197
197
  owaspCategoryMapping?: OwaspScannerConfig;
198
198
  uiPurpose?: ScannerToggle;
199
+ runtimeConfiguration?: ScannerToggle;
200
+ dataStores?: ScannerToggle;
201
+ apiSurface?: ScannerToggle;
202
+ operationalResilience?: ScannerToggle;
203
+ assetBranding?: ScannerToggle;
204
+ ossGovernance?: ScannerToggle;
199
205
  }
200
206
  interface VibgrateConfig {
201
207
  include?: string[];
@@ -487,6 +493,111 @@ interface UiPurposeResult {
487
493
  topEvidence: UiPurposeEvidenceItem[];
488
494
  unknownSignals: string[];
489
495
  }
496
+ interface RuntimeConfigurationResult {
497
+ environmentVariables: string[];
498
+ featureFlags: string[];
499
+ hiddenConfigFiles: string[];
500
+ dotEnvFiles: string[];
501
+ secretsInjectionPaths: string[];
502
+ containerEntrypoints: string[];
503
+ startupArguments: string[];
504
+ jvmFlags: string[];
505
+ threadPoolSettings: string[];
506
+ }
507
+ interface DatabaseTechnology {
508
+ kind: 'sql' | 'nosql';
509
+ brand: string;
510
+ version: string | null;
511
+ evidence: string;
512
+ }
513
+ interface DataStoresResult {
514
+ databaseTechnologies: DatabaseTechnology[];
515
+ connectionStrings: string[];
516
+ connectionPoolSettings: string[];
517
+ replicationSettings: string[];
518
+ readReplicaSettings: string[];
519
+ failoverSettings: string[];
520
+ collationAndEncoding: string[];
521
+ queryTimeoutDefaults: string[];
522
+ manualIndexes: string[];
523
+ tables: string[];
524
+ views: string[];
525
+ storedProcedures: string[];
526
+ triggers: string[];
527
+ rowLevelSecurityPolicies: string[];
528
+ otherServices: string[];
529
+ }
530
+ interface OpenApiSpecification {
531
+ path: string;
532
+ format: 'json' | 'yaml' | 'yml';
533
+ version: string | null;
534
+ title: string | null;
535
+ endpointCount: number | null;
536
+ }
537
+ interface ApiIntegration {
538
+ provider: string;
539
+ endpoint: string;
540
+ version: string | null;
541
+ parameters: string[];
542
+ configOptions: string[];
543
+ authHints: string[];
544
+ }
545
+ interface ApiSurfaceResult {
546
+ integrations: ApiIntegration[];
547
+ openApiSpecifications: OpenApiSpecification[];
548
+ webhookUrls: string[];
549
+ callbackEndpoints: string[];
550
+ apiVersionPins: string[];
551
+ tokenExpirationPolicies: string[];
552
+ rateLimitOverrides: string[];
553
+ customHeaders: string[];
554
+ corsPolicies: string[];
555
+ oauthScopes: string[];
556
+ apiTokens: string[];
557
+ }
558
+ interface OperationalResilienceResult {
559
+ implicitTimeouts: string[];
560
+ defaultPaginationSize: string[];
561
+ implicitRetryLogic: string[];
562
+ defaultLocale: string[];
563
+ defaultCurrency: string[];
564
+ implicitTimezone: string[];
565
+ defaultCharacterEncoding: string[];
566
+ sessionStores: string[];
567
+ distributedLocks: string[];
568
+ jobSchedulers: string[];
569
+ idempotencyKeys: string[];
570
+ rateLimitingCounters: string[];
571
+ circuitBreakerState: string[];
572
+ abTestToggles: string[];
573
+ regionalEnablementRules: string[];
574
+ betaAccessGroups: string[];
575
+ licensingEnforcementLogic: string[];
576
+ killSwitches: string[];
577
+ connectorRetryLogic: string[];
578
+ apiPollingIntervals: string[];
579
+ fieldMappings: string[];
580
+ schemaRegistryRules: string[];
581
+ deadLetterQueueBehavior: string[];
582
+ dataMaskingRules: string[];
583
+ transformationLogic: string[];
584
+ timezoneHandling: string[];
585
+ encryptionSettings: string[];
586
+ hardcodedSecretSignals: string[];
587
+ }
588
+ interface AssetBrandingResult {
589
+ faviconFiles: Array<{
590
+ path: string;
591
+ base64: string;
592
+ }>;
593
+ productLogos: string[];
594
+ }
595
+ interface OssGovernanceResult {
596
+ directDependencies: number;
597
+ transitiveDependencies: number;
598
+ knownVulnerabilities: string[];
599
+ licenseRisks: string[];
600
+ }
490
601
  interface ExtendedScanResults {
491
602
  platformMatrix?: PlatformMatrixResult;
492
603
  dependencyRisk?: DependencyRiskResult;
@@ -503,6 +614,12 @@ interface ExtendedScanResults {
503
614
  codeQuality?: CodeQualityResult;
504
615
  owaspCategoryMapping?: OwaspCategoryMappingResult;
505
616
  uiPurpose?: UiPurposeResult;
617
+ runtimeConfiguration?: RuntimeConfigurationResult;
618
+ dataStores?: DataStoresResult;
619
+ apiSurface?: ApiSurfaceResult;
620
+ operationalResilience?: OperationalResilienceResult;
621
+ assetBranding?: AssetBrandingResult;
622
+ ossGovernance?: OssGovernanceResult;
506
623
  }
507
624
  interface OwaspFinding {
508
625
  ruleId: string;
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  formatText,
6
6
  generateFindings,
7
7
  runScan
8
- } from "./chunk-IQ4T2HLG.js";
8
+ } from "./chunk-DMYMJUQP.js";
9
9
  import "./chunk-TBE6NQ5Z.js";
10
10
  export {
11
11
  computeDriftScore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibgrate/cli",
3
- "version": "1.0.64",
3
+ "version": "1.0.66",
4
4
  "description": "CLI for measuring upgrade drift across Node, .NET, Python & Java projects",
5
5
  "type": "module",
6
6
  "bin": {