border-customs 0.8.0 → 0.8.1

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.js CHANGED
@@ -37459,6 +37459,19 @@ function matchHostProfile(text, _o) {
37459
37459
  flush();
37460
37460
  return hits;
37461
37461
  }
37462
+ var BINDING_TOKEN = String.raw`\b(?:q` + "wen" + String.raw`|deep` + "seek" + String.raw`|kimi|g` + "lm" + String.raw`|ernie|dou` + "bao" + String.raw`|hunyuan|sp` + "ark" + String.raw`)[-\.\d][\w.-]*\b`;
37463
+ var BINDING_VENDOR_RE = new RegExp(BINDING_TOKEN, "gi");
37464
+ var BINDING_CONTEXT_RE = /(?:model|模型|default|默认|provider)/i;
37465
+ function matchMachineBinding(text, _o) {
37466
+ const hits = [];
37467
+ text.split("\n").forEach((body, idx) => {
37468
+ if (!BINDING_CONTEXT_RE.test(body)) return;
37469
+ for (const m of body.matchAll(BINDING_VENDOR_RE)) {
37470
+ hits.push({ rule: "exfil-machine-binding", matched: m[0] ?? "", line: idx + 1, message: "Machine binding: a vendor inference-model id pinned next to model/default/provider context \u2014 device authorization belongs in operator-local config, not portable surfaces (L-MACHINE-LOCAL)." });
37471
+ }
37472
+ });
37473
+ return hits;
37474
+ }
37462
37475
 
37463
37476
  // src/exfil/rules.ts
37464
37477
  var OCTET = "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])";
@@ -39649,8 +39662,9 @@ async function computeCheckRulesHash(input2) {
39649
39662
  promptTemplatePaths: existsSync7(template) ? [template] : []
39650
39663
  });
39651
39664
  }
39665
+ var CHECK_LENS_ID = "range+tip-tree/1";
39652
39666
  function computeCheckKey(input2) {
39653
- return sha256(stableStringify({ ...input2 }));
39667
+ return sha256(stableStringify({ ...input2, lens: CHECK_LENS_ID }));
39654
39668
  }
39655
39669
 
39656
39670
  // src/engines/trufflehog.ts
@@ -42866,7 +42880,8 @@ function exposureSet(cfg, options = {}) {
42866
42880
  }
42867
42881
 
42868
42882
  // src/check.ts
42869
- import { join as join23 } from "node:path";
42883
+ import { createHash as createHash15 } from "node:crypto";
42884
+ import { join as join25 } from "node:path";
42870
42885
 
42871
42886
  // src/rules/aiArtifacts.ts
42872
42887
  import { spawnSync as spawnSync12 } from "node:child_process";
