vibefortress 0.1.32 → 0.1.50
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/.claude-plugin/plugin.json +1 -1
- package/dist/MANIFEST.sha256 +4 -4
- package/dist/cli.js +21 -2
- package/dist/hook.js +155 -39
- package/dist/panel.html +1 -1
- package/dist/release.json +9 -9
- package/dist/release.sig +1 -1
- package/dist/server.js +2274 -1104
- package/package.json +1 -1
package/dist/MANIFEST.sha256
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
43acc2492adebae8e98d838a0f0b4b6e8e1d12073385b67306c123287a6051ad THIRD_PARTY_LICENSES.txt
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
f1f23262dcf2fe49120dcc50d8d5c228d3c4ccd34976931c06bb9b1ec1ad44d2 cli.js
|
|
3
|
+
e9b01001a1309f59f2c371b49548dcb87308ded13fc98182f260da3ff5eb3dcc hook.js
|
|
4
|
+
501dc52451ad036d7345b1cdf152b0e9ab6ecaaee718b13459983356f97ce43f panel.html
|
|
5
5
|
cb454e01aa8690db6a4589c97d9d683b866d93fae4163b516951ae1cc27774a8 rules/vibefort.yaml
|
|
6
6
|
86821fb93d339d54a9a5a1488128ec2bb313d04ec0213a6b88a58f9d6e94068c scanners.lock.json
|
|
7
|
-
|
|
7
|
+
80f999e816df2538c046d671835fbe4f3497bbbc452d4170fe201967014c2e9b server.js
|
package/dist/cli.js
CHANGED
|
@@ -245,11 +245,13 @@ var init_clients = __esm({
|
|
|
245
245
|
command(e, "remove the hosted service", ["claude", "mcp", "remove", "--scope", "user", REMOTE_NAME], hasRemote, true)
|
|
246
246
|
];
|
|
247
247
|
const a = [];
|
|
248
|
+
const pluginBringsRemote = o.local && mcpUrl(e.env) === DEFAULT_MCP_URL;
|
|
249
|
+
if (pluginBringsRemote && hasRemote()) a.push(command(e, "remove the separate hosted-service entry (the plugin includes it now)", ["claude", "mcp", "remove", "--scope", "user", REMOTE_NAME], hasRemote, true));
|
|
248
250
|
if (o.local) a.push(
|
|
249
251
|
command(e, "add the Vibe Fortress marketplace", ["claude", "plugin", "marketplace", "add", source], hasMarket),
|
|
250
|
-
command(e,
|
|
252
|
+
command(e, `install the plugin (local scanner, ${pluginBringsRemote ? "hosted service, " : ""}skills, hooks)`, ["claude", "plugin", "install", plugin, "--scope", "user"], hasPlugin)
|
|
251
253
|
);
|
|
252
|
-
if (o.remote) a.push(command(e, "add the hosted service", ["claude", "mcp", "add", "--transport", "http", "--scope", "user", REMOTE_NAME, mcpUrl(e.env)], hasRemote));
|
|
254
|
+
if (o.remote && !pluginBringsRemote) a.push(command(e, "add the hosted service", ["claude", "mcp", "add", "--transport", "http", "--scope", "user", REMOTE_NAME, mcpUrl(e.env)], hasRemote));
|
|
253
255
|
return a;
|
|
254
256
|
}
|
|
255
257
|
};
|
|
@@ -467,6 +469,12 @@ var HELP = `Vibe Fortress: security that keeps up with vibe coding.
|
|
|
467
469
|
Usage:
|
|
468
470
|
npx vibefortress find your AI tools and install Vibe Fortress in them
|
|
469
471
|
npx vibefortress uninstall remove what the installer added
|
|
472
|
+
npx vibefortress collect --scan <id> --code <code>
|
|
473
|
+
npx vibefortress check --scan <id> --code <code>
|
|
474
|
+
first step of a deep scan: describe the checked-out commit (file list from git,
|
|
475
|
+
packages from the lockfile), run the local tools and send the results (never code)
|
|
476
|
+
to Vibe Fortress. Options: --path <folder>, --base <commit>, --server <address>,
|
|
477
|
+
--dry-run (print what would be sent, send nothing)
|
|
470
478
|
|
|
471
479
|
Options:
|
|
472
480
|
--yes, -y do not ask, install in every tool found
|
|
@@ -505,6 +513,17 @@ async function main(argv) {
|
|
|
505
513
|
const r = spawnSync2(process.execPath, [hook, rest[0]], { stdio: "inherit" });
|
|
506
514
|
return r.status ?? 0;
|
|
507
515
|
}
|
|
516
|
+
if (cmd === "collect" || cmd === "check") {
|
|
517
|
+
const built = join3(here, "server.js"), entry = existsSync4(built) ? built : join3(here, "..", "local", "index.ts");
|
|
518
|
+
if (!existsSync4(entry)) {
|
|
519
|
+
console.error("vibefortress: server.js is missing from this install.");
|
|
520
|
+
return 1;
|
|
521
|
+
}
|
|
522
|
+
process.env.VIBEFORT_ENTRY = cmd;
|
|
523
|
+
process.env.VIBEFORT_COLLECT_ARGS = JSON.stringify(rest);
|
|
524
|
+
await import(pathToFileURL(entry).href);
|
|
525
|
+
return -1;
|
|
526
|
+
}
|
|
508
527
|
const { runInstaller: runInstaller2, parseArgs: parseArgs2, realEnv: realEnv2 } = await Promise.resolve().then(() => (init_run(), run_exports));
|
|
509
528
|
const parsed = parseArgs2(cmd === "install" || cmd === "uninstall" ? rest : argv, cmd === "uninstall" ? "uninstall" : "install");
|
|
510
529
|
if ("error" in parsed) {
|
package/dist/hook.js
CHANGED
|
@@ -19749,6 +19749,17 @@ var env = external_exports.object({
|
|
|
19749
19749
|
APP_SECRET: external_exports.string().min(16).optional(),
|
|
19750
19750
|
GITHUB_CLIENT_ID: external_exports.string().optional(),
|
|
19751
19751
|
GITHUB_CLIENT_SECRET: external_exports.string().optional(),
|
|
19752
|
+
SIGNIN_LOCKED: external_exports.enum(["0", "1"]).default("0"),
|
|
19753
|
+
SERVE_SITE: external_exports.enum(["0", "1"]).default("0"),
|
|
19754
|
+
GITLAB_CLIENT_ID: external_exports.string().optional(),
|
|
19755
|
+
GITLAB_CLIENT_SECRET: external_exports.string().optional(),
|
|
19756
|
+
GITLAB_BASE_URL: external_exports.string().default("https://gitlab.com"),
|
|
19757
|
+
GOOGLE_CLIENT_ID: external_exports.string().optional(),
|
|
19758
|
+
GOOGLE_CLIENT_SECRET: external_exports.string().optional(),
|
|
19759
|
+
APPLE_CLIENT_ID: external_exports.string().optional(),
|
|
19760
|
+
APPLE_TEAM_ID: external_exports.string().optional(),
|
|
19761
|
+
APPLE_KEY_ID: external_exports.string().optional(),
|
|
19762
|
+
APPLE_PRIVATE_KEY: external_exports.string().optional(),
|
|
19752
19763
|
BILLING_MODE: external_exports.enum(["fake", "stripe"]).default("fake"),
|
|
19753
19764
|
STRIPE_SECRET_KEY: external_exports.string().optional(),
|
|
19754
19765
|
STRIPE_WEBHOOK_SECRET: external_exports.string().optional(),
|
|
@@ -19770,18 +19781,38 @@ var env = external_exports.object({
|
|
|
19770
19781
|
TRIAL_DAYS: num(30),
|
|
19771
19782
|
PERIOD_GRACE_DAYS: num(3),
|
|
19772
19783
|
SKILL_TOKEN_BUDGET: num(12e3),
|
|
19784
|
+
ANSWER_CHAR_BUDGET: external_exports.coerce.number().min(12e3).default(3e4),
|
|
19773
19785
|
MAX_RESEARCH_TASKS: num(12),
|
|
19774
19786
|
MAX_INVESTIGATIONS: num(8),
|
|
19775
19787
|
SWEEP: external_exports.enum(["on", "off"]).default("on"),
|
|
19776
19788
|
SWEEP_FILES_PER_TASK: num(10),
|
|
19777
19789
|
SPOT_CHECKS: num(3),
|
|
19790
|
+
SPOT_CHECKS_MAX: num(6),
|
|
19791
|
+
RISK_STEPS_MAX: num(20),
|
|
19792
|
+
STEP_FILES: num(8),
|
|
19793
|
+
STEP_KB: num(120),
|
|
19794
|
+
SWEEP_FILES_MAX: num(80),
|
|
19795
|
+
LARGE_FILE_KB: num(40),
|
|
19796
|
+
MAX_INVESTIGATIONS_CEILING: num(16),
|
|
19778
19797
|
INVENTORY_MAX: num(2e3),
|
|
19798
|
+
INVENTORY_OMITTED_MAX: num(400),
|
|
19799
|
+
TREE_MODE: external_exports.enum(["real", "fake", "off"]).default("real"),
|
|
19800
|
+
TREE_TIMEOUT_MS: num(8e3),
|
|
19801
|
+
DEPS_PER_REPORT: num(1e3),
|
|
19802
|
+
COLLECT_CODE_TTL_MIN: num(60),
|
|
19803
|
+
COLLECT_MAX_BYTES: num(8e6),
|
|
19804
|
+
FULL_COVERAGE: external_exports.enum(["on", "off"]).default("on"),
|
|
19805
|
+
STEP_LINES: num(1500),
|
|
19806
|
+
COVER_STEP_FILES: num(12),
|
|
19807
|
+
PROOF_CHECK: external_exports.enum(["on", "off"]).default("on"),
|
|
19808
|
+
PROOF_CODE_TTL_MIN: num(60),
|
|
19809
|
+
REVIEW_REOPEN_MAX: num(2),
|
|
19779
19810
|
ATTACK_CLASSES: external_exports.enum(["on", "off"]).default("off"),
|
|
19780
19811
|
ATTACK_CLASS_BUDGET: num(3e3),
|
|
19781
19812
|
TEAM_SEATS: num(5),
|
|
19782
19813
|
INVITE_TTL_DAYS: num(7),
|
|
19783
19814
|
LAPSE_READONLY_DAYS: num(30),
|
|
19784
|
-
TEAM_WORKSPACES_PER_OWNER: num(
|
|
19815
|
+
TEAM_WORKSPACES_PER_OWNER: num(10),
|
|
19785
19816
|
INVITE_CREATE_PER_DAY: num(20),
|
|
19786
19817
|
INVITEE_RETENTION_DAYS: num(30),
|
|
19787
19818
|
INVITE_DELIVERY: external_exports.enum(["dev"]).default("dev"),
|
|
@@ -19811,6 +19842,8 @@ var config2 = {
|
|
|
19811
19842
|
host: env.HOST,
|
|
19812
19843
|
/** Gated private beta (feature 032, D-264): allows AUTH_MODE=dev and BILLING_MODE=fake in production, only with DEV_ACCESS_TOKEN. */
|
|
19813
19844
|
privateBeta: env.PRIVATE_BETA === "1",
|
|
19845
|
+
/** Serve the marketing site (site/dist) from this server, and send other host names of the domain to PUBLIC_URL (D-303). */
|
|
19846
|
+
serveSite: env.SERVE_SITE === "1",
|
|
19814
19847
|
publicUrl: env.PUBLIC_URL.replace(/\/$/, ""),
|
|
19815
19848
|
databaseUrl: env.DATABASE_URL,
|
|
19816
19849
|
pgliteDir: env.PGLITE_DIR,
|
|
@@ -19818,18 +19851,45 @@ var config2 = {
|
|
|
19818
19851
|
devAccessToken: env.DEV_ACCESS_TOKEN,
|
|
19819
19852
|
appSecret: env.APP_SECRET ?? randomBytes(32).toString("hex"),
|
|
19820
19853
|
github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET },
|
|
19854
|
+
/** Website sign-in providers (feature 035, D-300). A provider is offered only when every value it needs is set. Secrets are never logged or returned. */
|
|
19855
|
+
signIn: {
|
|
19856
|
+
gitlab: { baseUrl: env.GITLAB_BASE_URL.replace(/\/$/, ""), clientId: env.GITLAB_CLIENT_ID, clientSecret: env.GITLAB_CLIENT_SECRET },
|
|
19857
|
+
google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET },
|
|
19858
|
+
apple: { clientId: env.APPLE_CLIENT_ID, teamId: env.APPLE_TEAM_ID, keyId: env.APPLE_KEY_ID, privateKey: env.APPLE_PRIVATE_KEY },
|
|
19859
|
+
emailLinkMinutes: 15,
|
|
19860
|
+
/** Public launch before any provider exists (D-303): every method is listed as "coming soon" and none can start. */
|
|
19861
|
+
locked: env.SIGNIN_LOCKED === "1"
|
|
19862
|
+
},
|
|
19821
19863
|
billingMode: env.BILLING_MODE,
|
|
19822
19864
|
stripe: { secretKey: env.STRIPE_SECRET_KEY, webhookSecret: env.STRIPE_WEBHOOK_SECRET, priceMonth: env.STRIPE_PRICE_MONTH ?? env.STRIPE_PRICE_ID, priceYear: env.STRIPE_PRICE_YEAR },
|
|
19823
19865
|
mantisDir: env.MANTIS_DIR,
|
|
19824
19866
|
osvMode: env.OSV_MODE,
|
|
19825
19867
|
skillTokenBudget: env.SKILL_TOKEN_BUDGET,
|
|
19826
|
-
/** Largest
|
|
19827
|
-
answerCharBudget:
|
|
19868
|
+
/** Largest tool answer, in characters, per channel (text block, and structuredContent as JSON). A host that forwards both channels sees twice this, so 30,000 keeps the worst case at about 15K tokens, under Claude Code's 25K-token tool-result limit (D-189, D-308). Skills that do not fit are served in parts (D-309). */
|
|
19869
|
+
answerCharBudget: env.ANSWER_CHAR_BUDGET,
|
|
19870
|
+
/** One limit for the threat-model text: stage contract, tool schema and refusal message (036, FR-029). */
|
|
19871
|
+
threatModelMaxChars: 6e3,
|
|
19828
19872
|
maxResearchTasks: env.MAX_RESEARCH_TASKS,
|
|
19829
19873
|
maxInvestigations: env.MAX_INVESTIGATIONS,
|
|
19830
19874
|
sweep: { enabled: env.SWEEP === "on", filesPerTask: Math.max(1, env.SWEEP_FILES_PER_TASK) },
|
|
19831
19875
|
spotChecks: Math.max(0, env.SPOT_CHECKS),
|
|
19876
|
+
spotChecksMax: Math.max(0, env.SPOT_CHECKS_MAX),
|
|
19877
|
+
/** Coverage engine budgets (037, D-320): they grow with the reviewable files and stop at these ceilings. */
|
|
19878
|
+
coverage: { riskStepsMax: Math.max(0, env.RISK_STEPS_MAX), stepFiles: Math.max(1, env.STEP_FILES), stepKb: Math.max(10, env.STEP_KB), sweepFilesMax: Math.max(0, env.SWEEP_FILES_MAX), largeFileKb: Math.max(1, env.LARGE_FILE_KB), investigationsCeiling: Math.max(1, env.MAX_INVESTIGATIONS_CEILING) },
|
|
19832
19879
|
inventoryMax: Math.max(1, env.INVENTORY_MAX),
|
|
19880
|
+
inventoryOmittedMax: Math.max(1, env.INVENTORY_OMITTED_MAX),
|
|
19881
|
+
/** File list fetched by the server from GitHub for public repositories, metadata only, no token (038, D-330). "fake" serves registered trees only (tests, local runs), "off" never fetches. */
|
|
19882
|
+
tree: { mode: env.TREE_MODE, timeoutMs: Math.max(500, env.TREE_TIMEOUT_MS) },
|
|
19883
|
+
/** Largest number of packages in one dependency report (038, D-332). */
|
|
19884
|
+
depsPerReport: Math.max(50, env.DEPS_PER_REPORT),
|
|
19885
|
+
/** Collector upload of a deep scan (039): one-time code lifetime and the size limit of a bundle. Count limits live with the bundle schema. */
|
|
19886
|
+
collect: { codeTtlMin: Math.max(1, env.COLLECT_CODE_TTL_MIN), maxBytes: Math.max(1e4, env.COLLECT_MAX_BYTES) },
|
|
19887
|
+
/** Every file that needs review gets a step (039, D-342, D-348). "off" restores the size-based budgets of 037. Steps are sized by lines when the collector counted them. */
|
|
19888
|
+
fullCoverage: { enabled: env.FULL_COVERAGE === "on", stepLines: Math.max(100, env.STEP_LINES), stepFiles: Math.max(1, env.COVER_STEP_FILES) },
|
|
19889
|
+
/** The check on the user's machine after triage (041, D-364): on when the scan has a collector upload; its one-time code lifetime. */
|
|
19890
|
+
proof: { enabled: env.PROOF_CHECK === "on", codeTtlMin: Math.max(1, env.PROOF_CODE_TTL_MIN) },
|
|
19891
|
+
/** How often a review step reopens for files left unread without an accepted reason (041, D-369); 0 restores the 039 behaviour. */
|
|
19892
|
+
reviewReopenMax: Math.max(0, env.REVIEW_REOPEN_MAX),
|
|
19833
19893
|
/** Local scanning (feature 013). Which engines run per plan is configuration (FR-017); safety basics stay on for every plan. */
|
|
19834
19894
|
localScans: {
|
|
19835
19895
|
engines: { secrets: true, deps: true, code: true, config: true },
|
|
@@ -19879,17 +19939,67 @@ var config2 = {
|
|
|
19879
19939
|
var SERVICES = ["supabase", "firebase", "vercel", "neon", "stripe", "clerk", "auth0", "cloudflare", "netlify", "railway", "render", "upstash", "aws", "azure", "gcloud", "kubernetes"];
|
|
19880
19940
|
|
|
19881
19941
|
// src/domain/mask.ts
|
|
19882
|
-
var
|
|
19942
|
+
var M = "\u2026[masked]";
|
|
19943
|
+
var cut = (t, n = 4) => `${t.slice(0, n)}${M}`;
|
|
19944
|
+
var TECH = /^(?:(?:sha(?:1|224|256|384|512)|base(?:32|58|64)|oauth2|ed25519|x25519|x509|utf(?:8|16)|md5|argon2|pbkdf2|bcrypt|scrypt|http[23]|ipv[46]|aes(?:128|192|256)|rsa(?:2048|3072|4096)|pkcs\d{1,2}|i18n|l10n|a11y|k8s|e2e|es20\d\d|[hrep]s(?:256|384|512))[a-z]{0,12}|utf8mb4|s3|ec2|es\d|tls1|v\d{1,3}|p\d{2,3}|\d{1,6}|\d{1,3}[a-z]{1,2})$/i;
|
|
19945
|
+
var SEP = /[-_.+=/:@]+/;
|
|
19946
|
+
var CRED_PREFIX = /^(?:whsec|sk|pk|rk|gh[pousr]|github_pat|glpat|xox[baprs]|npm|AKIA|ASIA|AIza|SG|eyJ)[_\-.]/;
|
|
19947
|
+
function camelWordy(s) {
|
|
19948
|
+
if (!/^[A-Za-z]+$/.test(s)) return false;
|
|
19949
|
+
if (s === s.toLowerCase() || s === s.toUpperCase()) return true;
|
|
19950
|
+
const words = s.match(/[A-Z]?[a-z]+|[A-Z]+(?![a-z])/g) ?? [];
|
|
19951
|
+
return words.filter((w) => w.length === 1).length <= 1 && words.length <= Math.max(1, Math.floor(s.length / 3));
|
|
19952
|
+
}
|
|
19953
|
+
function wordy(t) {
|
|
19954
|
+
if (CRED_PREFIX.test(t)) return false;
|
|
19955
|
+
const segs = t.split(SEP).filter(Boolean);
|
|
19956
|
+
return segs.length > 0 && segs.every((s) => TECH.test(s) || camelWordy(s));
|
|
19957
|
+
}
|
|
19958
|
+
function pinnedId(t, off, str) {
|
|
19959
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)) return /\bfinding(?:\s+id)?\s*[:#]?\s*[`'"]?$/i.test(str.slice(Math.max(0, off - 16), off));
|
|
19960
|
+
if (!/^[0-9a-f]{7,64}$/.test(t)) return false;
|
|
19961
|
+
return /(?:@|sha(?:256|512):|\b(?:commit|sha|digest|blob|tree|revision|rev|pinned to|pin to|pin)\s*[:=]?\s*[`'"]?)$/i.test(str.slice(Math.max(0, off - 24), off));
|
|
19962
|
+
}
|
|
19963
|
+
function valueLike(v) {
|
|
19964
|
+
if (v.includes(M)) return false;
|
|
19965
|
+
if (/\d/.test(v) && /[A-Za-z]/.test(v)) return !wordy(v);
|
|
19966
|
+
if (/[!#$%^&*~@]/.test(v) && !/^[\w.+-]+@[\w-]+\.[\w.-]+$/.test(v)) return true;
|
|
19967
|
+
if (/^[A-Za-z]{12,}$/.test(v)) return !camelWordy(v);
|
|
19968
|
+
return v.length >= 20 && !wordy(v);
|
|
19969
|
+
}
|
|
19970
|
+
var KNOWN_TOKENS = /(?<![A-Za-z0-9])(?:AKIA[0-9A-Z]{16}|ASIA[0-9A-Z]{16}|gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{3,}|sk_(?:live|test)_[A-Za-z0-9]{8,}|sk-[A-Za-z0-9_-]{20,}|eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}|SG\.[A-Za-z0-9_-]{10,}|glpat-[A-Za-z0-9_-]{16,}|npm_[A-Za-z0-9]{16,}|whsec_[A-Za-z0-9]{16,}|r[ks]_(?:live|test)_[A-Za-z0-9]{8,}|pk_live_[A-Za-z0-9]{8,}|AIza[0-9A-Za-z_-]{30,})|(?<![A-Za-z0-9_])[a-z]{30,}(?![A-Za-z0-9_])/g;
|
|
19971
|
+
var KEYWORD = /^(?:string|number|boolean|bool|int|str|true|false|null|undefined|none|nil|any|unknown|void|never|required|optional|missing|value|values|redacted|\*{2,}|\d{1,7})$/i;
|
|
19972
|
+
var CODE_REF = /^[A-Za-z_$][\w$]*(?:\??\.[\w$]+)+\(?$|[([<{]|^\$/;
|
|
19883
19973
|
function maskAlways(s) {
|
|
19884
|
-
return s.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, "-----BEGIN PRIVATE KEY-----\u2026[masked]-----END PRIVATE KEY-----").replace(/\b(Bearer|Basic|Token)\s+[A-Za-z0-9._~+\/=-]{8,}/gi, (
|
|
19885
|
-
const q2 = /^["'`]/.test(v) ? v[0] : "";
|
|
19886
|
-
|
|
19887
|
-
|
|
19974
|
+
return s.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, "-----BEGIN PRIVATE KEY-----\u2026[masked]-----END PRIVATE KEY-----").replace(/\b(Bearer|Basic|Token)\s+([A-Za-z0-9._~+\/=-]{8,})/gi, (m, a, v) => valueLike(v) ? `${a} ${M}` : m).replace(/\b(passw(?:or)?d|passphrase|secret|token|api[ _-]?key|key)\s+(?:is|was)\s+(["'`]?)([^\s"'`,;)]{4,})/gi, (m, a, q2, v) => valueLike(v) || q2 && (/^pass/i.test(a) || !wordy(v)) ? `${a} is ${M}` : m).replace(/\b(secret|password|passphrase|token|pw)\s+(?=[^\s"'`,;)]*\d)([^\s"'`,;)]{6,})/gi, (m, a, v) => wordy(v) || /^\d{1,4}(?:[-/.:]\d{1,4})+$/.test(v) ? m : `${a} ${M}`).replace(KNOWN_TOKENS, (t) => cut(t)).replace(/(:\/\/[^\/\s:@]+:)([^@\s\/]+)(@)/g, (_m, a, _v, c) => `${a}${M}${c}`).replace(/((?:key|secret|passw(?:or)?d|pwd|pw|pass|token|credential)[A-Za-z0-9_]{0,80}["']?\s{0,20}(?:[=!]==?|[:=])\s{0,20})("[^"\n]*"|'[^'\n]*'|`[^`\n]*`|[^\s"'`,;)}]{3,})/gi, (m, a, v, off, str) => {
|
|
19975
|
+
const q2 = /^["'`]/.test(v) ? v[0] : "", inner = q2 ? v.slice(1, -1) : v;
|
|
19976
|
+
if (inner.includes(M)) return m;
|
|
19977
|
+
if (!q2 && (KEYWORD.test(inner.replace(/\.+$/, "")) || CODE_REF.test(inner))) return m;
|
|
19978
|
+
const word = a.match(/^[A-Za-z0-9_]+/)?.[0] ?? "", pre = str.slice(0, off).slice(-1);
|
|
19979
|
+
const inIdentifier = /[_0-9]/.test(pre) || /[a-z]/.test(pre) && /^[A-Z]/.test(word) || /[A-Z]/.test(pre) && word === word.toUpperCase();
|
|
19980
|
+
const loneWord = /^(?:keys?|pass)$/i.test(word) && !inIdentifier;
|
|
19981
|
+
if (loneWord && !valueLike(inner)) return m;
|
|
19982
|
+
return `${a}${q2}${inner.slice(0, 2)}${M}${q2}`;
|
|
19983
|
+
}).replace(/(?<![A-Za-z0-9_\/.+])(?=[A-Za-z0-9\/+]*[A-Z])(?=[A-Za-z0-9\/+]*[a-z])(?=[A-Za-z0-9\/+]*[\d\/+])[A-Za-z0-9\/+]{40}(?![A-Za-z0-9_\/.+])/g, (t) => wordy(t) ? t : cut(t)).replace(/(?<![A-Za-z0-9_\-])[A-Z0-9]{4,}(?:-[A-Z0-9]{4,}){2,}(?![A-Za-z0-9_\-])/g, (t, off, str) => {
|
|
19984
|
+
const g = t.split("-");
|
|
19985
|
+
if (/\d/.test(t)) return g.every((x) => x.length === g[0].length) || !wordy(t) ? cut(t) : t;
|
|
19986
|
+
return /\b(?:secret|key|token|passw(?:or)?d|licen[cs]e|serial|credential)s?\W{0,3}(?:is\W{0,2})?$/i.test(str.slice(Math.max(0, off - 24), off)) ? cut(t) : t;
|
|
19987
|
+
}).replace(/(?<![A-Za-z0-9_\-+=/.])(?=[A-Za-z0-9_+=]*\d)(?=[A-Za-z0-9_+=]*[A-Za-z])[A-Za-z0-9_+=]{16,19}(?![A-Za-z0-9_\-+=/.])/g, (t, off, str) => wordy(t) || pinnedId(t, off, str) ? t : cut(t)).replace(/(?<![A-Za-z0-9_\-+=/.])(?=[A-Za-z0-9_\-+=]*\d)(?=[A-Za-z0-9_\-+=]*[A-Za-z])[A-Za-z0-9_\-+=]{20,}(?![A-Za-z0-9_\-+=/.])/g, (t, off, str) => wordy(t) || pinnedId(t, off, str) ? t : cut(t)).replace(/(?<![A-Za-z0-9_\-+=/.:@])(?=[A-Za-z0-9_]*\d)(?=[A-Za-z0-9_]*[A-Za-z])[A-Za-z0-9_]{8,}(?![A-Za-z0-9_\-+=/.:@]|…)/g, (t, off, str) => /^[0-9a-f]{8,19}$/.test(t) || wordy(t) || pinnedId(t, off, str) ? t : `${t.slice(0, 2)}${M}`);
|
|
19988
|
+
}
|
|
19989
|
+
function nameLike(v) {
|
|
19990
|
+
const words = v.replace(/\$\{\{[^}]*\}\}|\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+|\?/g, " ").trim().split(/[\s=,;()*<>]+/).map((w) => w.replace(/^[([{<]+|[)\]}>.,:;!?]+$/g, "")).filter(Boolean);
|
|
19991
|
+
return words.every((w) => wordy(w));
|
|
19888
19992
|
}
|
|
19889
19993
|
function maskExcerpt(s) {
|
|
19890
19994
|
return maskAlways(s).split("\n").map((line) => {
|
|
19891
19995
|
const secretLine = /key|secret|pass|pwd|token|cred|auth/i.test(line);
|
|
19892
|
-
return line.replace(/(["'`])([^"'`\n]*)\1/g, (m, q2, v
|
|
19996
|
+
return line.replace(/(["'`])([^"'`\n]*)\1/g, (m, q2, v, off, str) => {
|
|
19997
|
+
if (v.includes(M) || v.length < 3) return m;
|
|
19998
|
+
const SECRET_ID = "[\\w.$\\]\\[]*(?:passw(?:or)?d|passphrase|pwd|pw|pass|secret|token|key|credential)\\w*";
|
|
19999
|
+
const compared = new RegExp(`${SECRET_ID}\\s*(?:[=!]==?)\\s*$`, "i").test(str.slice(0, off)) || new RegExp(`^\\s*(?:[=!]==?)\\s*${SECRET_ID}`, "i").test(str.slice(off + m.length));
|
|
20000
|
+
const hit = secretLine ? compared || !nameLike(v) : /^\S{12,}$/.test(v) && !/[\/.]/.test(v) && !nameLike(v);
|
|
20001
|
+
return hit ? `${q2}${v.slice(0, 2)}${M}${q2}` : m;
|
|
20002
|
+
});
|
|
19893
20003
|
}).join("\n");
|
|
19894
20004
|
}
|
|
19895
20005
|
|
|
@@ -20944,39 +21054,45 @@ var sev = (s) => {
|
|
|
20944
21054
|
const v = (s ?? "").toUpperCase();
|
|
20945
21055
|
return v === "CRITICAL" ? "CRITICAL" : v === "HIGH" ? "HIGH" : v === "LOW" ? "LOW" : "MEDIUM";
|
|
20946
21056
|
};
|
|
20947
|
-
var
|
|
20948
|
-
|
|
20949
|
-
|
|
20950
|
-
|
|
20951
|
-
|
|
20952
|
-
|
|
20953
|
-
|
|
20954
|
-
|
|
20955
|
-
|
|
20956
|
-
|
|
20957
|
-
|
|
20958
|
-
|
|
20959
|
-
|
|
20960
|
-
|
|
20961
|
-
|
|
20962
|
-
|
|
20963
|
-
|
|
20964
|
-
vulns
|
|
20965
|
-
|
|
20966
|
-
|
|
20967
|
-
|
|
20968
|
-
|
|
20969
|
-
|
|
20970
|
-
|
|
20971
|
-
|
|
20972
|
-
|
|
21057
|
+
var OSV_BATCH = 1e3;
|
|
21058
|
+
function osvSourceWith(fetchFn = fetch) {
|
|
21059
|
+
return {
|
|
21060
|
+
async lookup(deps) {
|
|
21061
|
+
const out = /* @__PURE__ */ new Map();
|
|
21062
|
+
let budget = 25;
|
|
21063
|
+
for (let at = 0; at < deps.length; at += OSV_BATCH) {
|
|
21064
|
+
const list = deps.slice(at, at + OSV_BATCH);
|
|
21065
|
+
const res2 = await fetchFn("https://api.osv.dev/v1/querybatch", {
|
|
21066
|
+
method: "POST",
|
|
21067
|
+
headers: { "content-type": "application/json" },
|
|
21068
|
+
signal: AbortSignal.timeout(2e4),
|
|
21069
|
+
body: JSON.stringify({ queries: list.map((d) => ({ package: { name: d.name, ecosystem: d.ecosystem ?? "npm" }, version: d.version })) })
|
|
21070
|
+
});
|
|
21071
|
+
if (!res2.ok) throw new Error(`OSV ${res2.status}`);
|
|
21072
|
+
const body = await res2.json();
|
|
21073
|
+
for (let i = 0; i < list.length; i++) {
|
|
21074
|
+
const vulns = [];
|
|
21075
|
+
for (const { id } of (body.results[i]?.vulns ?? []).slice(0, 3)) {
|
|
21076
|
+
if (budget-- <= 0) {
|
|
21077
|
+
vulns.push({ id, summary: "Known vulnerability", severity: "MEDIUM" });
|
|
21078
|
+
continue;
|
|
21079
|
+
}
|
|
21080
|
+
try {
|
|
21081
|
+
const d = await (await fetchFn(`https://api.osv.dev/v1/vulns/${encodeURIComponent(id)}`, { signal: AbortSignal.timeout(8e3) })).json();
|
|
21082
|
+
const fixed = d.affected?.flatMap((a) => a.ranges?.flatMap((r) => r.events?.map((e) => e.fixed).filter(Boolean) ?? []) ?? [])[0];
|
|
21083
|
+
vulns.push({ id, summary: String(d.summary ?? d.details ?? "Known vulnerability").slice(0, 200), severity: sev(d.database_specific?.severity), fixedIn: fixed });
|
|
21084
|
+
} catch {
|
|
21085
|
+
vulns.push({ id, summary: "Known vulnerability", severity: "MEDIUM" });
|
|
21086
|
+
}
|
|
21087
|
+
}
|
|
21088
|
+
if (vulns.length) out.set(depKey(list[i]), vulns);
|
|
20973
21089
|
}
|
|
20974
21090
|
}
|
|
20975
|
-
|
|
21091
|
+
return out;
|
|
20976
21092
|
}
|
|
20977
|
-
|
|
20978
|
-
|
|
20979
|
-
|
|
21093
|
+
};
|
|
21094
|
+
}
|
|
21095
|
+
var osvSource = osvSourceWith();
|
|
20980
21096
|
var fakeVulnSource = {
|
|
20981
21097
|
async lookup(deps) {
|
|
20982
21098
|
const out = /* @__PURE__ */ new Map();
|