vibefortress 0.1.32 → 0.1.52

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.52"
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
+ b5647dd61e739195912a5132d55b671dba78e1a100de72af11951ba8a0983306 hook.js
4
+ 501dc52451ad036d7345b1cdf152b0e9ab6ecaaee718b13459983356f97ce43f panel.html
5
5
  cb454e01aa8690db6a4589c97d9d683b866d93fae4163b516951ae1cc27774a8 rules/vibefort.yaml
6
6
  86821fb93d339d54a9a5a1488128ec2bb313d04ec0213a6b88a58f9d6e94068c scanners.lock.json
7
- fe5e74742162915b779a4370c80a45ff238f54ff2c50dce91481688c3d59dcb9 server.js
7
+ 9d9301fe27919ce338c60d78568155888f305da98fa04b1e45ddb350426b36c6 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,12 +19749,28 @@ 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
+ CONTACT_EMAIL: external_exports.string().email().optional(),
19753
+ REVIEWER_LOGIN: external_exports.enum(["0", "1"]).default("0"),
19754
+ REVIEWER_LOGIN_SECRET: external_exports.string().optional(),
19755
+ REVIEWER_LOGIN_UNTIL: external_exports.string().optional(),
19756
+ SIGNIN_LOCKED: external_exports.enum(["0", "1"]).default("0"),
19757
+ SERVE_SITE: external_exports.enum(["0", "1"]).default("0"),
19758
+ GITLAB_CLIENT_ID: external_exports.string().optional(),
19759
+ GITLAB_CLIENT_SECRET: external_exports.string().optional(),
19760
+ GITLAB_BASE_URL: external_exports.string().default("https://gitlab.com"),
19761
+ GOOGLE_CLIENT_ID: external_exports.string().optional(),
19762
+ GOOGLE_CLIENT_SECRET: external_exports.string().optional(),
19763
+ APPLE_CLIENT_ID: external_exports.string().optional(),
19764
+ APPLE_TEAM_ID: external_exports.string().optional(),
19765
+ APPLE_KEY_ID: external_exports.string().optional(),
19766
+ APPLE_PRIVATE_KEY: external_exports.string().optional(),
19752
19767
  BILLING_MODE: external_exports.enum(["fake", "stripe"]).default("fake"),
19753
19768
  STRIPE_SECRET_KEY: external_exports.string().optional(),
19754
19769
  STRIPE_WEBHOOK_SECRET: external_exports.string().optional(),
19755
19770
  STRIPE_PRICE_ID: external_exports.string().optional(),
19756
19771
  STRIPE_PRICE_MONTH: external_exports.string().optional(),
19757
19772
  STRIPE_PRICE_YEAR: external_exports.string().optional(),
19773
+ STRIPE_API_VERSION: external_exports.string().default("2025-09-30.clover"),
19758
19774
  MANTIS_DIR: external_exports.string().default("vendor/mantis"),
19759
19775
  OSV_MODE: external_exports.enum(["real", "fake"]).default("real"),
19760
19776
  FREE_REPOS: num(1),
