@westbayberry/dg 1.0.31 → 1.0.34

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.
Files changed (2) hide show
  1. package/dist/index.mjs +160 -54
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -53,7 +53,7 @@ __export(auth_exports, {
53
53
  import { readFileSync, writeFileSync, chmodSync, unlinkSync, existsSync } from "node:fs";
54
54
  import { join } from "node:path";
55
55
  import { homedir } from "node:os";
56
- import { exec } from "node:child_process";
56
+ import { spawn } from "node:child_process";
57
57
  import { randomUUID } from "node:crypto";
58
58
  async function createAuthSession() {
59
59
  let res;
@@ -154,24 +154,35 @@ function getOrCreateDeviceId() {
154
154
  return deviceId;
155
155
  }
156
156
  function openBrowser(url) {
157
- if (!/^https?:\/\//i.test(url)) return;
158
- const escaped = url.replace(/"/g, '\\"');
157
+ try {
158
+ const parsed = new URL(url);
159
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return;
160
+ } catch {
161
+ return;
162
+ }
159
163
  let cmd;
164
+ let args;
160
165
  switch (process.platform) {
161
166
  case "darwin":
162
- cmd = `open "${escaped}"`;
167
+ cmd = "open";
168
+ args = [url];
163
169
  break;
164
170
  case "linux":
165
- cmd = `xdg-open "${escaped}"`;
171
+ cmd = "xdg-open";
172
+ args = [url];
166
173
  break;
167
174
  case "win32":
168
- cmd = `start "" "${escaped}"`;
175
+ cmd = "cmd.exe";
176
+ args = ["/c", "start", "", url];
169
177
  break;
170
178
  default:
171
179
  return;
172
180
  }
173
- exec(cmd, () => {
174
- });
181
+ try {
182
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
183
+ child.unref();
184
+ } catch {
185
+ }
175
186
  }
176
187
  var WEB_BASE, CONFIG_FILE;
177
188
  var init_auth = __esm({
@@ -211,6 +222,22 @@ function loadDgrc() {
211
222
  }
212
223
  return {};
213
224
  }
225
+ function validateApiUrl(url) {
226
+ try {
227
+ const parsed = new URL(url);
228
+ const isLocal = parsed.hostname === "localhost" || parsed.hostname.startsWith("127.");
229
+ if (parsed.protocol !== "https:" && !isLocal) {
230
+ process.stderr.write(`Error: API URL must use HTTPS (got ${parsed.protocol}). Use localhost for local testing.
231
+ `);
232
+ process.exit(1);
233
+ }
234
+ return url;
235
+ } catch {
236
+ process.stderr.write(`Error: Invalid API URL: ${url}
237
+ `);
238
+ process.exit(1);
239
+ }
240
+ }
214
241
  function getVersion() {
215
242
  try {
216
243
  const thisDir = dirname(fileURLToPath(import.meta.url));
@@ -286,12 +313,14 @@ function parseConfig(argv) {
286
313
  return {
287
314
  apiKey,
288
315
  deviceId,
289
- apiUrl: values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com",
316
+ apiUrl: validateApiUrl(
317
+ values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com"
318
+ ),
290
319
  mode: modeRaw,
291
320
  blockThreshold,
292
321
  warnThreshold,
293
322
  maxPackages,
294
- allowlist: allowlistRaw ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean) : dgrc.allowlist ?? [],
323
+ allowlist: (allowlistRaw ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean) : dgrc.allowlist ?? []).map((s) => s.toLowerCase()),
295
324
  json: values.json,
296
325
  scanAll: values["scan-all"],
297
326
  baseLockfile: values["base-lockfile"] ?? null,
@@ -370,7 +399,7 @@ var init_config = __esm({
370
399
  });
371
400
 
372
401
  // src/npm-wrapper.ts
373
- import { spawn } from "node:child_process";
402
+ import { spawn as spawn2 } from "node:child_process";
374
403
  import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
375
404
  import { join as join3 } from "node:path";
376
405
  function parseNpmArgs(args) {
@@ -451,7 +480,7 @@ function parsePackageSpec(spec) {
451
480
  }
452
481
  async function resolveVersion(spec) {
453
482
  return new Promise((resolve) => {
454
- const child = spawn("npm", ["view", spec, "version"], {
483
+ const child = spawn2("npm", ["view", spec, "version"], {
455
484
  stdio: ["pipe", "pipe", "pipe"]
456
485
  });
457
486
  let stdout = "";
@@ -509,7 +538,7 @@ async function resolvePackages(specs) {
509
538
  }
510
539
  function runNpm(args) {
511
540
  return new Promise((resolve) => {
512
- const child = spawn("npm", args, {
541
+ const child = spawn2("npm", args, {
513
542
  stdio: "inherit",
514
543
  shell: false
515
544
  });
@@ -27588,9 +27617,9 @@ var init_ansi_styles = __esm({
27588
27617
 
27589
27618
  // node_modules/wrap-ansi/index.js
27590
27619
  function wrapAnsi(string, columns, options) {
27591
- return String(string).normalize().replaceAll("\r\n", "\n").split("\n").map((line) => exec2(line, columns, options)).join("\n");
27620
+ return String(string).normalize().replaceAll("\r\n", "\n").split("\n").map((line) => exec(line, columns, options)).join("\n");
27592
27621
  }
27593
- var ESCAPES, END_CODE, ANSI_ESCAPE_BELL, ANSI_CSI, ANSI_OSC, ANSI_SGR_TERMINATOR, ANSI_ESCAPE_LINK, wrapAnsiCode, wrapAnsiHyperlink, wordLengths, wrapWord, stringVisibleTrimSpacesRight, exec2;
27622
+ var ESCAPES, END_CODE, ANSI_ESCAPE_BELL, ANSI_CSI, ANSI_OSC, ANSI_SGR_TERMINATOR, ANSI_ESCAPE_LINK, wrapAnsiCode, wrapAnsiHyperlink, wordLengths, wrapWord, stringVisibleTrimSpacesRight, exec;
27594
27623
  var init_wrap_ansi = __esm({
27595
27624
  "node_modules/wrap-ansi/index.js"() {
27596
27625
  init_string_width();
@@ -27662,7 +27691,7 @@ var init_wrap_ansi = __esm({
27662
27691
  }
27663
27692
  return words.slice(0, last).join(" ") + words.slice(last).join("");
27664
27693
  };
27665
- exec2 = (string, columns, options = {}) => {
27694
+ exec = (string, columns, options = {}) => {
27666
27695
  if (options.trim !== false && string.trim() === "") {
27667
27696
  return "";
27668
27697
  }
@@ -30753,7 +30782,7 @@ var require_websocket = __commonJS({
30753
30782
  var tls = __require("tls");
30754
30783
  var { randomBytes, createHash } = __require("crypto");
30755
30784
  var { Duplex, Readable } = __require("stream");
30756
- var { URL } = __require("url");
30785
+ var { URL: URL2 } = __require("url");
30757
30786
  var PerMessageDeflate = require_permessage_deflate();
30758
30787
  var Receiver2 = require_receiver();
30759
30788
  var Sender2 = require_sender();
@@ -31246,11 +31275,11 @@ var require_websocket = __commonJS({
31246
31275
  );
31247
31276
  }
31248
31277
  let parsedUrl;
31249
- if (address instanceof URL) {
31278
+ if (address instanceof URL2) {
31250
31279
  parsedUrl = address;
31251
31280
  } else {
31252
31281
  try {
31253
- parsedUrl = new URL(address);
31282
+ parsedUrl = new URL2(address);
31254
31283
  } catch (e) {
31255
31284
  throw new SyntaxError(`Invalid URL: ${address}`);
31256
31285
  }
@@ -31387,7 +31416,7 @@ var require_websocket = __commonJS({
31387
31416
  req.abort();
31388
31417
  let addr;
31389
31418
  try {
31390
- addr = new URL(location, address);
31419
+ addr = new URL2(location, address);
31391
31420
  } catch (e) {
31392
31421
  const err = new SyntaxError(`Invalid URL: ${location}`);
31393
31422
  emitErrorAndClose(websocket, err);
@@ -38919,6 +38948,38 @@ var init_LoginApp = __esm({
38919
38948
  }
38920
38949
  });
38921
38950
 
38951
+ // src/sanitize.ts
38952
+ import { stripVTControlCharacters } from "node:util";
38953
+ function sanitize(s) {
38954
+ return stripVTControlCharacters(s);
38955
+ }
38956
+ function sanitizeResponse(response) {
38957
+ return {
38958
+ ...response,
38959
+ packages: response.packages.map((pkg) => ({
38960
+ ...pkg,
38961
+ name: sanitize(pkg.name),
38962
+ version: sanitize(pkg.version),
38963
+ findings: pkg.findings.map((f) => ({
38964
+ ...f,
38965
+ id: sanitize(f.id ?? ""),
38966
+ title: sanitize(f.title),
38967
+ evidence: f.evidence.map(sanitize)
38968
+ })),
38969
+ reasons: (pkg.reasons ?? []).map(sanitize),
38970
+ recommendation: pkg.recommendation ? sanitize(pkg.recommendation) : void 0
38971
+ })),
38972
+ safeVersions: Object.fromEntries(
38973
+ Object.entries(response.safeVersions).map(([k, v]) => [sanitize(k), sanitize(v)])
38974
+ )
38975
+ };
38976
+ }
38977
+ var init_sanitize = __esm({
38978
+ "src/sanitize.ts"() {
38979
+ "use strict";
38980
+ }
38981
+ });
38982
+
38922
38983
  // src/api.ts
38923
38984
  function buildHeaders(config) {
38924
38985
  const headers = {
@@ -39065,7 +39126,11 @@ async function callAnalyzeBatch(packages, config) {
39065
39126
  body
39066
39127
  );
39067
39128
  }
39068
- return await response.json();
39129
+ const raw = await response.json();
39130
+ if (!raw || typeof raw.score !== "number" || !Array.isArray(raw.packages)) {
39131
+ throw new APIError("Invalid API response format", 0, "");
39132
+ }
39133
+ return sanitizeResponse(raw);
39069
39134
  }
39070
39135
  async function callPyPIAnalyzeAPI(packages, config, onProgress) {
39071
39136
  const batchSize = config.apiKey ? BATCH_SIZE : ANON_BATCH_SIZE;
@@ -39139,12 +39204,17 @@ async function callPyPIBatch(packages, config) {
39139
39204
  const body = await response.text();
39140
39205
  throw new APIError(`API returned ${response.status}: ${body}`, response.status, body);
39141
39206
  }
39142
- return await response.json();
39207
+ const raw = await response.json();
39208
+ if (!raw || typeof raw.score !== "number" || !Array.isArray(raw.packages)) {
39209
+ throw new APIError("Invalid API response format", 0, "");
39210
+ }
39211
+ return sanitizeResponse(raw);
39143
39212
  }
39144
39213
  var APIError, TrialExhaustedError, BATCH_SIZE, ANON_BATCH_SIZE, MAX_RETRIES, RETRY_DELAY_MS;
39145
39214
  var init_api = __esm({
39146
39215
  "src/api.ts"() {
39147
39216
  "use strict";
39217
+ init_sanitize();
39148
39218
  APIError = class extends Error {
39149
39219
  constructor(message, statusCode, body) {
39150
39220
  super(message);
@@ -39183,7 +39253,9 @@ function parseLockfile(content) {
39183
39253
  version: e.version ?? "",
39184
39254
  resolved: e.resolved,
39185
39255
  integrity: e.integrity,
39186
- dev: e.dev
39256
+ dev: e.dev,
39257
+ optional: e.optional,
39258
+ hasPlatformRestriction: !!(e.os || e.cpu)
39187
39259
  });
39188
39260
  }
39189
39261
  }
@@ -39405,9 +39477,16 @@ var init_parse_package_json = __esm({
39405
39477
  });
39406
39478
 
39407
39479
  // src/lockfile.ts
39408
- import { execFileSync } from "node:child_process";
39409
- import { readFileSync as readFileSync6, existsSync as existsSync5 } from "node:fs";
39480
+ import { execFileSync as execFileSync2 } from "node:child_process";
39481
+ import { readFileSync as readFileSync6, existsSync as existsSync5, statSync } from "node:fs";
39410
39482
  import { join as join5 } from "node:path";
39483
+ function readFileSafe(path) {
39484
+ const size = statSync(path).size;
39485
+ if (size > MAX_LOCKFILE_BYTES) {
39486
+ throw new Error(`Lockfile too large (${(size / 1024 / 1024).toFixed(0)} MB, max 50 MB): ${path}`);
39487
+ }
39488
+ return readFileSync6(path, "utf-8");
39489
+ }
39411
39490
  function discoverChanges(cwd2, config) {
39412
39491
  if (config.workspace) {
39413
39492
  cwd2 = join5(cwd2, config.workspace);
@@ -39418,13 +39497,14 @@ function discoverChanges(cwd2, config) {
39418
39497
  "No lockfile found (package-lock.json, yarn.lock, or pnpm-lock.yaml). Run from your project root or use --base-lockfile."
39419
39498
  );
39420
39499
  }
39421
- const headContent = readFileSync6(lockfileInfo.path, "utf-8");
39500
+ const headContent = readFileSafe(lockfileInfo.path);
39422
39501
  const headParsed = parseLockfileByType(headContent, lockfileInfo.type);
39423
39502
  const directDeps = getDirectDeps(cwd2);
39424
39503
  if (config.scanAll) {
39425
39504
  const packages2 = [];
39426
39505
  for (const [name, entry] of headParsed.packages) {
39427
39506
  if (packages2.length >= config.maxPackages) break;
39507
+ if (entry.optional && entry.hasPlatformRestriction) continue;
39428
39508
  packages2.push({
39429
39509
  name,
39430
39510
  version: entry.version,
@@ -39438,7 +39518,7 @@ function discoverChanges(cwd2, config) {
39438
39518
  if (!existsSync5(config.baseLockfile)) {
39439
39519
  throw new Error(`Base lockfile not found: ${config.baseLockfile}`);
39440
39520
  }
39441
- const baseContent2 = readFileSync6(config.baseLockfile, "utf-8");
39521
+ const baseContent2 = readFileSafe(config.baseLockfile);
39442
39522
  const baseParsed = parseLockfile(baseContent2);
39443
39523
  const diff2 = diffLockfiles(baseParsed, headParsed, config.maxPackages, directDeps);
39444
39524
  return {
@@ -39459,7 +39539,7 @@ function discoverChanges(cwd2, config) {
39459
39539
  }
39460
39540
  const pkgJsonPath = join5(cwd2, "package.json");
39461
39541
  if (existsSync5(pkgJsonPath)) {
39462
- const headPkgJson = readFileSync6(pkgJsonPath, "utf-8");
39542
+ const headPkgJson = readFileSafe(pkgJsonPath);
39463
39543
  const basePkgJson = getGitBaseFile(cwd2, "package.json");
39464
39544
  if (basePkgJson !== null) {
39465
39545
  const diff2 = diffPackageJsons(basePkgJson, headPkgJson, config.maxPackages);
@@ -39480,6 +39560,7 @@ function discoverChanges(cwd2, config) {
39480
39560
  const packages = [];
39481
39561
  for (const [name, entry] of headParsed.packages) {
39482
39562
  if (packages.length >= config.maxPackages) break;
39563
+ if (entry.optional && entry.hasPlatformRestriction) continue;
39483
39564
  packages.push({
39484
39565
  name,
39485
39566
  version: entry.version,
@@ -39514,7 +39595,7 @@ function parseLockfileByType(content, type) {
39514
39595
  }
39515
39596
  function getDirectDeps(cwd2) {
39516
39597
  try {
39517
- const content = readFileSync6(join5(cwd2, "package.json"), "utf-8");
39598
+ const content = readFileSafe(join5(cwd2, "package.json"));
39518
39599
  const pkg = JSON.parse(content);
39519
39600
  return /* @__PURE__ */ new Set([
39520
39601
  ...Object.keys(pkg.dependencies ?? {}),
@@ -39526,7 +39607,7 @@ function getDirectDeps(cwd2) {
39526
39607
  }
39527
39608
  function getGitBaseLockfile(cwd2) {
39528
39609
  try {
39529
- const mergeBase = execFileSync("git", ["merge-base", "HEAD", "main"], {
39610
+ const mergeBase = execFileSync2("git", ["merge-base", "HEAD", "main"], {
39530
39611
  cwd: cwd2,
39531
39612
  encoding: "utf-8",
39532
39613
  stdio: ["pipe", "pipe", "pipe"]
@@ -39534,7 +39615,7 @@ function getGitBaseLockfile(cwd2) {
39534
39615
  if (!mergeBase) return null;
39535
39616
  for (const name of ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]) {
39536
39617
  try {
39537
- return execFileSync("git", ["show", `${mergeBase}:${name}`], {
39618
+ return execFileSync2("git", ["show", `${mergeBase}:${name}`], {
39538
39619
  cwd: cwd2,
39539
39620
  encoding: "utf-8",
39540
39621
  stdio: ["pipe", "pipe", "pipe"]
@@ -39550,13 +39631,13 @@ function getGitBaseLockfile(cwd2) {
39550
39631
  }
39551
39632
  function getGitBaseFile(cwd2, filename) {
39552
39633
  try {
39553
- const mergeBase = execFileSync("git", ["merge-base", "HEAD", "main"], {
39634
+ const mergeBase = execFileSync2("git", ["merge-base", "HEAD", "main"], {
39554
39635
  cwd: cwd2,
39555
39636
  encoding: "utf-8",
39556
39637
  stdio: ["pipe", "pipe", "pipe"]
39557
39638
  }).trim();
39558
39639
  if (!mergeBase) return null;
39559
- return execFileSync("git", ["show", `${mergeBase}:${filename}`], {
39640
+ return execFileSync2("git", ["show", `${mergeBase}:${filename}`], {
39560
39641
  cwd: cwd2,
39561
39642
  encoding: "utf-8",
39562
39643
  stdio: ["pipe", "pipe", "pipe"]
@@ -39574,7 +39655,7 @@ function toPackageInput(change) {
39574
39655
  };
39575
39656
  }
39576
39657
  function parsePythonDepFile(projectDir, depFile) {
39577
- const content = readFileSync6(join5(projectDir, depFile), "utf-8");
39658
+ const content = readFileSafe(join5(projectDir, depFile));
39578
39659
  if (depFile === "Pipfile.lock") {
39579
39660
  try {
39580
39661
  const parsed = JSON.parse(content);
@@ -39622,6 +39703,7 @@ function parsePythonDepFile(projectDir, depFile) {
39622
39703
  }
39623
39704
  return packages;
39624
39705
  }
39706
+ var MAX_LOCKFILE_BYTES;
39625
39707
  var init_lockfile = __esm({
39626
39708
  "src/lockfile.ts"() {
39627
39709
  "use strict";
@@ -39630,6 +39712,7 @@ var init_lockfile = __esm({
39630
39712
  init_parse_pnpm_lock();
39631
39713
  init_diff2();
39632
39714
  init_parse_package_json();
39715
+ MAX_LOCKFILE_BYTES = 50 * 1024 * 1024;
39633
39716
  }
39634
39717
  });
39635
39718
 
@@ -39638,7 +39721,7 @@ var discover_exports = {};
39638
39721
  __export(discover_exports, {
39639
39722
  discoverProjects: () => discoverProjects
39640
39723
  });
39641
- import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync, statSync } from "node:fs";
39724
+ import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync, lstatSync } from "node:fs";
39642
39725
  import { join as join6, relative, basename } from "node:path";
39643
39726
  function discoverProjects(root) {
39644
39727
  const projects = [];
@@ -39689,7 +39772,8 @@ function walk(dir, root, depth, out) {
39689
39772
  if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue;
39690
39773
  const full = join6(dir, entry);
39691
39774
  try {
39692
- if (statSync(full).isDirectory()) {
39775
+ const st = lstatSync(full);
39776
+ if (st.isDirectory() && !st.isSymbolicLink()) {
39693
39777
  walk(full, root, depth + 1, out);
39694
39778
  }
39695
39779
  } catch {
@@ -39980,7 +40064,7 @@ async function runStatic(config) {
39980
40064
  process.exit(0);
39981
40065
  }
39982
40066
  const packages = discovery.packages.filter(
39983
- (p) => !config.allowlist.includes(p.name)
40067
+ (p) => !config.allowlist.includes(p.name.toLowerCase())
39984
40068
  );
39985
40069
  if (packages.length === 0) {
39986
40070
  process.stderr.write(
@@ -40088,7 +40172,7 @@ async function runStaticNpm(npmArgs, config) {
40088
40172
  return scanAndInstallStatic(resolved, parsed, config);
40089
40173
  }
40090
40174
  async function scanAndInstallStatic(resolved, parsed, config) {
40091
- const toScan = resolved.filter((p) => !config.allowlist.includes(p.name));
40175
+ const toScan = resolved.filter((p) => !config.allowlist.includes(p.name.toLowerCase()));
40092
40176
  if (toScan.length === 0) {
40093
40177
  process.stderr.write(
40094
40178
  import_chalk4.default.dim(" All packages are allowlisted. Passing through to npm.\n")
@@ -40271,7 +40355,7 @@ var hook_exports = {};
40271
40355
  __export(hook_exports, {
40272
40356
  handleHookCommand: () => handleHookCommand
40273
40357
  });
40274
- import { execFileSync as execFileSync2 } from "node:child_process";
40358
+ import { execFileSync as execFileSync3 } from "node:child_process";
40275
40359
  import {
40276
40360
  existsSync as existsSync7,
40277
40361
  readFileSync as readFileSync8,
@@ -40283,7 +40367,7 @@ import {
40283
40367
  import { join as join7 } from "node:path";
40284
40368
  function findGitDir() {
40285
40369
  try {
40286
- return execFileSync2("git", ["rev-parse", "--git-dir"], {
40370
+ return execFileSync3("git", ["rev-parse", "--git-dir"], {
40287
40371
  encoding: "utf-8",
40288
40372
  stdio: ["pipe", "pipe", "pipe"]
40289
40373
  }).trim();
@@ -40518,7 +40602,7 @@ function useNpmWrapper(npmArgs, config) {
40518
40602
  return;
40519
40603
  }
40520
40604
  const toScan = resolved.filter(
40521
- (p) => !config.allowlist.includes(p.name)
40605
+ (p) => !config.allowlist.includes(p.name.toLowerCase())
40522
40606
  );
40523
40607
  if (toScan.length === 0) {
40524
40608
  dispatch({ type: "INSTALLING" });
@@ -40728,6 +40812,7 @@ var init_ScoreHeader = __esm({
40728
40812
  severityCounts
40729
40813
  }) => {
40730
40814
  const logo = renderLogo(action);
40815
+ const showLogo = (process.stdout.columns ?? 80) >= 60;
40731
40816
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
40732
40817
  Box_default,
40733
40818
  {
@@ -40767,7 +40852,7 @@ var init_ScoreHeader = __esm({
40767
40852
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { dimColor: true, children: `\x1B]8;;https://westbayberry.com\x07westbayberry.com\x1B]8;;\x07` })
40768
40853
  ] })
40769
40854
  ] }),
40770
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexDirection: "column", marginLeft: 2, children: logo.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: line }, i)) })
40855
+ showLogo && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Box_default, { flexDirection: "column", marginLeft: 2, children: logo.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Text, { children: line }, i)) })
40771
40856
  ] })
40772
40857
  }
40773
40858
  );
@@ -41002,6 +41087,7 @@ var init_ErrorView = __esm({
41002
41087
  "use strict";
41003
41088
  await init_build2();
41004
41089
  import_chalk8 = __toESM(require_source());
41090
+ init_sanitize();
41005
41091
  import_jsx_runtime7 = __toESM(require_jsx_runtime());
41006
41092
  ErrorView = ({ error }) => {
41007
41093
  const hint = getHint(error);
@@ -41015,7 +41101,7 @@ var init_ErrorView = __esm({
41015
41101
  paddingRight: 1,
41016
41102
  children: [
41017
41103
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: import_chalk8.default.red.bold("\u2718 Error") }),
41018
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: error.message }),
41104
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Text, { children: sanitize(error.message) }),
41019
41105
  hint && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Text, { children: [
41020
41106
  import_chalk8.default.yellow("\u2192"),
41021
41107
  " ",
@@ -41211,7 +41297,7 @@ function useScan(config) {
41211
41297
  }
41212
41298
  try {
41213
41299
  const discovery = discoverChanges(process.cwd(), config);
41214
- const packages = discovery.packages.filter((p) => !config.allowlist.includes(p.name));
41300
+ const packages = discovery.packages.filter((p) => !config.allowlist.includes(p.name.toLowerCase()));
41215
41301
  if (packages.length === 0) {
41216
41302
  const message = discovery.packages.length === 0 ? "No package changes detected." : "All changed packages are allowlisted.";
41217
41303
  dispatch({ type: "DISCOVERY_EMPTY", message });
@@ -41273,7 +41359,7 @@ async function scanProjects(projects, config, dispatch) {
41273
41359
  const discovery = discoverChanges(proj.path, fullScanConfig);
41274
41360
  for (const pkg of discovery.packages) {
41275
41361
  const key = `${pkg.name}@${pkg.version}`;
41276
- if (!config.allowlist.includes(pkg.name) && !seenNpm.has(key)) {
41362
+ if (!config.allowlist.includes(pkg.name.toLowerCase()) && !seenNpm.has(key)) {
41277
41363
  seenNpm.add(key);
41278
41364
  npmPackages.push(pkg);
41279
41365
  }
@@ -41286,7 +41372,7 @@ async function scanProjects(projects, config, dispatch) {
41286
41372
  const packages = parsePythonDepFile(proj.path, proj.depFile);
41287
41373
  for (const pkg of packages) {
41288
41374
  const key = `${pkg.name}@${pkg.version}`;
41289
- if (!config.allowlist.includes(pkg.name) && !seenPypi.has(key)) {
41375
+ if (!config.allowlist.includes(pkg.name.toLowerCase()) && !seenPypi.has(key)) {
41290
41376
  seenPypi.add(key);
41291
41377
  pypiPackages.push(pkg);
41292
41378
  }
@@ -41858,6 +41944,15 @@ var init_InteractiveResultsView = __esm({
41858
41944
  const newVp = adjustViewport(cursor, expandedIndex, expandLevel, clamped);
41859
41945
  dispatchView({ type: "MOVE", cursor, viewport: newVp });
41860
41946
  }, [availableRows]);
41947
+ (0, import_react31.useEffect)(() => {
41948
+ const dp = detailPaneRef.current;
41949
+ if (dp && detailLines.length > 0) {
41950
+ const maxScroll = Math.max(0, detailLines.length - detailContentRows);
41951
+ if (dp.scroll > maxScroll) {
41952
+ setDetailPane({ groupIndex: dp.groupIndex, scroll: maxScroll });
41953
+ }
41954
+ }
41955
+ }, [detailContentRows, detailLines.length]);
41861
41956
  use_input_default((input, key) => {
41862
41957
  if (groups.length === 0) {
41863
41958
  if (input === "q" || key.return) onExit();
@@ -41906,6 +42001,11 @@ var init_InteractiveResultsView = __esm({
41906
42001
  }
41907
42002
  return;
41908
42003
  }
42004
+ if (groups.length === 0) {
42005
+ if (input === "q") onExit();
42006
+ else if (input === "/") setSearchMode(true);
42007
+ return;
42008
+ }
41909
42009
  const { cursor, expandLevel: expLvl, expandedIndex: expIdx, viewport: vpStart } = viewRef.current;
41910
42010
  if (key.upArrow || input === "k") {
41911
42011
  const next = Math.max(0, cursor - 1);
@@ -41956,6 +42056,7 @@ var init_InteractiveResultsView = __esm({
41956
42056
  const aboveCount = view.viewport;
41957
42057
  const belowCount = groups.length - visibleEnd;
41958
42058
  const nameCol = Math.max(20, innerWidth - 22);
42059
+ const clampedCursor = groups.length > 0 ? Math.min(view.cursor, groups.length - 1) : 0;
41959
42060
  if (showHelp) {
41960
42061
  const isDetail = detailPane !== null;
41961
42062
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
@@ -42194,7 +42295,7 @@ var init_InteractiveResultsView = __esm({
42194
42295
  children: [
42195
42296
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { justifyContent: "space-between", children: [
42196
42297
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { bold: true, children: "Flagged Packages" }),
42197
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: searchQuery ? `${groups.length} of ${allGroupCount}` : `${view.cursor + 1}/${groups.length}` })
42298
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Text, { dimColor: true, children: searchQuery ? `${groups.length} of ${allGroupCount}` : `${clampedCursor + 1}/${groups.length}` })
42198
42299
  ] }),
42199
42300
  aboveCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
42200
42301
  import_chalk10.default.cyan(" \u2191"),
@@ -42204,7 +42305,7 @@ var init_InteractiveResultsView = __esm({
42204
42305
  ] }),
42205
42306
  visibleGroups.map((group, visIdx) => {
42206
42307
  const globalIdx = view.viewport + visIdx;
42207
- const isCursor = globalIdx === view.cursor;
42308
+ const isCursor = globalIdx === clampedCursor;
42208
42309
  const level = getLevel(globalIdx);
42209
42310
  const rep = group.packages[0];
42210
42311
  const { label, color } = actionBadge3(rep.score, config);
@@ -42406,6 +42507,7 @@ var init_ProjectSelector = __esm({
42406
42507
  import_react32 = __toESM(require_react());
42407
42508
  await init_build2();
42408
42509
  import_chalk11 = __toESM(require_source());
42510
+ init_sanitize();
42409
42511
  import_jsx_runtime11 = __toESM(require_jsx_runtime());
42410
42512
  ProjectSelector = ({ projects, onConfirm, onCancel }) => {
42411
42513
  const [cursor, setCursor] = (0, import_react32.useState)(0);
@@ -42434,7 +42536,7 @@ var init_ProjectSelector = __esm({
42434
42536
  onCancel();
42435
42537
  }
42436
42538
  });
42437
- const ecosystemLabel = (eco) => eco === "npm" ? import_chalk11.default.red("npm") : import_chalk11.default.blue("pip");
42539
+ const ecosystemLabel = (eco) => eco === "npm" ? import_chalk11.default.magenta("npm") : import_chalk11.default.blue("pip");
42438
42540
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
42439
42541
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
42440
42542
  import_chalk11.default.cyan("\u25C6"),
@@ -42458,7 +42560,7 @@ var init_ProjectSelector = __esm({
42458
42560
  prefix,
42459
42561
  check,
42460
42562
  " ",
42461
- proj.relativePath.padEnd(40),
42563
+ sanitize(proj.relativePath).padEnd(40),
42462
42564
  " ",
42463
42565
  ecosystemLabel(proj.ecosystem).padEnd(5),
42464
42566
  " ",
@@ -42593,7 +42695,7 @@ var init_App2 = __esm({
42593
42695
  const content = (() => {
42594
42696
  switch (state.phase) {
42595
42697
  case "discovering":
42596
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Spinner2, { label: "Searching for dependencies..." });
42698
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Spinner2, { label: `Searching for dependencies in ${process.cwd()} ...` });
42597
42699
  case "selecting":
42598
42700
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
42599
42701
  ProjectSelector,
@@ -42656,7 +42758,7 @@ init_npm_wrapper();
42656
42758
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
42657
42759
  import { homedir as homedir3 } from "node:os";
42658
42760
  import { join as join4 } from "node:path";
42659
- import { spawn as spawn2, execSync } from "node:child_process";
42761
+ import { spawn as spawn3, execFileSync } from "node:child_process";
42660
42762
  var PKG_NAME = "@westbayberry/dg";
42661
42763
  var CACHE_FILE = join4(homedir3(), ".dg-update-check.json");
42662
42764
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
@@ -42709,7 +42811,7 @@ function isNewer(latest, current) {
42709
42811
  }
42710
42812
  function spawnBackgroundUpdate(version) {
42711
42813
  try {
42712
- const child = spawn2("npm", ["install", "-g", `${PKG_NAME}@${version}`], {
42814
+ const child = spawn3("npm", ["install", "-g", `${PKG_NAME}@${version}`], {
42713
42815
  detached: true,
42714
42816
  stdio: "ignore"
42715
42817
  });
@@ -42755,7 +42857,7 @@ async function runUpdate(currentVersion) {
42755
42857
  process.stderr.write(chalk10.dim(` Installing ${PKG_NAME}@${latest}...
42756
42858
  `));
42757
42859
  try {
42758
- execSync(`npm install -g ${PKG_NAME}@${latest}`, { stdio: "inherit" });
42860
+ execFileSync("npm", ["install", "-g", `${PKG_NAME}@${latest}`], { stdio: "inherit" });
42759
42861
  writeCache({ latest, checkedAt: Date.now() });
42760
42862
  process.stderr.write(
42761
42863
  chalk10.green(`
@@ -42774,6 +42876,10 @@ async function runUpdate(currentVersion) {
42774
42876
 
42775
42877
  // src/bin.ts
42776
42878
  var CLI_VERSION = getVersion();
42879
+ process.on("SIGINT", () => {
42880
+ process.stderr.write("\n");
42881
+ process.exit(130);
42882
+ });
42777
42883
  var isInteractive = process.stdout.isTTY === true && !process.env.CI && !process.env.NO_COLOR;
42778
42884
  async function main() {
42779
42885
  const rawCommand = process.argv[2];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "1.0.31",
3
+ "version": "1.0.34",
4
4
  "description": "Supply chain security scanner for npm and Python dependencies — detects malicious packages, typosquatting, dependency confusion, and 26+ attack patterns",
5
5
  "bin": {
6
6
  "dependency-guardian": "dist/index.mjs",