@grwnd/pi-governance 3.0.2 → 3.1.0

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/dist/index.cjs CHANGED
@@ -3085,7 +3085,15 @@ __export(index_exports, {
3085
3085
  createApprovalFlow: () => createApprovalFlow,
3086
3086
  createIdentityChain: () => createIdentityChain,
3087
3087
  createPolicyEngine: () => createPolicyEngine,
3088
+ detectTyposquat: () => detectTyposquat,
3089
+ evaluateInstall: () => evaluateInstall,
3090
+ fetchRegistryMetadata: () => fetchRegistryMetadata,
3091
+ levenshteinDistance: () => levenshteinDistance,
3088
3092
  loadConfig: () => loadConfig,
3093
+ normalizedSimilarity: () => normalizedSimilarity,
3094
+ parseInstallCommand: () => parseInstallCommand,
3095
+ queryVulnerabilities: () => queryVulnerabilities,
3096
+ queryVulnerabilitiesBatch: () => queryVulnerabilitiesBatch,
3089
3097
  renderTemplate: () => render,
3090
3098
  startWizardServer: () => startWizardServer
3091
3099
  });
@@ -3234,6 +3242,32 @@ var DlpConfig = import_typebox.Type.Object({
3234
3242
  allowlist: import_typebox.Type.Optional(import_typebox.Type.Array(DlpAllowlistEntryConfig)),
3235
3243
  role_overrides: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), DlpRoleOverrideConfig))
3236
3244
  });
3245
+ var DependencyGuardianChecksConfig = import_typebox.Type.Object({
3246
+ existence: import_typebox.Type.Boolean({ default: true }),
3247
+ reputation: import_typebox.Type.Boolean({ default: true }),
3248
+ typosquatting: import_typebox.Type.Boolean({ default: true }),
3249
+ install_scripts: import_typebox.Type.Boolean({ default: true }),
3250
+ vulnerabilities: import_typebox.Type.Boolean({ default: true })
3251
+ });
3252
+ var DependencyGuardianConfig = import_typebox.Type.Object({
3253
+ enabled: import_typebox.Type.Boolean({ default: true }),
3254
+ checks: import_typebox.Type.Optional(DependencyGuardianChecksConfig),
3255
+ risk_thresholds: import_typebox.Type.Optional(
3256
+ import_typebox.Type.Object({
3257
+ min_age_days: import_typebox.Type.Number({ default: 30, minimum: 0 }),
3258
+ min_weekly_downloads: import_typebox.Type.Number({ default: 100, minimum: 0 })
3259
+ })
3260
+ ),
3261
+ on_risk: import_typebox.Type.Optional(
3262
+ import_typebox.Type.Union([import_typebox.Type.Literal("escalate"), import_typebox.Type.Literal("block"), import_typebox.Type.Literal("audit")], {
3263
+ default: "escalate"
3264
+ })
3265
+ ),
3266
+ allowlist: import_typebox.Type.Optional(import_typebox.Type.Array(import_typebox.Type.String())),
3267
+ blocklist: import_typebox.Type.Optional(import_typebox.Type.Array(import_typebox.Type.String())),
3268
+ blocklist_patterns: import_typebox.Type.Optional(import_typebox.Type.Array(import_typebox.Type.String())),
3269
+ custom_registry_bypass: import_typebox.Type.Boolean({ default: true })
3270
+ });
3237
3271
  var GovernanceConfigSchema = import_typebox.Type.Object({
3238
3272
  auth: import_typebox.Type.Optional(AuthConfig),
3239
3273
  policy: import_typebox.Type.Optional(PolicyConfig),
@@ -3241,6 +3275,7 @@ var GovernanceConfigSchema = import_typebox.Type.Object({
3241
3275
  hitl: import_typebox.Type.Optional(HitlConfig),
3242
3276
  audit: import_typebox.Type.Optional(AuditConfig),
3243
3277
  dlp: import_typebox.Type.Optional(DlpConfig),
3278
+ dependency_guardian: import_typebox.Type.Optional(DependencyGuardianConfig),
3244
3279
  org_units: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), OrgUnitOverride))
3245
3280
  });
3246
3281
 
@@ -4061,6 +4096,1015 @@ var DlpMasker = class {
4061
4096
  }
4062
4097
  };
4063
4098
 