@@ -19770,18 +19786,38 @@ var env = external_exports.object({
19770
19786
  TRIAL_DAYS: num(30),
19771
19787
  PERIOD_GRACE_DAYS: num(3),
19772
19788
  SKILL_TOKEN_BUDGET: num(12e3),
19789
+ ANSWER_CHAR_BUDGET: external_exports.coerce.number().min(12e3).default(3e4),
19773
19790
  MAX_RESEARCH_TASKS: num(12),
19774
19791
  MAX_INVESTIGATIONS: num(8),
19775
19792
  SWEEP: external_exports.enum(["on", "off"]).default("on"),
19776
19793
  SWEEP_FILES_PER_TASK: num(10),
19777
19794
  SPOT_CHECKS: num(3),
19795
+ SPOT_CHECKS_MAX: num(6),
19796
+ RISK_STEPS_MAX: num(20),
19797
+ STEP_FILES: num(8),
19798
+ STEP_KB: num(120),
19799
+ SWEEP_FILES_MAX: num(80),
19800
+ LARGE_FILE_KB: num(40),
19801
+ MAX_INVESTIGATIONS_CEILING: num(16),
19778
19802
  INVENTORY_MAX: num(2e3),
19803
+ INVENTORY_OMITTED_MAX: num(400),
19804
+ TREE_MODE: external_exports.enum(["real", "fake", "off"]).default("real"),
19805
+ TREE_TIMEOUT_MS: num(8e3),
19806
+ DEPS_PER_REPORT: num(1e3),
19807
+ COLLECT_CODE_TTL_MIN: num(60),
19808
+ COLLECT_MAX_BYTES: num(8e6),
19809
+ FULL_COVERAGE: external_exports.enum(["on", "off"]).default("on"),
19810
+ STEP_LINES: num(1500),
19811
+ COVER_STEP_FILES: num(12),
19812
+ PROOF_CHECK: external_exports.enum(["on", "off"]).default("on"),
19813
+ PROOF_CODE_TTL_MIN: num(60),
19814
+ REVIEW_REOPEN_MAX: num(2),
19779
19815
  ATTACK_CLASSES: external_exports.enum(["on", "off"]).default("off"),
19780
19816
  ATTACK_CLASS_BUDGET: num(3e3),
19781
19817
  TEAM_SEATS: num(5),
19782
19818
  INVITE_TTL_DAYS: num(7),
19783
19819
  LAPSE_READONLY_DAYS: num(30),
19784
- TEAM_WORKSPACES_PER_OWNER: num(1),
19820
+ TEAM_WORKSPACES_PER_OWNER: num(10),
19785
19821
  INVITE_CREATE_PER_DAY: num(20),
19786
19822
  INVITEE_RETENTION_DAYS: num(30),
19787
19823
  INVITE_DELIVERY: external_exports.enum(["dev"]).default("dev"),
@@ -19811,6 +19847,12 @@ var config2 = {
19811
19847
  host: env.HOST,
19812
19848
  /** Gated private beta (feature 032, D-264): allows AUTH_MODE=dev and BILLING_MODE=fake in production, only with DEV_ACCESS_TOKEN. */
19813
19849
  privateBeta: env.PRIVATE_BETA === "1",
19850
+ /** The owner's contact address shown on the legal pages (043, D-399). */
19851
+ contactEmail: env.CONTACT_EMAIL,
19852
+ /** Temporary review account for app directory reviewers (043, D-396). Off unless all three values are set and valid: see src/auth/reviewer.ts. */
19853
+ reviewer: { flag: env.REVIEWER_LOGIN === "1", secret: env.REVIEWER_LOGIN_SECRET ?? "", until: env.REVIEWER_LOGIN_UNTIL ?? "" },
19854
+ /** Serve the marketing site (site/dist) from this server, and send other host names of the domain to PUBLIC_URL (D-303). */
19855
+ serveSite: env.SERVE_SITE === "1",
19814
19856
  publicUrl: env.PUBLIC_URL.replace(/\/$/, ""),
19815
19857
  databaseUrl: env.DATABASE_URL,
19816
19858
  pgliteDir: env.PGLITE_DIR,
@@ -19818,18 +19860,45 @@ var config2 = {
19818
19860
  devAccessToken: env.DEV_ACCESS_TOKEN,
19819
19861
  appSecret: env.APP_SECRET ?? randomBytes(32).toString("hex"),
19820
19862
  github: { clientId: env.GITHUB_CLIENT_ID, clientSecret: env.GITHUB_CLIENT_SECRET },
19863
+ /** 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. */
19864
+ signIn: {
19865
+ gitlab: { baseUrl: env.GITLAB_BASE_URL.replace(/\/$/, ""), clientId: env.GITLAB_CLIENT_ID, clientSecret: env.GITLAB_CLIENT_SECRET },
19866
+ google: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET },
19867
+ apple: { clientId: env.APPLE_CLIENT_ID, teamId: env.APPLE_TEAM_ID, keyId: env.APPLE_KEY_ID, privateKey: env.APPLE_PRIVATE_KEY },
19868
+ emailLinkMinutes: 15,
19869
+ /** Public launch before any provider exists (D-303): every method is listed as "coming soon" and none can start. */
19870
+ locked: env.SIGNIN_LOCKED === "1"
19871
+ },
19821
19872
  billingMode: env.BILLING_MODE,
19822
- 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 },
19873
+ 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, apiVersion: env.STRIPE_API_VERSION },
19823
19874
  mantisDir: env.MANTIS_DIR,
19824
19875
  osvMode: env.OSV_MODE,
19825
19876
  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,
19877
+ /** 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). */
19878
+ answerCharBudget: env.ANSWER_CHAR_BUDGET,
19879
+ /** One limit for the threat-model text: stage contract, tool schema and refusal message (036, FR-029). */
19880
+ threatModelMaxChars: 6e3,
19828
19881
  maxResearchTasks: env.MAX_RESEARCH_TASKS,
19829
19882
  maxInvestigations: env.MAX_INVESTIGATIONS,
19830
19883
  sweep: { enabled: env.SWEEP === "on", filesPerTask: Math.max(1, env.SWEEP_FILES_PER_TASK) },
19831
19884
  spotChecks: Math.max(0, env.SPOT_CHECKS),
19885
+ spotChecksMax: Math.max(0, env.SPOT_CHECKS_MAX),
19886
+ /** Coverage engine budgets (037, D-320): they grow with the reviewable files and stop at these ceilings. */
19887
+ 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
19888
  inventoryMax: Math.max(1, env.INVENTORY_MAX),
19889
+ inventoryOmittedMax: Math.max(1, env.INVENTORY_OMITTED_MAX),
19890
+ /** 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. */
19891
+ tree: { mode: env.TREE_MODE, timeoutMs: Math.max(500, env.TREE_TIMEOUT_MS) },
19892
+ /** Largest number of packages in one dependency report (038, D-332). */
19893
+ depsPerReport: Math.max(50, env.DEPS_PER_REPORT),
19894
+ /** 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. */
19895
+ collect: { codeTtlMin: Math.max(1, env.COLLECT_CODE_TTL_MIN), maxBytes: Math.max(1e4, env.COLLECT_MAX_BYTES) },
19896
+ /** 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. */
19897
+ fullCoverage: { enabled: env.FULL_COVERAGE === "on", stepLines: Math.max(100, env.STEP_LINES), stepFiles: Math.max(1, env.COVER_STEP_FILES) },
19898
+ /** 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. */
19899
+ proof: { enabled: env.PROOF_CHECK === "on", codeTtlMin: Math.max(1, env.PROOF_CODE_TTL_MIN) },
19900
+ /** How often a review step reopens for files left unread without an accepted reason (041, D-369); 0 restores the 039 behaviour. */
19901
+ reviewReopenMax: Math.max(0, env.REVIEW_REOPEN_MAX),
19833
19902
  /** Local scanning (feature 013). Which engines run per plan is configuration (FR-017); safety basics stay on for every plan. */
19834
19903
  localScans: {
19835
19904
  engines: { secrets: true, deps: true, code: true, config: true },
@@ -19879,17 +19948,67 @@ var config2 = {
19879
19948
  var SERVICES = ["supabase", "firebase", "vercel", "neon", "stripe", "clerk", "auth0", "cloudflare", "netlify", "railway", "render", "upstash", "aws", "azure", "gcloud", "kubernetes"];
19880
19949
 
19881
19950
  // 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;
19951
+ var M = "\u2026[masked]";
19952
+ var cut = (t, n = 4) => `${t.slice(0, n)}${M}`;
19953
+ 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;
19954
+ var SEP = /[-_.+=/:@]+/;
19955
+ var CRED_PREFIX = /^(?:whsec|sk|pk|rk|gh[pousr]|github_pat|glpat|xox[baprs]|npm|AKIA|ASIA|AIza|SG|eyJ)[_\-.]/;
19956
+ function camelWordy(s) {
19957
+ if (!/^[A-Za-z]+$/.test(s)) return false;
19958
+ if (s === s.toLowerCase() || s === s.toUpperCase()) return true;
19959
+ const words = s.match(/[A-Z]?[a-z]+|[A-Z]+(?![a-z])/g) ?? [];
19960
+ return words.filter((w) => w.length === 1).length <= 1 && words.length <= Math.max(1, Math.floor(s.length / 3));
19961
+ }
19962
+ function wordy(t) {
19963
+ if (CRED_PREFIX.test(t)) return false;
19964
+ const segs = t.split(SEP).filter(Boolean);
19965
+ return segs.length > 0 && segs.every((s) => TECH.test(s) || camelWordy(s));
19966
+ }
19967
+ function pinnedId(t, off, str) {
19968
+ 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));
19969
+ if (!/^[0-9a-f]{7,64}$/.test(t)) return false;
19970
+ 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));
19971
+ }
19972
+ function valueLike(v) {
19973
+ if (v.includes(M)) return false;
19974
+ if (/\d/.test(v) && /[A-Za-z]/.test(v)) return !wordy(v);
19975
+ if (/[!#$%^&*~@]/.test(v) && !/^[\w.+-]+@[\w-]+\.[\w.-]+$/.test(v)) return true;
19976
+ if (/^[A-Za-z]{12,}$/.test(v)) return !camelWordy(v);
19977
+ return v.length >= 20 && !wordy(v);
19978
+ }
19979
+ 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;
19980
+ 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;
19981
+ var CODE_REF = /^[A-Za-z_$][\w$]*(?:\??\.[\w$]+)+\(?$|[([<{]|^\$/;
19883
19982
  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]`);
19983
+ 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) => {
19984
+ const q2 = /^["'`]/.test(v) ? v[0] : "", inner = q2 ? v.slice(1, -1) : v;
19985
+ if (inner.includes(M)) return m;
19986
+ if (!q2 && (KEYWORD.test(inner.replace(/\.+$/, "")) || CODE_REF.test(inner))) return m;
19987
+ const word = a.match(/^[A-Za-z0-9_]+/)?.[0] ?? "", pre = str.slice(0, off).slice(-1);
19988
+ const inIdentifier = /[_0-9]/.test(pre) || /[a-z]/.test(pre) && /^[A-Z]/.test(word) || /[A-Z]/.test(pre) && word === word.toUpperCase();
19989
+ const loneWord = /^(?:keys?|pass)$/i.test(word) && !inIdentifier;
19990
+ if (loneWord && !valueLike(inner)) return m;
19991
+ return `${a}${q2}${inner.slice(0, 2)}${M}${q2}`;
19992
+ }).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) => {
19993
+ const g = t.split("-");
19994
+ if (/\d/.test(t)) return g.every((x) => x.length === g[0].length) || !wordy(t) ? cut(t) : t;
19995
+ 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;
19996
+ }).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}`);
19997
+ }
19998
+ function nameLike(v) {
19999
+ const words = v.replace(/\$\{\{[^}]*\}\}|\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+|\?/g, " ").trim().split(/[\s=,;()*<>]+/).map((w) => w.replace(/^[([{<]+|[)\]}>.,:;!?]+$/g, "")).filter(Boolean);
20000
+ return words.every((w) => wordy(w));
19888
20001
  }
19889
20002
  function maskExcerpt(s) {
19890
20003
  return maskAlways(s).split("\n").map((line) => {
19891
20004
  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);
20005
+ return line.replace(/(["'`])([^"'`\n]*)\1/g, (m, q2, v, off, str) => {
20006
+ if (v.includes(M) || v.length < 3) return m;
20007
+ const SECRET_ID = "[\\w.$\\]\\[]*(?:passw(?:or)?d|passphrase|pwd|pw|pass|secret|token|key|credential)\\w*";
20008
+ 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));
20009
+ const hit = secretLine ? compared || !nameLike(v) : /^\S{12,}$/.test(v) && !/[\/.]/.test(v) && !nameLike(v);
20010
+ return hit ? `${q2}${v.slice(0, 2)}${M}${q2}` : m;
20011
+ });
19893
20012
  }).join("\n");