@@ -43050,6 +43065,7 @@ function scanAiArtifacts(o) {
43050
43065
 
43051
43066
  // src/check/exclusions.ts
43052
43067
  import { resolve as resolve11 } from "node:path";
43068
+ var PATH_PATTERN_RULE_PREFIX = "path-pattern:";
43053
43069
  function toRepoRelative(path4, repoDir) {
43054
43070
  if (path4 === void 0 || path4 === "") return null;
43055
43071
  const abs = resolve11(repoDir, path4);
@@ -43062,6 +43078,21 @@ function isBorderStatePath(repoRelPath) {
43062
43078
  function filterBorderStateFindings(findings, repoDir) {
43063
43079
  return findings.filter((f) => !isBorderStatePath(toRepoRelative(f.path, repoDir)));
43064
43080
  }
43081
+ function suppressGateConfigPathPatterns(findings, repoDir, configSource) {
43082
+ const kept = [];
43083
+ let suppressed = 0;
43084
+ const cfgAbs = configSource === void 0 ? "" : resolve11(configSource);
43085
+ if (cfgAbs === "") return { kept: [...findings], suppressed: 0 };
43086
+ for (const f of findings) {
43087
+ const outer = (f.path ?? "").split("!")[0] ?? "";
43088
+ if (f.rule.startsWith(PATH_PATTERN_RULE_PREFIX) && outer !== "" && resolve11(repoDir, outer) === cfgAbs) {
43089
+ suppressed += 1;
43090
+ continue;
43091
+ }
43092
+ kept.push(f);
43093
+ }
43094
+ return { kept, suppressed };
43095
+ }
43065
43096
 
43066
43097
  // src/check/allow.ts
43067
43098
  function scopePath(finding, repoDir) {
@@ -43122,7 +43153,8 @@ var EXFIL_RULE_IDS = [
43122
43153
  "exfil-ssh-target",
43123
43154
  "exfil-home-path",
43124
43155
  "exfil-cred-location",
43125
- "exfil-host-profile"
43156
+ "exfil-host-profile",
43157
+ "exfil-machine-binding"
43126
43158
  ];
43127
43159
  var EXFIL_MESSAGE_ID_SUFFIX = ":message";
43128
43160
  var EXFIL_TABLE_SEVERITY = {
@@ -43130,7 +43162,8 @@ var EXFIL_TABLE_SEVERITY = {
43130
43162
  "exfil-ssh-target": "HIGH",
43131
43163
  "exfil-home-path": "MEDIUM",
43132
43164
  "exfil-cred-location": "MEDIUM",
43133
- "exfil-host-profile": "MEDIUM"
43165
+ "exfil-host-profile": "MEDIUM",
43166
+ "exfil-machine-binding": "MEDIUM"
43134
43167
  };
43135
43168
  var EXFIL_HIGH_FAMILY = ["exfil-rfc1918", "exfil-ssh-target"];
43136
43169
  var EXFIL_SECRET_VALUE_RULES = EXFIL_HIGH_FAMILY;
@@ -43139,19 +43172,21 @@ var TWIN_HIGH = {
43139
43172
  "exfil-ssh-target": "CRITICAL",
43140
43173
  "exfil-home-path": null,
43141
43174
  "exfil-cred-location": null,
43142
- "exfil-host-profile": null
43175
+ "exfil-host-profile": null,
43176
+ "exfil-machine-binding": null
43143
43177
  };
43144
43178
  var NULL_ROW = {
43145
43179
  "exfil-rfc1918": null,
43146
43180
  "exfil-ssh-target": null,
43147
43181
  "exfil-home-path": null,
43148
43182
  "exfil-cred-location": null,
43149
- "exfil-host-profile": null
43183
+ "exfil-host-profile": null,
43184
+ "exfil-machine-binding": null
43150
43185
  };
43151
43186
  var EXFIL_MATRIX = {
43152
43187
  tree: {
43153
43188
  // native tree leg = MEDIUM family补发 only (v2 double-emission revoked).
43154
- native: { ...NULL_ROW, "exfil-home-path": "MEDIUM", "exfil-cred-location": "MEDIUM", "exfil-host-profile": "MEDIUM" },
43189
+ native: { ...NULL_ROW, "exfil-home-path": "MEDIUM", "exfil-cred-location": "MEDIUM", "exfil-host-profile": "MEDIUM", "exfil-machine-binding": "MEDIUM" },
43155
43190
  // gitleaks.ts:81 hardcodes CRITICAL for every ingested finding.
43156
43191
  "gitleaks-twin": TWIN_HIGH,
43157
43192
  // secretlint.ts:299-309 has no HIGH rung — error maps to CRITICAL as-is.
@@ -43202,7 +43237,8 @@ var PREDICATES = {
43202
43237
  "exfil-ssh-target": matchSshTarget,
43203
43238
  "exfil-home-path": matchHomePath,
43204
43239
  "exfil-cred-location": matchCredLocation,
43205
- "exfil-host-profile": matchHostProfile
43240
+ "exfil-host-profile": matchHostProfile,
43241
+ "exfil-machine-binding": matchMachineBinding
43206
43242
  };
43207
43243
  function scanFacet(input2, facet) {
43208
43244
  const channel = "native";
@@ -43287,6 +43323,17 @@ function scanExfilTree(o) {
43287
43323
  return findings;
43288
43324
  }
43289
43325
 
43326
+ // src/check/tipTreeLens.ts
43327
+ import { mkdtempSync as mkdtempSync5, rmSync as rmSync12 } from "node:fs";
43328
+ import { tmpdir as tmpdir4 } from "node:os";
43329
+ import { join as join24 } from "node:path";
43330
+
43331
+ // src/commands/exfil.ts
43332
+ import { createHash as createHash14 } from "node:crypto";
43333
+ import { existsSync as existsSync15, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync4, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
43334
+ import { tmpdir as tmpdir3 } from "node:os";
43335
+ import { dirname as dirname12, join as join23 } from "node:path";
43336
+
43290
43337
  // src/check/messageScan.ts
43291
43338
  import { spawnSync as spawnSync13 } from "node:child_process";
43292
43339
  import { dirname as dirname10, resolve as resolve13 } from "node:path";
@@ -43404,44 +43451,6 @@ function scanCommitMessages(o) {
43404
43451
  return findings;
43405
43452
  }
43406
43453
 
43407
- // src/check/proofValve.ts
43408
- var ROUNDTRIP_PROOF_MISSING_RULE = "roundtrip-proof-missing";
43409
- var ROUNDTRIP_PROOF_STALE_RULE = "roundtrip-proof-stale";
43410
- var RESIDUE_RULE_IDS = new Set(Object.keys(RESIDUE_SEVERITIES));
43411
- function hasBlockingResidueCapability(findings) {
43412
- return findings.some((f) => RESIDUE_RULE_IDS.has(f.rule) && isBlocking(f.severity));
43413
- }
43414
- function proofFinding(rule, art, cause) {
43415
- return {
43416
- rule,
43417
- severity: "CRITICAL",
43418
- target: "artifact",
43419
- path: art.file,
43420
- engine: "native",
43421
- message: `${art.file} (sha256 ${art.sha256.slice(0, 12)}\u2026) tripped the residue scan while residue.requireProof is on, but ${cause}. Run 'border roundtrip <spec>' for exactly these bytes to mint the ledger proof.`,
43422
- ...redact(`${art.sha256}:${cause}`),
43423
- snippet: `${art.file} ${art.sha256.slice(0, 12)}`
43424
- };
43425
- }
43426
- function proofFindings(o) {
43427
- const out = [];
43428
- for (const art of o.artifacts) {
43429
- const rec = latestRoundtripForSha(o.records, art.sha256);
43430
- if (rec === null) {
43431
- out.push(proofFinding(ROUNDTRIP_PROOF_MISSING_RULE, art, "no roundtrip proof record exists for its digest"));
43432
- } else if (rec.rulesHash !== o.rulesHash) {
43433
- out.push(
43434
- proofFinding(
43435
- ROUNDTRIP_PROOF_STALE_RULE,
43436
- art,
43437
- `its proof record predates the current rules policy (recorded ${rec.rulesHash.slice(0, 12)}\u2026, required ${o.rulesHash.slice(0, 12)}\u2026)`
43438
- )
43439
- );
43440
- }
43441
- }
43442
- return out;
43443
- }
43444
-
43445
43454
  // src/check/tagScan.ts
43446
43455
  import { spawnSync as spawnSync14 } from "node:child_process";
43447
43456
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync10, writeFileSync as writeFileSync4 } from "node:fs";
@@ -43556,78 +43565,386 @@ function scanTagMessages(o) {
43556
43565
  }
43557
43566
  }
43558
43567
 
43559
- // src/check.ts
43560
- var TRACKED_BORDER_RULE = "repo-tracks-border-state";
43561
- var GUARD_PATH_CAP = 50;
43562
- function trackedBorderStateFinding(repoDir, headSha, env2) {
43563
- const listing = runGitChecked(repoDir, ["ls-files", "--", `${BORDER_STATE_DIR}/`], { ...env2 !== void 0 ? { env: env2 } : {} }).split("\n").filter((p) => p !== "");
43564
- if (listing.length === 0) return null;
43565
- const shown = listing.slice(0, GUARD_PATH_CAP);
43566
- const rest = listing.length - shown.length;
43567
- const [first = `${BORDER_STATE_DIR}/`] = shown;
43568
- const message = `repository tracks ${String(listing.length)} file(s) under ${BORDER_STATE_DIR}/ \u2014 border's state directory must stay untracked: ${shown.join(", ")}${rest > 0 ? ` (+${String(rest)} more)` : ""}. Remove with 'git rm -r --cached ${BORDER_STATE_DIR}/'.`;
43569
- return {
43570
- rule: TRACKED_BORDER_RULE,
43571
- severity: "CRITICAL",
43572
- target: "git",
43573
- path: first,
43574
- commit: headSha,
43575
- engine: "native",
43576
- message,
43577
- ...redact(`${headSha}:${BORDER_STATE_DIR}`),
43578
- snippet: `${BORDER_STATE_DIR}/ tracked`
43568
+ // src/commands/exfilGit.ts
43569
+ import { spawnSync as spawnSync15 } from "node:child_process";
43570
+ import { dirname as dirname11, resolve as resolve15 } from "node:path";
43571
+ var GIT_TIMEOUT_MS = 55e3;
43572
+ var MAX_BUFFER_BYTES = 32 * 1024 * 1024;
43573
+ var MAX_BLOB_BYTES = 2 * 1024 * 1024;
43574
+ var BATCH_SHAS = 500;
43575
+ function makeGit(repoDir, env2) {
43576
+ const clean = {};
43577
+ for (const [name, value] of Object.entries(env2)) {
43578
+ if (!name.startsWith("GIT_")) clean[name] = value;
43579
+ }
43580
+ clean.GIT_CEILING_DIRECTORIES = dirname11(resolve15(repoDir));
43581
+ clean.GIT_OPTIONAL_LOCKS = "0";
43582
+ clean.GIT_TERMINAL_PROMPT = "0";
43583
+ const spawnWith = (args, input2) => {
43584
+ const r = spawnSync15("git", ["-C", resolve15(repoDir), ...args], {
43585
+ timeout: GIT_TIMEOUT_MS,
43586
+ maxBuffer: MAX_BUFFER_BYTES,
43587
+ env: clean,
43588
+ ...input2 !== void 0 ? { input: input2 } : {}
43589
+ });
43590
+ if (r.error !== void 0) return { stdout: Buffer.alloc(0), fail: `git ${args.slice(0, 2).join(" ")}: ${r.error.message}` };
43591
+ if (r.status !== 0) {
43592
+ const why = r.status === null ? `killed by ${String(r.signal)} (timeout ${String(GIT_TIMEOUT_MS)}ms)` : `exited ${String(r.status)}`;
43593
+ return { stdout: Buffer.alloc(0), fail: `git ${args.slice(0, 3).join(" ")} ${why}: ${(r.stderr?.toString("utf8") ?? "").trim().slice(0, 200)}` };
43594
+ }
43595
+ return { stdout: r.stdout ?? Buffer.alloc(0), fail: null };
43579
43596
  };
43580
- }
43581
- function legOptions(o, sanitizer) {
43582
- return {
43583
- ...o.env !== void 0 ? { env: o.env } : {},
43584
- sanitizer
43597
+ const runBuffer = (args, input2) => {
43598
+ const r = spawnWith(args, input2);
43599
+ if (r.fail !== null) throw new ConfigError("git-failed", r.fail);
43600
+ return r.stdout;
43585
43601
  };
43602
+ return { run: (args) => runBuffer(args).toString("utf8"), runBuffer };
43586
43603
  }
43587
- function historyRefRange(ctx) {
43588
- return ctx.refSet.length > 0 ? ctx.refSet.join(" ") : ctx.headSha;
43604
+ function parseLsTree(line) {
43605
+ const m = /^\d{6} (\w+) ([0-9a-f]{40})\s+(\d+)\t(.+)$/.exec(line);
43606
+ if (m === null || m[1] !== "blob") return null;
43607
+ return { sha: m[2] ?? "", path: m[4] ?? "", size: Number(m[3]) };
43589
43608
  }
43590
- async function executeCheck(o) {
43591
- const envOpt = o.env !== void 0 ? { env: o.env } : {};
43592
- const ctx = gatherContext(o.repoDir, envOpt);
43593
- const { handle, warning } = acquireLock(o.repoDir);
43594
- try {
43595
- return await runPipeline(o, ctx, warning);
43596
- } finally {
43597
- releaseLock(handle);
43609
+ function tipTree(git, rev) {
43610
+ return git.run(["-c", "core.quotePath=false", "ls-tree", "-r", "--long", rev]).split("\n").map(parseLsTree).filter((b) => b !== null);
43611
+ }
43612
+ function historyBlobs(git, rev) {
43613
+ const paths = /* @__PURE__ */ new Map();
43614
+ for (const line of git.run(["rev-list", "--objects", rev]).split("\n")) {
43615
+ if (line === "") continue;
43616
+ const sp = line.indexOf(" ");
43617
+ if (sp <= 0) continue;
43618
+ if (!paths.has(line.slice(0, sp))) paths.set(line.slice(0, sp), line.slice(sp + 1));
43619
+ }
43620
+ const shas = [...paths.keys()];
43621
+ const out = [];
43622
+ for (let i = 0; i < shas.length; i += BATCH_SHAS) {
43623
+ const checked = git.runBuffer(["cat-file", "--batch-check"], `${shas.slice(i, i + BATCH_SHAS).join("\n")}
43624
+ `).toString("utf8");
43625
+ for (const line of checked.split("\n")) {
43626
+ const m = /^([0-9a-f]{40}) (\w+) (\d+)$/.exec(line);
43627
+ if (m === null || m[2] !== "blob") continue;
43628
+ const path4 = paths.get(m[1] ?? "");
43629
+ if (path4 !== void 0) out.push({ sha: m[1] ?? "", path: path4, size: Number(m[3]) });
43630
+ }
43598
43631
  }
43632
+ return out;
43599
43633
  }
43600
- async function runPipeline(o, ctx, lockWarning) {
43601
- const repoDir = ctx.repoDir;
43602
- const envOpt = o.env !== void 0 ? { env: o.env } : {};
43634
+ function blobTexts(git, blobs) {
43635
+ const sizes = new Map(blobs.map((b) => [b.sha, b.size]));
43636
+ const wanted = [...new Set(blobs.map((b) => b.sha))].filter((s) => (sizes.get(s) ?? 0) <= MAX_BLOB_BYTES);
43637
+ const out = /* @__PURE__ */ new Map();
43638
+ for (let i = 0; i < wanted.length; i += BATCH_SHAS) {
43639
+ const buf = git.runBuffer(["cat-file", "--batch", "--buffer"], `${wanted.slice(i, i + BATCH_SHAS).join("\n")}
43640
+ `);
43641
+ let pos = 0;
43642
+ while (pos < buf.length) {
43643
+ const nl = buf.indexOf(10, pos);
43644
+ if (nl === -1) break;
43645
+ const parts = buf.subarray(pos, nl).toString("utf8").split(" ");
43646
+ pos = nl + 1;
43647
+ const sha = parts[0] ?? "";
43648
+ if (parts[1] === "missing" || parts.length < 3) continue;
43649
+ const size = Number(parts[2]);
43650
+ const body = buf.subarray(pos, pos + size);
43651
+ pos += size + 1;
43652
+ if (!body.includes(0)) out.set(sha, body.toString("utf8"));
43653
+ }
43654
+ }
43655
+ return out;
43656
+ }
43657
+
43658
+ // src/commands/exfil.ts
43659
+ var EXFIL_USAGE = "usage: border exfil <ref|url> [ref...] [--deep] [--json]";
43660
+ var URL_SHAPE_RE = /^(?:[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+|\S+@\S+:\S+)$/;
43661
+ var isExfilId = (rule) => rule.startsWith("exfil-");
43662
+ function toTreeFinding(f) {
43663
+ return {
43664
+ rule: f.id,
43665
+ severity: f.severity,
43666
+ target: "tree",
43667
+ path: f.source,
43668
+ line: f.line,
43669
+ engine: f.engine,
43670
+ message: f.message,
43671
+ valueDigest: f.valueDigest,
43672
+ snippet: f.snippet
43673
+ };
43674
+ }
43675
+ function scanBlobsNative(git, blobs) {
43676
+ const texts = blobTexts(git, blobs);
43603
43677
  const findings = [];
43604
- const guard = trackedBorderStateFinding(repoDir, ctx.headSha, o.env);
43605
- const probe = await probeEngines(o.cfg, {
43606
- ...envOpt,
43607
- ...o.requireOverride !== void 0 ? { requireOverride: o.requireOverride } : {}
43608
- });
43609
- findings.push(...probe.findings);
43610
- if (guard !== null) findings.push(guard);
43611
- const broken = new Set(probe.findings.map((f) => f.engine));
43612
- const sanitizer = new TextSanitizer();
43613
- findings.push(...filterBorderStateFindings(detectHostileConfig({ repoDir, target: "git", ...envOpt }), repoDir));
43614
- if (!broken.has("gitleaks")) {
43615
- const eng = legOptions(o, sanitizer);
43616
- findings.push(...filterBorderStateFindings(scanGitHistory({ repoDir, refRange: historyRefRange(ctx), target: "git", ...eng }), repoDir));
43617
- findings.push(...filterBorderStateFindings(scanTree({ dir: repoDir, stateDir: join23(repoDir, BORDER_STATE_DIR), target: "tree", ...eng }), repoDir));
43618
- findings.push(...filterBorderStateFindings(scanTagMessages({ repoDir, target: "git", ...eng }), repoDir));
43678
+ for (const b of blobs) {
43679
+ const text = texts.get(b.sha);
43680
+ if (text === void 0) continue;
43681
+ for (const f of scanTreeText({ text, source: b.path })) findings.push(toTreeFinding(f));
43619
43682
  }
43620
- if (!broken.has("secretlint")) {
43621
- findings.push(...await scanGitTrackedFiles({ repoDir, target: "git", rules: o.cfg.rules, ...legOptions(o, sanitizer) }));
43683
+ return findings;
43684
+ }
43685
+ async function scanTipTree(git, treeRoot, rev, env2, opts) {
43686
+ const blobs = tipTree(git, rev);
43687
+ const texts = blobTexts(git, blobs);
43688
+ const native = [];
43689
+ const written = [];
43690
+ for (const b of blobs) {
43691
+ const text = texts.get(b.sha);
43692
+ if (text === void 0) continue;
43693
+ for (const f of scanTreeText({
43694
+ text,
43695
+ source: b.path,
43696
+ ...opts?.sanitizer !== void 0 ? { onMatch: (rawValue, valueDigest) => opts.sanitizer?.register(valueDigest, rawValue) } : {}
43697
+ })) {
43698
+ native.push(toTreeFinding(f));
43699
+ }
43700
+ if (b.path.split("/").includes("..") || b.path === "") continue;
43701
+ const abs = join23(treeRoot, b.path);
43702
+ if (existsSync15(abs)) continue;
43703
+ mkdirSync10(dirname12(abs), { recursive: true });
43704
+ writeFileSync5(abs, text, "utf8");
43705
+ written.push(b.path);
43622
43706
  }
43623
- if (o.cfg.engines.trufflehog && !broken.has("trufflehog")) {
43624
- findings.push(...scanTrufflehog({ repoDir, target: "git", ...legOptions(o, sanitizer) }));
43707
+ const eng = { ...opts?.sanitizer !== void 0 ? { sanitizer: opts.sanitizer } : {} };
43708
+ const lint = opts?.skipSecretlint === true ? [] : (await scanPaths({ dir: treeRoot, files: written, target: "tree", env: env2, ...eng })).filter((f) => isExfilId(f.rule));
43709
+ const gl = opts?.skipGitleaks === true ? [] : scanTree({ dir: treeRoot, stateDir: join23(treeRoot, ".border-state"), target: "tree", env: env2, ...eng }).filter((f) => isExfilId(f.rule)).map((f) => f.path !== void 0 && f.path.startsWith(`${treeRoot}/`) ? { ...f, path: f.path.slice(treeRoot.length + 1) } : f);
43710
+ return [...native, ...lint, ...gl];
43711
+ }
43712
+ function exfilRulesDigest() {
43713
+ const h = createHash14("sha256");
43714
+ for (const p of [...resolveExfilFingerprintFiles({}), GITLEAKS_VENDORED_CONFIG]) {
43715
+ let bytes;
43716
+ try {
43717
+ bytes = readFileSync15(p);
43718
+ } catch {
43719
+ throw new ConfigError("unreadable", `border exfil: rule input unreadable (fail closed): ${p}`);
43720
+ }
43721
+ h.update(p).update("\0").update(bytes).update("\0");
43625
43722
  }
43626
- findings.push(...scanAiArtifacts({ repoDir, refSet: [...ctx.refSet], cfg: o.cfg.rules, ...envOpt }));
43627
- findings.push(...scanIdentity({ repoDir, refSet: [...ctx.refSet], cfg: o.cfg, ...envOpt }));
43628
- findings.push(...filterBorderStateFindings(scanExfilTree({ repoDir, hosts: o.cfg.rules.hosts, ...legOptions(o, sanitizer) }), repoDir));
43629
- findings.push(...filterBorderStateFindings(
43630
- scanCommitMessages({
43723
+ return h.digest("hex");
43724
+ }
43725
+ function sha256Hex2(text) {
43726
+ return createHash14("sha256").update(text, "utf8").digest("hex");
43727
+ }
43728
+ async function runExfilCore(ctx) {
43729
+ const positionals = ctx.positionals;
43730
+ if (positionals.length === 0) throw new UnknownArgError(`border exfil expects <ref|url> [ref...] \u2014 ${EXFIL_USAGE}`);
43731
+ const deep = ctx.flags.deep === true;
43732
+ const first = positionals[0] ?? "";
43733
+ const sandbox = { dir: "", tree: "" };
43734
+ try {
43735
+ const isUrl = URL_SHAPE_RE.test(first);
43736
+ let repoDir;
43737
+ let units;
43738
+ let exposure = [];
43739
+ if (isUrl) {
43740
+ const url2 = first;
43741
+ exposure = [sanitizeUrl(url2)];
43742
+ sandbox.dir = mkdtempSync4(join23(tmpdir3(), "border-exfil-"));
43743
+ const git2 = makeGit(sandbox.dir, ctx.env);
43744
+ git2.run(["init", "-q"]);
43745
+ git2.run(["remote", "add", "origin", url2]);
43746
+ const specs = positionals.slice(1).length === 0 ? ["HEAD"] : positionals.slice(1);
43747
+ units = [];
43748
+ for (const spec of specs) {
43749
+ git2.run(["fetch", "-q", "--no-tags", "origin", spec]);
43750
+ units.push({ label: spec, rev: git2.run(["rev-parse", "FETCH_HEAD"]).trim() });
43751
+ }
43752
+ repoDir = sandbox.dir;
43753
+ } else {
43754
+ repoDir = resolveRepoDir(ctx.cwd, { env: ctx.env });
43755
+ const git2 = makeGit(repoDir, ctx.env);
43756
+ units = positionals.map((ref) => ({ label: ref, rev: git2.run(["rev-parse", "--verify", ref]).trim() }));
43757
+ }
43758
+ const git = makeGit(repoDir, ctx.env);
43759
+ sandbox.tree = mkdtempSync4(join23(tmpdir3(), "border-exfil-tree-"));
43760
+ const findings = [];
43761
+ const seenBlob = /* @__PURE__ */ new Set();
43762
+ for (const unit of units) {
43763
+ findings.push(...await scanTipTree(git, sandbox.tree, unit.rev, ctx.env));
43764
+ if (deep) {
43765
+ const fresh = historyBlobs(git, unit.rev).filter((b) => {
43766
+ const key = `${b.sha}\0${b.path}`;
43767
+ if (seenBlob.has(key)) return false;
43768
+ seenBlob.add(key);
43769
+ return true;
43770
+ });
43771
+ findings.push(...scanBlobsNative(git, fresh));
43772
+ }
43773
+ }
43774
+ if (isUrl) ctx.stderr("border exfil: URL mode scanned fetched tips only \u2014 tag notes are not fetched (remote-side leg, check/push own it)");
43775
+ else findings.push(...scanTagMessages({ repoDir, target: "git", env: ctx.env }));
43776
+ if (deep) {
43777
+ findings.push(...scanGitHistory({ repoDir, refRange: units.map((u) => u.rev).join(" "), target: "git", env: ctx.env }).filter((f) => isExfilId(f.rule)));
43778
+ findings.push(
43779
+ ...scanCommitMessages({
43780
+ repoDir,
43781
+ refSet: units.map((u) => u.rev),
43782
+ remotes: [],
43783
+ hosts: [],
43784
+ runGit: git.run,
43785
+ env: ctx.env
43786
+ })
43787
+ );
43788
+ }
43789
+ const verdict = computeVerdict(findings);
43790
+ const report = {
43791
+ schemaVersion: 1,
43792
+ key: sha256Hex2(`exfil:${isUrl ? "url" : "ref"}:${positionals.join(" ")}:${deep ? "deep" : "fast"}:${units.map((u) => u.rev).join(" ")}`),
43793
+ head: units[0]?.rev ?? "",
43794
+ dirty: false,
43795
+ exposureSet: exposure,
43796
+ refSet: positionals,
43797
+ rulesHash: exfilRulesDigest(),
43798
+ verdict,
43799
+ counts: countFindings(findings),
43800
+ findings,
43801
+ ts: (/* @__PURE__ */ new Date()).toISOString()
43802
+ };
43803
+ if (ctx.flags.json) ctx.stdout(renderReportJson(report));
43804
+ else {
43805
+ const scope = `${isUrl ? "URL" : "refs"} ${positionals.join(",")}${deep ? " (deep)" : ""}`;
43806
+ ctx.stdout(`border exfil ${scope} ${report.verdict}: ${String(report.counts.total)} finding(s), ${String(report.counts.blocking)} blocking`);
43807
+ for (const f of report.findings) {
43808
+ ctx.stdout(` ${f.severity} ${f.rule} [${f.engine}] ${f.path ?? f.commit ?? f.target}${f.line !== void 0 ? `:${String(f.line)}` : ""} ${f.message}`);
43809
+ }
43810
+ }
43811
+ return exitCodeFromVerdict(verdict);
43812
+ } finally {
43813
+ if (sandbox.dir !== "") rmSync11(sandbox.dir, { recursive: true, force: true });
43814
+ if (sandbox.tree !== "") rmSync11(sandbox.tree, { recursive: true, force: true });
43815
+ }
43816
+ }
43817
+ var runExfil = (ctx) => runExfilCore(ctx);
43818
+
43819
+ // src/check/tipTreeLens.ts
43820
+ async function scanTipTreeLens(o) {
43821
+ const env2 = o.env ?? process.env;
43822
+ const git = makeGit(o.repoDir, env2);
43823
+ const treeRoot = mkdtempSync5(join24(tmpdir4(), "border-check-tip-tree-"));
43824
+ try {
43825
+ return await scanTipTree(git, treeRoot, o.rev, env2, {
43826
+ ...o.sanitizer !== void 0 ? { sanitizer: o.sanitizer } : {},
43827
+ ...o.skipGitleaks === true ? { skipGitleaks: true } : {},
43828
+ ...o.skipSecretlint === true ? { skipSecretlint: true } : {}
43829
+ });
43830
+ } finally {
43831
+ rmSync12(treeRoot, { recursive: true, force: true });
43832
+ }
43833
+ }
43834
+
43835
+ // src/check/proofValve.ts
43836
+ var ROUNDTRIP_PROOF_MISSING_RULE = "roundtrip-proof-missing";
43837
+ var ROUNDTRIP_PROOF_STALE_RULE = "roundtrip-proof-stale";
43838
+ var RESIDUE_RULE_IDS = new Set(Object.keys(RESIDUE_SEVERITIES));
43839
+ function hasBlockingResidueCapability(findings) {
43840
+ return findings.some((f) => RESIDUE_RULE_IDS.has(f.rule) && isBlocking(f.severity));
43841
+ }
43842
+ function proofFinding(rule, art, cause) {
43843
+ return {
43844
+ rule,
43845
+ severity: "CRITICAL",
43846
+ target: "artifact",
43847
+ path: art.file,
43848
+ engine: "native",
43849
+ message: `${art.file} (sha256 ${art.sha256.slice(0, 12)}\u2026) tripped the residue scan while residue.requireProof is on, but ${cause}. Run 'border roundtrip <spec>' for exactly these bytes to mint the ledger proof.`,
43850
+ ...redact(`${art.sha256}:${cause}`),
43851
+ snippet: `${art.file} ${art.sha256.slice(0, 12)}`
43852
+ };
43853
+ }
43854
+ function proofFindings(o) {
43855
+ const out = [];
43856
+ for (const art of o.artifacts) {
43857
+ const rec = latestRoundtripForSha(o.records, art.sha256);
43858
+ if (rec === null) {
43859
+ out.push(proofFinding(ROUNDTRIP_PROOF_MISSING_RULE, art, "no roundtrip proof record exists for its digest"));
43860
+ } else if (rec.rulesHash !== o.rulesHash) {
43861
+ out.push(
43862
+ proofFinding(
43863
+ ROUNDTRIP_PROOF_STALE_RULE,
43864
+ art,
43865
+ `its proof record predates the current rules policy (recorded ${rec.rulesHash.slice(0, 12)}\u2026, required ${o.rulesHash.slice(0, 12)}\u2026)`
43866
+ )
43867
+ );
43868
+ }
43869
+ }
43870
+ return out;
43871
+ }
43872
+
43873
+ // src/check.ts
43874
+ var TRACKED_BORDER_RULE = "repo-tracks-border-state";
43875
+ var GUARD_PATH_CAP = 50;
43876
+ function trackedBorderStateFinding(repoDir, headSha, env2) {
43877
+ const listing = runGitChecked(repoDir, ["ls-files", "--", `${BORDER_STATE_DIR}/`], { ...env2 !== void 0 ? { env: env2 } : {} }).split("\n").filter((p) => p !== "");
43878
+ if (listing.length === 0) return null;
43879
+ const shown = listing.slice(0, GUARD_PATH_CAP);
43880
+ const rest = listing.length - shown.length;
43881
+ const [first = `${BORDER_STATE_DIR}/`] = shown;
43882
+ const message = `repository tracks ${String(listing.length)} file(s) under ${BORDER_STATE_DIR}/ \u2014 border's state directory must stay untracked: ${shown.join(", ")}${rest > 0 ? ` (+${String(rest)} more)` : ""}. Remove with 'git rm -r --cached ${BORDER_STATE_DIR}/'.`;
43883
+ return {
43884
+ rule: TRACKED_BORDER_RULE,
43885
+ severity: "CRITICAL",
43886
+ target: "git",
43887
+ path: first,
43888
+ commit: headSha,
43889
+ engine: "native",
43890
+ message,
43891
+ ...redact(`${headSha}:${BORDER_STATE_DIR}`),
43892
+ snippet: `${BORDER_STATE_DIR}/ tracked`
43893
+ };
43894
+ }
43895
+ function legOptions(o, sanitizer) {
43896
+ return {
43897
+ ...o.env !== void 0 ? { env: o.env } : {},
43898
+ sanitizer
43899
+ };
43900
+ }
43901
+ function lensIdentity(f) {
43902
+ return `${f.rule}\0${f.engine}\0${f.path ?? ""}\0${String(f.line ?? -1)}\0${f.valueDigest}`;
43903
+ }
43904
+ function historyRefRange(ctx) {
43905
+ return ctx.refSet.length > 0 ? ctx.refSet.join(" ") : ctx.headSha;
43906
+ }
43907
+ async function executeCheck(o) {
43908
+ const envOpt = o.env !== void 0 ? { env: o.env } : {};
43909
+ const ctx = gatherContext(o.repoDir, envOpt);
43910
+ const { handle, warning } = acquireLock(o.repoDir);
43911
+ try {
43912
+ return await runPipeline(o, ctx, warning);
43913
+ } finally {
43914
+ releaseLock(handle);
43915
+ }
43916
+ }
43917
+ async function runPipeline(o, ctx, lockWarning) {
43918
+ const repoDir = ctx.repoDir;
43919
+ const envOpt = o.env !== void 0 ? { env: o.env } : {};
43920
+ const findings = [];
43921
+ const guard = trackedBorderStateFinding(repoDir, ctx.headSha, o.env);
43922
+ const probe = await probeEngines(o.cfg, {
43923
+ ...envOpt,
43924
+ ...o.requireOverride !== void 0 ? { requireOverride: o.requireOverride } : {}
43925
+ });
43926
+ findings.push(...probe.findings);
43927
+ if (guard !== null) findings.push(guard);
43928
+ const broken = new Set(probe.findings.map((f) => f.engine));
43929
+ const sanitizer = new TextSanitizer();
43930
+ findings.push(...filterBorderStateFindings(detectHostileConfig({ repoDir, target: "git", ...envOpt }), repoDir));
43931
+ if (!broken.has("gitleaks")) {
43932
+ const eng = legOptions(o, sanitizer);
43933
+ findings.push(...filterBorderStateFindings(scanGitHistory({ repoDir, refRange: historyRefRange(ctx), target: "git", ...eng }), repoDir));
43934
+ findings.push(...filterBorderStateFindings(scanTree({ dir: repoDir, stateDir: join25(repoDir, BORDER_STATE_DIR), target: "tree", ...eng }), repoDir));
43935
+ findings.push(...filterBorderStateFindings(scanTagMessages({ repoDir, target: "git", ...eng }), repoDir));
43936
+ }
43937
+ if (!broken.has("secretlint")) {
43938
+ findings.push(...await scanGitTrackedFiles({ repoDir, target: "git", rules: o.cfg.rules, ...legOptions(o, sanitizer) }));
43939
+ }
43940
+ if (o.cfg.engines.trufflehog && !broken.has("trufflehog")) {
43941
+ findings.push(...scanTrufflehog({ repoDir, target: "git", ...legOptions(o, sanitizer) }));
43942
+ }
43943
+ findings.push(...scanAiArtifacts({ repoDir, refSet: [...ctx.refSet], cfg: o.cfg.rules, ...envOpt }));
43944
+ findings.push(...scanIdentity({ repoDir, refSet: [...ctx.refSet], cfg: o.cfg, ...envOpt }));
43945
+ findings.push(...filterBorderStateFindings(scanExfilTree({ repoDir, hosts: o.cfg.rules.hosts, ...legOptions(o, sanitizer) }), repoDir));
43946
+ findings.push(...filterBorderStateFindings(
43947
+ scanCommitMessages({
43631
43948
  repoDir,
43632
43949
  refSet: ctx.refSet.length > 0 ? [...ctx.refSet] : [ctx.headSha],
43633
43950
  remotes: o.cfg.targets.git.remotes,
@@ -43636,6 +43953,23 @@ async function runPipeline(o, ctx, lockWarning) {
43636
43953
  }),
43637
43954
  repoDir
43638
43955
  ));
43956
+ if (o.effectiveTargets.includes("git")) {
43957
+ const tip = await scanTipTreeLens({
43958
+ repoDir,
43959
+ rev: ctx.headSha,
43960
+ ...envOpt,
43961
+ sanitizer,
43962
+ skipGitleaks: broken.has("gitleaks"),
43963
+ skipSecretlint: broken.has("secretlint")
43964
+ });
43965
+ const seen = new Set(findings.map(lensIdentity));
43966
+ for (const f of filterBorderStateFindings(tip, repoDir)) {
43967
+ const id = lensIdentity(f);
43968
+ if (seen.has(id)) continue;
43969
+ seen.add(id);
43970
+ findings.push(f);
43971
+ }
43972
+ }
43639
43973
  const ledgerArtifacts = [];
43640
43974
  for (const channel of publishChannels()) {
43641
43975
  if (!o.effectiveTargets.includes(channel.id) || !channel.configured(o.cfg)) continue;
@@ -43677,6 +44011,21 @@ async function runPipeline(o, ctx, lockWarning) {
43677
44011
  findings.push(...proofFindings({ artifacts: ledgerArtifacts, records: readLedger(repoDir).records, rulesHash }));
43678
44012
  }
43679
44013
  const exposure = [...exposureSet(o.cfg, { cwd: repoDir })];
44014
+ const cfgExempt = suppressGateConfigPathPatterns(findings, repoDir, o.configSource);
44015
+ if (cfgExempt.suppressed > 0) {
44016
+ findings.length = 0;
44017
+ findings.push(...cfgExempt.kept);
44018
+ findings.push({
44019
+ rule: "gate-config-path-exempt",
44020
+ severity: "INFO",
44021
+ target: "config",
44022
+ ...o.configSource !== void 0 ? { path: o.configSource } : {},
44023
+ engine: "native",
44024
+ message: `${String(cfgExempt.suppressed)} path-pattern finding(s) suppressed for the gate's own config file (self-referential rule-ID bytes; credential engines still cover it)`,
44025
+ valueDigest: createHash15("sha256").update(`gate-config-exempt:${String(o.configSource)}`).digest("hex"),
44026
+ snippet: "gate-config-path-exempt"
44027
+ });
44028
+ }
43680
44029
  const allow = applyAllowList(findings, o.cfg.allow, repoDir);
43681
44030
  const report = {
43682
44031
  schemaVersion: 1,
@@ -43771,7 +44120,10 @@ async function runCheck(ctx) {
43771
44120
  configDigest,
43772
44121
  effectiveTargets,
43773
44122
  ...env2 !== void 0 ? { env: env2 } : {},
43774
- ...ctx.flags.requireEngine !== void 0 && ctx.flags.requireEngine.length > 0 ? { requireOverride: ctx.flags.requireEngine } : {}
44123
+ ...ctx.flags.requireEngine !== void 0 && ctx.flags.requireEngine.length > 0 ? { requireOverride: ctx.flags.requireEngine } : {},
44124
+ // config self-exemption binds to a REAL file path only — the "git-remote"/
44125
+ // "<...>" source sentinels must never resolve onto a scanned content path.
44126
+ .../^\/.*$/.test(effective.source) && !effective.source.startsWith("<") ? { configSource: effective.source } : {}
43775
44127
  });
43776
44128
  } catch (err) {
43777
44129
  if (err instanceof BorderLockHeldError) {
@@ -43810,265 +44162,15 @@ var NO_TARGETS_PROBE_CFG = {
43810
44162
  engines: { require: [], trufflehog: false }
43811
44163
  };
43812
44164
 
43813
- // src/commands/exfil.ts
43814
- import { createHash as createHash14 } from "node:crypto";
43815
- import { existsSync as existsSync15, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync4, readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
43816
- import { tmpdir as tmpdir3 } from "node:os";
43817
- import { dirname as dirname12, join as join24 } from "node:path";
43818
-
43819
- // src/commands/exfilGit.ts
43820
- import { spawnSync as spawnSync15 } from "node:child_process";
43821
- import { dirname as dirname11, resolve as resolve15 } from "node:path";
43822
- var GIT_TIMEOUT_MS = 55e3;
43823
- var MAX_BUFFER_BYTES = 32 * 1024 * 1024;
43824
- var MAX_BLOB_BYTES = 2 * 1024 * 1024;
43825
- var BATCH_SHAS = 500;
43826
- function makeGit(repoDir, env2) {
43827
- const clean = {};
43828
- for (const [name, value] of Object.entries(env2)) {
43829
- if (!name.startsWith("GIT_")) clean[name] = value;
43830
- }
43831
- clean.GIT_CEILING_DIRECTORIES = dirname11(resolve15(repoDir));
43832
- clean.GIT_OPTIONAL_LOCKS = "0";
43833
- clean.GIT_TERMINAL_PROMPT = "0";
43834
- const spawnWith = (args, input2) => {
43835
- const r = spawnSync15("git", ["-C", resolve15(repoDir), ...args], {
43836
- timeout: GIT_TIMEOUT_MS,
43837
- maxBuffer: MAX_BUFFER_BYTES,
43838
- env: clean,
43839
- ...input2 !== void 0 ? { input: input2 } : {}
43840
- });
43841
- if (r.error !== void 0) return { stdout: Buffer.alloc(0), fail: `git ${args.slice(0, 2).join(" ")}: ${r.error.message}` };
43842
- if (r.status !== 0) {
43843
- const why = r.status === null ? `killed by ${String(r.signal)} (timeout ${String(GIT_TIMEOUT_MS)}ms)` : `exited ${String(r.status)}`;
43844
- return { stdout: Buffer.alloc(0), fail: `git ${args.slice(0, 3).join(" ")} ${why}: ${(r.stderr?.toString("utf8") ?? "").trim().slice(0, 200)}` };
43845
- }
43846
- return { stdout: r.stdout ?? Buffer.alloc(0), fail: null };
43847
- };
43848
- const runBuffer = (args, input2) => {
43849
- const r = spawnWith(args, input2);
43850
- if (r.fail !== null) throw new ConfigError("git-failed", r.fail);
43851
- return r.stdout;
43852
- };
43853
- return { run: (args) => runBuffer(args).toString("utf8"), runBuffer };
43854
- }
43855
- function parseLsTree(line) {
43856
- const m = /^\d{6} (\w+) ([0-9a-f]{40})\s+(\d+)\t(.+)$/.exec(line);
43857
- if (m === null || m[1] !== "blob") return null;
43858
- return { sha: m[2] ?? "", path: m[4] ?? "", size: Number(m[3]) };
43859
- }
43860
- function tipTree(git, rev) {
43861
- return git.run(["-c", "core.quotePath=false", "ls-tree", "-r", "--long", rev]).split("\n").map(parseLsTree).filter((b) => b !== null);
43862
- }
43863
- function historyBlobs(git, rev) {
43864
- const paths = /* @__PURE__ */ new Map();
43865
- for (const line of git.run(["rev-list", "--objects", rev]).split("\n")) {
43866
- if (line === "") continue;
43867
- const sp = line.indexOf(" ");
43868
- if (sp <= 0) continue;
43869
- if (!paths.has(line.slice(0, sp))) paths.set(line.slice(0, sp), line.slice(sp + 1));
43870
- }
43871
- const shas = [...paths.keys()];
43872
- const out = [];
43873
- for (let i = 0; i < shas.length; i += BATCH_SHAS) {
43874
- const checked = git.runBuffer(["cat-file", "--batch-check"], `${shas.slice(i, i + BATCH_SHAS).join("\n")}
43875
- `).toString("utf8");
43876
- for (const line of checked.split("\n")) {
43877
- const m = /^([0-9a-f]{40}) (\w+) (\d+)$/.exec(line);
43878
- if (m === null || m[2] !== "blob") continue;
43879
- const path4 = paths.get(m[1] ?? "");
43880
- if (path4 !== void 0) out.push({ sha: m[1] ?? "", path: path4, size: Number(m[3]) });
43881
- }
43882
- }
43883
- return out;
43884
- }
43885
- function blobTexts(git, blobs) {
43886
- const sizes = new Map(blobs.map((b) => [b.sha, b.size]));
43887
- const wanted = [...new Set(blobs.map((b) => b.sha))].filter((s) => (sizes.get(s) ?? 0) <= MAX_BLOB_BYTES);
43888
- const out = /* @__PURE__ */ new Map();
43889
- for (let i = 0; i < wanted.length; i += BATCH_SHAS) {
43890
- const buf = git.runBuffer(["cat-file", "--batch", "--buffer"], `${wanted.slice(i, i + BATCH_SHAS).join("\n")}
43891
- `);
43892
- let pos = 0;
43893
- while (pos < buf.length) {
43894
- const nl = buf.indexOf(10, pos);
43895
- if (nl === -1) break;
43896
- const parts = buf.subarray(pos, nl).toString("utf8").split(" ");
43897
- pos = nl + 1;
43898
- const sha = parts[0] ?? "";
43899
- if (parts[1] === "missing" || parts.length < 3) continue;
43900
- const size = Number(parts[2]);
43901
- const body = buf.subarray(pos, pos + size);
43902
- pos += size + 1;
43903
- if (!body.includes(0)) out.set(sha, body.toString("utf8"));
43904
- }
43905
- }
43906
- return out;
43907
- }
43908
-
43909
- // src/commands/exfil.ts
43910
- var EXFIL_USAGE = "usage: border exfil <ref|url> [ref...] [--deep] [--json]";
43911
- var URL_SHAPE_RE = /^(?:[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+|\S+@\S+:\S+)$/;
43912
- var isExfilId = (rule) => rule.startsWith("exfil-");
43913
- function toTreeFinding(f) {
43914
- return {
43915
- rule: f.id,
43916
- severity: f.severity,
43917
- target: "tree",
43918
- path: f.source,
43919
- line: f.line,
43920
- engine: f.engine,
43921
- message: f.message,
43922
- valueDigest: f.valueDigest,
43923
- snippet: f.snippet
43924
- };
43925
- }
43926
- function scanBlobsNative(git, blobs) {
43927
- const texts = blobTexts(git, blobs);
43928
- const findings = [];
43929
- for (const b of blobs) {
43930
- const text = texts.get(b.sha);
43931
- if (text === void 0) continue;
43932
- for (const f of scanTreeText({ text, source: b.path })) findings.push(toTreeFinding(f));
43933
- }
43934
- return findings;
43935
- }
43936
- async function scanTipTree(git, treeRoot, rev, env2) {
43937
- const blobs = tipTree(git, rev);
43938
- const texts = blobTexts(git, blobs);
43939
- const native = [];
43940
- const written = [];
43941
- for (const b of blobs) {
43942
- const text = texts.get(b.sha);
43943
- if (text === void 0) continue;
43944
- for (const f of scanTreeText({ text, source: b.path })) native.push(toTreeFinding(f));
43945
- if (b.path.split("/").includes("..") || b.path === "") continue;
43946
- const abs = join24(treeRoot, b.path);
43947
- if (existsSync15(abs)) continue;
43948
- mkdirSync10(dirname12(abs), { recursive: true });
43949
- writeFileSync5(abs, text, "utf8");
43950
- written.push(b.path);
43951
- }
43952
- const lint = (await scanPaths({ dir: treeRoot, files: written, target: "tree", env: env2 })).filter((f) => isExfilId(f.rule));
43953
- const gl = scanTree({ dir: treeRoot, stateDir: join24(treeRoot, ".border-state"), target: "tree", env: env2 }).filter((f) => isExfilId(f.rule)).map((f) => f.path !== void 0 && f.path.startsWith(`${treeRoot}/`) ? { ...f, path: f.path.slice(treeRoot.length + 1) } : f);
43954
- return [...native, ...lint, ...gl];
43955
- }
43956
- function exfilRulesDigest() {
43957
- const h = createHash14("sha256");
43958
- for (const p of [...resolveExfilFingerprintFiles({}), GITLEAKS_VENDORED_CONFIG]) {
43959
- let bytes;
43960
- try {
43961
- bytes = readFileSync15(p);
43962
- } catch {
43963
- throw new ConfigError("unreadable", `border exfil: rule input unreadable (fail closed): ${p}`);
43964
- }
43965
- h.update(p).update("\0").update(bytes).update("\0");
43966
- }
43967
- return h.digest("hex");
43968
- }
43969
- function sha256Hex2(text) {
43970
- return createHash14("sha256").update(text, "utf8").digest("hex");
43971
- }
43972
- async function runExfilCore(ctx) {
43973
- const positionals = ctx.positionals;
43974
- if (positionals.length === 0) throw new UnknownArgError(`border exfil expects <ref|url> [ref...] \u2014 ${EXFIL_USAGE}`);
43975
- const deep = ctx.flags.deep === true;
43976
- const first = positionals[0] ?? "";
43977
- const sandbox = { dir: "", tree: "" };
43978
- try {
43979
- const isUrl = URL_SHAPE_RE.test(first);
43980
- let repoDir;
43981
- let units;
43982
- let exposure = [];
43983
- if (isUrl) {
43984
- const url2 = first;
43985
- exposure = [sanitizeUrl(url2)];
43986
- sandbox.dir = mkdtempSync4(join24(tmpdir3(), "border-exfil-"));
43987
- const git2 = makeGit(sandbox.dir, ctx.env);
43988
- git2.run(["init", "-q"]);
43989
- git2.run(["remote", "add", "origin", url2]);
43990
- const specs = positionals.slice(1).length === 0 ? ["HEAD"] : positionals.slice(1);
43991
- units = [];
43992
- for (const spec of specs) {
43993
- git2.run(["fetch", "-q", "--no-tags", "origin", spec]);
43994
- units.push({ label: spec, rev: git2.run(["rev-parse", "FETCH_HEAD"]).trim() });
43995
- }
43996
- repoDir = sandbox.dir;
43997
- } else {
43998
- repoDir = resolveRepoDir(ctx.cwd, { env: ctx.env });
43999
- const git2 = makeGit(repoDir, ctx.env);
44000
- units = positionals.map((ref) => ({ label: ref, rev: git2.run(["rev-parse", "--verify", ref]).trim() }));
44001
- }
44002
- const git = makeGit(repoDir, ctx.env);
44003
- sandbox.tree = mkdtempSync4(join24(tmpdir3(), "border-exfil-tree-"));
44004
- const findings = [];
44005
- const seenBlob = /* @__PURE__ */ new Set();
44006
- for (const unit of units) {
44007
- findings.push(...await scanTipTree(git, sandbox.tree, unit.rev, ctx.env));
44008
- if (deep) {
44009
- const fresh = historyBlobs(git, unit.rev).filter((b) => {
44010
- const key = `${b.sha}\0${b.path}`;
44011
- if (seenBlob.has(key)) return false;
44012
- seenBlob.add(key);
44013
- return true;
44014
- });
44015
- findings.push(...scanBlobsNative(git, fresh));
44016
- }
44017
- }
44018
- if (isUrl) ctx.stderr("border exfil: URL mode scanned fetched tips only \u2014 tag notes are not fetched (remote-side leg, check/push own it)");
44019
- else findings.push(...scanTagMessages({ repoDir, target: "git", env: ctx.env }));
44020
- if (deep) {
44021
- findings.push(...scanGitHistory({ repoDir, refRange: units.map((u) => u.rev).join(" "), target: "git", env: ctx.env }).filter((f) => isExfilId(f.rule)));
44022
- findings.push(
44023
- ...scanCommitMessages({
44024
- repoDir,
44025
- refSet: units.map((u) => u.rev),
44026
- remotes: [],
44027
- hosts: [],
44028
- runGit: git.run,
44029
- env: ctx.env
44030
- })
44031
- );
44032
- }
44033
- const verdict = computeVerdict(findings);
44034
- const report = {
44035
- schemaVersion: 1,
44036
- key: sha256Hex2(`exfil:${isUrl ? "url" : "ref"}:${positionals.join(" ")}:${deep ? "deep" : "fast"}:${units.map((u) => u.rev).join(" ")}`),
44037
- head: units[0]?.rev ?? "",
44038
- dirty: false,
44039
- exposureSet: exposure,
44040
- refSet: positionals,
44041
- rulesHash: exfilRulesDigest(),
44042
- verdict,
44043
- counts: countFindings(findings),
44044
- findings,
44045
- ts: (/* @__PURE__ */ new Date()).toISOString()
44046
- };
44047
- if (ctx.flags.json) ctx.stdout(renderReportJson(report));
44048
- else {
44049
- const scope = `${isUrl ? "URL" : "refs"} ${positionals.join(",")}${deep ? " (deep)" : ""}`;
44050
- ctx.stdout(`border exfil ${scope} ${report.verdict}: ${String(report.counts.total)} finding(s), ${String(report.counts.blocking)} blocking`);
44051
- for (const f of report.findings) {
44052
- ctx.stdout(` ${f.severity} ${f.rule} [${f.engine}] ${f.path ?? f.commit ?? f.target}${f.line !== void 0 ? `:${String(f.line)}` : ""} ${f.message}`);
44053
- }
44054
- }
44055
- return exitCodeFromVerdict(verdict);
44056
- } finally {
44057
- if (sandbox.dir !== "") rmSync11(sandbox.dir, { recursive: true, force: true });
44058
- if (sandbox.tree !== "") rmSync11(sandbox.tree, { recursive: true, force: true });
44059
- }
44060
- }
44061
- var runExfil = (ctx) => runExfilCore(ctx);
44062
-
44063
44165
  // src/llm/bundle.ts
44064
- import { createHash as createHash15 } from "node:crypto";
44166
+ import { createHash as createHash16 } from "node:crypto";
44065
44167
  import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "node:fs";
44066
- import { dirname as dirname14, join as join26 } from "node:path";
44168
+ import { dirname as dirname14, join as join27 } from "node:path";
44067
44169
 
44068
44170
  // src/llm/diff.ts
44069
44171
  import { spawnSync as spawnSync16 } from "node:child_process";
44070
- import { mkdtempSync as mkdtempSync5, rmSync as rmSync12, writeFileSync as writeFileSync6 } from "node:fs";
44071
- import { dirname as dirname13, join as join25, resolve as resolve16 } from "node:path";
44172
+ import { mkdtempSync as mkdtempSync6, rmSync as rmSync13, writeFileSync as writeFileSync6 } from "node:fs";
44173
+ import { dirname as dirname13, join as join26, resolve as resolve16 } from "node:path";
44072
44174
  var GIT_DIFF_MAX_BUFFER = 64 * 1024 * 1024;
44073
44175
  function gitOut(repoDir, args, o, allowFail = false) {
44074
44176
  const abs = resolve16(repoDir);
@@ -44165,24 +44267,24 @@ function collectRawDeltas(repoDir, baseSha, headSha, o = {}) {
44165
44267
  var MASK_CHUNK_CHARS = 2e6;
44166
44268
  function maskViaEngineDetection(repoDir, texts, o = {}) {
44167
44269
  const sanitizer = new TextSanitizer();
44168
- const sandbox = mkdtempSync5(join25(repoDir, BORDER_STATE_DIR, "llm-mask-"));
44270
+ const sandbox = mkdtempSync6(join26(repoDir, BORDER_STATE_DIR, "llm-mask-"));
44169
44271
  try {
44170
44272
  let idx = 0;
44171
44273
  for (const text of texts) {
44172
44274
  if (text === "") continue;
44173
44275
  for (let off = 0; off < text.length; off += MASK_CHUNK_CHARS, idx += 1) {
44174
- writeFileSync6(join25(sandbox, `detect-${String(idx)}.txt`), text.slice(off, off + MASK_CHUNK_CHARS), "utf8");
44276
+ writeFileSync6(join26(sandbox, `detect-${String(idx)}.txt`), text.slice(off, off + MASK_CHUNK_CHARS), "utf8");
44175
44277
  }
44176
44278
  }
44177
44279
  scanTree({
44178
44280
  dir: sandbox,
44179
- stateDir: join25(repoDir, BORDER_STATE_DIR),
44281
+ stateDir: join26(repoDir, BORDER_STATE_DIR),
44180
44282
  target: "llm-mask",
44181
44283
  sanitizer,
44182
44284
  ...engineEnv(o)
44183
44285
  });
44184
44286
  } finally {
44185
- rmSync12(sandbox, { recursive: true, force: true });
44287
+ rmSync13(sandbox, { recursive: true, force: true });
44186
44288
  }
44187
44289
  return (text) => sanitizer.sanitize(text);
44188
44290
  }
@@ -44234,7 +44336,7 @@ async function runLlmRequestCore(ctx) {
44234
44336
  ctx.stderr("border: llm-request: no check record for the current state \u2014 run 'border check' first (the bundle derives from the recorded ctx)");
44235
44337
  return EXIT_ERROR;
44236
44338
  }
44237
- const absReport = join26(repoDir, record2.reportPath);
44339
+ const absReport = join27(repoDir, record2.reportPath);
44238
44340
  if (!existsSync16(absReport)) {
44239
44341
  ctx.stderr(`border: llm-request: report archive missing (${record2.reportPath} pruned?) \u2014 run 'border check' again`);
44240
44342
  return EXIT_ERROR;
@@ -44291,9 +44393,9 @@ async function runLlmRequestCore(ctx) {
44291
44393
  fileDeltas,
44292
44394
  artifacts: record2.artifacts,
44293
44395
  deterministic: { verdict: report.verdict, counts: report.counts, findings: report.findings },
44294
- promptTemplate: { path: templatePath, sha256: createHash15("sha256").update(readFileSync16(templatePath)).digest("hex") }
44396
+ promptTemplate: { path: templatePath, sha256: createHash16("sha256").update(readFileSync16(templatePath)).digest("hex") }
44295
44397
  };
44296
- const outPath = join26(dirname14(absReport), "llm-request.json");
44398
+ const outPath = join27(dirname14(absReport), "llm-request.json");
44297
44399
  mkdirSync11(dirname14(outPath), { recursive: true });
44298
44400
  writeFileSync7(outPath, `${JSON.stringify(bundle, null, 2)}
44299
44401
  `, "utf8");
@@ -44303,14 +44405,14 @@ async function runLlmRequestCore(ctx) {
44303
44405
  }
44304
44406
 
44305
44407
  // src/llm/ingest.ts
44306
- import { createHash as createHash16 } from "node:crypto";
44408
+ import { createHash as createHash17 } from "node:crypto";
44307
44409
  import { existsSync as existsSync17, readFileSync as readFileSync17, readdirSync as readdirSync9 } from "node:fs";
44308
- import { isAbsolute, join as join27, resolve as resolve17 } from "node:path";
44410
+ import { isAbsolute, join as join28, resolve as resolve17 } from "node:path";
44309
44411
  var RULE_RE = /^[a-z0-9-]+$/;
44310
44412
  var MAX_TEXT = 4e3;
44311
44413
  var ALLOWED_KEYS = /* @__PURE__ */ new Set(["rule", "severity", "target", "path", "line", "commit", "message", "snippet", "engine", "valueDigest"]);
44312
44414
  function sha2562(text) {
44313
- return createHash16("sha256").update(text, "utf8").digest("hex");
44415
+ return createHash17("sha256").update(text, "utf8").digest("hex");
44314
44416
  }
44315
44417
  function usageError(ctx, why) {
44316
44418
  ctx.stderr(`border: llm-ingest: ${why}`);
@@ -44324,7 +44426,7 @@ function findLatestBundle(repoDir) {
44324
44426
  return "no llm-request bundle found \u2014 run 'border llm-request' first";
44325
44427
  }
44326
44428
  for (const d of dirs.sort().reverse()) {
44327
- const p = join27(runsDir(repoDir), d, "llm-request.json");
44429
+ const p = join28(runsDir(repoDir), d, "llm-request.json");
44328
44430
  if (!existsSync17(p)) continue;
44329
44431
  let parsed;
44330
44432
  try {
@@ -44337,7 +44439,7 @@ function findLatestBundle(repoDir) {
44337
44439
  if (b.schemaVersion !== 1 || typeof b.key !== "string" || !/^[0-9a-f]{64}$/.test(b.key) || typeof b.head !== "string" || !/^[0-9a-f]{40}$/.test(b.head) || !Array.isArray(b.fileDeltas) || b.deterministic === void 0) {
44338
44440
  return `bundle ${p} fails its own schema \u2014 re-run 'border llm-request'`;
44339
44441
  }
44340
- return { dir: join27(runsDir(repoDir), d), bundle: b };
44442
+ return { dir: join28(runsDir(repoDir), d), bundle: b };
44341
44443
  }
44342
44444
  return "no llm-request bundle found \u2014 run 'border llm-request' first";
44343
44445
  }
@@ -44482,23 +44584,23 @@ async function runLlmRequest(ctx) {
44482
44584
  }
44483
44585
 
44484
44586
  // src/commands/opencode.ts
44485
- import { existsSync as existsSync22, lstatSync, mkdirSync as mkdirSync12, readFileSync as readFileSync20, rmSync as rmSync13, writeFileSync as writeFileSync8 } from "node:fs";
44486
- import { dirname as dirname16, join as join31 } from "node:path";
44587
+ import { existsSync as existsSync22, lstatSync, mkdirSync as mkdirSync12, readFileSync as readFileSync20, rmSync as rmSync14, writeFileSync as writeFileSync8 } from "node:fs";
44588
+ import { dirname as dirname16, join as join32 } from "node:path";
44487
44589
  import { fileURLToPath as fileURLToPath6 } from "node:url";
44488
44590
 
44489
44591
  // src/opencode/inspect.ts
44490
44592
  import { existsSync as existsSync21, readFileSync as readFileSync19 } from "node:fs";
44491
- import { join as join30 } from "node:path";
44593
+ import { join as join31 } from "node:path";
44492
44594
 
44493
44595
  // src/opencode/jsonconfig.ts
44494
44596
  import { existsSync as existsSync19, readdirSync as readdirSync10, readFileSync as readFileSync18 } from "node:fs";
44495
- import { join as join29 } from "node:path";
44597
+ import { join as join30 } from "node:path";
44496
44598
 
44497
44599
  // src/opencode/contract.ts
44498
- import { createHash as createHash17 } from "node:crypto";
44600
+ import { createHash as createHash18 } from "node:crypto";
44499
44601
  import { existsSync as existsSync18 } from "node:fs";
44500
44602
  import os3 from "node:os";
44501
- import { dirname as dirname15, join as join28 } from "node:path";
44603
+ import { dirname as dirname15, join as join29 } from "node:path";
44502
44604
  import { fileURLToPath as fileURLToPath5 } from "node:url";
44503
44605
  var PLUGIN_MARKER = "// border-opencode-plugin v";
44504
44606
  var COMMAND_MARKER = "<!-- border-opencode-command -->";
@@ -44506,7 +44608,7 @@ var BORDER_PACKAGE = "border-customs";
44506
44608
  var DEFAULT_REGISTRY_BASE = "https://registry.npmjs.org";
44507
44609
  var REGISTRY_TIMEOUT_MS2 = 15e3;
44508
44610
  function sha256Hex3(bytes) {
44509
- return createHash17("sha256").update(bytes).digest("hex");
44611
+ return createHash18("sha256").update(bytes).digest("hex");
44510
44612
  }
44511
44613
  function firstLine(bytes) {
44512
44614
  const text = bytes.toString("utf8");
@@ -44517,22 +44619,22 @@ function opencodeConfigRoot(env2) {
44517
44619
  const xdg = env2["XDG_CONFIG_HOME"] ?? "";
44518
44620
  if (xdg.length > 0) return xdg;
44519
44621
  const home = env2["HOME"] ?? "";
44520
- return join28(home.length > 0 ? home : os3.homedir(), ".config");
44622
+ return join29(home.length > 0 ? home : os3.homedir(), ".config");
44521
44623
  }
44522
44624
  function opencodeCacheRoot(env2) {
44523
44625
  const xdg = env2["XDG_CACHE_HOME"] ?? "";
44524
- const base = xdg.length > 0 ? xdg : join28(env2["HOME"] ?? os3.homedir(), ".cache");
44525
- return join28(base, "opencode", "packages");
44626
+ const base = xdg.length > 0 ? xdg : join29(env2["HOME"] ?? os3.homedir(), ".cache");
44627
+ return join29(base, "opencode", "packages");
44526
44628
  }
44527
44629
  function routeAPluginPath(env2) {
44528
- return join28(opencodeConfigRoot(env2), "opencode", "plugins", "border.ts");
44630
+ return join29(opencodeConfigRoot(env2), "opencode", "plugins", "border.ts");
44529
44631
  }
44530
44632
  function packagedAssetPath(moduleUrl, fileName) {
44531
44633
  const here = dirname15(fileURLToPath5(moduleUrl));
44532
44634
  const candidates = [
44533
- join28(here, "..", "plugin", fileName),
44635
+ join29(here, "..", "plugin", fileName),
44534
44636
  // dist (bundled) mode: <root>/plugin/<f>
44535
- join28(here, "..", "..", "plugin", fileName)
44637
+ join29(here, "..", "..", "plugin", fileName)
44536
44638
  // src mode: <root>/plugin/<f>
44537
44639
  ];
44538
44640
  return candidates.find((p) => existsSync18(p)) ?? candidates[1];
@@ -44540,11 +44642,11 @@ function packagedAssetPath(moduleUrl, fileName) {
44540
44642
  function packagedDistEntry(moduleUrl) {
44541
44643
  const here = dirname15(fileURLToPath5(moduleUrl));
44542
44644
  const candidates = [
44543
- join28(here, "index.js"),
44645
+ join29(here, "index.js"),
44544
44646
  // dist (bundled) mode: <root>/dist/index.js
44545
- join28(here, "..", "dist", "index.js"),
44647
+ join29(here, "..", "dist", "index.js"),
44546
44648
  // src/opencode mode one level under root
44547
- join28(here, "..", "..", "dist", "index.js")
44649
+ join29(here, "..", "..", "dist", "index.js")
44548
44650
  // src mode: <root>/dist/index.js
44549
44651
  ];
44550
44652
  return candidates.find((p) => existsSync18(p)) ?? candidates[2];
@@ -44640,22 +44742,22 @@ function listCacheDirs(cacheRoot, pkg) {
44640
44742
  return [];
44641
44743
  }
44642
44744
  return names.map((name) => {
44643
- const pkgRoot = join29(cacheRoot, name, "node_modules", pkg);
44745
+ const pkgRoot = join30(cacheRoot, name, "node_modules", pkg);
44644
44746
  let packageVersion = null;
44645
44747
  try {
44646
- const meta3 = parseJsonc(readFileSync18(join29(pkgRoot, "package.json"), "utf8"));
44748
+ const meta3 = parseJsonc(readFileSync18(join30(pkgRoot, "package.json"), "utf8"));
44647
44749
  if (isRecord2(meta3) && typeof meta3["version"] === "string") packageVersion = meta3["version"];
44648
44750
  } catch {
44649
44751
  packageVersion = null;
44650
44752
  }
44651
44753
  let markerLine = null;
44652
44754
  try {
44653
- markerLine = firstLine(readFileSync18(join29(pkgRoot, "plugin", "border.ts")));
44755
+ markerLine = firstLine(readFileSync18(join30(pkgRoot, "plugin", "border.ts")));
44654
44756
  } catch {
44655
44757
  markerLine = null;
44656
44758
  }
44657
44759
  const markerVersion = markerLine !== null && markerLine.startsWith(PLUGIN_MARKER) ? markerLine.slice(PLUGIN_MARKER.length).trim() : null;
44658
- return { dir: join29(cacheRoot, name), tag: name.slice(pkg.length + 1), packageVersion, markerVersion, markerLine };
44760
+ return { dir: join30(cacheRoot, name), tag: name.slice(pkg.length + 1), packageVersion, markerVersion, markerLine };
44659
44761
  });
44660
44762
  }
44661
44763
 
@@ -44966,14 +45068,14 @@ function gatherFacts(env2, cwd, deps) {
44966
45068
  const pkg = deps.package ?? BORDER_PACKAGE;
44967
45069
  const pluginDrop = routeAPluginPath(env2);
44968
45070
  const cacheRoot = deps.cacheRoot ?? opencodeCacheRoot(env2);
44969
- const globalPath = deps.globalConfigPath ?? join30(opencodeConfigRoot(env2), "opencode", "opencode.jsonc");
45071
+ const globalPath = deps.globalConfigPath ?? join31(opencodeConfigRoot(env2), "opencode", "opencode.jsonc");
44970
45072
  const routeAInstalled = existsSync21(pluginDrop);
44971
45073
  return {
44972
45074
  pkg,
44973
45075
  env: env2,
44974
45076
  cacheRoot,
44975
45077
  cacheDirs: listCacheDirs(cacheRoot, pkg),
44976
- configs: [readPluginDeclarations(globalPath, pkg), readPluginDeclarations(join30(cwd, "opencode.jsonc"), pkg)],
45078
+ configs: [readPluginDeclarations(globalPath, pkg), readPluginDeclarations(join31(cwd, "opencode.jsonc"), pkg)],
44977
45079
  packagedPlugin: readPackagedPlugin(packagedAssetPath(import.meta.url, "border.ts")),
44978
45080
  routeAPlugin: pluginDrop,
44979
45081
  routeAInstalled,
@@ -45014,18 +45116,18 @@ async function runInspectCore(ctx, deps = {}) {
45014
45116
  function resolvePluginAsset(moduleUrl, fileName) {
45015
45117
  const here = dirname16(fileURLToPath6(moduleUrl));
45016
45118
  const candidates = [
45017
- join31(here, "..", "plugin", fileName),
45119
+ join32(here, "..", "plugin", fileName),
45018
45120
  // dist mode (bundled): <root>/plugin/<f>
45019
- join31(here, "..", "..", "plugin", fileName)
45121
+ join32(here, "..", "..", "plugin", fileName)
45020
45122
  // src mode: <root>/plugin/<f>
45021
45123
  ];
45022
45124
  return candidates.find((p) => existsSync22(p)) ?? candidates[1];
45023
45125
  }
45024
45126
  function targetsFor(env2) {
45025
- const root = join31(opencodeConfigRoot(env2), "opencode");
45127
+ const root = join32(opencodeConfigRoot(env2), "opencode");
45026
45128
  return [
45027
- { fileName: "border.ts", destination: join31(root, "plugins", "border.ts"), marker: PLUGIN_MARKER },
45028
- { fileName: "border-command.md", destination: join31(root, "commands", "border.md"), marker: COMMAND_MARKER }
45129
+ { fileName: "border.ts", destination: join32(root, "plugins", "border.ts"), marker: PLUGIN_MARKER },
45130
+ { fileName: "border-command.md", destination: join32(root, "commands", "border.md"), marker: COMMAND_MARKER }
45029
45131
  ];
45030
45132
  }
45031
45133
  function carriesMarker(bytes, marker) {
@@ -45149,7 +45251,7 @@ function opencodeUninstall(ctx) {
45149
45251
  `refusing to remove ${target.destination} \u2014 the file is not ours (its first line lacks the marker '${target.marker}'); nothing was removed anywhere`
45150
45252
  );
45151
45253
  }
45152
- rmSync13(target.destination);
45254
+ rmSync14(target.destination);
45153
45255
  ctx.stdout(`${target.destination}: removed`);
45154
45256
  }
45155
45257
  ctx.stdout("note: restart opencode for the tool and command to disappear.");
@@ -45278,9 +45380,9 @@ function confirmRemoteBranch(i) {
45278
45380
 
45279
45381
  // src/push/landing.ts
45280
45382
  import { spawnSync as spawnSync18 } from "node:child_process";
45281
- import { mkdtempSync as mkdtempSync6, rmSync as rmSync14 } from "node:fs";
45282
- import { tmpdir as tmpdir4 } from "node:os";
45283
- import { dirname as dirname17, join as join32, resolve as resolve18 } from "node:path";
45383
+ import { mkdtempSync as mkdtempSync7, rmSync as rmSync15 } from "node:fs";
45384
+ import { tmpdir as tmpdir5 } from "node:os";
45385
+ import { dirname as dirname17, join as join33, resolve as resolve18 } from "node:path";
45284
45386
  var LANDINGS_REF_PREFIX = "refs/border-landings/";
45285
45387
  async function runLandingVerification(o) {
45286
45388
  if (o.legs.length === 0) return null;
@@ -45303,6 +45405,12 @@ async function runLandingVerification(o) {
45303
45405
  );
45304
45406
  return EXIT_ERROR;
45305
45407
  }
45408
+ const allow = applyAllowList(findings, o.allow ?? [], o.repoDir);
45409
+ findings = allow.kept;
45410
+ if (allow.allowHits.length > 0) {
45411
+ const hits = allow.allowHits.map((h) => `${h.rule}#entry${String(h.entryIndex)}:${String(h.count)}`).join(", ");
45412
+ o.err(`border: ${leg.target.target} landing: ${String(allow.allowHits.reduce((n, h) => n + h.count, 0))} tip finding(s) exempted by config allow pins (${hits}) \u2014 same entries the check verdict honored, echoed loud`);
45413
+ }
45306
45414
  const blocking = findings.filter((f) => isBlocking(f.severity)).length;
45307
45415
  const verdict = blocking > 0 ? "blocked" : "clean";
45308
45416
  sink(buildLandingRecord({ key: o.key, target: leg.target.target, ref: `refs/heads/${leg.branch}`, remoteSha: sha, verdict, blocking, rulesHash }));
@@ -45355,11 +45463,11 @@ async function defaultScanRemoteTip(o, remote, branch, remoteSha) {
45355
45463
  ownedTempRef = landingRef;
45356
45464
  rev = git.run(["rev-parse", landingRef]).trim();
45357
45465
  }
45358
- const treeRoot = mkdtempSync6(join32(tmpdir4(), "border-landing-tree-"));
45466
+ const treeRoot = mkdtempSync7(join33(tmpdir5(), "border-landing-tree-"));
45359
45467
  try {
45360
45468
  return await scanTipTree(git, treeRoot, rev, o.env);
45361
45469
  } finally {
45362
- rmSync14(treeRoot, { recursive: true, force: true });
45470
+ rmSync15(treeRoot, { recursive: true, force: true });
45363
45471
  }
45364
45472
  } finally {
45365
45473
  if (ownedTempRef !== null) {
@@ -45539,6 +45647,9 @@ async function executeYesPush(ctx, loaded) {
45539
45647
  key: state.key,
45540
45648
  legs: legs.map((leg) => ({ target: leg, branch })),
45541
45649
  env: env2,
45650
+ // the SAME allow entries the certifying check honored — one standard
45651
+ // across faces (2026-10-04 incident fix; see push/landing.ts doctrine).
45652
+ allow: loaded.config.allow,
45542
45653
  out: ctx.stdout,
45543
45654
  err: ctx.stderr
45544
45655
  });
@@ -45561,17 +45672,17 @@ async function executeYesPush(ctx, loaded) {
45561
45672
  }
45562
45673
 
45563
45674
  // src/roundtrip/orchestrate.ts
45564
- import { mkdtempSync as mkdtempSync7, rmSync as rmSync15, writeFileSync as writeFileSync9 } from "node:fs";
45675
+ import { mkdtempSync as mkdtempSync8, rmSync as rmSync16, writeFileSync as writeFileSync9 } from "node:fs";
45565
45676
  import { randomBytes } from "node:crypto";
45566
- import { tmpdir as tmpdir5 } from "node:os";
45567
- import { join as join33 } from "node:path";
45677
+ import { tmpdir as tmpdir6 } from "node:os";
45678
+ import { join as join34 } from "node:path";
45568
45679
 
45569
45680
  // src/scan/fetch.ts
45570
- import { createHash as createHash18 } from "node:crypto";
45681
+ import { createHash as createHash19 } from "node:crypto";
45571
45682
  import { basename as basename5 } from "node:path";
45572
45683
  var SCAN_MAX_ARTIFACT_BYTES = 200 * 1024 * 1024;
45573
45684
  var SCAN_REQUEST_TIMEOUT_MS = 6e4;
45574
- var SCAN_USER_AGENT = "border-customs/0.8.0 (+https://github.com/sumteclab/border; border scan)";
45685
+ var SCAN_USER_AGENT = "border-customs/0.8.1 (+https://github.com/gate-corp/border; border scan)";
45575
45686
  var CRATES_DOWNLOAD_HOST = "https://static.crates.io";
45576
45687
  var defaultScanFetcher = async (url2, init) => {
45577
45688
  if (!/^https?:\/\//.test(url2)) throw new EngineRunError(`border scan refuses non-http(s) URL: ${url2}`, null);
@@ -45697,7 +45808,7 @@ async function fetchArtifact(spec, o) {
45697
45808
  }
45698
45809
  }
45699
45810
  function sha256Hex4(data) {
45700
- return createHash18("sha256").update(data).digest("hex");
45811
+ return createHash19("sha256").update(data).digest("hex");
45701
45812
  }
45702
45813
 
45703
45814
  // src/scan/spec.ts
@@ -45741,7 +45852,7 @@ function parseScanSpec(raw) {
45741
45852
 
45742
45853
  // src/roundtrip/localArtifact.ts
45743
45854
  import { createReadStream, existsSync as existsSync23, statSync as statSync3 } from "node:fs";
45744
- import { createHash as createHash19 } from "node:crypto";
45855
+ import { createHash as createHash20 } from "node:crypto";
45745
45856
  import { basename as basename6, resolve as resolve19 } from "node:path";
45746
45857
  var EXT_TABLE = /* @__PURE__ */ new Map([
45747
45858
  [".whl", "pypi"],
@@ -45824,7 +45935,7 @@ function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
45824
45935
  return null;
45825
45936
  }
45826
45937
  async function readLocalArtifact(absPath, cap = SCAN_MAX_ARTIFACT_BYTES) {
45827
- const hash2 = createHash19("sha256");
45938
+ const hash2 = createHash20("sha256");
45828
45939
  const parts = [];
45829
45940
  let total = 0;
45830
45941
  for await (const chunk of createReadStream(absPath)) {
@@ -46050,7 +46161,7 @@ var CLAIM_SCANNER_PY = [
46050
46161
  ].join("\n");
46051
46162
 
46052
46163
  // src/roundtrip/manifest.ts
46053
- import { createHash as createHash20 } from "node:crypto";
46164
+ import { createHash as createHash21 } from "node:crypto";
46054
46165
  var RT_ENGINE = "roundtrip";
46055
46166
  var RT_RESIDUE_RULE = "roundtrip-residue-file";
46056
46167
  var RT_MODIFY_RULE = "roundtrip-modified-file";
@@ -46101,7 +46212,7 @@ var PERSISTENCE_PATTERNS = [
46101
46212
  function isPersistenceSurface(path4) {
46102
46213
  return PERSISTENCE_PATTERNS.some((re) => re.test(path4));
46103
46214
  }
46104
- var digestOf = (what) => createHash20("sha256").update(what).digest("hex");
46215
+ var digestOf = (what) => createHash21("sha256").update(what).digest("hex");
46105
46216
  function classifyResidue(label, diff, attribution = null) {
46106
46217
  const findings = [];
46107
46218
  for (const e of diff.added) {
@@ -46303,12 +46414,12 @@ async function runRoundtripCore(ctx, deps = {}) {
46303
46414
  ctx.stderr("roundtrip: docker unavailable \u2014 cannot verify");
46304
46415
  return EXIT_ERROR;
46305
46416
  }
46306
- const baseDir = mkdtempSync7(join33(tmpdir5(), "border-roundtrip-"));
46417
+ const baseDir = mkdtempSync8(join34(tmpdir6(), "border-roundtrip-"));
46307
46418
  try {
46308
46419
  const localRead = local !== null ? await readLocalArtifact(local.absPath) : null;
46309
46420
  const bytes = localRead?.bytes ?? (await fetchArtifact(spec, { fetcher: deps.fetcher ?? defaultScanFetcher })).bytes;
46310
46421
  const artifactSha256 = localRead !== null ? localRead.sha256 : sha256Hex4(bytes);
46311
- const hostPath = join33(baseDir, "artifact");
46422
+ const hostPath = join34(baseDir, "artifact");
46312
46423
  writeFileSync9(hostPath, bytes);
46313
46424
  const container = containerNameFor(label);
46314
46425
  const closure = spec.ecosystem === "pypi" ? resolvePipClosure(exec, profile, hostPath, label, spec) : null;
@@ -46353,7 +46464,7 @@ async function runRoundtripCore(ctx, deps = {}) {
46353
46464
  }
46354
46465
  return exitCodeFromVerdict(verdict);
46355
46466
  } finally {
46356
- rmSync15(baseDir, { recursive: true, force: true });
46467
+ rmSync16(baseDir, { recursive: true, force: true });
46357
46468
  }
46358
46469
  }
46359
46470
  function parsesRegistrySpec(raw) {
@@ -46409,9 +46520,9 @@ function dockerAvailableGuard(exec) {
46409
46520
  var runRoundtrip = (ctx) => runRoundtripCore(ctx);
46410
46521
 
46411
46522
  // src/scan/scan.ts
46412
- import { mkdtempSync as mkdtempSync8 } from "node:fs";
46413
- import { tmpdir as tmpdir6 } from "node:os";
46414
- import { join as join35 } from "node:path";
46523
+ import { mkdtempSync as mkdtempSync9 } from "node:fs";
46524
+ import { tmpdir as tmpdir7 } from "node:os";
46525
+ import { join as join36 } from "node:path";
46415
46526
 
46416
46527
  // src/scan/config.ts
46417
46528
  function buildScanConfigYaml(eco) {
@@ -46438,8 +46549,8 @@ function synthesizeScanConfig(eco) {
46438
46549
  }
46439
46550
 
46440
46551
  // src/scan/materialize.ts
46441
- import { existsSync as existsSync24, mkdirSync as mkdirSync13, readdirSync as readdirSync11, readFileSync as readFileSync21, rmSync as rmSync16, writeFileSync as writeFileSync10 } from "node:fs";
46442
- import { join as join34 } from "node:path";
46552
+ import { existsSync as existsSync24, mkdirSync as mkdirSync13, readdirSync as readdirSync11, readFileSync as readFileSync21, rmSync as rmSync17, writeFileSync as writeFileSync10 } from "node:fs";
46553
+ import { join as join35 } from "node:path";
46443
46554
  var CRATES_ENVELOPE_FILES = ["Cargo.toml.orig", ".cargo-ok", ".cargo_vcs_info.json"];
46444
46555
  var fail3 = (why) => {
46445
46556
  throw new Error(`border scan cannot materialize the artifact (${why}) \u2014 refusing to stage an unusable tree (fail-closed)`);
@@ -46456,12 +46567,12 @@ function unwrapSingleDir(dir) {
46456
46567
  const entries = readdirSync11(dir, { withFileTypes: true });
46457
46568
  const dirs = entries.filter((e) => e.isDirectory());
46458
46569
  if (entries.length === 1 && dirs.length === 1 && dirs[0] !== void 0) {
46459
- return join34(dir, dirs[0].name);
46570
+ return join35(dir, dirs[0].name);
46460
46571
  }
46461
46572
  return dir;
46462
46573
  }
46463
46574
  function requireManifest(repoDir, ecosystem, candidates) {
46464
- if (!candidates.some((rel) => existsSync24(join34(repoDir, rel)))) {
46575
+ if (!candidates.some((rel) => existsSync24(join35(repoDir, rel)))) {
46465
46576
  fail3(`${ecosystem} package tree has none of [${candidates.join(", ")}] at its root`);
46466
46577
  }
46467
46578
  }
@@ -46471,18 +46582,18 @@ function materializePackage(o) {
46471
46582
  `);
46472
46583
  });
46473
46584
  const safe = safeArtifactName(o.filename, `${o.ecosystem}-artifact`);
46474
- const archive = join34(o.baseDir, safe);
46585
+ const archive = join35(o.baseDir, safe);
46475
46586
  writeFileSync10(archive, o.bytes);
46476
- const extractDir = join34(o.baseDir, "extract");
46587
+ const extractDir = join35(o.baseDir, "extract");
46477
46588
  mkdirSync13(extractDir, { recursive: true });
46478
46589
  extractFlat(archive, extractDir);
46479
46590
  let repoDir = unwrapSingleDir(extractDir);
46480
46591
  if (o.ecosystem === "rubygems") {
46481
46592
  const outer = repoDir;
46482
46593
  const dataCandidates = findNativeMissArchives(outer).filter((f) => f.endsWith("data.tar.gz"));
46483
- const inner = dataCandidates[0] ?? join34(outer, "data.tar.gz");
46594
+ const inner = dataCandidates[0] ?? join35(outer, "data.tar.gz");
46484
46595
  if (!existsSync24(inner)) fail3(".gem archive contains no data.tar.gz");
46485
- const gemTree = join34(o.baseDir, "gem-tree");
46596
+ const gemTree = join35(o.baseDir, "gem-tree");
46486
46597
  mkdirSync13(gemTree, { recursive: true });
46487
46598
  extractFlat(inner, gemTree);
46488
46599
  repoDir = unwrapSingleDir(gemTree);
@@ -46493,10 +46604,10 @@ function materializePackage(o) {
46493
46604
  } else {
46494
46605
  requireManifest(repoDir, o.ecosystem, ["Cargo.toml"]);
46495
46606
  for (const file2 of CRATES_ENVELOPE_FILES) {
46496
- const path4 = join34(repoDir, file2);
46607
+ const path4 = join35(repoDir, file2);
46497
46608
  if (!existsSync24(path4)) continue;
46498
46609
  const original = readFileSync21(path4);
46499
- rmSync16(path4, { force: true });
46610
+ rmSync17(path4, { force: true });
46500
46611
  note(`border scan: crates envelope normalized: ${file2} sha256=${sha256Hex4(original)}`);
46501
46612
  }
46502
46613
  }
@@ -46523,7 +46634,7 @@ async function runScanCore(ctx, deps = {}) {
46523
46634
  }
46524
46635
  const cfg = synthesizeScanConfig(spec.ecosystem);
46525
46636
  const fetcher = deps.fetcher ?? defaultScanFetcher;
46526
- const baseDir = mkdtempSync8(join35(tmpdir6(), "border-scan-"));
46637
+ const baseDir = mkdtempSync9(join36(tmpdir7(), "border-scan-"));
46527
46638
  try {
46528
46639
  const { bytes, filename } = await fetchArtifact(spec, { fetcher });
46529
46640
  const env2 = { ...ctx.env };