4099
+ // src/lib/deps/parser.ts
4100
+ var FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
4101
+ // npm/yarn/pnpm
4102
+ "--registry",
4103
+ "--save-prefix",
4104
+ "--tag",
4105
+ "--cache",
4106
+ "--prefix",
4107
+ // pip
4108
+ "-r",
4109
+ "--requirement",
4110
+ "-c",
4111
+ "--constraint",
4112
+ "-e",
4113
+ "--editable",
4114
+ "-t",
4115
+ "--target",
4116
+ "--index-url",
4117
+ "-i",
4118
+ "--extra-index-url",
4119
+ "--find-links",
4120
+ "-f",
4121
+ "--root",
4122
+ "--prefix",
4123
+ // cargo
4124
+ "--git",
4125
+ "--branch",
4126
+ "--rev",
4127
+ "--path",
4128
+ "--version"
4129
+ ]);
4130
+ var CUSTOM_REGISTRY_FLAGS = /* @__PURE__ */ new Set(["--registry", "--index-url", "-i", "--extra-index-url"]);
4131
+ var LOCKFILE_PATTERNS = [
4132
+ /\bnpm\s+ci\b/,
4133
+ /\bpnpm\s+install\s+--frozen-lockfile\b/,
4134
+ /\byarn\s+install\s+--frozen-lockfile\b/,
4135
+ /\bpip\s+install\b.*--require-hashes\b/
4136
+ ];
4137
+ var MANAGER_MATCHERS = [
4138
+ { manager: "npm", ecosystem: "npm", subcommandPattern: /\bnpm\s+(install|i|add)\s/ },
4139
+ { manager: "yarn", ecosystem: "npm", subcommandPattern: /\byarn\s+(add|install)\s/ },
4140
+ { manager: "pnpm", ecosystem: "npm", subcommandPattern: /\bpnpm\s+(add|install|i)\s/ },
4141
+ { manager: "pip", ecosystem: "pypi", subcommandPattern: /\bpip\s+install\s/ },
4142
+ { manager: "cargo", ecosystem: "crates.io", subcommandPattern: /\bcargo\s+(add|install)\s/ }
4143
+ ];
4144
+ function splitVersionFromName(raw, ecosystem) {
4145
+ if (ecosystem === "npm") {
4146
+ const atIdx = raw.lastIndexOf("@");
4147
+ if (atIdx > 0) {
4148
+ return { name: raw.slice(0, atIdx), version: raw.slice(atIdx + 1) };
4149
+ }
4150
+ return { name: raw };
4151
+ }
4152
+ if (ecosystem === "pypi") {
4153
+ const match = raw.match(/^([a-zA-Z0-9_.-]+)([=<>!~]+.+)?$/);
4154
+ if (match) {
4155
+ return { name: match[1], version: match[2] };
4156
+ }
4157
+ return { name: raw };
4158
+ }
4159
+ if (ecosystem === "crates.io") {
4160
+ const atIdx = raw.indexOf("@");
4161
+ if (atIdx > 0) {
4162
+ return { name: raw.slice(0, atIdx), version: raw.slice(atIdx + 1) };
4163
+ }
4164
+ return { name: raw };
4165
+ }
4166
+ return { name: raw };
4167
+ }
4168
+ function parseInstallCommand(command) {
4169
+ const trimmed = command.trim();
4170
+ const isLockfileInstall = LOCKFILE_PATTERNS.some((p) => p.test(trimmed));
4171
+ if (/\bnpm\s+ci\b/.test(trimmed)) {
4172
+ return {
4173
+ manager: "npm",
4174
+ packages: [],
4175
+ flags: [],
4176
+ raw: trimmed,
4177
+ isLockfileInstall: true,
4178
+ usesCustomRegistry: false
4179
+ };
4180
+ }
4181
+ for (const matcher of MANAGER_MATCHERS) {
4182
+ if (!matcher.subcommandPattern.test(trimmed)) continue;
4183
+ const subMatch = trimmed.match(matcher.subcommandPattern);
4184
+ if (!subMatch) continue;
4185
+ const afterSubcommand = trimmed.slice(subMatch.index + subMatch[0].length);
4186
+ const tokens = tokenize(afterSubcommand);
4187
+ const packages = [];
4188
+ const flags = [];
4189
+ let usesCustomRegistry = false;
4190
+ for (let i = 0; i < tokens.length; i++) {
4191
+ const token = tokens[i];
4192
+ if (token.startsWith("-")) {
4193
+ flags.push(token);
4194
+ const flagName = token.includes("=") ? token.slice(0, token.indexOf("=")) : token;
4195
+ if (CUSTOM_REGISTRY_FLAGS.has(flagName)) {
4196
+ usesCustomRegistry = true;
4197
+ }
4198
+ if (FLAGS_WITH_VALUE.has(flagName) || token.includes("=")) {
4199
+ if (!token.includes("=")) i++;
4200
+ }
4201
+ continue;
4202
+ }
4203
+ if (matcher.manager === "pip" && (token.endsWith(".txt") || token.endsWith(".cfg"))) {
4204
+ continue;
4205
+ }
4206
+ if (matcher.manager === "pip" && (token.startsWith("/") || token.startsWith("./") || token.startsWith("http://") || token.startsWith("https://") || token.startsWith("git+"))) {
4207
+ continue;
4208
+ }
4209
+ const { name, version } = splitVersionFromName(token, matcher.ecosystem);
4210
+ if (name) {
4211
+ packages.push({ name, version, ecosystem: matcher.ecosystem });
4212
+ }
4213
+ }
4214
+ return {
4215
+ manager: matcher.manager,
4216
+ packages,
4217
+ flags,
4218
+ raw: trimmed,
4219
+ isLockfileInstall,
4220
+ usesCustomRegistry
4221
+ };
4222
+ }
4223
+ return void 0;
4224
+ }
4225
+ function tokenize(input) {
4226
+ const tokens = [];
4227
+ let current = "";
4228
+ let inSingleQuote = false;
4229
+ let inDoubleQuote = false;
4230
+ for (let i = 0; i < input.length; i++) {
4231
+ const ch = input[i];
4232
+ if (ch === "'" && !inDoubleQuote) {
4233
+ inSingleQuote = !inSingleQuote;
4234
+ continue;
4235
+ }
4236
+ if (ch === '"' && !inSingleQuote) {
4237
+ inDoubleQuote = !inDoubleQuote;
4238
+ continue;
4239
+ }
4240
+ if (ch === " " && !inSingleQuote && !inDoubleQuote) {
4241
+ if (current) tokens.push(current);
4242
+ current = "";
4243
+ } else {
4244
+ current += ch;
4245
+ }
4246
+ }
4247
+ if (current) tokens.push(current);
4248
+ return tokens;
4249
+ }
4250
+
4251
+ // src/lib/deps/registry.ts
4252
+ var REQUEST_TIMEOUT_MS = 5e3;
4253
+ function withTimeout(ms) {
4254
+ return AbortSignal.timeout(ms);
4255
+ }
4256
+ async function fetchNpmMetadata(name) {
4257
+ const encodedName = name.startsWith("@") ? `@${encodeURIComponent(name.slice(1))}` : encodeURIComponent(name);
4258
+ const url = `https://registry.npmjs.org/${encodedName}`;
4259
+ let res;
4260
+ try {
4261
+ res = await fetch(url, { signal: withTimeout(REQUEST_TIMEOUT_MS) });
4262
+ } catch {
4263
+ return notFound(name, "npm");
4264
+ }
4265
+ if (!res.ok) return notFound(name, "npm");
4266
+ const data = await res.json();
4267
+ const latest = data["dist-tags"]?.["latest"];
4268
+ const latestVersion = latest ? data.versions?.[latest] : void 0;
4269
+ const scripts = latestVersion?.scripts ?? {};
4270
+ const hasInstallScripts = !!(scripts["preinstall"] || scripts["install"] || scripts["postinstall"]);
4271
+ let weeklyDownloads;
4272
+ try {
4273
+ const dlUrl = `https://api.npmjs.org/downloads/point/last-week/${encodedName}`;
4274
+ const dlRes = await fetch(dlUrl, { signal: withTimeout(REQUEST_TIMEOUT_MS) });
4275
+ if (dlRes.ok) {
4276
+ const dlData = await dlRes.json();
4277
+ weeklyDownloads = dlData.downloads;
4278
+ }
4279
+ } catch {
4280
+ }
4281
+ return {
4282
+ name,
4283
+ ecosystem: "npm",
4284
+ exists: true,
4285
+ createdAt: data.time?.["created"] ? new Date(data.time["created"]) : void 0,
4286
+ modifiedAt: data.time?.["modified"] ? new Date(data.time["modified"]) : void 0,
4287
+ latestVersion: latest,
4288
+ weeklyDownloads,
4289
+ maintainerCount: data.maintainers?.length,
4290
+ hasRepository: !!data.repository,
4291
+ hasReadme: !!(data.readme && data.readme.length > 10),
4292
+ hasInstallScripts,
4293
+ description: data.description,
4294
+ license: data.license
4295
+ };
4296
+ }
4297
+ async function fetchPyPIMetadata(name) {
4298
+ const url = `https://pypi.org/pypi/${encodeURIComponent(name)}/json`;
4299
+ let res;
4300
+ try {
4301
+ res = await fetch(url, { signal: withTimeout(REQUEST_TIMEOUT_MS) });
4302
+ } catch {
4303
+ return notFound(name, "pypi");
4304
+ }
4305
+ if (!res.ok) return notFound(name, "pypi");
4306
+ const data = await res.json();
4307
+ let earliest;
4308
+ for (const files of Object.values(data.releases)) {
4309
+ for (const file of files) {
4310
+ const d = new Date(file.upload_time);
4311
+ if (!earliest || d < earliest) earliest = d;
4312
+ }
4313
+ }
4314
+ const projectUrls = data.info.project_urls ?? {};
4315
+ const hasRepo = !!(data.info.home_page || projectUrls["Source"] || projectUrls["Repository"] || projectUrls["GitHub"] || projectUrls["Homepage"]);
4316
+ const hasMaintainer = !!(data.info.maintainer || data.info.author);
4317
+ let weeklyDownloads;
4318
+ try {
4319
+ const statsUrl = `https://pypistats.org/api/packages/${encodeURIComponent(name)}/recent`;
4320
+ const statsRes = await fetch(statsUrl, {
4321
+ signal: withTimeout(REQUEST_TIMEOUT_MS),
4322
+ headers: { Accept: "application/json" }
4323
+ });
4324
+ if (statsRes.ok) {
4325
+ const statsData = await statsRes.json();
4326
+ weeklyDownloads = statsData.data?.last_week;
4327
+ }
4328
+ } catch {
4329
+ }
4330
+ return {
4331
+ name,
4332
+ ecosystem: "pypi",
4333
+ exists: true,
4334
+ createdAt: earliest,
4335
+ modifiedAt: data.urls.length > 0 ? new Date(data.urls[data.urls.length - 1].upload_time) : void 0,
4336
+ latestVersion: data.info.version,
4337
+ weeklyDownloads,
4338
+ maintainerCount: hasMaintainer ? 1 : 0,
4339
+ hasRepository: hasRepo,
4340
+ hasReadme: !!(data.info.description && data.info.description.length > 10),
4341
+ hasInstallScripts: false,
4342
+ // PyPI doesn't have post-install scripts in the same way
4343
+ description: data.info.summary,
4344
+ license: data.info.license
4345
+ };
4346
+ }
4347
+ function notFound(name, ecosystem) {
4348
+ return {
4349
+ name,
4350
+ ecosystem,
4351
+ exists: false,
4352
+ hasRepository: false,
4353
+ hasReadme: false,
4354
+ hasInstallScripts: false
4355
+ };
4356
+ }
4357
+ var cache = /* @__PURE__ */ new Map();
4358
+ var MAX_CACHE_SIZE = 200;
4359
+ function cacheKey(name, ecosystem) {
4360
+ return `${ecosystem}:${name}`;
4361
+ }
4362
+ async function fetchRegistryMetadata(name, ecosystem) {
4363
+ const key = cacheKey(name, ecosystem);
4364
+ const cached = cache.get(key);
4365
+ if (cached) return cached;
4366
+ let result;
4367
+ switch (ecosystem) {
4368
+ case "npm":
4369
+ result = await fetchNpmMetadata(name);
4370
+ break;
4371
+ case "pypi":
4372
+ result = await fetchPyPIMetadata(name);
4373
+ break;
4374
+ default:
4375
+ result = notFound(name, ecosystem);
4376
+ }
4377
+ if (cache.size >= MAX_CACHE_SIZE) {
4378
+ const firstKey = cache.keys().next().value;
4379
+ if (firstKey !== void 0) cache.delete(firstKey);
4380
+ }
4381
+ cache.set(key, result);
4382
+ return result;
4383
+ }
4384
+
4385
+ // src/lib/deps/vulnerabilities.ts
4386
+ var REQUEST_TIMEOUT_MS2 = 5e3;
4387
+ function osvEcosystem(ecosystem) {
4388
+ switch (ecosystem) {
4389
+ case "npm":
4390
+ return "npm";
4391
+ case "pypi":
4392
+ return "PyPI";
4393
+ case "crates.io":
4394
+ return "crates.io";
4395
+ default:
4396
+ return ecosystem;
4397
+ }
4398
+ }
4399
+ function parseSeverity(vuln) {
4400
+ const sevEntry = vuln.severity?.find((s) => s.type === "CVSS_V3");
4401
+ if (!sevEntry) return "medium";
4402
+ const scoreStr = sevEntry.score;
4403
+ const numericMatch = scoreStr.match(/(\d+\.\d+)/);
4404
+ if (numericMatch) {
4405
+ const score = parseFloat(numericMatch[1]);
4406
+ if (score >= 9) return "critical";
4407
+ if (score >= 7) return "high";
4408
+ if (score >= 4) return "medium";
4409
+ return "low";
4410
+ }
4411
+ return "medium";
4412
+ }
4413
+ function extractFixedVersion(vuln) {
4414
+ for (const affected of vuln.affected ?? []) {
4415
+ for (const range of affected.ranges ?? []) {
4416
+ for (const event of range.events ?? []) {
4417
+ if (event.fixed) return event.fixed;
4418
+ }
4419
+ }
4420
+ }
4421
+ return void 0;
4422
+ }
4423
+ function mapVuln(vuln) {
4424
+ return {
4425
+ id: vuln.id,
4426
+ summary: vuln.summary ?? vuln.details?.slice(0, 200) ?? "No description",
4427
+ severity: parseSeverity(vuln),
4428
+ fixedIn: extractFixedVersion(vuln),
4429
+ aliases: vuln.aliases ?? []
4430
+ };
4431
+ }
4432
+ async function queryVulnerabilities(name, ecosystem, version) {
4433
+ const body = {
4434
+ package: { name, ecosystem: osvEcosystem(ecosystem) }
4435
+ };
4436
+ if (version) body.version = version;
4437
+ try {
4438
+ const res = await fetch("https://api.osv.dev/v1/query", {
4439
+ method: "POST",
4440
+ headers: { "Content-Type": "application/json" },
4441
+ body: JSON.stringify(body),
4442
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
4443
+ });
4444
+ if (!res.ok) {
4445
+ return { package: name, ecosystem, vulnerabilities: [], error: `OSV HTTP ${res.status}` };
4446
+ }
4447
+ const data = await res.json();
4448
+ return {
4449
+ package: name,
4450
+ ecosystem,
4451
+ vulnerabilities: (data.vulns ?? []).map(mapVuln)
4452
+ };
4453
+ } catch (err) {
4454
+ return {
4455
+ package: name,
4456
+ ecosystem,
4457
+ vulnerabilities: [],
4458
+ error: err instanceof Error ? err.message : "Unknown error"
4459
+ };
4460
+ }
4461
+ }
4462
+ async function queryVulnerabilitiesBatch(packages) {
4463
+ if (packages.length === 0) return [];
4464
+ if (packages.length === 1) {
4465
+ const pkg = packages[0];
4466
+ return [await queryVulnerabilities(pkg.name, pkg.ecosystem, pkg.version)];
4467
+ }
4468
+ const queries = packages.map((pkg) => {
4469
+ const q = {
4470
+ package: { name: pkg.name, ecosystem: osvEcosystem(pkg.ecosystem) }
4471
+ };
4472
+ if (pkg.version) q.version = pkg.version;
4473
+ return q;
4474
+ });
4475
+ try {
4476
+ const res = await fetch("https://api.osv.dev/v1/querybatch", {
4477
+ method: "POST",
4478
+ headers: { "Content-Type": "application/json" },
4479
+ body: JSON.stringify({ queries }),
4480
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS2)
4481
+ });
4482
+ if (!res.ok) {
4483
+ return packages.map((pkg) => ({
4484
+ package: pkg.name,
4485
+ ecosystem: pkg.ecosystem,
4486
+ vulnerabilities: [],
4487
+ error: `OSV HTTP ${res.status}`
4488
+ }));
4489
+ }
4490
+ const data = await res.json();
4491
+ return data.results.map((result, i) => ({
4492
+ package: packages[i].name,
4493
+ ecosystem: packages[i].ecosystem,
4494
+ vulnerabilities: (result.vulns ?? []).map(mapVuln)
4495
+ }));
4496
+ } catch (err) {
4497
+ const errMsg = err instanceof Error ? err.message : "Unknown error";
4498
+ return packages.map((pkg) => ({
4499
+ package: pkg.name,
4500
+ ecosystem: pkg.ecosystem,
4501
+ vulnerabilities: [],
4502
+ error: errMsg
4503
+ }));
4504
+ }
4505
+ }
4506
+
4507
+ // src/lib/deps/levenshtein.ts
4508
+ function levenshteinDistance(a, b) {
4509
+ if (a === b) return 0;
4510
+ if (a.length === 0) return b.length;
4511
+ if (b.length === 0) return a.length;
4512
+ if (a.length > b.length) [a, b] = [b, a];
4513
+ const m = a.length;
4514
+ const n = b.length;
4515
+ const row = new Array(m + 1);
4516
+ for (let j = 0; j <= m; j++) row[j] = j;
4517
+ for (let i = 1; i <= n; i++) {
4518
+ let corner = row[0];
4519
+ row[0] = i;
4520
+ for (let j = 1; j <= m; j++) {
4521
+ const temp = row[j];
4522
+ if (b[i - 1] === a[j - 1]) {
4523
+ row[j] = corner;
4524
+ } else {
4525
+ row[j] = 1 + Math.min(corner, temp, row[j - 1]);
4526
+ }
4527
+ corner = temp;
4528
+ }
4529
+ }
4530
+ return row[m];
4531
+ }
4532
+ function normalizedSimilarity(a, b) {
4533
+ const maxLen = Math.max(a.length, b.length);
4534
+ if (maxLen === 0) return 1;
4535
+ return 1 - levenshteinDistance(a, b) / maxLen;
4536
+ }
4537
+ function normalizeName(name) {
4538
+ const stripped = name.replace(/^@[^/]+\//, "");
4539
+ return stripped.replace(/[-_.]/g, "").toLowerCase();
4540
+ }
4541
+ function detectTyposquat(name, corpus) {
4542
+ const normalized = normalizeName(name);
4543
+ let bestMatch;
4544
+ let bestDistance = Infinity;
4545
+ for (const target of corpus) {
4546
+ const normalizedTarget = normalizeName(target);
4547
+ if (normalizedTarget === normalized) return void 0;
4548
+ const distance = levenshteinDistance(normalized, normalizedTarget);
4549
+ const similarity = normalizedSimilarity(normalized, normalizedTarget);
4550
+ const shouldFlag = distance === 1 || distance === 2 && normalized.length >= 5 || similarity >= 0.85;
4551
+ if (shouldFlag && distance < bestDistance) {
4552
+ bestDistance = distance;
4553
+ bestMatch = { target, distance, similarity };
4554
+ }
4555
+ }
4556
+ return bestMatch;
4557
+ }
4558
+
4559
+ // src/lib/deps/allowlist.ts
4560
+ var DEFAULT_NPM_ALLOWLIST = [
4561
+ // Frameworks
4562
+ "express",
4563
+ "react",
4564
+ "react-dom",
4565
+ "next",
4566
+ "vue",
4567
+ "angular",
4568
+ "svelte",
4569
+ "fastify",
4570
+ "koa",
4571
+ "hapi",
4572
+ "nest",
4573
+ "nuxt",
4574
+ // Build tools
4575
+ "typescript",
4576
+ "webpack",
4577
+ "vite",
4578
+ "esbuild",
4579
+ "tsup",
4580
+ "rollup",
4581
+ "parcel",
4582
+ "babel",
4583
+ "swc",
4584
+ // Quality tools
4585
+ "prettier",
4586
+ "eslint",
4587
+ "vitest",
4588
+ "jest",
4589
+ "mocha",
4590
+ "chai",
4591
+ "sinon",
4592
+ "husky",
4593
+ "lint-staged",
4594
+ // Utilities
4595
+ "lodash",
4596
+ "axios",
4597
+ "chalk",
4598
+ "commander",
4599
+ "debug",
4600
+ "async",
4601
+ "uuid",
4602
+ "semver",
4603
+ "glob",
4604
+ "fs-extra",
4605
+ "mkdirp",
4606
+ "rimraf",
4607
+ "minimist",
4608
+ "yargs",
4609
+ "dotenv",
4610
+ "nanoid",
4611
+ "date-fns",
4612
+ "dayjs",
4613
+ "moment",
4614
+ "rxjs",
4615
+ "immer",
4616
+ // Validation
4617
+ "zod",
4618
+ "joi",
4619
+ "ajv",
4620
+ "yup",
4621
+ // HTTP / API
4622
+ "cors",
4623
+ "body-parser",
4624
+ "cookie-parser",
4625
+ "morgan",
4626
+ "helmet",
4627
+ "jsonwebtoken",
4628
+ "bcrypt",
4629
+ "passport",
4630
+ // Database
4631
+ "mongoose",
4632
+ "sequelize",
4633
+ "prisma",
4634
+ "typeorm",
4635
+ "knex",
4636
+ "pg",
4637
+ "mysql2",
4638
+ "redis",
4639
+ "ioredis",
4640
+ "better-sqlite3",
4641
+ // Realtime
4642
+ "socket.io",
4643
+ "ws",
4644
+ // State management
4645
+ "zustand",
4646
+ "redux",
4647
+ "mobx",
4648
+ // CSS
4649
+ "tailwindcss",
4650
+ "postcss",
4651
+ "autoprefixer",
4652
+ "sass",
4653
+ "styled-components",
4654
+ "clsx",
4655
+ "classnames",
4656
+ // Media
4657
+ "sharp",
4658
+ "puppeteer",
4659
+ "cheerio",
4660
+ "marked",
4661
+ "highlight.js",
4662
+ // Types
4663
+ "tslib"
4664
+ ];
4665
+ var DEFAULT_PYPI_ALLOWLIST = [
4666
+ // Web
4667
+ "requests",
4668
+ "flask",
4669
+ "django",
4670
+ "fastapi",
4671
+ "aiohttp",
4672
+ "httpx",
4673
+ "uvicorn",
4674
+ "gunicorn",
4675
+ "starlette",
4676
+ // Data
4677
+ "numpy",
4678
+ "pandas",
4679
+ "scipy",
4680
+ "matplotlib",
4681
+ "scikit-learn",
4682
+ "pillow",
4683
+ // Infrastructure
4684
+ "boto3",
4685
+ "botocore",
4686
+ "urllib3",
4687
+ "certifi",
4688
+ "charset-normalizer",
4689
+ "idna",
4690
+ // Core
4691
+ "setuptools",
4692
+ "pip",
4693
+ "wheel",
4694
+ "packaging",
4695
+ "typing-extensions",
4696
+ "six",
4697
+ "python-dateutil",
4698
+ "pyyaml",
4699
+ "tomli",
4700
+ "filelock",
4701
+ "attrs",
4702
+ "click",
4703
+ "importlib-metadata",
4704
+ "zipp",
4705
+ "platformdirs",
4706
+ // Crypto
4707
+ "cryptography",
4708
+ "cffi",
4709
+ "pycparser",
4710
+ "jmespath",
4711
+ "pyasn1",
4712
+ // DB
4713
+ "sqlalchemy",
4714
+ "psycopg2",
4715
+ "redis",
4716
+ "celery",
4717
+ // Template / Markup
4718
+ "jinja2",
4719
+ "markupsafe",
4720
+ "beautifulsoup4",
4721
+ "lxml",
4722
+ "scrapy",
4723
+ // Validation
4724
+ "pydantic",
4725
+ // Testing
4726
+ "pytest",
4727
+ "tox",
4728
+ "coverage",
4729
+ "mock"
4730
+ ];
4731
+ var DEFAULT_BLOCKLIST_EXACT = [
4732
+ // npm typosquats (historically malicious)
4733
+ "crossenv",
4734
+ "cross-env.js",
4735
+ "d3.js",
4736
+ "fabric-js",
4737
+ "ffmpegs",
4738
+ "gruntcli",
4739
+ "http-proxy.js",
4740
+ "jquery.js",
4741
+ "mongose",
4742
+ "mssql-node",
4743
+ "nodecaffe",
4744
+ "nodefabric",
4745
+ "nodemailer-js",
4746
+ "noderequest",
4747
+ "nodesass",
4748
+ "opencv.js",
4749
+ "openssl.js",
4750
+ "shadowsock",
4751
+ "sqliter",
4752
+ "sqlserver",
4753
+ // npm compromised
4754
+ "flatmap-stream",
4755
+ // PyPI typosquats
4756
+ "colourama",
4757
+ "python3-dateutil",
4758
+ "requesocks",
4759
+ "requesst",
4760
+ "beautifulsup",
4761
+ "numppy",
4762
+ "numpys",
4763
+ "djanga",
4764
+ "urlib3"
4765
+ ];
4766
+ var DEFAULT_BLOCKLIST_PATTERNS = [
4767
+ /-free-download$/,
4768
+ /-crack$/,
4769
+ /-keygen$/,
4770
+ /-license-key$/,
4771
+ /-hack$/,
4772
+ /-serial$/,
4773
+ /-activation$/,
4774
+ /-premium-free$/,
4775
+ /-generator-free$/
4776
+ ];
4777
+ function buildAllowBlockLists(ecosystem, userAllowlist, userBlocklist, userBlocklistPatterns) {
4778
+ const base = ecosystem === "npm" ? DEFAULT_NPM_ALLOWLIST : ecosystem === "pypi" ? DEFAULT_PYPI_ALLOWLIST : [];
4779
+ return {
4780
+ allowlist: [...base, ...userAllowlist],
4781
+ blocklist: [...DEFAULT_BLOCKLIST_EXACT, ...userBlocklist],
4782
+ blocklistPatterns: [
4783
+ ...DEFAULT_BLOCKLIST_PATTERNS,
4784
+ ...userBlocklistPatterns.map((p) => new RegExp(p))
4785
+ ]
4786
+ };
4787
+ }
4788
+ function isAllowlisted(name, allowlist) {
4789
+ return allowlist.includes(name);
4790
+ }
4791
+ function isBlocklisted(name, blocklist, blocklistPatterns) {
4792
+ if (blocklist.includes(name)) return true;
4793
+ return blocklistPatterns.some((p) => p.test(name));
4794
+ }
4795
+
4796
+ // src/lib/deps/risk.ts
4797
+ var DEFAULT_THRESHOLDS = {
4798
+ minAgeDays: 30,
4799
+ minWeeklyDownloads: 100
4800
+ };
4801
+ var SEVERITY_ORDER2 = {
4802
+ info: 0,
4803
+ low: 1,
4804
+ medium: 2,
4805
+ high: 3,
4806
+ critical: 4
4807
+ };
4808
+ function maxSeverity(a, b) {
4809
+ return SEVERITY_ORDER2[a] >= SEVERITY_ORDER2[b] ? a : b;
4810
+ }
4811
+ function computeRiskReport(metadata, vulns, typosquatMatch, isBlocklisted2, isAllowlisted2, thresholds = DEFAULT_THRESHOLDS) {
4812
+ const signals = [];
4813
+ let overallRisk = "info";
4814
+ if (!metadata.exists) {
4815
+ signals.push({
4816
+ name: "package_not_found",
4817
+ severity: "critical",
4818
+ detail: `Package "${metadata.name}" does not exist on ${metadata.ecosystem}`
4819
+ });
4820
+ overallRisk = "critical";
4821
+ }
4822
+ if (isBlocklisted2) {
4823
+ signals.push({
4824
+ name: "on_blocklist",
4825
+ severity: "critical",
4826
+ detail: `Package "${metadata.name}" is on the blocklist`
4827
+ });
4828
+ overallRisk = "critical";
4829
+ }
4830
+ for (const vuln of vulns) {
4831
+ signals.push({
4832
+ name: "known_vulnerability",
4833
+ severity: vuln.severity,
4834
+ detail: `${vuln.id}: ${vuln.summary}${vuln.fixedIn ? ` (fixed in ${vuln.fixedIn})` : ""}`
4835
+ });
4836
+ overallRisk = maxSeverity(overallRisk, vuln.severity);
4837
+ }
4838
+ if (isAllowlisted2 && signals.length === 0) {
4839
+ return {
4840
+ package: metadata.name,
4841
+ ecosystem: metadata.ecosystem,
4842
+ overallRisk: "info",
4843
+ signals: [],
4844
+ vulnerabilities: vulns,
4845
+ recommendation: "allow",
4846
+ metadata
4847
+ };
4848
+ }
4849
+ if (!isAllowlisted2 && metadata.exists) {
4850
+ if (metadata.createdAt) {
4851
+ const ageDays = (Date.now() - metadata.createdAt.getTime()) / (1e3 * 60 * 60 * 24);
4852
+ if (ageDays < 7) {
4853
+ signals.push({
4854
+ name: "very_new_package",
4855
+ severity: "high",
4856
+ detail: `Created ${Math.floor(ageDays)} days ago`
4857
+ });
4858
+ overallRisk = maxSeverity(overallRisk, "high");
4859
+ } else if (ageDays < thresholds.minAgeDays) {
4860
+ signals.push({
4861
+ name: "new_package",
4862
+ severity: "medium",
4863
+ detail: `Created ${Math.floor(ageDays)} days ago (threshold: ${thresholds.minAgeDays})`
4864
+ });
4865
+ overallRisk = maxSeverity(overallRisk, "medium");
4866
+ }
4867
+ }
4868
+ if (metadata.weeklyDownloads !== void 0) {
4869
+ if (metadata.weeklyDownloads < thresholds.minWeeklyDownloads) {
4870
+ const sev = metadata.weeklyDownloads < 10 ? "high" : "medium";
4871
+ signals.push({
4872
+ name: "low_downloads",
4873
+ severity: sev,
4874
+ detail: `${metadata.weeklyDownloads} weekly downloads (threshold: ${thresholds.minWeeklyDownloads})`
4875
+ });
4876
+ overallRisk = maxSeverity(overallRisk, sev);
4877
+ }
4878
+ }
4879
+ if (typosquatMatch) {
4880
+ signals.push({
4881
+ name: "typosquat_suspect",
4882
+ severity: "high",
4883
+ detail: `Similar to "${typosquatMatch.target}" (edit distance: ${typosquatMatch.distance})`
4884
+ });
4885
+ overallRisk = maxSeverity(overallRisk, "high");
4886
+ }
4887
+ if (metadata.hasInstallScripts) {
4888
+ signals.push({
4889
+ name: "has_install_scripts",
4890
+ severity: "medium",
4891
+ detail: "Package has preinstall/install/postinstall scripts"
4892
+ });
4893
+ overallRisk = maxSeverity(overallRisk, "medium");
4894
+ }
4895
+ if (!metadata.hasRepository) {
4896
+ signals.push({
4897
+ name: "no_repository",
4898
+ severity: "low",
4899
+ detail: "No source repository URL in metadata"
4900
+ });
4901
+ overallRisk = maxSeverity(overallRisk, "low");
4902
+ }
4903
+ if (!metadata.hasReadme) {
4904
+ signals.push({
4905
+ name: "no_readme",
4906
+ severity: "low",
4907
+ detail: "No README content"
4908
+ });
4909
+ overallRisk = maxSeverity(overallRisk, "low");
4910
+ }
4911
+ if (!metadata.license) {
4912
+ signals.push({
4913
+ name: "no_license",
4914
+ severity: "low",
4915
+ detail: "No license declared"
4916
+ });
4917
+ overallRisk = maxSeverity(overallRisk, "low");
4918
+ }
4919
+ if (metadata.maintainerCount !== void 0 && metadata.maintainerCount <= 1) {
4920
+ signals.push({
4921
+ name: "single_maintainer",
4922
+ severity: "info",
4923
+ detail: `${metadata.maintainerCount} maintainer(s)`
4924
+ });
4925
+ }
4926
+ }
4927
+ let recommendation;
4928
+ if (SEVERITY_ORDER2[overallRisk] >= SEVERITY_ORDER2["critical"]) {
4929
+ recommendation = "block";
4930
+ } else if (SEVERITY_ORDER2[overallRisk] >= SEVERITY_ORDER2["medium"]) {
4931
+ recommendation = "escalate";
4932
+ } else {
4933
+ recommendation = "allow";
4934
+ }
4935
+ return {
4936
+ package: metadata.name,
4937
+ ecosystem: metadata.ecosystem,
4938
+ overallRisk,
4939
+ signals,
4940
+ vulnerabilities: vulns,
4941
+ recommendation,
4942
+ metadata
4943
+ };
4944
+ }
4945
+
4946
+ // src/lib/deps/guardian.ts
4947
+ var DEFAULT_CONFIG2 = {
4948
+ enabled: true,
4949
+ checks: {
4950
+ existence: true,
4951
+ reputation: true,
4952
+ typosquatting: true,
4953
+ install_scripts: true,
4954
+ vulnerabilities: true
4955
+ },
4956
+ risk_thresholds: {
4957
+ min_age_days: 30,
4958
+ min_weekly_downloads: 100
4959
+ },
4960
+ on_risk: "escalate",
4961
+ allowlist: [],
4962
+ blocklist: [],
4963
+ blocklist_patterns: [],
4964
+ custom_registry_bypass: true
4965
+ };
4966
+ async function evaluateInstall(command, config = DEFAULT_CONFIG2) {
4967
+ if (!config.enabled) {
4968
+ return skippedResult(command, "Dependency guardian is disabled");
4969
+ }
4970
+ const parsed = parseInstallCommand(command);
4971
+ if (!parsed) {
4972
+ return skippedResult(command, "Not a recognized install command");
4973
+ }
4974
+ if (parsed.isLockfileInstall) {
4975
+ return skippedResult(command, "Lock-file install (pinned dependencies)");
4976
+ }
4977
+ if (config.custom_registry_bypass && parsed.usesCustomRegistry) {
4978
+ return skippedResult(command, "Custom registry detected (bypass enabled)");
4979
+ }
4980
+ if (parsed.packages.length === 0) {
4981
+ return skippedResult(command, "No specific packages to validate");
4982
+ }
4983
+ const ecosystems = [...new Set(parsed.packages.map((p) => p.ecosystem))];
4984
+ const listsPerEcosystem = new Map(
4985
+ ecosystems.map((eco) => [
4986
+ eco,
4987
+ buildAllowBlockLists(eco, config.allowlist, config.blocklist, config.blocklist_patterns)
4988
+ ])
4989
+ );
4990
+ const metadataPromises = parsed.packages.map(async (pkg) => {
4991
+ if (!config.checks.existence && !config.checks.reputation && !config.checks.install_scripts) {
4992
+ return void 0;
4993
+ }
4994
+ return fetchRegistryMetadata(pkg.name, pkg.ecosystem);
4995
+ });
4996
+ const vulnPromise = config.checks.vulnerabilities ? queryVulnerabilitiesBatch(
4997
+ parsed.packages.map((p) => ({
4998
+ name: p.name,
4999
+ ecosystem: p.ecosystem,
5000
+ version: p.version
5001
+ }))
5002
+ ) : Promise.resolve(
5003
+ parsed.packages.map((p) => ({
5004
+ package: p.name,
5005
+ ecosystem: p.ecosystem,
5006
+ vulnerabilities: []
5007
+ }))
5008
+ );
5009
+ const [metadataResults, vulnResults] = await Promise.all([
5010
+ Promise.all(metadataPromises),
5011
+ vulnPromise
5012
+ ]);
5013
+ const reports = [];
5014
+ for (let i = 0; i < parsed.packages.length; i++) {
5015
+ const pkg = parsed.packages[i];
5016
+ const lists = listsPerEcosystem.get(pkg.ecosystem);
5017
+ const allowed = isAllowlisted(pkg.name, lists.allowlist);
5018
+ const blocked = isBlocklisted(pkg.name, lists.blocklist, lists.blocklistPatterns);
5019
+ const metadata = metadataResults[i] ?? {
5020
+ name: pkg.name,
5021
+ ecosystem: pkg.ecosystem,
5022
+ exists: true,
5023
+ // assume exists if we didn't check
5024
+ hasRepository: true,
5025
+ hasReadme: true,
5026
+ hasInstallScripts: false
5027
+ };
5028
+ const vulns = vulnResults[i]?.vulnerabilities ?? [];
5029
+ const typosquatMatch = config.checks.typosquatting && !allowed ? detectTyposquat(pkg.name, lists.allowlist) : void 0;
5030
+ const report = computeRiskReport(metadata, vulns, typosquatMatch, blocked, allowed, {
5031
+ minAgeDays: config.risk_thresholds.min_age_days,
5032
+ minWeeklyDownloads: config.risk_thresholds.min_weekly_downloads
5033
+ });
5034
+ reports.push(report);
5035
+ }
5036
+ let overallRecommendation = "allow";
5037
+ for (const report of reports) {
5038
+ if (report.recommendation === "block") {
5039
+ overallRecommendation = "block";
5040
+ break;
5041
+ }
5042
+ if (report.recommendation === "escalate") {
5043
+ overallRecommendation = "escalate";
5044
+ }
5045
+ }
5046
+ if (config.on_risk === "audit" && overallRecommendation === "escalate") {
5047
+ overallRecommendation = "allow";
5048
+ }
5049
+ if (config.on_risk === "block" && overallRecommendation === "escalate") {
5050
+ overallRecommendation = "block";
5051
+ }
5052
+ const summary = formatSummary(command, reports, overallRecommendation);
5053
+ return {
5054
+ command,
5055
+ packages: reports,
5056
+ overallRecommendation,
5057
+ summary,
5058
+ auditMetadata: {
5059
+ command,
5060
+ manager: parsed.manager,
5061
+ packages: reports.map((r) => ({
5062
+ name: r.package,
5063
+ ecosystem: r.ecosystem,
5064
+ risk: r.overallRisk,
5065
+ signals: r.signals.map((s) => s.name),
5066
+ vulnCount: r.vulnerabilities.length
5067
+ }))
5068
+ },
5069
+ skipped: false
5070
+ };
5071
+ }
5072
+ function skippedResult(command, reason) {
5073
+ return {
5074
+ command,
5075
+ packages: [],
5076
+ overallRecommendation: "allow",
5077
+ summary: reason,
5078
+ auditMetadata: { command, skipped: true, reason },
5079
+ skipped: true,
5080
+ skipReason: reason
5081
+ };
5082
+ }
5083
+ function formatSummary(command, reports, recommendation) {
5084
+ const lines = [];
5085
+ lines.push(`Command: ${command}`);
5086
+ lines.push("");
5087
+ for (const report of reports) {
5088
+ if (report.signals.length === 0 && report.vulnerabilities.length === 0) continue;
5089
+ lines.push(`Package: ${report.package} (${report.ecosystem})`);
5090
+ lines.push(`Risk: ${report.overallRisk.toUpperCase()}`);
5091
+ if (report.signals.length > 0) {
5092
+ lines.push("Signals:");
5093
+ for (const signal of report.signals) {
5094
+ const icon = signal.severity === "critical" || signal.severity === "high" ? "!!" : signal.severity === "medium" ? "! " : " ";
5095
+ lines.push(` ${icon} ${signal.name}: ${signal.detail}`);
5096
+ }
5097
+ }
5098
+ lines.push("");
5099
+ }
5100
+ if (recommendation === "block") {
5101
+ lines.push("Recommendation: BLOCK");
5102
+ } else if (recommendation === "escalate") {
5103
+ lines.push("Recommendation: Requires human approval");
5104
+ }
5105
+ return lines.join("\n");
5106
+ }
5107
+
4064
5108
  // src/lib/budget/tracker.ts
4065
5109
  var BudgetTracker = class {
4066
5110
  _used = 0;
@@ -6021,7 +7065,15 @@ function startWizardServer(options) {
6021
7065
  createApprovalFlow,
6022
7066
  createIdentityChain,
6023
7067
  createPolicyEngine,
7068
+ detectTyposquat,
7069
+ evaluateInstall,
7070
+ fetchRegistryMetadata,
7071
+ levenshteinDistance,
6024
7072
  loadConfig,
7073
+ normalizedSimilarity,
7074
+ parseInstallCommand,
7075
+ queryVulnerabilities,
7076
+ queryVulnerabilitiesBatch,
6025
7077
  renderTemplate,
6026
7078
  startWizardServer
6027
7079
  });