@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 +1 -0
- package/dist/{baseline-HGBNL6CN.js → baseline-IEG2JITU.js} +2 -2
- package/dist/{chunk-IQ4T2HLG.js → chunk-DMYMJUQP.js} +718 -114
- package/dist/{chunk-GCMUJKM7.js → chunk-IHDUX5MC.js} +1 -1
- package/dist/cli.js +3 -3
- package/dist/index.d.ts +117 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
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
|
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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/
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
{
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
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
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
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
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
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
|
|
7506
|
-
|
|
7507
|
-
|
|
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
|
-
|
|
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
|
|
7512
|
-
|
|
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
|
|
7515
|
-
const
|
|
7516
|
-
const
|
|
7517
|
-
|
|
7518
|
-
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
|
|
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
|
|
8023
|
+
return {
|
|
8024
|
+
faviconFiles,
|
|
8025
|
+
productLogos: uniq(logoCandidates.map((entry) => entry.relPath))
|
|
8026
|
+
};
|
|
7527
8027
|
}
|
|
7528
|
-
function
|
|
7529
|
-
const
|
|
7530
|
-
const
|
|
7531
|
-
|
|
7532
|
-
|
|
7533
|
-
|
|
7534
|
-
|
|
7535
|
-
|
|
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
|
|
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 >=
|
|
7643
|
-
if (score >=
|
|
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
|
-
|
|
7649
|
-
|
|
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
|
-
|
|
7654
|
-
"classDef
|
|
7655
|
-
|
|
7656
|
-
"classDef
|
|
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 =
|
|
7743
|
-
const relSolutionPath =
|
|
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 =
|
|
7750
|
-
projectPaths.add(
|
|
8271
|
+
const absProjectPath = path24.resolve(dir, projectRelative.replace(/\\/g, "/"));
|
|
8272
|
+
projectPaths.add(path24.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
|
|
7751
8273
|
}
|
|
7752
|
-
const solutionName =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
8893
|
+
const vibgrateDir = path24.join(rootDir, ".vibgrate");
|
|
8294
8894
|
await ensureDir(vibgrateDir);
|
|
8295
|
-
await writeJsonFile(
|
|
8296
|
-
await writeJsonFile(
|
|
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 =
|
|
8318
|
-
const projectVibgrateDir =
|
|
8917
|
+
const projectDir = path24.resolve(rootDir, project.path);
|
|
8918
|
+
const projectVibgrateDir = path24.join(projectDir, ".vibgrate");
|
|
8319
8919
|
await ensureDir(projectVibgrateDir);
|
|
8320
|
-
await writeJsonFile(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
8371
|
-
let name =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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);
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
baselineCommand
|
|
4
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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