19894
20013
  }
19895
20014
 
@@ -20944,39 +21063,45 @@ var sev = (s) => {
20944
21063
  const v = (s ?? "").toUpperCase();
20945
21064
  return v === "CRITICAL" ? "CRITICAL" : v === "HIGH" ? "HIGH" : v === "LOW" ? "LOW" : "MEDIUM";
20946
21065
  };
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" });
21066
+ var OSV_BATCH = 1e3;
21067
+ function osvSourceWith(fetchFn = fetch) {
21068
+ return {
21069
+ async lookup(deps) {
21070
+ const out = /* @__PURE__ */ new Map();
21071
+ let budget = 25;
21072
+ for (let at = 0; at < deps.length; at += OSV_BATCH) {
21073
+ const list = deps.slice(at, at + OSV_BATCH);
21074
+ const res2 = await fetchFn("https://api.osv.dev/v1/querybatch", {
21075
+ method: "POST",
21076
+ headers: { "content-type": "application/json" },
21077
+ signal: AbortSignal.timeout(2e4),
21078
+ body: JSON.stringify({ queries: list.map((d) => ({ package: { name: d.name, ecosystem: d.ecosystem ?? "npm" }, version: d.version })) })
21079
+ });
21080
+ if (!res2.ok) throw new Error(`OSV ${res2.status}`);
21081
+ const body = await res2.json();
21082
+ for (let i = 0; i < list.length; i++) {
21083
+ const vulns = [];
21084
+ for (const { id } of (body.results[i]?.vulns ?? []).slice(0, 3)) {
21085
+ if (budget-- <= 0) {
21086
+ vulns.push({ id, summary: "Known vulnerability", severity: "MEDIUM" });
21087
+ continue;
21088
+ }
21089
+ try {
21090
+ const d = await (await fetchFn(`https://api.osv.dev/v1/vulns/${encodeURIComponent(id)}`, { signal: AbortSignal.timeout(8e3) })).json();
21091
+ const fixed = d.affected?.flatMap((a) => a.ranges?.flatMap((r) => r.events?.map((e) => e.fixed).filter(Boolean) ?? []) ?? [])[0];
21092
+ vulns.push({ id, summary: String(d.summary ?? d.details ?? "Known vulnerability").slice(0, 200), severity: sev(d.database_specific?.severity), fixedIn: fixed });
21093
+ } catch {
21094
+ vulns.push({ id, summary: "Known vulnerability", severity: "MEDIUM" });
21095
+ }
21096
+ }
21097
+ if (vulns.length) out.set(depKey(list[i]), vulns);
20973
21098
  }
20974
21099
  }
20975
- if (vulns.length) out.set(depKey(list[i]), vulns);
21100
+ return out;
20976
21101
  }
20977
- return out;
20978
- }
20979
- };
21102
+ };
21103
+ }
21104
+ var osvSource = osvSourceWith();
20980
21105
  var fakeVulnSource = {
20981
21106
  async lookup(deps) {
20982
21107
  const out = /* @__PURE__ */ new Map();