@wrongstack/tools 0.8.4 → 0.8.5

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/builtin.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawn, execSync, spawnSync } from 'node:child_process';
1
+ import { spawn, execFileSync, spawnSync } from 'node:child_process';
2
2
  import { buildChildEnv, stripAnsi, detectNewlineStyle, normalizeToLf, toStyle, atomicWrite, unifiedDiff, compileGlob, loadPlan, emptyPlan, clearPlan, savePlan, getPlanTemplate, addPlanItem, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, formatPlan } from '@wrongstack/core';
3
3
  import * as path from 'node:path';
4
4
  import { dirname } from 'node:path';
@@ -11,6 +11,7 @@ import { statSync, mkdirSync, writeFileSync } from 'node:fs';
11
11
  import * as ts from 'typescript';
12
12
  import * as dns from 'node:dns/promises';
13
13
  import * as net from 'node:net';
14
+ import { Agent } from 'undici';
14
15
 
15
16
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
17
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
@@ -1716,7 +1717,7 @@ function syncGoParse(filePath, content, lang) {
1716
1717
  mkdirSync(tmpDir, { recursive: true });
1717
1718
  const scriptPath = path.join(tmpDir, "parse.go");
1718
1719
  writeFileSync(scriptPath, GO_PARSE_SCRIPT, "utf8");
1719
- const stdout = execSync(`go run "${scriptPath}"`, {
1720
+ const stdout = execFileSync("go", ["run", scriptPath], {
1720
1721
  input: content,
1721
1722
  timeout: 15e3,
1722
1723
  encoding: "utf8",
@@ -1962,7 +1963,7 @@ function syncPyParse(filePath, lang) {
1962
1963
  mkdirSync(tmpDir, { recursive: true });
1963
1964
  const scriptPath = path.join(tmpDir, "parse.py");
1964
1965
  writeFileSync(scriptPath, PY_PARSE_SCRIPT, "utf8");
1965
- const stdout = execSync(`python "${scriptPath}" "${filePath}"`, {
1966
+ const stdout = execFileSync("python", [scriptPath, filePath], {
1966
1967
  timeout: 15e3,
1967
1968
  encoding: "utf8",
1968
1969
  windowsHide: true
@@ -2000,11 +2001,19 @@ function parseSymbols4(opts) {
2000
2001
  }
2001
2002
  function checkNativeParser() {
2002
2003
  try {
2003
- execSync("rustc --version", { stdio: "pipe" });
2004
+ execFileSync("rustc", ["--version"], { stdio: "pipe" });
2004
2005
  const toolsDir = path.join(process.cwd(), "tools");
2005
2006
  try {
2006
- execSync(
2007
- "cargo metadata --no-deps --format-version 1 --manifest-path " + path.join(toolsDir, "Cargo.toml"),
2007
+ execFileSync(
2008
+ "cargo",
2009
+ [
2010
+ "metadata",
2011
+ "--no-deps",
2012
+ "--format-version",
2013
+ "1",
2014
+ "--manifest-path",
2015
+ path.join(toolsDir, "Cargo.toml")
2016
+ ],
2008
2017
  { stdio: "pipe" }
2009
2018
  );
2010
2019
  return true;
@@ -3324,8 +3333,20 @@ var BLOCKED_ARG_PATTERNS = {
3324
3333
  // python -c/--command executes arbitrary code; python -m runs modules
3325
3334
  python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],
3326
3335
  // git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack;
3327
- // -C <dir> changes working directory, bypassing cwd sandbox
3328
- git: [/^--exec=/, /^--upload-pack=/, /^--receive-pack=/, /^-C$/],
3336
+ // -C <dir> changes working directory, bypassing cwd sandbox;
3337
+ // -c/--config <k>=<v> injects config that runs commands
3338
+ // (e.g. core.sshCommand, core.pager, http.proxy, alias.x=!cmd).
3339
+ git: [
3340
+ /^--exec=/,
3341
+ /^--upload-pack=/,
3342
+ /^--receive-pack=/,
3343
+ /^-C$/,
3344
+ /^-c$/,
3345
+ /^--config$/,
3346
+ /^-c=/,
3347
+ /^--config=/,
3348
+ /^--config-env=/
3349
+ ],
3329
3350
  // node -r/--require preloads arbitrary modules; --eval executes code
3330
3351
  node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],
3331
3352
  // go run could execute arbitrary .go files; -ldflags could inject build-time code
@@ -3511,6 +3532,48 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
3511
3532
  var MAX_BYTES = 131072;
3512
3533
  var TIMEOUT_MS2 = 2e4;
3513
3534
  var ALLOW_PRIVATE = process.env["WRONGSTACK_FETCH_ALLOW_PRIVATE"] === "1";
3535
+ function guardedLookup(hostname, options, callback) {
3536
+ dns.lookup(hostname, { all: true }).then((records) => {
3537
+ const family = options?.family;
3538
+ const byFamily = family === 4 || family === 6 ? records.filter((r) => r.family === family) : records;
3539
+ const list = byFamily.length > 0 ? byFamily : records;
3540
+ if (!ALLOW_PRIVATE) {
3541
+ for (const r of list) {
3542
+ const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
3543
+ if (bad) {
3544
+ callback(
3545
+ Object.assign(new Error(`fetch: resolved to private address ${r.address}`), {
3546
+ code: "EAI_FAIL"
3547
+ })
3548
+ );
3549
+ return;
3550
+ }
3551
+ }
3552
+ }
3553
+ if (options?.all) {
3554
+ callback(
3555
+ null,
3556
+ list.map((r) => ({ address: r.address, family: r.family }))
3557
+ );
3558
+ return;
3559
+ }
3560
+ const first = list[0];
3561
+ if (!first) {
3562
+ callback(
3563
+ Object.assign(new Error(`fetch: no address for ${hostname}`), { code: "ENOTFOUND" })
3564
+ );
3565
+ return;
3566
+ }
3567
+ callback(null, first.address, first.family);
3568
+ }).catch((err) => callback(err));
3569
+ }
3570
+ var pinnedAgent;
3571
+ function getPinnedDispatcher() {
3572
+ if (!pinnedAgent) {
3573
+ pinnedAgent = new Agent({ connect: { lookup: guardedLookup } });
3574
+ }
3575
+ return pinnedAgent;
3576
+ }
3514
3577
  async function fetchWithRedirectLimit(url, maxRedirects, signal) {
3515
3578
  const headers = {
3516
3579
  "user-agent": "WrongStack/1.0 (+https://wrongstack.com)",
@@ -3527,11 +3590,13 @@ async function fetchWithRedirectLimit(url, maxRedirects, signal) {
3527
3590
  throw new Error("fetch: redirect to http:// blocked (HTTPS required by default)");
3528
3591
  }
3529
3592
  await assertNotPrivate(parsed.hostname);
3530
- const res = await fetch(currentUrl, {
3593
+ const init = {
3531
3594
  redirect: "manual",
3532
3595
  signal,
3533
- headers
3534
- });
3596
+ headers,
3597
+ dispatcher: getPinnedDispatcher()
3598
+ };
3599
+ const res = await fetch(currentUrl, init);
3535
3600
  if (res.status < 300 || res.status > 399) {
3536
3601
  return res;
3537
3602
  }
@@ -4906,17 +4971,35 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
4906
4971
  let stdout = "";
4907
4972
  let stderr = "";
4908
4973
  const MAX = 2e5;
4974
+ let settled = false;
4975
+ const empty = () => ({
4976
+ source: `docker:${service}`,
4977
+ entries: [],
4978
+ total: 0,
4979
+ truncated: false,
4980
+ stream_mode: false
4981
+ });
4982
+ const finish = (result) => {
4983
+ if (settled) return;
4984
+ settled = true;
4985
+ clearTimeout(timer);
4986
+ resolve6(result);
4987
+ };
4909
4988
  const child = spawn("docker", args, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
4989
+ const timer = setTimeout(() => {
4990
+ child.kill("SIGTERM");
4991
+ finish(empty());
4992
+ }, DOCKER_LOGS_TIMEOUT_MS);
4910
4993
  child.stdout?.on("data", (c) => {
4911
4994
  if (stdout.length < MAX) stdout += c.toString();
4912
4995
  });
4913
4996
  child.stderr?.on("data", (c) => {
4914
4997
  if (stderr.length < MAX) stderr += c.toString();
4915
4998
  });
4916
- child.on("close", (code) => {
4999
+ child.on("close", () => {
4917
5000
  const output = stdout + stderr;
4918
5001
  const entries = parseLogLines(output, filterRe);
4919
- resolve6({
5002
+ finish({
4920
5003
  source: `docker:${service}`,
4921
5004
  entries,
4922
5005
  total: entries.length,
@@ -4924,17 +5007,10 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
4924
5007
  stream_mode: false
4925
5008
  });
4926
5009
  });
4927
- child.on("error", (e) => {
4928
- resolve6({
4929
- source: `docker:${service}`,
4930
- entries: [],
4931
- total: 0,
4932
- truncated: false,
4933
- stream_mode: false
4934
- });
4935
- });
5010
+ child.on("error", () => finish(empty()));
4936
5011
  });
4937
5012
  }
5013
+ var DOCKER_LOGS_TIMEOUT_MS = 3e3;
4938
5014
  var MAX_TAIL_LINES = 1e5;
4939
5015
  async function fileLogs(path18, lines, filterRe, stream) {
4940
5016
  const { createInterface } = await import('node:readline');
@@ -5465,6 +5541,7 @@ var replaceTool = {
5465
5541
  const dryRun = input.dry_run ?? false;
5466
5542
  const filesInput = Array.isArray(input.files) ? input.files.join(",") : input.files;
5467
5543
  const fileList = await resolveFiles2(filesInput, ctx, globRe);
5544
+ const realRoot = await fs11.realpath(ctx.projectRoot).catch(() => ctx.projectRoot);
5468
5545
  const results = [];
5469
5546
  let totalReplacements = 0;
5470
5547
  for (const absPath of fileList) {
@@ -5480,7 +5557,7 @@ var replaceTool = {
5480
5557
  } catch {
5481
5558
  continue;
5482
5559
  }
5483
- const rel = path.relative(ctx.projectRoot, realPath);
5560
+ const rel = path.relative(realRoot, realPath);
5484
5561
  if (rel.startsWith("..") || path.isAbsolute(rel)) continue;
5485
5562
  const stat11 = await fs11.stat(realPath).catch(() => null);
5486
5563
  if (!stat11 || !stat11.isFile()) continue;