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.
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "name": "vibefortress",
3
- "version": "0.1.32"
3
+ "version": "0.1.50"
4
4
  }
@@ -1,7 +1,7 @@
1
1
  43acc2492adebae8e98d838a0f0b4b6e8e1d12073385b67306c123287a6051ad THIRD_PARTY_LICENSES.txt
2
- 45a1ef03ac75000857c354793859a816e8d315bb084aa2603f53f32b84512057 cli.js
3
- dbedd28219ffba9330854b3f4ba94a5939c8687d965f904f897e9e25d95eed1c hook.js
4
- 6209434629050e761c4542d8ac95d5cd674365b9faa7582116020c8f8e9783e1 panel.html
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
- fe5e74742162915b779a4370c80a45ff238f54ff2c50dce91481688c3d59dcb9 server.js
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, "install the plugin (local scanner, skills, hooks)", ["claude", "plugin", "install", plugin, "--scope", "user"], hasPlugin)
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(1),
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 next_task answer, in characters, per channel (text block, and structuredContent as JSON). About 15K tokens: under Claude Code's 25K-token tool-result limit with room to spare (D-189). */
19827
- answerCharBudget: 6e4,
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 KNOWN_TOKENS = /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;
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, (_m, a) => `${a} \u2026[masked]`).replace(/\b(passw(?:or)?d|passphrase|secret|token|api[ _-]?key|key)\s+(?:is|was)\s+["'`]?([^\s"'`,;)]{4,})/gi, (_m, a) => `${a} is \u2026[masked]`).replace(/\b(secret|password|passphrase|token|pw)\s+(?=[^\s"'`,;)]*\d)([^\s"'`,;)]{6,})/gi, (_m, a) => `${a} \u2026[masked]`).replace(KNOWN_TOKENS, (t) => `${t.slice(0, 4)}\u2026[masked]`).replace(/(:\/\/[^\/\s:@]+:)([^@\s\/]+)(@)/g, (_m, a, _v, c) => `${a}\u2026[masked]${c}`).replace(/((?:key|secret|passw(?:or)?d|pwd|pw|pass|token|credential)[A-Za-z0-9_]*["']?\s*[:=]\s*)("[^"\n]*"|'[^'\n]*'|`[^`\n]*`|[^\s"'`,;)}]{3,})/gi, (_m, a, v) => {
19885
- const q2 = /^["'`]/.test(v) ? v[0] : "";
19886
- return `${a}${q2}${v.replace(/^["'`]|["'`]$/g, "").slice(0, 2)}\u2026[masked]${q2}`;
19887
- }).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) => `${t.slice(0, 4)}\u2026[masked]`).replace(/(?<![A-Za-z0-9_\-])[A-Z0-9]{4,}(?:-[A-Z0-9]{4,}){2,}(?![A-Za-z0-9_\-])/g, (t) => `${t.slice(0, 4)}\u2026[masked]`).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) => `${t.slice(0, 4)}\u2026[masked]`).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) => `${t.slice(0, 4)}\u2026[masked]`).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) => /^[0-9a-f]{8,19}$/.test(t) ? t : `${t.slice(0, 2)}\u2026[masked]`);
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) => secretLine && v.length >= 3 || /^\S{12,}$/.test(v) && !/[\/.]/.test(v) && !v.includes("\u2026[masked]") ? `${q2}${v.slice(0, 2)}\u2026[masked]${q2}` : m);
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 osvSource = {
20948
- async lookup(deps) {
20949
- const out = /* @__PURE__ */ new Map();
20950
- const list = deps.slice(0, 300);
20951
- const res2 = await fetch("https://api.osv.dev/v1/querybatch", {
20952
- method: "POST",
20953
- headers: { "content-type": "application/json" },
20954
- signal: AbortSignal.timeout(15e3),
20955
- body: JSON.stringify({ queries: list.map((d) => ({ package: { name: d.name, ecosystem: d.ecosystem ?? "npm" }, version: d.version })) })
20956
- });
20957
- if (!res2.ok) throw new Error(`OSV ${res2.status}`);
20958
- const body = await res2.json();
20959
- let budget = 25;
20960
- for (let i = 0; i < list.length; i++) {
20961
- const vulns = [];
20962
- for (const { id } of (body.results[i]?.vulns ?? []).slice(0, 3)) {
20963
- if (budget-- <= 0) {
20964
- vulns.push({ id, summary: "Known vulnerability", severity: "MEDIUM" });
20965
- continue;
20966
- }
20967
- try {
20968
- const d = await (await fetch(`https://api.osv.dev/v1/vulns/${encodeURIComponent(id)}`, { signal: AbortSignal.timeout(8e3) })).json();
20969
- const fixed = d.affected?.flatMap((a) => a.ranges?.flatMap((r) => r.events?.map((e) => e.fixed).filter(Boolean) ?? []) ?? [])[0];
20970
- vulns.push({ id, summary: String(d.summary ?? d.details ?? "Known vulnerability").slice(0, 200), severity: sev(d.database_specific?.severity), fixedIn: fixed });
20971
- } catch {
20972
- vulns.push({ id, summary: "Known vulnerability", severity: "MEDIUM" });
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
- if (vulns.length) out.set(depKey(list[i]), vulns);
21091
+ return out;
20976
21092
  }
20977
- return out;
20978
- }
20979
- };
21093
+ };
21094
+ }
21095
+ var osvSource = osvSourceWith();
20980
21096
  var fakeVulnSource = {
20981
21097
  async lookup(deps) {
20982
21098
  const out = /* @__PURE__ */ new Map();