@vibgrate/cli 1.0.64 → 1.0.65
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-WRLXEJGA.js} +2 -2
- package/dist/{chunk-GCMUJKM7.js → chunk-VGZRUFB4.js} +1 -1
- package/dist/{chunk-IQ4T2HLG.js → chunk-XDZGC6KJ.js} +466 -33
- 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
|
|
|
@@ -1251,7 +1251,7 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
|
|
|
1251
1251
|
});
|
|
1252
1252
|
|
|
1253
1253
|
// src/commands/scan.ts
|
|
1254
|
-
import * as
|
|
1254
|
+
import * as path24 from "path";
|
|
1255
1255
|
import { Command as Command3 } from "commander";
|
|
1256
1256
|
import chalk6 from "chalk";
|
|
1257
1257
|
|
|
@@ -7446,6 +7446,355 @@ function isUsefulString(s) {
|
|
|
7446
7446
|
return true;
|
|
7447
7447
|
}
|
|
7448
7448
|
|
|
7449
|
+
// src/scanners/requirements-scanners.ts
|
|
7450
|
+
import * as path23 from "path";
|
|
7451
|
+
var MAX_SCAN_BYTES = 75e4;
|
|
7452
|
+
function uniq(values) {
|
|
7453
|
+
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
7454
|
+
}
|
|
7455
|
+
function pushMatch(matches, value, sourceFile) {
|
|
7456
|
+
if (!value) return;
|
|
7457
|
+
const normalized = value.trim();
|
|
7458
|
+
if (!normalized) return;
|
|
7459
|
+
matches.push(`${normalized} (${sourceFile})`);
|
|
7460
|
+
}
|
|
7461
|
+
function extract(content, pattern, sourceFile) {
|
|
7462
|
+
const out = [];
|
|
7463
|
+
let match;
|
|
7464
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
7465
|
+
const captured = match.slice(1).find((v) => typeof v === "string" && v.length > 0);
|
|
7466
|
+
pushMatch(out, captured ?? match[0], sourceFile);
|
|
7467
|
+
}
|
|
7468
|
+
return out;
|
|
7469
|
+
}
|
|
7470
|
+
function detectDbBrand(raw) {
|
|
7471
|
+
const value = raw.toLowerCase();
|
|
7472
|
+
if (value.includes("postgres")) return { kind: "sql", brand: "PostgreSQL", version: null, evidence: raw };
|
|
7473
|
+
if (value.includes("mysql") || value.includes("mariadb")) return { kind: "sql", brand: "MySQL/MariaDB", version: null, evidence: raw };
|
|
7474
|
+
if (value.includes("mssql") || value.includes("sqlserver")) return { kind: "sql", brand: "SQL Server", version: null, evidence: raw };
|
|
7475
|
+
if (value.includes("oracle")) return { kind: "sql", brand: "Oracle", version: null, evidence: raw };
|
|
7476
|
+
if (value.includes("sqlite")) return { kind: "sql", brand: "SQLite", version: null, evidence: raw };
|
|
7477
|
+
if (value.includes("mongodb")) return { kind: "nosql", brand: "MongoDB", version: null, evidence: raw };
|
|
7478
|
+
if (value.includes("redis")) return { kind: "nosql", brand: "Redis", version: null, evidence: raw };
|
|
7479
|
+
if (value.includes("cassandra")) return { kind: "nosql", brand: "Cassandra", version: null, evidence: raw };
|
|
7480
|
+
if (value.includes("dynamodb")) return { kind: "nosql", brand: "DynamoDB", version: null, evidence: raw };
|
|
7481
|
+
if (value.includes("neo4j")) return { kind: "nosql", brand: "Neo4j", version: null, evidence: raw };
|
|
7482
|
+
return null;
|
|
7483
|
+
}
|
|
7484
|
+
async function scanTextFiles(rootDir, fileCache) {
|
|
7485
|
+
const entries = await fileCache.walkDir(rootDir);
|
|
7486
|
+
const files = entries.filter((entry) => entry.isFile).map((entry) => entry.absPath);
|
|
7487
|
+
const out = [];
|
|
7488
|
+
for (const file of files) {
|
|
7489
|
+
try {
|
|
7490
|
+
const relPath = path23.relative(rootDir, file).replace(/\\/g, "/");
|
|
7491
|
+
const content = await fileCache.readTextFile(file);
|
|
7492
|
+
if (!content || content.length > MAX_SCAN_BYTES) continue;
|
|
7493
|
+
out.push({ relPath, content });
|
|
7494
|
+
} catch {
|
|
7495
|
+
}
|
|
7496
|
+
}
|
|
7497
|
+
return out;
|
|
7498
|
+
}
|
|
7499
|
+
async function scanRuntimeConfiguration(rootDir, fileCache) {
|
|
7500
|
+
const files = await scanTextFiles(rootDir, fileCache);
|
|
7501
|
+
const result = {
|
|
7502
|
+
environmentVariables: [],
|
|
7503
|
+
featureFlags: [],
|
|
7504
|
+
hiddenConfigFiles: [],
|
|
7505
|
+
dotEnvFiles: [],
|
|
7506
|
+
secretsInjectionPaths: [],
|
|
7507
|
+
containerEntrypoints: [],
|
|
7508
|
+
startupArguments: [],
|
|
7509
|
+
jvmFlags: [],
|
|
7510
|
+
threadPoolSettings: []
|
|
7511
|
+
};
|
|
7512
|
+
for (const file of files) {
|
|
7513
|
+
if (file.relPath.split("/").some((segment) => segment.startsWith("."))) {
|
|
7514
|
+
result.hiddenConfigFiles.push(file.relPath);
|
|
7515
|
+
}
|
|
7516
|
+
if (/\.env(\.|$)/i.test(file.relPath)) {
|
|
7517
|
+
result.dotEnvFiles.push(file.relPath);
|
|
7518
|
+
}
|
|
7519
|
+
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));
|
|
7520
|
+
result.featureFlags.push(...extract(file.content, /\b(?:feature[_-]?flag|ff_|is[A-Z][A-Za-z0-9]+Enabled|launchdarkly|unleash)\b/gi, file.relPath));
|
|
7521
|
+
result.secretsInjectionPaths.push(...extract(file.content, /\b(?:vault|secrets?\/(?:mount|inject|path)|aws\/secretsmanager|gcp\/secretmanager)\b/gi, file.relPath));
|
|
7522
|
+
result.containerEntrypoints.push(...extract(file.content, /\b(?:ENTRYPOINT|CMD)\s+([^\n]+)/g, file.relPath));
|
|
7523
|
+
result.startupArguments.push(...extract(file.content, /\b(?:--[a-z0-9-]+=?[\w:/.-]*)/gi, file.relPath));
|
|
7524
|
+
result.jvmFlags.push(...extract(file.content, /\b(-Xmx\S+|-Xms\S+|-XX:[^\s'"\]]+)/g, file.relPath));
|
|
7525
|
+
result.threadPoolSettings.push(...extract(file.content, /\b(?:thread[_-]?pool|max[_-]?threads|min[_-]?threads|worker[_-]?threads)\b[^\n]*/gi, file.relPath));
|
|
7526
|
+
}
|
|
7527
|
+
result.environmentVariables = uniq(result.environmentVariables);
|
|
7528
|
+
result.featureFlags = uniq(result.featureFlags);
|
|
7529
|
+
result.hiddenConfigFiles = uniq(result.hiddenConfigFiles);
|
|
7530
|
+
result.dotEnvFiles = uniq(result.dotEnvFiles);
|
|
7531
|
+
result.secretsInjectionPaths = uniq(result.secretsInjectionPaths);
|
|
7532
|
+
result.containerEntrypoints = uniq(result.containerEntrypoints);
|
|
7533
|
+
result.startupArguments = uniq(result.startupArguments).slice(0, 250);
|
|
7534
|
+
result.jvmFlags = uniq(result.jvmFlags);
|
|
7535
|
+
result.threadPoolSettings = uniq(result.threadPoolSettings);
|
|
7536
|
+
return result;
|
|
7537
|
+
}
|
|
7538
|
+
async function scanDataStores(rootDir, fileCache) {
|
|
7539
|
+
const files = await scanTextFiles(rootDir, fileCache);
|
|
7540
|
+
const dbTechnologies = [];
|
|
7541
|
+
const result = {
|
|
7542
|
+
databaseTechnologies: [],
|
|
7543
|
+
connectionStrings: [],
|
|
7544
|
+
connectionPoolSettings: [],
|
|
7545
|
+
replicationSettings: [],
|
|
7546
|
+
readReplicaSettings: [],
|
|
7547
|
+
failoverSettings: [],
|
|
7548
|
+
collationAndEncoding: [],
|
|
7549
|
+
queryTimeoutDefaults: [],
|
|
7550
|
+
manualIndexes: [],
|
|
7551
|
+
tables: [],
|
|
7552
|
+
views: [],
|
|
7553
|
+
storedProcedures: [],
|
|
7554
|
+
triggers: [],
|
|
7555
|
+
rowLevelSecurityPolicies: [],
|
|
7556
|
+
otherServices: []
|
|
7557
|
+
};
|
|
7558
|
+
for (const file of files) {
|
|
7559
|
+
const connStrings = extract(file.content, /\b(?:postgres(?:ql)?:\/\/[^\s'"`]+|mysql:\/\/[^\s'"`]+|mongodb(?:\+srv)?:\/\/[^\s'"`]+|redis:\/\/[^\s'"`]+)\b/gi, file.relPath);
|
|
7560
|
+
result.connectionStrings.push(...connStrings);
|
|
7561
|
+
const dbEvidence = [
|
|
7562
|
+
...extract(file.content, /\b(?:postgres|postgresql|mysql|mariadb|mssql|sqlserver|oracle|sqlite|mongodb|redis|cassandra|dynamodb|neo4j)\b/gi, file.relPath),
|
|
7563
|
+
...connStrings
|
|
7564
|
+
];
|
|
7565
|
+
for (const evidence of dbEvidence) {
|
|
7566
|
+
const detected = detectDbBrand(evidence);
|
|
7567
|
+
if (detected) dbTechnologies.push(detected);
|
|
7568
|
+
}
|
|
7569
|
+
result.connectionPoolSettings.push(...extract(file.content, /\b(?:pool(?:Size|_size)?|maxPoolSize|minPoolSize|connectionLimit)\b[^\n]*/gi, file.relPath));
|
|
7570
|
+
result.replicationSettings.push(...extract(file.content, /\b(?:replication|cluster|replicaSet)\b[^\n]*/gi, file.relPath));
|
|
7571
|
+
result.readReplicaSettings.push(...extract(file.content, /\b(?:read[_-]?replica|reader[_-]?endpoint)\b[^\n]*/gi, file.relPath));
|
|
7572
|
+
result.failoverSettings.push(...extract(file.content, /\b(?:failover|multi[_-]?az|secondary[_-]?host)\b[^\n]*/gi, file.relPath));
|
|
7573
|
+
result.collationAndEncoding.push(...extract(file.content, /\b(?:collation|charset|encoding|utf-?8)\b[^\n]*/gi, file.relPath));
|
|
7574
|
+
result.queryTimeoutDefaults.push(...extract(file.content, /\b(?:statement_timeout|query[_-]?timeout|socketTimeout|commandTimeout)\b[^\n]*/gi, file.relPath));
|
|
7575
|
+
result.manualIndexes.push(...extract(file.content, /\bcreate\s+(?:unique\s+)?index\s+[^;\n]+/gi, file.relPath));
|
|
7576
|
+
result.tables.push(...extract(file.content, /\bcreate\s+table\s+([a-zA-Z0-9_."]+)/gi, file.relPath));
|
|
7577
|
+
result.views.push(...extract(file.content, /\bcreate\s+view\s+([a-zA-Z0-9_."]+)/gi, file.relPath));
|
|
7578
|
+
result.storedProcedures.push(...extract(file.content, /\bcreate\s+(?:or\s+replace\s+)?procedure\s+([a-zA-Z0-9_."]+)/gi, file.relPath));
|
|
7579
|
+
result.triggers.push(...extract(file.content, /\bcreate\s+trigger\s+([a-zA-Z0-9_."]+)/gi, file.relPath));
|
|
7580
|
+
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));
|
|
7581
|
+
result.otherServices.push(...extract(file.content, /\b(?:redis:\/\/[^\s'"`]+|amqp:\/\/[^\s'"`]+|kafka:\/\/[^\s'"`]+|elasticsearch:\/\/[^\s'"`]+)\b/gi, file.relPath));
|
|
7582
|
+
}
|
|
7583
|
+
const dedupDb = /* @__PURE__ */ new Map();
|
|
7584
|
+
for (const db of dbTechnologies) dedupDb.set(`${db.kind}:${db.brand}:${db.evidence}`, db);
|
|
7585
|
+
result.databaseTechnologies = [...dedupDb.values()].sort((a, b) => a.brand.localeCompare(b.brand));
|
|
7586
|
+
result.connectionStrings = uniq(result.connectionStrings);
|
|
7587
|
+
result.connectionPoolSettings = uniq(result.connectionPoolSettings);
|
|
7588
|
+
result.replicationSettings = uniq(result.replicationSettings);
|
|
7589
|
+
result.readReplicaSettings = uniq(result.readReplicaSettings);
|
|
7590
|
+
result.failoverSettings = uniq(result.failoverSettings);
|
|
7591
|
+
result.collationAndEncoding = uniq(result.collationAndEncoding);
|
|
7592
|
+
result.queryTimeoutDefaults = uniq(result.queryTimeoutDefaults);
|
|
7593
|
+
result.manualIndexes = uniq(result.manualIndexes);
|
|
7594
|
+
result.tables = uniq(result.tables);
|
|
7595
|
+
result.views = uniq(result.views);
|
|
7596
|
+
result.storedProcedures = uniq(result.storedProcedures);
|
|
7597
|
+
result.triggers = uniq(result.triggers);
|
|
7598
|
+
result.rowLevelSecurityPolicies = uniq(result.rowLevelSecurityPolicies);
|
|
7599
|
+
result.otherServices = uniq(result.otherServices);
|
|
7600
|
+
return result;
|
|
7601
|
+
}
|
|
7602
|
+
function detectOpenApiSpecification(relPath, content) {
|
|
7603
|
+
const lowerPath = relPath.toLowerCase();
|
|
7604
|
+
const format = lowerPath.endsWith(".json") ? "json" : lowerPath.endsWith(".yaml") ? "yaml" : lowerPath.endsWith(".yml") ? "yml" : null;
|
|
7605
|
+
if (!format) return null;
|
|
7606
|
+
const hasOpenApi = /\bopenapi\s*[:=]\s*['"]?3\./i.test(content) || /\bswagger\s*[:=]\s*['"]?2\.0/i.test(content) || /"openapi"\s*:\s*"3\./i.test(content);
|
|
7607
|
+
const likelyName = /(openapi|swagger).*(\.ya?ml|\.json)$/i.test(lowerPath) || /\/api-docs\b/i.test(lowerPath);
|
|
7608
|
+
if (!hasOpenApi && !likelyName) return null;
|
|
7609
|
+
const version = content.match(/\bopenapi\s*[:=]\s*['"]?([^'"\s,]+)/i)?.[1] ?? content.match(/\bswagger\s*[:=]\s*['"]?([^'"\s,]+)/i)?.[1] ?? null;
|
|
7610
|
+
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;
|
|
7611
|
+
const endpointCount = content.match(/(^|\n)\s*\/[^\n:]+:\s*(get|post|put|patch|delete|options|head)/gim)?.length ?? content.match(/"\/[^"]+"\s*:\s*\{/g)?.length ?? null;
|
|
7612
|
+
return { path: relPath, format, version, title, endpointCount };
|
|
7613
|
+
}
|
|
7614
|
+
async function scanApiSurface(rootDir, fileCache) {
|
|
7615
|
+
const files = await scanTextFiles(rootDir, fileCache);
|
|
7616
|
+
const integrations = [];
|
|
7617
|
+
const result = {
|
|
7618
|
+
integrations: [],
|
|
7619
|
+
openApiSpecifications: [],
|
|
7620
|
+
webhookUrls: [],
|
|
7621
|
+
callbackEndpoints: [],
|
|
7622
|
+
apiVersionPins: [],
|
|
7623
|
+
tokenExpirationPolicies: [],
|
|
7624
|
+
rateLimitOverrides: [],
|
|
7625
|
+
customHeaders: [],
|
|
7626
|
+
corsPolicies: [],
|
|
7627
|
+
oauthScopes: [],
|
|
7628
|
+
apiTokens: []
|
|
7629
|
+
};
|
|
7630
|
+
for (const file of files) {
|
|
7631
|
+
const openApiSpec = detectOpenApiSpecification(file.relPath, file.content);
|
|
7632
|
+
if (openApiSpec) result.openApiSpecifications.push(openApiSpec);
|
|
7633
|
+
const endpoints = extract(file.content, /\bhttps?:\/\/[^\s'"`]+/gi, file.relPath);
|
|
7634
|
+
for (const endpoint of endpoints) {
|
|
7635
|
+
const provider = endpoint.replace(/^https?:\/\//, "").split("/")[0] ?? "unknown";
|
|
7636
|
+
const versionMatch = endpoint.match(/\/(v\d+(?:\.\d+)?)\b/i);
|
|
7637
|
+
const params = endpoint.match(/[?&]([a-zA-Z0-9_.-]+)=/g)?.map((p) => p.replace(/[?&=]/g, "")) ?? [];
|
|
7638
|
+
integrations.push({
|
|
7639
|
+
provider,
|
|
7640
|
+
endpoint,
|
|
7641
|
+
version: versionMatch ? versionMatch[1] : null,
|
|
7642
|
+
parameters: params,
|
|
7643
|
+
configOptions: extract(file.content, /\b(?:baseUrl|apiUrl|endpoint|timeout|retries|apiVersion)\b[^\n]*/gi, file.relPath),
|
|
7644
|
+
authHints: extract(file.content, /\b(?:api[_-]?key|bearer\s+[a-z0-9_.-]+|client[_-]?secret|oauth)\b[^\n]*/gi, file.relPath)
|
|
7645
|
+
});
|
|
7646
|
+
}
|
|
7647
|
+
result.webhookUrls.push(...extract(file.content, /\bhttps?:\/\/[^\s'"`]*(?:webhook|hooks?)[^\s'"`]*/gi, file.relPath));
|
|
7648
|
+
result.callbackEndpoints.push(...extract(file.content, /\bhttps?:\/\/[^\s'"`]*(?:callback|redirect_uri)[^\s'"`]*/gi, file.relPath));
|
|
7649
|
+
result.apiVersionPins.push(...extract(file.content, /\bapi[_-]?version\b[^\n]*/gi, file.relPath));
|
|
7650
|
+
result.tokenExpirationPolicies.push(...extract(file.content, /\b(?:token[_-]?expiry|expires[_-]?in|refresh[_-]?token[_-]?ttl)\b[^\n]*/gi, file.relPath));
|
|
7651
|
+
result.rateLimitOverrides.push(...extract(file.content, /\b(?:rate[_-]?limit|max[_-]?requests|throttle)\b[^\n]*/gi, file.relPath));
|
|
7652
|
+
result.customHeaders.push(...extract(file.content, /\b(?:x-[a-z0-9-]+|authorization|user-agent)\b[^\n]*/gi, file.relPath));
|
|
7653
|
+
result.corsPolicies.push(...extract(file.content, /\b(?:cors|access-control-allow-origin|allowedOrigins)\b[^\n]*/gi, file.relPath));
|
|
7654
|
+
result.oauthScopes.push(...extract(file.content, /\b(?:scope|scopes)\b[^\n]*(?:openid|profile|email|read|write)/gi, file.relPath));
|
|
7655
|
+
result.apiTokens.push(...extract(file.content, /\b(?:api[_-]?token|access[_-]?token|bearer[_-]?token)\b[^\n]*/gi, file.relPath));
|
|
7656
|
+
}
|
|
7657
|
+
const integrationMap = /* @__PURE__ */ new Map();
|
|
7658
|
+
for (const integration of integrations) {
|
|
7659
|
+
const key = `${integration.provider}:${integration.endpoint}`;
|
|
7660
|
+
integrationMap.set(key, {
|
|
7661
|
+
...integration,
|
|
7662
|
+
parameters: uniq(integration.parameters),
|
|
7663
|
+
configOptions: uniq(integration.configOptions),
|
|
7664
|
+
authHints: uniq(integration.authHints)
|
|
7665
|
+
});
|
|
7666
|
+
}
|
|
7667
|
+
result.integrations = [...integrationMap.values()].sort((a, b) => a.provider.localeCompare(b.provider));
|
|
7668
|
+
result.openApiSpecifications = [...new Map(result.openApiSpecifications.map((spec) => [spec.path, spec])).values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
7669
|
+
result.webhookUrls = uniq(result.webhookUrls);
|
|
7670
|
+
result.callbackEndpoints = uniq(result.callbackEndpoints);
|
|
7671
|
+
result.apiVersionPins = uniq(result.apiVersionPins);
|
|
7672
|
+
result.tokenExpirationPolicies = uniq(result.tokenExpirationPolicies);
|
|
7673
|
+
result.rateLimitOverrides = uniq(result.rateLimitOverrides);
|
|
7674
|
+
result.customHeaders = uniq(result.customHeaders);
|
|
7675
|
+
result.corsPolicies = uniq(result.corsPolicies);
|
|
7676
|
+
result.oauthScopes = uniq(result.oauthScopes);
|
|
7677
|
+
result.apiTokens = uniq(result.apiTokens);
|
|
7678
|
+
return result;
|
|
7679
|
+
}
|
|
7680
|
+
async function scanOperationalResilience(rootDir, fileCache) {
|
|
7681
|
+
const files = await scanTextFiles(rootDir, fileCache);
|
|
7682
|
+
const result = {
|
|
7683
|
+
implicitTimeouts: [],
|
|
7684
|
+
defaultPaginationSize: [],
|
|
7685
|
+
implicitRetryLogic: [],
|
|
7686
|
+
defaultLocale: [],
|
|
7687
|
+
defaultCurrency: [],
|
|
7688
|
+
implicitTimezone: [],
|
|
7689
|
+
defaultCharacterEncoding: [],
|
|
7690
|
+
sessionStores: [],
|
|
7691
|
+
distributedLocks: [],
|
|
7692
|
+
jobSchedulers: [],
|
|
7693
|
+
idempotencyKeys: [],
|
|
7694
|
+
rateLimitingCounters: [],
|
|
7695
|
+
circuitBreakerState: [],
|
|
7696
|
+
abTestToggles: [],
|
|
7697
|
+
regionalEnablementRules: [],
|
|
7698
|
+
betaAccessGroups: [],
|
|
7699
|
+
licensingEnforcementLogic: [],
|
|
7700
|
+
killSwitches: [],
|
|
7701
|
+
connectorRetryLogic: [],
|
|
7702
|
+
apiPollingIntervals: [],
|
|
7703
|
+
fieldMappings: [],
|
|
7704
|
+
schemaRegistryRules: [],
|
|
7705
|
+
deadLetterQueueBehavior: [],
|
|
7706
|
+
dataMaskingRules: [],
|
|
7707
|
+
transformationLogic: [],
|
|
7708
|
+
timezoneHandling: [],
|
|
7709
|
+
encryptionSettings: [],
|
|
7710
|
+
hardcodedSecretSignals: []
|
|
7711
|
+
};
|
|
7712
|
+
for (const file of files) {
|
|
7713
|
+
result.implicitTimeouts.push(...extract(file.content, /\b(?:timeout|requestTimeout|connectTimeout|readTimeout)\b[^\n]*/gi, file.relPath));
|
|
7714
|
+
result.defaultPaginationSize.push(...extract(file.content, /\b(?:pageSize|limit|perPage|defaultPageSize)\b[^\n]*/gi, file.relPath));
|
|
7715
|
+
result.implicitRetryLogic.push(...extract(file.content, /\b(?:retry|backoff|maxRetries)\b[^\n]*/gi, file.relPath));
|
|
7716
|
+
result.defaultLocale.push(...extract(file.content, /\b(?:defaultLocale|locale\s*[:=]|en-US|en_GB)\b[^\n]*/gi, file.relPath));
|
|
7717
|
+
result.defaultCurrency.push(...extract(file.content, /\b(?:defaultCurrency|currency\s*[:=]|USD|EUR|GBP)\b[^\n]*/gi, file.relPath));
|
|
7718
|
+
result.implicitTimezone.push(...extract(file.content, /\b(?:timezone|timeZone|UTC|GMT)\b[^\n]*/gi, file.relPath));
|
|
7719
|
+
result.defaultCharacterEncoding.push(...extract(file.content, /\b(?:charset|encoding|UTF-?8|ISO-8859-1)\b[^\n]*/gi, file.relPath));
|
|
7720
|
+
result.sessionStores.push(...extract(file.content, /\b(?:sessionStore|redisStore|memoryStore)\b[^\n]*/gi, file.relPath));
|
|
7721
|
+
result.distributedLocks.push(...extract(file.content, /\b(?:distributed[_-]?lock|redlock|mutex)\b[^\n]*/gi, file.relPath));
|
|
7722
|
+
result.jobSchedulers.push(...extract(file.content, /\b(?:cron|schedule|bullmq|agenda|job[_-]?scheduler)\b[^\n]*/gi, file.relPath));
|
|
7723
|
+
result.idempotencyKeys.push(...extract(file.content, /\b(?:idempotency[_-]?key|Idempotency-Key)\b[^\n]*/gi, file.relPath));
|
|
7724
|
+
result.rateLimitingCounters.push(...extract(file.content, /\b(?:rate[_-]?limit|throttle|quota)\b[^\n]*/gi, file.relPath));
|
|
7725
|
+
result.circuitBreakerState.push(...extract(file.content, /\b(?:circuit[_-]?breaker|half[_-]?open|open[_-]?state)\b[^\n]*/gi, file.relPath));
|
|
7726
|
+
result.abTestToggles.push(...extract(file.content, /\b(?:a\/b|ab[_-]?test|experiment[_-]?flag)\b[^\n]*/gi, file.relPath));
|
|
7727
|
+
result.regionalEnablementRules.push(...extract(file.content, /\b(?:region[_-]?enabled|geo[_-]?fence|country[_-]?allow)\b[^\n]*/gi, file.relPath));
|
|
7728
|
+
result.betaAccessGroups.push(...extract(file.content, /\b(?:beta[_-]?users?|early[_-]?access|allowlist)\b[^\n]*/gi, file.relPath));
|
|
7729
|
+
result.licensingEnforcementLogic.push(...extract(file.content, /\b(?:license[_-]?key|entitlement|plan[_-]?check)\b[^\n]*/gi, file.relPath));
|
|
7730
|
+
result.killSwitches.push(...extract(file.content, /\b(?:kill[_-]?switch|disable[_-]?all|emergency[_-]?stop)\b[^\n]*/gi, file.relPath));
|
|
7731
|
+
result.connectorRetryLogic.push(...extract(file.content, /\b(?:connector|integration)\b[^\n]*(?:retry|backoff)/gi, file.relPath));
|
|
7732
|
+
result.apiPollingIntervals.push(...extract(file.content, /\b(?:poll(?:ing)?[_-]?interval|sync[_-]?interval)\b[^\n]*/gi, file.relPath));
|
|
7733
|
+
result.fieldMappings.push(...extract(file.content, /\b(?:field[_-]?mapping|mapFields?|column[_-]?map)\b[^\n]*/gi, file.relPath));
|
|
7734
|
+
result.schemaRegistryRules.push(...extract(file.content, /\b(?:schema[_-]?registry|avro|protobuf)\b[^\n]*/gi, file.relPath));
|
|
7735
|
+
result.deadLetterQueueBehavior.push(...extract(file.content, /\b(?:dead[_-]?letter|dlq)\b[^\n]*/gi, file.relPath));
|
|
7736
|
+
result.dataMaskingRules.push(...extract(file.content, /\b(?:data[_-]?mask|redact|pii[_-]?mask)\b[^\n]*/gi, file.relPath));
|
|
7737
|
+
result.transformationLogic.push(...extract(file.content, /\b(?:transform|mapper|normaliz(?:e|ation))\b[^\n]*/gi, file.relPath));
|
|
7738
|
+
result.timezoneHandling.push(...extract(file.content, /\b(?:convertTimezone|tz\(|moment\.tz|DateTimeZone)\b[^\n]*/gi, file.relPath));
|
|
7739
|
+
result.encryptionSettings.push(...extract(file.content, /\b(?:kms|encrypt(?:ion)?|cipher|tls|minTlsVersion)\b[^\n]*/gi, file.relPath));
|
|
7740
|
+
result.hardcodedSecretSignals.push(...extract(file.content, /\b(?:password|passwd|connectionString|api[_-]?key|secret)\b\s*[:=]\s*['"][^'"]{4,}['"]/gi, file.relPath));
|
|
7741
|
+
}
|
|
7742
|
+
Object.keys(result).forEach((key) => {
|
|
7743
|
+
result[key] = uniq(result[key]);
|
|
7744
|
+
});
|
|
7745
|
+
return result;
|
|
7746
|
+
}
|
|
7747
|
+
async function scanAssetBranding(rootDir, fileCache) {
|
|
7748
|
+
const entries = await fileCache.walkDir(rootDir);
|
|
7749
|
+
const faviconCandidates = entries.filter((entry) => entry.isFile && /favicon/i.test(entry.name));
|
|
7750
|
+
const logoCandidates = entries.filter((entry) => entry.isFile && /logo/i.test(entry.name));
|
|
7751
|
+
const faviconFiles = [];
|
|
7752
|
+
for (const entry of faviconCandidates.slice(0, 10)) {
|
|
7753
|
+
try {
|
|
7754
|
+
const content = await fileCache.readTextFile(entry.absPath);
|
|
7755
|
+
if (!content) continue;
|
|
7756
|
+
const encoded = Buffer.from(content).toString("base64");
|
|
7757
|
+
faviconFiles.push({
|
|
7758
|
+
path: entry.relPath,
|
|
7759
|
+
base64: encoded.slice(0, 1e4)
|
|
7760
|
+
});
|
|
7761
|
+
} catch {
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
return {
|
|
7765
|
+
faviconFiles,
|
|
7766
|
+
productLogos: uniq(logoCandidates.map((entry) => entry.relPath))
|
|
7767
|
+
};
|
|
7768
|
+
}
|
|
7769
|
+
async function scanOssGovernance(rootDir, fileCache) {
|
|
7770
|
+
const files = await scanTextFiles(rootDir, fileCache);
|
|
7771
|
+
const directDeps = /* @__PURE__ */ new Set();
|
|
7772
|
+
const transitiveDeps = /* @__PURE__ */ new Set();
|
|
7773
|
+
const vulns = [];
|
|
7774
|
+
const licenseRisks = [];
|
|
7775
|
+
for (const file of files) {
|
|
7776
|
+
if (/package(-lock)?\.json$|pnpm-lock\.yaml$|poetry\.lock$|pom\.xml$/i.test(file.relPath)) {
|
|
7777
|
+
const depMatches = file.content.match(/"([@a-zA-Z0-9_.\/-]+)"\s*:/g) ?? [];
|
|
7778
|
+
depMatches.forEach((match) => transitiveDeps.add(match.replace(/["\s:]/g, "")));
|
|
7779
|
+
}
|
|
7780
|
+
if (/package\.json$/i.test(file.relPath)) {
|
|
7781
|
+
const depMatches = file.content.match(/"([@a-zA-Z0-9_.\/-]+)"\s*:\s*"[~^0-9*><=.-]+"/g) ?? [];
|
|
7782
|
+
depMatches.forEach((match) => {
|
|
7783
|
+
const pkg2 = match.split(":")[0]?.replace(/"/g, "").trim();
|
|
7784
|
+
if (pkg2) directDeps.add(pkg2);
|
|
7785
|
+
});
|
|
7786
|
+
}
|
|
7787
|
+
vulns.push(...extract(file.content, /\b(?:CVE-\d{4}-\d{4,}|critical vulnerability|high severity)\b[^\n]*/gi, file.relPath));
|
|
7788
|
+
licenseRisks.push(...extract(file.content, /\b(?:GPL-3\.0|AGPL|SSPL|BUSL|source[- ]available)\b[^\n]*/gi, file.relPath));
|
|
7789
|
+
}
|
|
7790
|
+
return {
|
|
7791
|
+
directDependencies: directDeps.size,
|
|
7792
|
+
transitiveDependencies: Math.max(transitiveDeps.size, directDeps.size),
|
|
7793
|
+
knownVulnerabilities: uniq(vulns),
|
|
7794
|
+
licenseRisks: uniq(licenseRisks)
|
|
7795
|
+
};
|
|
7796
|
+
}
|
|
7797
|
+
|
|
7449
7798
|
// src/utils/compact-evidence.ts
|
|
7450
7799
|
var CATEGORY_PATTERNS = [
|
|
7451
7800
|
{ category: "pricing", pattern: /price|pricing|billing|subscri|trial|credit|plan|tier|upgrade|premium|pro|enterprise/i },
|
|
@@ -7639,21 +7988,27 @@ function escapeLabel(input) {
|
|
|
7639
7988
|
}
|
|
7640
7989
|
function scoreClass(score) {
|
|
7641
7990
|
if (score === void 0 || Number.isNaN(score)) return "scoreUnknown";
|
|
7642
|
-
if (score >=
|
|
7643
|
-
if (score >=
|
|
7991
|
+
if (score >= 80) return "scoreHigh";
|
|
7992
|
+
if (score >= 50) return "scoreModerate";
|
|
7644
7993
|
return "scoreLow";
|
|
7645
7994
|
}
|
|
7646
7995
|
function nodeLabel(project) {
|
|
7647
7996
|
const score = project.drift?.score;
|
|
7648
|
-
|
|
7649
|
-
|
|
7997
|
+
if (typeof score === "number") {
|
|
7998
|
+
return `${project.name} (${score})`;
|
|
7999
|
+
}
|
|
8000
|
+
return project.name;
|
|
7650
8001
|
}
|
|
7651
8002
|
function buildDefs() {
|
|
7652
8003
|
return [
|
|
7653
|
-
|
|
7654
|
-
"classDef
|
|
7655
|
-
|
|
7656
|
-
"classDef
|
|
8004
|
+
// Emerald border for high scores (>= 80) - border-emerald-500
|
|
8005
|
+
"classDef scoreHigh fill:#1e293b,stroke:#10b981,color:#f1f5f9,stroke-width:2px",
|
|
8006
|
+
// Amber border for moderate scores (50-79) - border-amber-500
|
|
8007
|
+
"classDef scoreModerate fill:#1e293b,stroke:#f59e0b,color:#f1f5f9,stroke-width:2px",
|
|
8008
|
+
// Red border for low scores (< 50) - border-red-500
|
|
8009
|
+
"classDef scoreLow fill:#1e293b,stroke:#ef4444,color:#f1f5f9,stroke-width:2px",
|
|
8010
|
+
// Slate border for unknown scores
|
|
8011
|
+
"classDef scoreUnknown fill:#1e293b,stroke:#64748b,color:#94a3b8,stroke-width:2px"
|
|
7657
8012
|
];
|
|
7658
8013
|
}
|
|
7659
8014
|
function generateWorkspaceRelationshipMermaid(projects) {
|
|
@@ -7739,17 +8094,17 @@ async function discoverSolutions(rootDir, fileCache) {
|
|
|
7739
8094
|
for (const solutionFile of solutionFiles) {
|
|
7740
8095
|
try {
|
|
7741
8096
|
const content = await fileCache.readTextFile(solutionFile);
|
|
7742
|
-
const dir =
|
|
7743
|
-
const relSolutionPath =
|
|
8097
|
+
const dir = path24.dirname(solutionFile);
|
|
8098
|
+
const relSolutionPath = path24.relative(rootDir, solutionFile).replace(/\\/g, "/");
|
|
7744
8099
|
const projectPaths = /* @__PURE__ */ new Set();
|
|
7745
8100
|
const projectRegex = /Project\("[^"]*"\)\s*=\s*"([^"]*)",\s*"([^"]+\.csproj)"/g;
|
|
7746
8101
|
let match;
|
|
7747
8102
|
while ((match = projectRegex.exec(content)) !== null) {
|
|
7748
8103
|
const projectRelative = match[2];
|
|
7749
|
-
const absProjectPath =
|
|
7750
|
-
projectPaths.add(
|
|
8104
|
+
const absProjectPath = path24.resolve(dir, projectRelative.replace(/\\/g, "/"));
|
|
8105
|
+
projectPaths.add(path24.relative(rootDir, absProjectPath).replace(/\\/g, "/"));
|
|
7751
8106
|
}
|
|
7752
|
-
const solutionName =
|
|
8107
|
+
const solutionName = path24.basename(solutionFile, path24.extname(solutionFile));
|
|
7753
8108
|
parsed.push({
|
|
7754
8109
|
path: relSolutionPath,
|
|
7755
8110
|
name: solutionName,
|
|
@@ -7792,7 +8147,13 @@ async function runScan(rootDir, opts) {
|
|
|
7792
8147
|
architecture: !maxPrivacyMode,
|
|
7793
8148
|
codeQuality: !maxPrivacyMode,
|
|
7794
8149
|
owaspCategoryMapping: !maxPrivacyMode,
|
|
7795
|
-
uiPurpose: !maxPrivacyMode
|
|
8150
|
+
uiPurpose: !maxPrivacyMode,
|
|
8151
|
+
runtimeConfiguration: !maxPrivacyMode,
|
|
8152
|
+
dataStores: true,
|
|
8153
|
+
apiSurface: !maxPrivacyMode,
|
|
8154
|
+
operationalResilience: !maxPrivacyMode,
|
|
8155
|
+
assetBranding: true,
|
|
8156
|
+
ossGovernance: true
|
|
7796
8157
|
};
|
|
7797
8158
|
let filesScanned = 0;
|
|
7798
8159
|
const progress = new ScanProgress(rootDir);
|
|
@@ -7821,7 +8182,13 @@ async function runScan(rootDir, opts) {
|
|
|
7821
8182
|
...scannerPolicy.architecture && scanners?.architecture?.enabled !== false ? [{ id: "architecture", label: "Architecture layers" }] : [],
|
|
7822
8183
|
...scannerPolicy.codeQuality && scanners?.codeQuality?.enabled !== false ? [{ id: "codequality", label: "Code quality metrics" }] : [],
|
|
7823
8184
|
...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" }] : []
|
|
8185
|
+
...!maxPrivacyMode && (opts.uiPurpose || scanners?.uiPurpose?.enabled === true) ? [{ id: "uipurpose", label: "UI purpose evidence" }] : [],
|
|
8186
|
+
...scannerPolicy.runtimeConfiguration && scanners?.runtimeConfiguration?.enabled !== false ? [{ id: "runtimecfg", label: "Runtime configuration" }] : [],
|
|
8187
|
+
...scannerPolicy.dataStores && scanners?.dataStores?.enabled !== false ? [{ id: "datastores", label: "Data stores & schema" }] : [],
|
|
8188
|
+
...scannerPolicy.apiSurface && scanners?.apiSurface?.enabled !== false ? [{ id: "apisurface", label: "API integrations" }] : [],
|
|
8189
|
+
...scannerPolicy.operationalResilience && scanners?.operationalResilience?.enabled !== false ? [{ id: "resilience", label: "Operational resilience" }] : [],
|
|
8190
|
+
...scannerPolicy.assetBranding && scanners?.assetBranding?.enabled !== false ? [{ id: "branding", label: "Branding assets" }] : [],
|
|
8191
|
+
...scannerPolicy.ossGovernance && scanners?.ossGovernance?.enabled !== false ? [{ id: "ossgov", label: "OSS governance" }] : []
|
|
7825
8192
|
] : [],
|
|
7826
8193
|
{ id: "drift", label: "Computing drift score" },
|
|
7827
8194
|
{ id: "findings", label: "Generating findings" }
|
|
@@ -7947,7 +8314,7 @@ async function runScan(rootDir, opts) {
|
|
|
7947
8314
|
project.drift = computeDriftScore([project]);
|
|
7948
8315
|
project.projectId = computeProjectId(project.path, project.name, workspaceId);
|
|
7949
8316
|
}
|
|
7950
|
-
const solutionsManifestPath =
|
|
8317
|
+
const solutionsManifestPath = path24.join(rootDir, ".vibgrate", "solutions.json");
|
|
7951
8318
|
const persistedSolutionIds = /* @__PURE__ */ new Map();
|
|
7952
8319
|
if (await pathExists(solutionsManifestPath)) {
|
|
7953
8320
|
try {
|
|
@@ -8124,6 +8491,66 @@ async function runScan(rootDir, opts) {
|
|
|
8124
8491
|
})
|
|
8125
8492
|
);
|
|
8126
8493
|
}
|
|
8494
|
+
if (scannerPolicy.runtimeConfiguration && scanners?.runtimeConfiguration?.enabled !== false) {
|
|
8495
|
+
progress.startStep("runtimecfg");
|
|
8496
|
+
scannerTasks.push(
|
|
8497
|
+
scanRuntimeConfiguration(rootDir, fileCache).then((result) => {
|
|
8498
|
+
extended.runtimeConfiguration = result;
|
|
8499
|
+
const count = result.environmentVariables.length + result.featureFlags.length + result.dotEnvFiles.length;
|
|
8500
|
+
progress.completeStep("runtimecfg", `${count} runtime signals`, count);
|
|
8501
|
+
})
|
|
8502
|
+
);
|
|
8503
|
+
}
|
|
8504
|
+
if (scannerPolicy.dataStores && scanners?.dataStores?.enabled !== false) {
|
|
8505
|
+
progress.startStep("datastores");
|
|
8506
|
+
scannerTasks.push(
|
|
8507
|
+
scanDataStores(rootDir, fileCache).then((result) => {
|
|
8508
|
+
extended.dataStores = result;
|
|
8509
|
+
const count = result.databaseTechnologies.length + result.tables.length + result.views.length;
|
|
8510
|
+
progress.completeStep("datastores", `${result.databaseTechnologies.length} engines \xB7 ${result.tables.length} tables`, count);
|
|
8511
|
+
})
|
|
8512
|
+
);
|
|
8513
|
+
}
|
|
8514
|
+
if (scannerPolicy.apiSurface && scanners?.apiSurface?.enabled !== false) {
|
|
8515
|
+
progress.startStep("apisurface");
|
|
8516
|
+
scannerTasks.push(
|
|
8517
|
+
scanApiSurface(rootDir, fileCache).then((result) => {
|
|
8518
|
+
extended.apiSurface = result;
|
|
8519
|
+
const count = result.integrations.length + result.webhookUrls.length;
|
|
8520
|
+
progress.completeStep("apisurface", `${result.integrations.length} integrations`, count);
|
|
8521
|
+
})
|
|
8522
|
+
);
|
|
8523
|
+
}
|
|
8524
|
+
if (scannerPolicy.operationalResilience && scanners?.operationalResilience?.enabled !== false) {
|
|
8525
|
+
progress.startStep("resilience");
|
|
8526
|
+
scannerTasks.push(
|
|
8527
|
+
scanOperationalResilience(rootDir, fileCache).then((result) => {
|
|
8528
|
+
extended.operationalResilience = result;
|
|
8529
|
+
const count = result.implicitTimeouts.length + result.implicitRetryLogic.length + result.killSwitches.length;
|
|
8530
|
+
progress.completeStep("resilience", `${count} resilience signals`, count);
|
|
8531
|
+
})
|
|
8532
|
+
);
|
|
8533
|
+
}
|
|
8534
|
+
if (scannerPolicy.assetBranding && scanners?.assetBranding?.enabled !== false) {
|
|
8535
|
+
progress.startStep("branding");
|
|
8536
|
+
scannerTasks.push(
|
|
8537
|
+
scanAssetBranding(rootDir, fileCache).then((result) => {
|
|
8538
|
+
extended.assetBranding = result;
|
|
8539
|
+
const count = result.faviconFiles.length + result.productLogos.length;
|
|
8540
|
+
progress.completeStep("branding", `${result.faviconFiles.length} favicons \xB7 ${result.productLogos.length} logos`, count);
|
|
8541
|
+
})
|
|
8542
|
+
);
|
|
8543
|
+
}
|
|
8544
|
+
if (scannerPolicy.ossGovernance && scanners?.ossGovernance?.enabled !== false) {
|
|
8545
|
+
progress.startStep("ossgov");
|
|
8546
|
+
scannerTasks.push(
|
|
8547
|
+
scanOssGovernance(rootDir, fileCache).then((result) => {
|
|
8548
|
+
extended.ossGovernance = result;
|
|
8549
|
+
const count = result.directDependencies + result.knownVulnerabilities.length + result.licenseRisks.length;
|
|
8550
|
+
progress.completeStep("ossgov", `${result.directDependencies} direct deps`, count);
|
|
8551
|
+
})
|
|
8552
|
+
);
|
|
8553
|
+
}
|
|
8127
8554
|
if (scannerPolicy.dependencyRisk && scanners?.dependencyRisk?.enabled !== false) {
|
|
8128
8555
|
progress.startStep("deprisk");
|
|
8129
8556
|
scannerTasks.push(
|
|
@@ -8164,7 +8591,7 @@ async function runScan(rootDir, opts) {
|
|
|
8164
8591
|
const summary = [`${up.topEvidence.length} evidence`, ...up.capped ? ["capped"] : []].join(" \xB7 ");
|
|
8165
8592
|
progress.completeStep("uipurpose", summary, up.topEvidence.length);
|
|
8166
8593
|
await Promise.all(allProjects.map(async (project) => {
|
|
8167
|
-
const projectDir =
|
|
8594
|
+
const projectDir = path24.join(rootDir, project.path);
|
|
8168
8595
|
const projectResult = await scanUiPurpose(projectDir, fileCache, 150);
|
|
8169
8596
|
if (projectResult.topEvidence.length > 0) {
|
|
8170
8597
|
project.uiPurpose = compactUiPurpose(projectResult);
|
|
@@ -8258,13 +8685,19 @@ async function runScan(rootDir, opts) {
|
|
|
8258
8685
|
if (extended.codeQuality) filesScanned += extended.codeQuality.filesAnalyzed;
|
|
8259
8686
|
if (extended.owaspCategoryMapping) filesScanned += extended.owaspCategoryMapping.scannedFiles;
|
|
8260
8687
|
if (extended.uiPurpose) filesScanned += extended.uiPurpose.topEvidence.length;
|
|
8688
|
+
if (extended.runtimeConfiguration) filesScanned += extended.runtimeConfiguration.hiddenConfigFiles.length;
|
|
8689
|
+
if (extended.dataStores) filesScanned += extended.dataStores.databaseTechnologies.length + extended.dataStores.tables.length;
|
|
8690
|
+
if (extended.apiSurface) filesScanned += extended.apiSurface.integrations.length;
|
|
8691
|
+
if (extended.operationalResilience) filesScanned += extended.operationalResilience.implicitTimeouts.length;
|
|
8692
|
+
if (extended.assetBranding) filesScanned += extended.assetBranding.faviconFiles.length + extended.assetBranding.productLogos.length;
|
|
8693
|
+
if (extended.ossGovernance) filesScanned += extended.ossGovernance.directDependencies;
|
|
8261
8694
|
const durationMs = Date.now() - scanStart;
|
|
8262
8695
|
const repository = await buildRepositoryInfo(rootDir, vcs.remoteUrl, extended.buildDeploy?.ci);
|
|
8263
8696
|
const artifact = {
|
|
8264
8697
|
schemaVersion: "1.0",
|
|
8265
8698
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8266
8699
|
vibgrateVersion: VERSION,
|
|
8267
|
-
rootPath:
|
|
8700
|
+
rootPath: path24.basename(rootDir),
|
|
8268
8701
|
...vcs.type !== "unknown" ? { vcs } : {},
|
|
8269
8702
|
repository,
|
|
8270
8703
|
projects: allProjects,
|
|
@@ -8278,7 +8711,7 @@ async function runScan(rootDir, opts) {
|
|
|
8278
8711
|
relationshipDiagram
|
|
8279
8712
|
};
|
|
8280
8713
|
if (opts.baseline) {
|
|
8281
|
-
const baselinePath =
|
|
8714
|
+
const baselinePath = path24.resolve(opts.baseline);
|
|
8282
8715
|
if (await pathExists(baselinePath)) {
|
|
8283
8716
|
try {
|
|
8284
8717
|
const baseline = await readJsonFile(baselinePath);
|
|
@@ -8290,10 +8723,10 @@ async function runScan(rootDir, opts) {
|
|
|
8290
8723
|
}
|
|
8291
8724
|
}
|
|
8292
8725
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
8293
|
-
const vibgrateDir =
|
|
8726
|
+
const vibgrateDir = path24.join(rootDir, ".vibgrate");
|
|
8294
8727
|
await ensureDir(vibgrateDir);
|
|
8295
|
-
await writeJsonFile(
|
|
8296
|
-
await writeJsonFile(
|
|
8728
|
+
await writeJsonFile(path24.join(vibgrateDir, "scan_result.json"), artifact);
|
|
8729
|
+
await writeJsonFile(path24.join(vibgrateDir, "solutions.json"), {
|
|
8297
8730
|
scannedAt: artifact.timestamp,
|
|
8298
8731
|
solutions: solutions.map((solution) => ({
|
|
8299
8732
|
solutionId: solution.solutionId,
|
|
@@ -8314,10 +8747,10 @@ async function runScan(rootDir, opts) {
|
|
|
8314
8747
|
if (!opts.noLocalArtifacts && !maxPrivacyMode) {
|
|
8315
8748
|
for (const project of allProjects) {
|
|
8316
8749
|
if (project.drift && project.path) {
|
|
8317
|
-
const projectDir =
|
|
8318
|
-
const projectVibgrateDir =
|
|
8750
|
+
const projectDir = path24.resolve(rootDir, project.path);
|
|
8751
|
+
const projectVibgrateDir = path24.join(projectDir, ".vibgrate");
|
|
8319
8752
|
await ensureDir(projectVibgrateDir);
|
|
8320
|
-
await writeJsonFile(
|
|
8753
|
+
await writeJsonFile(path24.join(projectVibgrateDir, "project_score.json"), {
|
|
8321
8754
|
projectId: project.projectId,
|
|
8322
8755
|
name: project.name,
|
|
8323
8756
|
type: project.type,
|
|
@@ -8337,7 +8770,7 @@ async function runScan(rootDir, opts) {
|
|
|
8337
8770
|
if (opts.format === "json") {
|
|
8338
8771
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
|
8339
8772
|
if (opts.out) {
|
|
8340
|
-
await writeTextFile(
|
|
8773
|
+
await writeTextFile(path24.resolve(opts.out), jsonStr);
|
|
8341
8774
|
console.log(chalk6.green("\u2714") + ` JSON written to ${opts.out}`);
|
|
8342
8775
|
} else {
|
|
8343
8776
|
console.log(jsonStr);
|
|
@@ -8346,7 +8779,7 @@ async function runScan(rootDir, opts) {
|
|
|
8346
8779
|
const sarif = formatSarif(artifact);
|
|
8347
8780
|
const sarifStr = JSON.stringify(sarif, null, 2);
|
|
8348
8781
|
if (opts.out) {
|
|
8349
|
-
await writeTextFile(
|
|
8782
|
+
await writeTextFile(path24.resolve(opts.out), sarifStr);
|
|
8350
8783
|
console.log(chalk6.green("\u2714") + ` SARIF written to ${opts.out}`);
|
|
8351
8784
|
} else {
|
|
8352
8785
|
console.log(sarifStr);
|
|
@@ -8355,20 +8788,20 @@ async function runScan(rootDir, opts) {
|
|
|
8355
8788
|
const markdown = formatMarkdown(artifact);
|
|
8356
8789
|
console.log(markdown);
|
|
8357
8790
|
if (opts.out) {
|
|
8358
|
-
await writeTextFile(
|
|
8791
|
+
await writeTextFile(path24.resolve(opts.out), markdown);
|
|
8359
8792
|
}
|
|
8360
8793
|
} else {
|
|
8361
8794
|
const text = formatText(artifact);
|
|
8362
8795
|
console.log(text);
|
|
8363
8796
|
if (opts.out) {
|
|
8364
|
-
await writeTextFile(
|
|
8797
|
+
await writeTextFile(path24.resolve(opts.out), text);
|
|
8365
8798
|
}
|
|
8366
8799
|
}
|
|
8367
8800
|
return artifact;
|
|
8368
8801
|
}
|
|
8369
8802
|
async function buildRepositoryInfo(rootDir, remoteUrl, ciSystems) {
|
|
8370
|
-
const packageJsonPath =
|
|
8371
|
-
let name =
|
|
8803
|
+
const packageJsonPath = path24.join(rootDir, "package.json");
|
|
8804
|
+
let name = path24.basename(rootDir);
|
|
8372
8805
|
let version;
|
|
8373
8806
|
if (await pathExists(packageJsonPath)) {
|
|
8374
8807
|
try {
|
|
@@ -8463,7 +8896,7 @@ function parseNonNegativeNumber(value, label) {
|
|
|
8463
8896
|
return parsed;
|
|
8464
8897
|
}
|
|
8465
8898
|
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 =
|
|
8899
|
+
const rootDir = path24.resolve(targetPath);
|
|
8467
8900
|
if (!await pathExists(rootDir)) {
|
|
8468
8901
|
console.error(chalk6.red(`Path does not exist: ${rootDir}`));
|
|
8469
8902
|
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-VGZRUFB4.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-XDZGC6KJ.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-WRLXEJGA.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