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/README.md +35 -9
- package/dist/assets/exfil-src/rules.ts +1 -1
- package/dist/assets/exfil-src/rulesPath.ts +28 -0
- package/dist/assets/exfil-src/scan.ts +2 -0
- package/dist/assets/exfil-src/severity.ts +7 -3
- package/dist/assets/package-lock.json +2 -2
- package/dist/index.js +546 -435
- package/package.json +1 -1
- package/plugin/border.ts +1 -1
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 {
|
|
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/
|
|
43560
|
-
|
|
43561
|
-
|
|
43562
|
-
|
|
43563
|
-
|
|
43564
|
-
|
|
43565
|
-
|
|
43566
|
-
|
|
43567
|
-
const
|
|
43568
|
-
|
|
43569
|
-
|
|
43570
|
-
|
|
43571
|
-
|
|
43572
|
-
|
|
43573
|
-
|
|
43574
|
-
|
|
43575
|
-
|
|
43576
|
-
|
|
43577
|
-
|
|
43578
|
-
|
|
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
|
-
|
|
43582
|
-
|
|
43583
|
-
|
|
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
|
|
43588
|
-
|
|
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
|
-
|
|
43591
|
-
|
|
43592
|
-
|
|
43593
|
-
|
|
43594
|
-
|
|
43595
|
-
|
|
43596
|
-
|
|
43597
|
-
|
|
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
|
-
|
|
43601
|
-
const
|
|
43602
|
-
const
|
|
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
|
|
43605
|
-
|
|
43606
|
-
|
|
43607
|
-
|
|
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
|
-
|
|
43621
|
-
|
|
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
|
-
|
|
43624
|
-
|
|
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
|
-
|
|
43627
|
-
|
|
43628
|
-
|
|
43629
|
-
|
|
43630
|
-
|
|
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
|
|
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
|
|
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
|
|
44071
|
-
import { dirname as dirname13, join as
|
|
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 =
|
|
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(
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
44396
|
+
promptTemplate: { path: templatePath, sha256: createHash16("sha256").update(readFileSync16(templatePath)).digest("hex") }
|
|
44295
44397
|
};
|
|
44296
|
-
const outPath =
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
44486
|
-
import { dirname as dirname16, join as
|
|
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
|
|
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
|
|
44597
|
+
import { join as join30 } from "node:path";
|
|
44496
44598
|
|
|
44497
44599
|
// src/opencode/contract.ts
|
|
44498
|
-
import { createHash as
|
|
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
|
|
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
|
|
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
|
|
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 :
|
|
44525
|
-
return
|
|
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
|
|
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
|
-
|
|
44635
|
+
join29(here, "..", "plugin", fileName),
|
|
44534
44636
|
// dist (bundled) mode: <root>/plugin/<f>
|
|
44535
|
-
|
|
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
|
-
|
|
44645
|
+
join29(here, "index.js"),
|
|
44544
44646
|
// dist (bundled) mode: <root>/dist/index.js
|
|
44545
|
-
|
|
44647
|
+
join29(here, "..", "dist", "index.js"),
|
|
44546
44648
|
// src/opencode mode one level under root
|
|
44547
|
-
|
|
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 =
|
|
44745
|
+
const pkgRoot = join30(cacheRoot, name, "node_modules", pkg);
|
|
44644
44746
|
let packageVersion = null;
|
|
44645
44747
|
try {
|
|
44646
|
-
const meta3 = parseJsonc(readFileSync18(
|
|
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(
|
|
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:
|
|
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 ??
|
|
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(
|
|
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
|
-
|
|
45119
|
+
join32(here, "..", "plugin", fileName),
|
|
45018
45120
|
// dist mode (bundled): <root>/plugin/<f>
|
|
45019
|
-
|
|
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 =
|
|
45127
|
+
const root = join32(opencodeConfigRoot(env2), "opencode");
|
|
45026
45128
|
return [
|
|
45027
|
-
{ fileName: "border.ts", destination:
|
|
45028
|
-
{ fileName: "border-command.md", destination:
|
|
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
|
-
|
|
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
|
|
45282
|
-
import { tmpdir as
|
|
45283
|
-
import { dirname as dirname17, join as
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
45567
|
-
import { join as
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
46413
|
-
import { tmpdir as
|
|
46414
|
-
import { join as
|
|
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
|
|
46442
|
-
import { join as
|
|
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
|
|
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(
|
|
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 =
|
|
46585
|
+
const archive = join35(o.baseDir, safe);
|
|
46475
46586
|
writeFileSync10(archive, o.bytes);
|
|
46476
|
-
const extractDir =
|
|
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] ??
|
|
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 =
|
|
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 =
|
|
46607
|
+
const path4 = join35(repoDir, file2);
|
|
46497
46608
|
if (!existsSync24(path4)) continue;
|
|
46498
46609
|
const original = readFileSync21(path4);
|
|
46499
|
-
|
|
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 =
|
|
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 };
|