border-customs 0.7.1 → 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 +66 -12
- 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 +1055 -432
- package/package.json +1 -1
- package/plugin/border-command.md +1 -1
- package/plugin/border.ts +4 -3
package/dist/index.js
CHANGED
|
@@ -13029,7 +13029,7 @@ var require_micromatch = __commonJS({
|
|
|
13029
13029
|
|
|
13030
13030
|
// src/index.ts
|
|
13031
13031
|
import { realpathSync } from "node:fs";
|
|
13032
|
-
import { fileURLToPath as
|
|
13032
|
+
import { fileURLToPath as fileURLToPath7, pathToFileURL } from "node:url";
|
|
13033
13033
|
|
|
13034
13034
|
// src/cli.ts
|
|
13035
13035
|
import { parseArgs } from "node:util";
|
|
@@ -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])";
|
|
@@ -38093,12 +38106,12 @@ function packOnce(o) {
|
|
|
38093
38106
|
throw new EngineRunError(`npm pack reported '${report.filename}' but no tarball landed in ${o.distDir}`, null);
|
|
38094
38107
|
}
|
|
38095
38108
|
const data = readFileSync3(tarballAbs);
|
|
38096
|
-
const
|
|
38109
|
+
const sha2563 = createHash3("sha256").update(data).digest("hex");
|
|
38097
38110
|
const sha1 = createHash3("sha1").update(data).digest("hex");
|
|
38098
38111
|
if (sha1 !== report.shasum || data.byteLength !== report.size) {
|
|
38099
38112
|
throw new EngineRunError(`npm pack report disagrees with the tarball bytes for ${report.filename} \u2014 corrupted or swapped artifact, failing closed`, null);
|
|
38100
38113
|
}
|
|
38101
|
-
return { filename: report.filename, sha256:
|
|
38114
|
+
return { filename: report.filename, sha256: sha2563, bytes: data.byteLength, tarballAbs };
|
|
38102
38115
|
}
|
|
38103
38116
|
function publintFinding(o, tarballAbs, identity) {
|
|
38104
38117
|
const candidates = o.publintBinPath !== void 0 ? [o.publintBinPath] : [
|
|
@@ -39141,7 +39154,7 @@ async function runNpmArtifactStage(o) {
|
|
|
39141
39154
|
const stateDir = join10(repoDir, BORDER_STATE_DIR);
|
|
39142
39155
|
const distDir = join10(stateDir, "dist");
|
|
39143
39156
|
mkdirSync3(distDir, { recursive: true });
|
|
39144
|
-
const { filename, sha256:
|
|
39157
|
+
const { filename, sha256: sha2563, bytes, tarballAbs } = packOnce({
|
|
39145
39158
|
repoDir,
|
|
39146
39159
|
distDir,
|
|
39147
39160
|
...o.env !== void 0 ? { env: o.env } : {},
|
|
@@ -39149,7 +39162,7 @@ async function runNpmArtifactStage(o) {
|
|
|
39149
39162
|
...o.npmTimeoutMs !== void 0 ? { npmTimeoutMs: o.npmTimeoutMs } : {}
|
|
39150
39163
|
});
|
|
39151
39164
|
const findings = [];
|
|
39152
|
-
const extractDir = join10(stateDir, "tmp",
|
|
39165
|
+
const extractDir = join10(stateDir, "tmp", sha2563.slice(0, 8));
|
|
39153
39166
|
rmSync3(extractDir, { recursive: true, force: true });
|
|
39154
39167
|
mkdirSync3(extractDir, { recursive: true });
|
|
39155
39168
|
try {
|
|
@@ -39254,7 +39267,7 @@ async function runNpmArtifactStage(o) {
|
|
|
39254
39267
|
if (publint !== null) findings.push(publint);
|
|
39255
39268
|
return {
|
|
39256
39269
|
findings,
|
|
39257
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${filename}`, sha256:
|
|
39270
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${filename}`, sha256: sha2563, bytes }
|
|
39258
39271
|
};
|
|
39259
39272
|
}
|
|
39260
39273
|
|
|
@@ -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
|
|
@@ -40354,9 +40368,9 @@ async function runRubygemsArtifactStage(o) {
|
|
|
40354
40368
|
throw new EngineRunError(`gem build produced no ${gemName} in ${relative3(repoDir, distDir)} \u2014 failing closed (was the gemspec's literal name/version re-read?)`, null);
|
|
40355
40369
|
}
|
|
40356
40370
|
const bytes = readFileSync7(dist);
|
|
40357
|
-
const
|
|
40371
|
+
const sha2563 = createHash6("sha256").update(bytes).digest("hex");
|
|
40358
40372
|
const findings = [];
|
|
40359
|
-
const extractDir = join12(stateDir, "tmp",
|
|
40373
|
+
const extractDir = join12(stateDir, "tmp", sha2563.slice(0, 8));
|
|
40360
40374
|
rmSync4(extractDir, { recursive: true, force: true });
|
|
40361
40375
|
mkdirSync4(extractDir, { recursive: true });
|
|
40362
40376
|
const dataDir = `${extractDir}-data`;
|
|
@@ -40434,7 +40448,7 @@ async function runRubygemsArtifactStage(o) {
|
|
|
40434
40448
|
}
|
|
40435
40449
|
return {
|
|
40436
40450
|
findings,
|
|
40437
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256:
|
|
40451
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${gemName}`, sha256: sha2563, bytes: bytes.length }
|
|
40438
40452
|
};
|
|
40439
40453
|
}
|
|
40440
40454
|
|
|
@@ -41996,9 +42010,9 @@ async function runCargoArtifactStage(o) {
|
|
|
41996
42010
|
const dist = join18(distDir, crateName);
|
|
41997
42011
|
renameSync(packed, dist);
|
|
41998
42012
|
const bytes = readFileSync11(dist);
|
|
41999
|
-
const
|
|
42013
|
+
const sha2563 = createHash11("sha256").update(bytes).digest("hex");
|
|
42000
42014
|
const findings = [];
|
|
42001
|
-
const extractDir = join18(stateDir, "tmp",
|
|
42015
|
+
const extractDir = join18(stateDir, "tmp", sha2563.slice(0, 8));
|
|
42002
42016
|
rmSync8(extractDir, { recursive: true, force: true });
|
|
42003
42017
|
mkdirSync8(extractDir, { recursive: true });
|
|
42004
42018
|
try {
|
|
@@ -42051,7 +42065,7 @@ async function runCargoArtifactStage(o) {
|
|
|
42051
42065
|
}
|
|
42052
42066
|
return {
|
|
42053
42067
|
findings,
|
|
42054
|
-
artifact: { file: `${BORDER_STATE_DIR}/dist/${crateName}`, sha256:
|
|
42068
|
+
artifact: { file: `${BORDER_STATE_DIR}/dist/${crateName}`, sha256: sha2563, bytes: bytes.length }
|
|
42055
42069
|
};
|
|
42056
42070
|
} finally {
|
|
42057
42071
|
rmSync8(targetDir, { recursive: true, force: true });
|
|
@@ -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,266 +43565,6 @@ 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`
|
|
43579
|
-
};
|
|
43580
|
-
}
|
|
43581
|
-
function legOptions(o, sanitizer) {
|
|
43582
|
-
return {
|
|
43583
|
-
...o.env !== void 0 ? { env: o.env } : {},
|
|
43584
|
-
sanitizer
|
|
43585
|
-
};
|
|
43586
|
-
}
|
|
43587
|
-
function historyRefRange(ctx) {
|
|
43588
|
-
return ctx.refSet.length > 0 ? ctx.refSet.join(" ") : ctx.headSha;
|
|
43589
|
-
}
|
|
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);
|
|
43598
|
-
}
|
|
43599
|
-
}
|
|
43600
|
-
async function runPipeline(o, ctx, lockWarning) {
|
|
43601
|
-
const repoDir = ctx.repoDir;
|
|
43602
|
-
const envOpt = o.env !== void 0 ? { env: o.env } : {};
|
|
43603
|
-
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));
|
|
43619
|
-
}
|
|
43620
|
-
if (!broken.has("secretlint")) {
|
|
43621
|
-
findings.push(...await scanGitTrackedFiles({ repoDir, target: "git", rules: o.cfg.rules, ...legOptions(o, sanitizer) }));
|
|
43622
|
-
}
|
|
43623
|
-
if (o.cfg.engines.trufflehog && !broken.has("trufflehog")) {
|
|
43624
|
-
findings.push(...scanTrufflehog({ repoDir, target: "git", ...legOptions(o, sanitizer) }));
|
|
43625
|
-
}
|
|
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({
|
|
43631
|
-
repoDir,
|
|
43632
|
-
refSet: ctx.refSet.length > 0 ? [...ctx.refSet] : [ctx.headSha],
|
|
43633
|
-
remotes: o.cfg.targets.git.remotes,
|
|
43634
|
-
hosts: o.cfg.rules.hosts,
|
|
43635
|
-
...legOptions(o, sanitizer)
|
|
43636
|
-
}),
|
|
43637
|
-
repoDir
|
|
43638
|
-
));
|
|
43639
|
-
const ledgerArtifacts = [];
|
|
43640
|
-
for (const channel of publishChannels()) {
|
|
43641
|
-
if (!o.effectiveTargets.includes(channel.id) || !channel.configured(o.cfg)) continue;
|
|
43642
|
-
const stage = await channel.stage({
|
|
43643
|
-
repoDir,
|
|
43644
|
-
cfg: o.cfg,
|
|
43645
|
-
sanitizer,
|
|
43646
|
-
...envOpt,
|
|
43647
|
-
skipGitleaks: broken.has("gitleaks"),
|
|
43648
|
-
skipSecretlint: broken.has("secretlint")
|
|
43649
|
-
});
|
|
43650
|
-
findings.push(...stage.findings);
|
|
43651
|
-
ledgerArtifacts.push(...stage.artifacts);
|
|
43652
|
-
}
|
|
43653
|
-
if ((o.cfg.release?.twin.length ?? 0) > 0) {
|
|
43654
|
-
findings.push(
|
|
43655
|
-
...await twinCoherenceFindings({
|
|
43656
|
-
pairs: o.cfg.release?.twin ?? [],
|
|
43657
|
-
artifacts: ledgerArtifacts,
|
|
43658
|
-
repoDir,
|
|
43659
|
-
...envOpt,
|
|
43660
|
-
sanitizer
|
|
43661
|
-
})
|
|
43662
|
-
);
|
|
43663
|
-
}
|
|
43664
|
-
findings.push(...await runRegistryProbes({ repoDir, cfg: o.cfg, effectiveTargets: o.effectiveTargets, ...envOpt }));
|
|
43665
|
-
if (o.cfg.residue?.enabled === false) {
|
|
43666
|
-
for (let i = findings.length - 1; i >= 0; i -= 1) {
|
|
43667
|
-
const f = findings[i];
|
|
43668
|
-
if (f !== void 0 && RESIDUE_RULE_IDS.has(f.rule)) findings.splice(i, 1);
|
|
43669
|
-
}
|
|
43670
|
-
}
|
|
43671
|
-
const rulesHash = await computeCheckRulesHash({
|
|
43672
|
-
engineVersions: probe.engineVersions,
|
|
43673
|
-
configDigest: o.configDigest,
|
|
43674
|
-
...o.env !== void 0 ? { env: o.env } : {}
|
|
43675
|
-
});
|
|
43676
|
-
if (o.cfg.residue?.requireProof === true && ledgerArtifacts.length > 0 && hasBlockingResidueCapability(findings)) {
|
|
43677
|
-
findings.push(...proofFindings({ artifacts: ledgerArtifacts, records: readLedger(repoDir).records, rulesHash }));
|
|
43678
|
-
}
|
|
43679
|
-
const exposure = [...exposureSet(o.cfg, { cwd: repoDir })];
|
|
43680
|
-
const allow = applyAllowList(findings, o.cfg.allow, repoDir);
|
|
43681
|
-
const report = {
|
|
43682
|
-
schemaVersion: 1,
|
|
43683
|
-
key: computeCheckKey({
|
|
43684
|
-
headSha: ctx.headSha,
|
|
43685
|
-
porcelainDigest: ctx.porcelainDigest,
|
|
43686
|
-
rulesHash,
|
|
43687
|
-
exposureSet: exposure,
|
|
43688
|
-
refSet: ctx.refSet,
|
|
43689
|
-
effectiveTargets: o.effectiveTargets
|
|
43690
|
-
}),
|
|
43691
|
-
head: ctx.headSha,
|
|
43692
|
-
dirty: ctx.dirty,
|
|
43693
|
-
exposureSet: exposure,
|
|
43694
|
-
refSet: ctx.refSet,
|
|
43695
|
-
rulesHash,
|
|
43696
|
-
verdict: computeVerdict(allow.kept),
|
|
43697
|
-
counts: countFindings(allow.kept),
|
|
43698
|
-
findings: allow.kept,
|
|
43699
|
-
...allow.allowHits.length > 0 ? { allowHits: allow.allowHits } : {},
|
|
43700
|
-
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
43701
|
-
};
|
|
43702
|
-
return {
|
|
43703
|
-
report,
|
|
43704
|
-
ctx,
|
|
43705
|
-
degraded: probe.degraded,
|
|
43706
|
-
sanitizedSummary: sanitizeSummary(report, sanitizer),
|
|
43707
|
-
lockWarning,
|
|
43708
|
-
artifacts: ledgerArtifacts.length > 0 ? ledgerArtifacts : null
|
|
43709
|
-
};
|
|
43710
|
-
}
|
|
43711
|
-
function sanitizeSummary(report, sanitizer) {
|
|
43712
|
-
const lines = report.findings.map(
|
|
43713
|
-
(f) => sanitizer.sanitize(`${f.severity} ${f.rule} ${f.engine} ${f.path ?? ""} ${f.commit ?? ""} ${f.message}`.replace(/\s+/g, " ").trim())
|
|
43714
|
-
);
|
|
43715
|
-
lines.unshift(sanitizer.sanitize(`border check ${report.verdict}: ${String(report.counts.total)} finding(s), ${String(report.counts.blocking)} blocking`));
|
|
43716
|
-
return lines.join("\n");
|
|
43717
|
-
}
|
|
43718
|
-
|
|
43719
|
-
// src/commands/check.ts
|
|
43720
|
-
async function runCheck(ctx) {
|
|
43721
|
-
const env2 = ctx.env;
|
|
43722
|
-
const load = loadConfig({
|
|
43723
|
-
cwd: ctx.cwd,
|
|
43724
|
-
...ctx.flags.config !== void 0 ? { configPath: ctx.flags.config } : {},
|
|
43725
|
-
...env2 !== void 0 ? { env: env2 } : {}
|
|
43726
|
-
});
|
|
43727
|
-
for (const warning of load.warnings) ctx.stderr(`border: ${warning}`);
|
|
43728
|
-
let effective;
|
|
43729
|
-
if (load.kind === "loaded") {
|
|
43730
|
-
effective = load;
|
|
43731
|
-
} else if (load.explicit !== void 0) {
|
|
43732
|
-
effective = { kind: "loaded", config: load.explicit.config, warnings: [], source: load.explicit.source };
|
|
43733
|
-
} else {
|
|
43734
|
-
if (ctx.flags.requireEngine !== void 0 && ctx.flags.requireEngine.length > 0) {
|
|
43735
|
-
const probe = await probeEngines(NO_TARGETS_PROBE_CFG, {
|
|
43736
|
-
requireOverride: ctx.flags.requireEngine,
|
|
43737
|
-
...env2 !== void 0 ? { env: env2 } : {}
|
|
43738
|
-
});
|
|
43739
|
-
if (probe.degraded) {
|
|
43740
|
-
for (const finding of probe.findings) ctx.stderr(`border: ${finding.message}`);
|
|
43741
|
-
return EXIT_ERROR;
|
|
43742
|
-
}
|
|
43743
|
-
}
|
|
43744
|
-
ctx.stdout(`border: ${NO_OP_MESSAGE}`);
|
|
43745
|
-
return EXIT_PASS;
|
|
43746
|
-
}
|
|
43747
|
-
const configDigest = computeConfigDigest(effective);
|
|
43748
|
-
const repoDir = resolveRepoDir(ctx.cwd, { env: env2 });
|
|
43749
|
-
const effectiveTargets = computeEffectiveTargets(effective.config, ctx.flags.targets);
|
|
43750
|
-
if (!ctx.flags.force) {
|
|
43751
|
-
const decision = await consultSkipLedger({
|
|
43752
|
-
repoDir,
|
|
43753
|
-
cfg: effective.config,
|
|
43754
|
-
configDigest,
|
|
43755
|
-
effectiveTargets,
|
|
43756
|
-
llm: ctx.flags.llm,
|
|
43757
|
-
...env2 !== void 0 ? { env: env2 } : {},
|
|
43758
|
-
...ctx.flags.requireEngine !== void 0 && ctx.flags.requireEngine.length > 0 ? { requireOverride: ctx.flags.requireEngine } : {}
|
|
43759
|
-
});
|
|
43760
|
-
for (const warning of decision.warnings) ctx.stderr(`border: WARNING ${warning}`);
|
|
43761
|
-
if (decision.skip !== null) {
|
|
43762
|
-
ctx.stdout(formatSkipLine(decision.skip));
|
|
43763
|
-
return exitCodeFromVerdict(decision.skip.verdict);
|
|
43764
|
-
}
|
|
43765
|
-
}
|
|
43766
|
-
let outcome;
|
|
43767
|
-
try {
|
|
43768
|
-
outcome = await executeCheck({
|
|
43769
|
-
repoDir,
|
|
43770
|
-
cfg: effective.config,
|
|
43771
|
-
configDigest,
|
|
43772
|
-
effectiveTargets,
|
|
43773
|
-
...env2 !== void 0 ? { env: env2 } : {},
|
|
43774
|
-
...ctx.flags.requireEngine !== void 0 && ctx.flags.requireEngine.length > 0 ? { requireOverride: ctx.flags.requireEngine } : {}
|
|
43775
|
-
});
|
|
43776
|
-
} catch (err) {
|
|
43777
|
-
if (err instanceof BorderLockHeldError) {
|
|
43778
|
-
ctx.stderr(`border: ${err.message}`);
|
|
43779
|
-
return EXIT_ERROR;
|
|
43780
|
-
}
|
|
43781
|
-
throw err;
|
|
43782
|
-
}
|
|
43783
|
-
if (outcome.lockWarning !== null) ctx.stderr(`border: WARNING ${outcome.lockWarning}`);
|
|
43784
|
-
if (outcome.ctx.dirty) ctx.stderr("border: working tree is dirty \u2014 findings may reference uncommitted files");
|
|
43785
|
-
if (ctx.flags.json) {
|
|
43786
|
-
ctx.stdout(renderReportJson(outcome.report));
|
|
43787
|
-
} else {
|
|
43788
|
-
for (const line of outcome.sanitizedSummary.split("\n")) ctx.stdout(`border: ${line}`);
|
|
43789
|
-
}
|
|
43790
|
-
if (outcome.degraded) {
|
|
43791
|
-
ctx.stderr("border: required engine(s) degraded \u2014 verdict is UNTRUSTWORTHY (exit 2 regardless of findings)");
|
|
43792
|
-
return EXIT_ERROR;
|
|
43793
|
-
}
|
|
43794
|
-
recordCheckRun({
|
|
43795
|
-
repoDir,
|
|
43796
|
-
report: outcome.report,
|
|
43797
|
-
ctx: outcome.ctx,
|
|
43798
|
-
effectiveTargets,
|
|
43799
|
-
llm: ctx.flags.llm,
|
|
43800
|
-
artifacts: outcome.artifacts,
|
|
43801
|
-
...env2 !== void 0 ? { env: env2 } : {}
|
|
43802
|
-
});
|
|
43803
|
-
return exitCodeFromVerdict(outcome.report.verdict);
|
|
43804
|
-
}
|
|
43805
|
-
var NO_TARGETS_PROBE_CFG = {
|
|
43806
|
-
version: 1,
|
|
43807
|
-
targets: { git: { remotes: [] } },
|
|
43808
|
-
rules: { authors: { emails: [], names: [] }, hosts: [], ips: [], pathPatterns: [], maxFileKB: 500 },
|
|
43809
|
-
allow: [],
|
|
43810
|
-
engines: { require: [], trufflehog: false }
|
|
43811
|
-
};
|
|
43812
|
-
|
|
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
43568
|
// src/commands/exfilGit.ts
|
|
43820
43569
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
43821
43570
|
import { dirname as dirname11, resolve as resolve15 } from "node:path";
|
|
@@ -43933,7 +43682,7 @@ function scanBlobsNative(git, blobs) {
|
|
|
43933
43682
|
}
|
|
43934
43683
|
return findings;
|
|
43935
43684
|
}
|
|
43936
|
-
async function scanTipTree(git, treeRoot, rev, env2) {
|
|
43685
|
+
async function scanTipTree(git, treeRoot, rev, env2, opts) {
|
|
43937
43686
|
const blobs = tipTree(git, rev);
|
|
43938
43687
|
const texts = blobTexts(git, blobs);
|
|
43939
43688
|
const native = [];
|
|
@@ -43941,16 +43690,23 @@ async function scanTipTree(git, treeRoot, rev, env2) {
|
|
|
43941
43690
|
for (const b of blobs) {
|
|
43942
43691
|
const text = texts.get(b.sha);
|
|
43943
43692
|
if (text === void 0) continue;
|
|
43944
|
-
for (const f of scanTreeText({
|
|
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
|
+
}
|
|
43945
43700
|
if (b.path.split("/").includes("..") || b.path === "") continue;
|
|
43946
|
-
const abs =
|
|
43701
|
+
const abs = join23(treeRoot, b.path);
|
|
43947
43702
|
if (existsSync15(abs)) continue;
|
|
43948
43703
|
mkdirSync10(dirname12(abs), { recursive: true });
|
|
43949
43704
|
writeFileSync5(abs, text, "utf8");
|
|
43950
43705
|
written.push(b.path);
|
|
43951
43706
|
}
|
|
43952
|
-
const
|
|
43953
|
-
const
|
|
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);
|
|
43954
43710
|
return [...native, ...lint, ...gl];
|
|
43955
43711
|
}
|
|
43956
43712
|
function exfilRulesDigest() {
|
|
@@ -43983,7 +43739,7 @@ async function runExfilCore(ctx) {
|
|
|
43983
43739
|
if (isUrl) {
|
|
43984
43740
|
const url2 = first;
|
|
43985
43741
|
exposure = [sanitizeUrl(url2)];
|
|
43986
|
-
sandbox.dir = mkdtempSync4(
|
|
43742
|
+
sandbox.dir = mkdtempSync4(join23(tmpdir3(), "border-exfil-"));
|
|
43987
43743
|
const git2 = makeGit(sandbox.dir, ctx.env);
|
|
43988
43744
|
git2.run(["init", "-q"]);
|
|
43989
43745
|
git2.run(["remote", "add", "origin", url2]);
|
|
@@ -44000,7 +43756,7 @@ async function runExfilCore(ctx) {
|
|
|
44000
43756
|
units = positionals.map((ref) => ({ label: ref, rev: git2.run(["rev-parse", "--verify", ref]).trim() }));
|
|
44001
43757
|
}
|
|
44002
43758
|
const git = makeGit(repoDir, ctx.env);
|
|
44003
|
-
sandbox.tree = mkdtempSync4(
|
|
43759
|
+
sandbox.tree = mkdtempSync4(join23(tmpdir3(), "border-exfil-tree-"));
|
|
44004
43760
|
const findings = [];
|
|
44005
43761
|
const seenBlob = /* @__PURE__ */ new Set();
|
|
44006
43762
|
for (const unit of units) {
|
|
@@ -44060,15 +43816,361 @@ async function runExfilCore(ctx) {
|
|
|
44060
43816
|
}
|
|
44061
43817
|
var runExfil = (ctx) => runExfilCore(ctx);
|
|
44062
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({
|
|
43948
|
+
repoDir,
|
|
43949
|
+
refSet: ctx.refSet.length > 0 ? [...ctx.refSet] : [ctx.headSha],
|
|
43950
|
+
remotes: o.cfg.targets.git.remotes,
|
|
43951
|
+
hosts: o.cfg.rules.hosts,
|
|
43952
|
+
...legOptions(o, sanitizer)
|
|
43953
|
+
}),
|
|
43954
|
+
repoDir
|
|
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
|
+
}
|
|
43973
|
+
const ledgerArtifacts = [];
|
|
43974
|
+
for (const channel of publishChannels()) {
|
|
43975
|
+
if (!o.effectiveTargets.includes(channel.id) || !channel.configured(o.cfg)) continue;
|
|
43976
|
+
const stage = await channel.stage({
|
|
43977
|
+
repoDir,
|
|
43978
|
+
cfg: o.cfg,
|
|
43979
|
+
sanitizer,
|
|
43980
|
+
...envOpt,
|
|
43981
|
+
skipGitleaks: broken.has("gitleaks"),
|
|
43982
|
+
skipSecretlint: broken.has("secretlint")
|
|
43983
|
+
});
|
|
43984
|
+
findings.push(...stage.findings);
|
|
43985
|
+
ledgerArtifacts.push(...stage.artifacts);
|
|
43986
|
+
}
|
|
43987
|
+
if ((o.cfg.release?.twin.length ?? 0) > 0) {
|
|
43988
|
+
findings.push(
|
|
43989
|
+
...await twinCoherenceFindings({
|
|
43990
|
+
pairs: o.cfg.release?.twin ?? [],
|
|
43991
|
+
artifacts: ledgerArtifacts,
|
|
43992
|
+
repoDir,
|
|
43993
|
+
...envOpt,
|
|
43994
|
+
sanitizer
|
|
43995
|
+
})
|
|
43996
|
+
);
|
|
43997
|
+
}
|
|
43998
|
+
findings.push(...await runRegistryProbes({ repoDir, cfg: o.cfg, effectiveTargets: o.effectiveTargets, ...envOpt }));
|
|
43999
|
+
if (o.cfg.residue?.enabled === false) {
|
|
44000
|
+
for (let i = findings.length - 1; i >= 0; i -= 1) {
|
|
44001
|
+
const f = findings[i];
|
|
44002
|
+
if (f !== void 0 && RESIDUE_RULE_IDS.has(f.rule)) findings.splice(i, 1);
|
|
44003
|
+
}
|
|
44004
|
+
}
|
|
44005
|
+
const rulesHash = await computeCheckRulesHash({
|
|
44006
|
+
engineVersions: probe.engineVersions,
|
|
44007
|
+
configDigest: o.configDigest,
|
|
44008
|
+
...o.env !== void 0 ? { env: o.env } : {}
|
|
44009
|
+
});
|
|
44010
|
+
if (o.cfg.residue?.requireProof === true && ledgerArtifacts.length > 0 && hasBlockingResidueCapability(findings)) {
|
|
44011
|
+
findings.push(...proofFindings({ artifacts: ledgerArtifacts, records: readLedger(repoDir).records, rulesHash }));
|
|
44012
|
+
}
|
|
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
|
+
}
|
|
44029
|
+
const allow = applyAllowList(findings, o.cfg.allow, repoDir);
|
|
44030
|
+
const report = {
|
|
44031
|
+
schemaVersion: 1,
|
|
44032
|
+
key: computeCheckKey({
|
|
44033
|
+
headSha: ctx.headSha,
|
|
44034
|
+
porcelainDigest: ctx.porcelainDigest,
|
|
44035
|
+
rulesHash,
|
|
44036
|
+
exposureSet: exposure,
|
|
44037
|
+
refSet: ctx.refSet,
|
|
44038
|
+
effectiveTargets: o.effectiveTargets
|
|
44039
|
+
}),
|
|
44040
|
+
head: ctx.headSha,
|
|
44041
|
+
dirty: ctx.dirty,
|
|
44042
|
+
exposureSet: exposure,
|
|
44043
|
+
refSet: ctx.refSet,
|
|
44044
|
+
rulesHash,
|
|
44045
|
+
verdict: computeVerdict(allow.kept),
|
|
44046
|
+
counts: countFindings(allow.kept),
|
|
44047
|
+
findings: allow.kept,
|
|
44048
|
+
...allow.allowHits.length > 0 ? { allowHits: allow.allowHits } : {},
|
|
44049
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
44050
|
+
};
|
|
44051
|
+
return {
|
|
44052
|
+
report,
|
|
44053
|
+
ctx,
|
|
44054
|
+
degraded: probe.degraded,
|
|
44055
|
+
sanitizedSummary: sanitizeSummary(report, sanitizer),
|
|
44056
|
+
lockWarning,
|
|
44057
|
+
artifacts: ledgerArtifacts.length > 0 ? ledgerArtifacts : null
|
|
44058
|
+
};
|
|
44059
|
+
}
|
|
44060
|
+
function sanitizeSummary(report, sanitizer) {
|
|
44061
|
+
const lines = report.findings.map(
|
|
44062
|
+
(f) => sanitizer.sanitize(`${f.severity} ${f.rule} ${f.engine} ${f.path ?? ""} ${f.commit ?? ""} ${f.message}`.replace(/\s+/g, " ").trim())
|
|
44063
|
+
);
|
|
44064
|
+
lines.unshift(sanitizer.sanitize(`border check ${report.verdict}: ${String(report.counts.total)} finding(s), ${String(report.counts.blocking)} blocking`));
|
|
44065
|
+
return lines.join("\n");
|
|
44066
|
+
}
|
|
44067
|
+
|
|
44068
|
+
// src/commands/check.ts
|
|
44069
|
+
async function runCheck(ctx) {
|
|
44070
|
+
const env2 = ctx.env;
|
|
44071
|
+
const load = loadConfig({
|
|
44072
|
+
cwd: ctx.cwd,
|
|
44073
|
+
...ctx.flags.config !== void 0 ? { configPath: ctx.flags.config } : {},
|
|
44074
|
+
...env2 !== void 0 ? { env: env2 } : {}
|
|
44075
|
+
});
|
|
44076
|
+
for (const warning of load.warnings) ctx.stderr(`border: ${warning}`);
|
|
44077
|
+
let effective;
|
|
44078
|
+
if (load.kind === "loaded") {
|
|
44079
|
+
effective = load;
|
|
44080
|
+
} else if (load.explicit !== void 0) {
|
|
44081
|
+
effective = { kind: "loaded", config: load.explicit.config, warnings: [], source: load.explicit.source };
|
|
44082
|
+
} else {
|
|
44083
|
+
if (ctx.flags.requireEngine !== void 0 && ctx.flags.requireEngine.length > 0) {
|
|
44084
|
+
const probe = await probeEngines(NO_TARGETS_PROBE_CFG, {
|
|
44085
|
+
requireOverride: ctx.flags.requireEngine,
|
|
44086
|
+
...env2 !== void 0 ? { env: env2 } : {}
|
|
44087
|
+
});
|
|
44088
|
+
if (probe.degraded) {
|
|
44089
|
+
for (const finding of probe.findings) ctx.stderr(`border: ${finding.message}`);
|
|
44090
|
+
return EXIT_ERROR;
|
|
44091
|
+
}
|
|
44092
|
+
}
|
|
44093
|
+
ctx.stdout(`border: ${NO_OP_MESSAGE}`);
|
|
44094
|
+
return EXIT_PASS;
|
|
44095
|
+
}
|
|
44096
|
+
const configDigest = computeConfigDigest(effective);
|
|
44097
|
+
const repoDir = resolveRepoDir(ctx.cwd, { env: env2 });
|
|
44098
|
+
const effectiveTargets = computeEffectiveTargets(effective.config, ctx.flags.targets);
|
|
44099
|
+
if (!ctx.flags.force) {
|
|
44100
|
+
const decision = await consultSkipLedger({
|
|
44101
|
+
repoDir,
|
|
44102
|
+
cfg: effective.config,
|
|
44103
|
+
configDigest,
|
|
44104
|
+
effectiveTargets,
|
|
44105
|
+
llm: ctx.flags.llm,
|
|
44106
|
+
...env2 !== void 0 ? { env: env2 } : {},
|
|
44107
|
+
...ctx.flags.requireEngine !== void 0 && ctx.flags.requireEngine.length > 0 ? { requireOverride: ctx.flags.requireEngine } : {}
|
|
44108
|
+
});
|
|
44109
|
+
for (const warning of decision.warnings) ctx.stderr(`border: WARNING ${warning}`);
|
|
44110
|
+
if (decision.skip !== null) {
|
|
44111
|
+
ctx.stdout(formatSkipLine(decision.skip));
|
|
44112
|
+
return exitCodeFromVerdict(decision.skip.verdict);
|
|
44113
|
+
}
|
|
44114
|
+
}
|
|
44115
|
+
let outcome;
|
|
44116
|
+
try {
|
|
44117
|
+
outcome = await executeCheck({
|
|
44118
|
+
repoDir,
|
|
44119
|
+
cfg: effective.config,
|
|
44120
|
+
configDigest,
|
|
44121
|
+
effectiveTargets,
|
|
44122
|
+
...env2 !== void 0 ? { env: env2 } : {},
|
|
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 } : {}
|
|
44127
|
+
});
|
|
44128
|
+
} catch (err) {
|
|
44129
|
+
if (err instanceof BorderLockHeldError) {
|
|
44130
|
+
ctx.stderr(`border: ${err.message}`);
|
|
44131
|
+
return EXIT_ERROR;
|
|
44132
|
+
}
|
|
44133
|
+
throw err;
|
|
44134
|
+
}
|
|
44135
|
+
if (outcome.lockWarning !== null) ctx.stderr(`border: WARNING ${outcome.lockWarning}`);
|
|
44136
|
+
if (outcome.ctx.dirty) ctx.stderr("border: working tree is dirty \u2014 findings may reference uncommitted files");
|
|
44137
|
+
if (ctx.flags.json) {
|
|
44138
|
+
ctx.stdout(renderReportJson(outcome.report));
|
|
44139
|
+
} else {
|
|
44140
|
+
for (const line of outcome.sanitizedSummary.split("\n")) ctx.stdout(`border: ${line}`);
|
|
44141
|
+
}
|
|
44142
|
+
if (outcome.degraded) {
|
|
44143
|
+
ctx.stderr("border: required engine(s) degraded \u2014 verdict is UNTRUSTWORTHY (exit 2 regardless of findings)");
|
|
44144
|
+
return EXIT_ERROR;
|
|
44145
|
+
}
|
|
44146
|
+
recordCheckRun({
|
|
44147
|
+
repoDir,
|
|
44148
|
+
report: outcome.report,
|
|
44149
|
+
ctx: outcome.ctx,
|
|
44150
|
+
effectiveTargets,
|
|
44151
|
+
llm: ctx.flags.llm,
|
|
44152
|
+
artifacts: outcome.artifacts,
|
|
44153
|
+
...env2 !== void 0 ? { env: env2 } : {}
|
|
44154
|
+
});
|
|
44155
|
+
return exitCodeFromVerdict(outcome.report.verdict);
|
|
44156
|
+
}
|
|
44157
|
+
var NO_TARGETS_PROBE_CFG = {
|
|
44158
|
+
version: 1,
|
|
44159
|
+
targets: { git: { remotes: [] } },
|
|
44160
|
+
rules: { authors: { emails: [], names: [] }, hosts: [], ips: [], pathPatterns: [], maxFileKB: 500 },
|
|
44161
|
+
allow: [],
|
|
44162
|
+
engines: { require: [], trufflehog: false }
|
|
44163
|
+
};
|
|
44164
|
+
|
|
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,43 +44584,551 @@ async function runLlmRequest(ctx) {
|
|
|
44482
44584
|
}
|
|
44483
44585
|
|
|
44484
44586
|
// src/commands/opencode.ts
|
|
44485
|
-
import {
|
|
44486
|
-
import {
|
|
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";
|
|
44589
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
44590
|
+
|
|
44591
|
+
// src/opencode/inspect.ts
|
|
44592
|
+
import { existsSync as existsSync21, readFileSync as readFileSync19 } from "node:fs";
|
|
44593
|
+
import { join as join31 } from "node:path";
|
|
44594
|
+
|
|
44595
|
+
// src/opencode/jsonconfig.ts
|
|
44596
|
+
import { existsSync as existsSync19, readdirSync as readdirSync10, readFileSync as readFileSync18 } from "node:fs";
|
|
44597
|
+
import { join as join30 } from "node:path";
|
|
44598
|
+
|
|
44599
|
+
// src/opencode/contract.ts
|
|
44600
|
+
import { createHash as createHash18 } from "node:crypto";
|
|
44601
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
44487
44602
|
import os3 from "node:os";
|
|
44488
|
-
import { dirname as dirname15, join as
|
|
44603
|
+
import { dirname as dirname15, join as join29 } from "node:path";
|
|
44489
44604
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
44490
44605
|
var PLUGIN_MARKER = "// border-opencode-plugin v";
|
|
44491
44606
|
var COMMAND_MARKER = "<!-- border-opencode-command -->";
|
|
44607
|
+
var BORDER_PACKAGE = "border-customs";
|
|
44608
|
+
var DEFAULT_REGISTRY_BASE = "https://registry.npmjs.org";
|
|
44609
|
+
var REGISTRY_TIMEOUT_MS2 = 15e3;
|
|
44610
|
+
function sha256Hex3(bytes) {
|
|
44611
|
+
return createHash18("sha256").update(bytes).digest("hex");
|
|
44612
|
+
}
|
|
44613
|
+
function firstLine(bytes) {
|
|
44614
|
+
const text = bytes.toString("utf8");
|
|
44615
|
+
const newline = text.indexOf("\n");
|
|
44616
|
+
return newline < 0 ? text : text.slice(0, newline);
|
|
44617
|
+
}
|
|
44492
44618
|
function opencodeConfigRoot(env2) {
|
|
44493
44619
|
const xdg = env2["XDG_CONFIG_HOME"] ?? "";
|
|
44494
44620
|
if (xdg.length > 0) return xdg;
|
|
44495
44621
|
const home = env2["HOME"] ?? "";
|
|
44496
|
-
return
|
|
44622
|
+
return join29(home.length > 0 ? home : os3.homedir(), ".config");
|
|
44497
44623
|
}
|
|
44498
|
-
function
|
|
44624
|
+
function opencodeCacheRoot(env2) {
|
|
44625
|
+
const xdg = env2["XDG_CACHE_HOME"] ?? "";
|
|
44626
|
+
const base = xdg.length > 0 ? xdg : join29(env2["HOME"] ?? os3.homedir(), ".cache");
|
|
44627
|
+
return join29(base, "opencode", "packages");
|
|
44628
|
+
}
|
|
44629
|
+
function routeAPluginPath(env2) {
|
|
44630
|
+
return join29(opencodeConfigRoot(env2), "opencode", "plugins", "border.ts");
|
|
44631
|
+
}
|
|
44632
|
+
function packagedAssetPath(moduleUrl, fileName) {
|
|
44499
44633
|
const here = dirname15(fileURLToPath5(moduleUrl));
|
|
44500
44634
|
const candidates = [
|
|
44501
|
-
|
|
44502
|
-
// dist
|
|
44503
|
-
|
|
44635
|
+
join29(here, "..", "plugin", fileName),
|
|
44636
|
+
// dist (bundled) mode: <root>/plugin/<f>
|
|
44637
|
+
join29(here, "..", "..", "plugin", fileName)
|
|
44504
44638
|
// src mode: <root>/plugin/<f>
|
|
44505
44639
|
];
|
|
44506
44640
|
return candidates.find((p) => existsSync18(p)) ?? candidates[1];
|
|
44507
44641
|
}
|
|
44508
|
-
function
|
|
44509
|
-
const
|
|
44510
|
-
|
|
44511
|
-
|
|
44512
|
-
|
|
44642
|
+
function packagedDistEntry(moduleUrl) {
|
|
44643
|
+
const here = dirname15(fileURLToPath5(moduleUrl));
|
|
44644
|
+
const candidates = [
|
|
44645
|
+
join29(here, "index.js"),
|
|
44646
|
+
// dist (bundled) mode: <root>/dist/index.js
|
|
44647
|
+
join29(here, "..", "dist", "index.js"),
|
|
44648
|
+
// src/opencode mode one level under root
|
|
44649
|
+
join29(here, "..", "..", "dist", "index.js")
|
|
44650
|
+
// src mode: <root>/dist/index.js
|
|
44513
44651
|
];
|
|
44652
|
+
return candidates.find((p) => existsSync18(p)) ?? candidates[2];
|
|
44514
44653
|
}
|
|
44515
|
-
|
|
44516
|
-
|
|
44654
|
+
|
|
44655
|
+
// src/opencode/jsonconfig.ts
|
|
44656
|
+
function stripJsoncComments(text) {
|
|
44657
|
+
let out = "";
|
|
44658
|
+
let inString = false;
|
|
44659
|
+
let inLine = false;
|
|
44660
|
+
let inBlock = false;
|
|
44661
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
44662
|
+
const ch = text[i];
|
|
44663
|
+
const next = text[i + 1] ?? "";
|
|
44664
|
+
if (inLine) {
|
|
44665
|
+
if (ch === "\n") {
|
|
44666
|
+
inLine = false;
|
|
44667
|
+
out += ch;
|
|
44668
|
+
}
|
|
44669
|
+
continue;
|
|
44670
|
+
}
|
|
44671
|
+
if (inBlock) {
|
|
44672
|
+
if (ch === "*" && next === "/") {
|
|
44673
|
+
inBlock = false;
|
|
44674
|
+
i += 1;
|
|
44675
|
+
out += " ";
|
|
44676
|
+
}
|
|
44677
|
+
continue;
|
|
44678
|
+
}
|
|
44679
|
+
if (inString) {
|
|
44680
|
+
if (ch === "\\") {
|
|
44681
|
+
out += ch + next;
|
|
44682
|
+
i += 1;
|
|
44683
|
+
continue;
|
|
44684
|
+
}
|
|
44685
|
+
if (ch === '"') inString = false;
|
|
44686
|
+
out += ch;
|
|
44687
|
+
continue;
|
|
44688
|
+
}
|
|
44689
|
+
if (ch === '"') {
|
|
44690
|
+
inString = true;
|
|
44691
|
+
out += ch;
|
|
44692
|
+
continue;
|
|
44693
|
+
}
|
|
44694
|
+
if (ch === "/" && next === "/") {
|
|
44695
|
+
inLine = true;
|
|
44696
|
+
i += 1;
|
|
44697
|
+
continue;
|
|
44698
|
+
}
|
|
44699
|
+
if (ch === "/" && next === "*") {
|
|
44700
|
+
inBlock = true;
|
|
44701
|
+
i += 1;
|
|
44702
|
+
continue;
|
|
44703
|
+
}
|
|
44704
|
+
out += ch;
|
|
44705
|
+
}
|
|
44706
|
+
return out;
|
|
44517
44707
|
}
|
|
44518
|
-
function
|
|
44519
|
-
|
|
44520
|
-
|
|
44521
|
-
|
|
44708
|
+
function parseJsonc(text) {
|
|
44709
|
+
return JSON.parse(stripJsoncComments(text).replace(/,(\s*[}\]])/g, "$1"));
|
|
44710
|
+
}
|
|
44711
|
+
var isRecord2 = (v) => typeof v === "object" && v !== null;
|
|
44712
|
+
function splitNpmSpec(entry) {
|
|
44713
|
+
const at = entry.lastIndexOf("@");
|
|
44714
|
+
if (at <= 0) return { name: entry, spec: null };
|
|
44715
|
+
return { name: entry.slice(0, at), spec: entry.slice(at + 1) };
|
|
44716
|
+
}
|
|
44717
|
+
function readPluginDeclarations(path4, pkg) {
|
|
44718
|
+
const none = { path: path4, status: "absent", specs: [] };
|
|
44719
|
+
let text;
|
|
44720
|
+
try {
|
|
44721
|
+
if (!existsSync19(path4)) return none;
|
|
44722
|
+
text = readFileSync18(path4, "utf8");
|
|
44723
|
+
} catch {
|
|
44724
|
+
return { path: path4, status: "error", specs: [] };
|
|
44725
|
+
}
|
|
44726
|
+
let parsed;
|
|
44727
|
+
try {
|
|
44728
|
+
parsed = parseJsonc(text);
|
|
44729
|
+
} catch {
|
|
44730
|
+
return { path: path4, status: "error", specs: [] };
|
|
44731
|
+
}
|
|
44732
|
+
if (!isRecord2(parsed)) return { path: path4, status: "error", specs: [] };
|
|
44733
|
+
const plugin = Array.isArray(parsed["plugin"]) ? parsed["plugin"] : [];
|
|
44734
|
+
const specs = plugin.filter((e) => typeof e === "string" && splitNpmSpec(e).name === pkg);
|
|
44735
|
+
return { path: path4, status: "ok", specs };
|
|
44736
|
+
}
|
|
44737
|
+
function listCacheDirs(cacheRoot, pkg) {
|
|
44738
|
+
let names;
|
|
44739
|
+
try {
|
|
44740
|
+
names = readdirSync10(cacheRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith(`${pkg}@`)).map((e) => e.name).sort();
|
|
44741
|
+
} catch {
|
|
44742
|
+
return [];
|
|
44743
|
+
}
|
|
44744
|
+
return names.map((name) => {
|
|
44745
|
+
const pkgRoot = join30(cacheRoot, name, "node_modules", pkg);
|
|
44746
|
+
let packageVersion = null;
|
|
44747
|
+
try {
|
|
44748
|
+
const meta3 = parseJsonc(readFileSync18(join30(pkgRoot, "package.json"), "utf8"));
|
|
44749
|
+
if (isRecord2(meta3) && typeof meta3["version"] === "string") packageVersion = meta3["version"];
|
|
44750
|
+
} catch {
|
|
44751
|
+
packageVersion = null;
|
|
44752
|
+
}
|
|
44753
|
+
let markerLine = null;
|
|
44754
|
+
try {
|
|
44755
|
+
markerLine = firstLine(readFileSync18(join30(pkgRoot, "plugin", "border.ts")));
|
|
44756
|
+
} catch {
|
|
44757
|
+
markerLine = null;
|
|
44758
|
+
}
|
|
44759
|
+
const markerVersion = markerLine !== null && markerLine.startsWith(PLUGIN_MARKER) ? markerLine.slice(PLUGIN_MARKER.length).trim() : null;
|
|
44760
|
+
return { dir: join30(cacheRoot, name), tag: name.slice(pkg.length + 1), packageVersion, markerVersion, markerLine };
|
|
44761
|
+
});
|
|
44762
|
+
}
|
|
44763
|
+
|
|
44764
|
+
// src/opencode/aspects/cacheVersion.ts
|
|
44765
|
+
var isExactVersion = (spec) => /^\d+\.\d+\.\d+([-+].*)?$/.test(spec);
|
|
44766
|
+
function verifyCacheVersion(facts) {
|
|
44767
|
+
const id = "cache-version";
|
|
44768
|
+
const { cacheDirs, configs, cacheRoot, pkg } = facts;
|
|
44769
|
+
const errored = configs.filter((c) => c.status === "error").map((c) => c.path);
|
|
44770
|
+
const declared = configs.some((c) => c.specs.length > 0);
|
|
44771
|
+
const where = configs.map((c) => `${c.path} [${c.status}]`).join(", ");
|
|
44772
|
+
if (cacheDirs.length === 0) {
|
|
44773
|
+
if (errored.length > 0) {
|
|
44774
|
+
return { id, verdict: "CANNOT", detail: `no cache directory under ${cacheRoot} and the configuration is unreadable (${errored.join(", ")}) \u2014 cannot tell whether a Route-B load is claimed` };
|
|
44775
|
+
}
|
|
44776
|
+
if (declared) {
|
|
44777
|
+
return { id, verdict: "CANNOT", detail: `NOT-CACHED: ${pkg} is declared (${configs.flatMap((c) => [...c.specs]).join(", ")}) but no cache directory exists under ${cacheRoot} \u2014 the load claim has no bytes behind it` };
|
|
44778
|
+
}
|
|
44779
|
+
return { id, verdict: "PASS", detail: `no ${pkg} cache directory under ${cacheRoot} and none declared (${where}) \u2014 Route B not in use, nothing to be stale` };
|
|
44780
|
+
}
|
|
44781
|
+
const pins = new Set(
|
|
44782
|
+
configs.flatMap((c) => [...c.specs]).map((s) => splitNpmSpec(s).spec).filter((s) => s !== null && isExactVersion(s))
|
|
44783
|
+
);
|
|
44784
|
+
const problems = [];
|
|
44785
|
+
for (const d of cacheDirs) {
|
|
44786
|
+
if (d.packageVersion === null) {
|
|
44787
|
+
problems.push(`${d.dir}: package.json missing or unreadable`);
|
|
44788
|
+
}
|
|
44789
|
+
if (d.markerLine === null) {
|
|
44790
|
+
problems.push(`${d.dir}: plugin/border.ts missing or unreadable in the cached package`);
|
|
44791
|
+
} else if (d.markerVersion === null) {
|
|
44792
|
+
problems.push(`${d.dir}: plugin/border.ts first line lacks the marker '${PLUGIN_MARKER}'`);
|
|
44793
|
+
} else if (d.packageVersion !== null && d.markerVersion !== d.packageVersion) {
|
|
44794
|
+
problems.push(`${d.dir}: marker says ${d.markerVersion}, package.json says ${d.packageVersion}`);
|
|
44795
|
+
}
|
|
44796
|
+
if (pins.has(d.tag) && d.packageVersion !== null && d.packageVersion !== d.tag) {
|
|
44797
|
+
problems.push(`${d.dir}: cached under exact spec '${d.tag}' but the package reports ${d.packageVersion}`);
|
|
44798
|
+
}
|
|
44799
|
+
}
|
|
44800
|
+
if (problems.length > 0) {
|
|
44801
|
+
return { id, verdict: "FAIL", detail: problems.join("; ") };
|
|
44802
|
+
}
|
|
44803
|
+
return {
|
|
44804
|
+
id,
|
|
44805
|
+
verdict: "PASS",
|
|
44806
|
+
detail: `cache self-consistent: ${cacheDirs.map((d) => `${d.tag}->${d.packageVersion ?? "?"}`).join(", ")}`
|
|
44807
|
+
};
|
|
44808
|
+
}
|
|
44809
|
+
|
|
44810
|
+
// src/opencode/aspects/dualRoute.ts
|
|
44811
|
+
function verifyDualRoute(facts) {
|
|
44812
|
+
const id = "dual-route";
|
|
44813
|
+
const { configs, routeAInstalled, routeAPlugin, pkg } = facts;
|
|
44814
|
+
const errored = configs.filter((c) => c.status === "error").map((c) => c.path);
|
|
44815
|
+
if (errored.length > 0) {
|
|
44816
|
+
return { id, verdict: "CANNOT", detail: `configuration unreadable (${errored.join(", ")}) \u2014 Route B presence cannot be counted` };
|
|
44817
|
+
}
|
|
44818
|
+
const declared = configs.flatMap((c) => [...c.specs]);
|
|
44819
|
+
const hasB = declared.length > 0;
|
|
44820
|
+
if (routeAInstalled && hasB) {
|
|
44821
|
+
return {
|
|
44822
|
+
id,
|
|
44823
|
+
verdict: "FAIL",
|
|
44824
|
+
detail: `both routes registered: Route A drop at ${routeAPlugin} AND Route B declaration ${declared.map((d) => `'${d}'`).join(", ")} \u2014 pick exactly one route (the dual-registration window blueprint \xA77 forbids)`
|
|
44825
|
+
};
|
|
44826
|
+
}
|
|
44827
|
+
if (routeAInstalled) {
|
|
44828
|
+
return { id, verdict: "PASS", detail: `Route A only: drop at ${routeAPlugin}, no ${pkg} config declaration` };
|
|
44829
|
+
}
|
|
44830
|
+
if (hasB) {
|
|
44831
|
+
return { id, verdict: "PASS", detail: `Route B only: ${declared.map((d) => `'${d}'`).join(", ")}, no file drop at ${routeAPlugin}` };
|
|
44832
|
+
}
|
|
44833
|
+
return { id, verdict: "PASS", detail: "neither route registered \u2014 no border plugin load claimed on the V1 surface" };
|
|
44834
|
+
}
|
|
44835
|
+
|
|
44836
|
+
// src/opencode/aspects/identity.ts
|
|
44837
|
+
import { execFile } from "node:child_process";
|
|
44838
|
+
import { existsSync as existsSync20 } from "node:fs";
|
|
44839
|
+
var HANDSHAKE_ARGV = ["--help"];
|
|
44840
|
+
var HANDSHAKE_BANNER_PREFIX = "border";
|
|
44841
|
+
var HANDSHAKE_USAGE_NEEDLE = "usage: border";
|
|
44842
|
+
var HANDSHAKE_TIMEOUT_MS = 2e4;
|
|
44843
|
+
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
44844
|
+
var defaultInspectRunner = (target, argv) => new Promise((resolve20) => {
|
|
44845
|
+
execFile(
|
|
44846
|
+
target.bin,
|
|
44847
|
+
[...target.prefix, ...argv],
|
|
44848
|
+
{ timeout: HANDSHAKE_TIMEOUT_MS, maxBuffer: MAX_OUTPUT_BYTES, shell: false },
|
|
44849
|
+
(error61, stdout, stderr) => {
|
|
44850
|
+
const out = stdout.slice(0, MAX_OUTPUT_BYTES);
|
|
44851
|
+
const err = stderr.slice(0, MAX_OUTPUT_BYTES);
|
|
44852
|
+
if (error61 === null) {
|
|
44853
|
+
resolve20({ status: 0, stdout: out, stderr: err });
|
|
44854
|
+
return;
|
|
44855
|
+
}
|
|
44856
|
+
const failure = error61;
|
|
44857
|
+
if (failure.code === "ENOENT" || failure.code === "EACCES" || failure.code === "ENOEXEC") {
|
|
44858
|
+
resolve20({ status: null, stdout: out, stderr: err });
|
|
44859
|
+
return;
|
|
44860
|
+
}
|
|
44861
|
+
const status = typeof failure.code === "number" ? failure.code : failure.killed === true || failure.signal != null ? 124 : -1;
|
|
44862
|
+
resolve20({ status, stdout: out, stderr: err });
|
|
44863
|
+
}
|
|
44864
|
+
);
|
|
44865
|
+
});
|
|
44866
|
+
function passesHandshake(outcome) {
|
|
44867
|
+
if (outcome.status !== 0) return false;
|
|
44868
|
+
const firstLine2 = (outcome.stdout.match(/^\s*(.*)/) ?? ["", ""])[1] ?? "";
|
|
44869
|
+
return firstLine2.startsWith(HANDSHAKE_BANNER_PREFIX) && outcome.stdout.includes(HANDSHAKE_USAGE_NEEDLE);
|
|
44870
|
+
}
|
|
44871
|
+
function identityCandidates(env2, distEntry) {
|
|
44872
|
+
const configured = env2["BORDER_BIN"];
|
|
44873
|
+
if (configured !== void 0 && configured.length > 0) {
|
|
44874
|
+
return [{ bin: configured, prefix: [], label: `BORDER_BIN=${configured}` }];
|
|
44875
|
+
}
|
|
44876
|
+
const candidates = [];
|
|
44877
|
+
if (existsSync20(distEntry)) {
|
|
44878
|
+
candidates.push({ bin: distEntry, prefix: [], label: "packaged dist/index.js (shebang exec)" });
|
|
44879
|
+
candidates.push({ bin: "node", prefix: [distEntry], label: "packaged dist/index.js under PATH node" });
|
|
44880
|
+
}
|
|
44881
|
+
candidates.push({ bin: "border", prefix: [], label: "border on PATH" });
|
|
44882
|
+
return candidates;
|
|
44883
|
+
}
|
|
44884
|
+
async function verifyIdentity(deps) {
|
|
44885
|
+
const id = "identity-handshake";
|
|
44886
|
+
const runner = deps.runner ?? defaultInspectRunner;
|
|
44887
|
+
const candidates = identityCandidates(deps.env, deps.distEntry);
|
|
44888
|
+
const reasons = [];
|
|
44889
|
+
let anySpawned = false;
|
|
44890
|
+
for (const candidate of candidates) {
|
|
44891
|
+
const probe = await runner(candidate, HANDSHAKE_ARGV);
|
|
44892
|
+
if (passesHandshake(probe)) {
|
|
44893
|
+
return { id, verdict: "PASS", detail: `${candidate.label} passed the identity handshake` };
|
|
44894
|
+
}
|
|
44895
|
+
if (probe.status !== null) {
|
|
44896
|
+
anySpawned = true;
|
|
44897
|
+
reasons.push(`${candidate.label}: spawned but --help did not answer as border (impostor?)`);
|
|
44898
|
+
} else {
|
|
44899
|
+
reasons.push(`${candidate.label}: unspawnable (ENOENT/EACCES/ENOEXEC)`);
|
|
44900
|
+
}
|
|
44901
|
+
}
|
|
44902
|
+
if (anySpawned) {
|
|
44903
|
+
return { id, verdict: "FAIL", detail: `a border binary was reachable but failed the identity handshake \u2014 impersonation shape: ${reasons.join("; ")}` };
|
|
44904
|
+
}
|
|
44905
|
+
return { id, verdict: "CANNOT", detail: `no border binary reachable \u2014 tried: ${reasons.join("; ")}` };
|
|
44906
|
+
}
|
|
44907
|
+
|
|
44908
|
+
// src/opencode/aspects/routeA.ts
|
|
44909
|
+
function verifyRouteAParity(facts) {
|
|
44910
|
+
const id = "route-a-parity";
|
|
44911
|
+
const { routeAPlugin, routeAInstalled } = facts;
|
|
44912
|
+
if (!routeAInstalled) {
|
|
44913
|
+
return { id, verdict: "PASS", detail: `${routeAPlugin}: absent \u2014 Route A not installed, nothing to be stale` };
|
|
44914
|
+
}
|
|
44915
|
+
if (facts.packagedPlugin === null) {
|
|
44916
|
+
return { id, verdict: "CANNOT", detail: "the packaged plugin asset is unreadable \u2014 the border install itself looks broken, parity cannot be judged" };
|
|
44917
|
+
}
|
|
44918
|
+
const installed = facts.routeABytes;
|
|
44919
|
+
if (installed === null) {
|
|
44920
|
+
return { id, verdict: "CANNOT", detail: `${routeAPlugin}: present but unreadable \u2014 parity cannot be judged` };
|
|
44921
|
+
}
|
|
44922
|
+
const installedSha = sha256Hex3(installed);
|
|
44923
|
+
const packagedSha = sha256Hex3(facts.packagedPlugin);
|
|
44924
|
+
if (installedSha === packagedSha) {
|
|
44925
|
+
return { id, verdict: "PASS", detail: `${routeAPlugin}: up-to-date installed=${installedSha} packaged=${packagedSha}` };
|
|
44926
|
+
}
|
|
44927
|
+
const ours = firstLine(installed).startsWith(PLUGIN_MARKER);
|
|
44928
|
+
const shape = ours ? "outdated (marker ours, bytes differ)" : "foreign (first line lacks the marker)";
|
|
44929
|
+
return { id, verdict: "FAIL", detail: `${routeAPlugin}: ${shape} installed=${installedSha} packaged=${packagedSha}` };
|
|
44930
|
+
}
|
|
44931
|
+
|
|
44932
|
+
// src/opencode/aspects/scope.ts
|
|
44933
|
+
var SCOPE_ID = "scope";
|
|
44934
|
+
var SCOPE_NAME = "v1-load-surface";
|
|
44935
|
+
var SCOPE_UNCOVERED = [
|
|
44936
|
+
"V2 project-local .opencode/plugins/",
|
|
44937
|
+
"V2 npm-prefix/node_modules installs",
|
|
44938
|
+
"V2 lazy activation state"
|
|
44939
|
+
];
|
|
44940
|
+
function verifyScope() {
|
|
44941
|
+
return {
|
|
44942
|
+
id: SCOPE_ID,
|
|
44943
|
+
verdict: "PASS",
|
|
44944
|
+
detail: `verdict scope = ${SCOPE_NAME}; NOT covered (declared blind, separate ledger item): ${SCOPE_UNCOVERED.join(", ")}`
|
|
44945
|
+
};
|
|
44946
|
+
}
|
|
44947
|
+
|
|
44948
|
+
// src/opencode/registry.ts
|
|
44949
|
+
var isRecord3 = (v) => typeof v === "object" && v !== null;
|
|
44950
|
+
var defaultRegistryFetcher = async (url2, init) => {
|
|
44951
|
+
if (!/^https?:\/\//.test(url2)) throw new Error(`border opencode inspect refuses non-http(s) URL: ${url2}`);
|
|
44952
|
+
const res = await fetch(url2, {
|
|
44953
|
+
redirect: "follow",
|
|
44954
|
+
signal: init.signal,
|
|
44955
|
+
headers: { "user-agent": "border-customs (+https://github.com/TachikomaGundam/Border-Customs; border opencode inspect)" }
|
|
44956
|
+
});
|
|
44957
|
+
const raw = res.headers.get("content-length");
|
|
44958
|
+
const parsed = raw === null ? Number.NaN : Number(raw);
|
|
44959
|
+
return {
|
|
44960
|
+
status: res.status,
|
|
44961
|
+
contentLength: Number.isFinite(parsed) && parsed >= 0 ? parsed : null,
|
|
44962
|
+
chunks: () => {
|
|
44963
|
+
if (res.body === null) {
|
|
44964
|
+
return {
|
|
44965
|
+
async *[Symbol.asyncIterator]() {
|
|
44966
|
+
}
|
|
44967
|
+
};
|
|
44968
|
+
}
|
|
44969
|
+
return res.body;
|
|
44970
|
+
}
|
|
44971
|
+
};
|
|
44972
|
+
};
|
|
44973
|
+
function npmTagUrl(pkg, tag, base = DEFAULT_REGISTRY_BASE) {
|
|
44974
|
+
const enc = pkg.startsWith("@") ? pkg.replace("/", "%2F") : encodeURIComponent(pkg);
|
|
44975
|
+
return `${base}/${enc}/${encodeURIComponent(tag)}`;
|
|
44976
|
+
}
|
|
44977
|
+
var REGISTRY_BODY_CAP = 8 * 1024 * 1024;
|
|
44978
|
+
async function fetchRegistryVersion(fetcher, url2, timeoutMs) {
|
|
44979
|
+
const res = await fetcher(url2, { signal: AbortSignal.timeout(timeoutMs) });
|
|
44980
|
+
if (res.status !== 200) throw new Error(`HTTP ${String(res.status)} from ${url2}`);
|
|
44981
|
+
const parts = [];
|
|
44982
|
+
let total = 0;
|
|
44983
|
+
for await (const chunk of res.chunks()) {
|
|
44984
|
+
total += chunk.byteLength;
|
|
44985
|
+
if (total > REGISTRY_BODY_CAP) throw new Error(`${url2} exceeds the ${String(REGISTRY_BODY_CAP)} B metadata cap`);
|
|
44986
|
+
parts.push(Buffer.from(chunk));
|
|
44987
|
+
}
|
|
44988
|
+
let parsed;
|
|
44989
|
+
try {
|
|
44990
|
+
parsed = JSON.parse(Buffer.concat(parts).toString("utf8"));
|
|
44991
|
+
} catch {
|
|
44992
|
+
throw new Error(`metadata response from ${url2} is not valid JSON`);
|
|
44993
|
+
}
|
|
44994
|
+
if (!isRecord3(parsed) || typeof parsed["version"] !== "string" || parsed["version"].length === 0) {
|
|
44995
|
+
throw new Error(`metadata response from ${url2} carries no "version" string`);
|
|
44996
|
+
}
|
|
44997
|
+
return parsed["version"];
|
|
44998
|
+
}
|
|
44999
|
+
|
|
45000
|
+
// src/opencode/aspects/specFreshness.ts
|
|
45001
|
+
var isExactVersion2 = (spec) => /^\d+\.\d+\.\d+([-+].*)?$/.test(spec);
|
|
45002
|
+
async function verifySpecFreshness(facts, deps = {}) {
|
|
45003
|
+
const id = "spec-freshness";
|
|
45004
|
+
const { configs, cacheDirs, pkg } = facts;
|
|
45005
|
+
const errored = configs.filter((c) => c.status === "error").map((c) => c.path);
|
|
45006
|
+
if (errored.length > 0) {
|
|
45007
|
+
return { id, verdict: "CANNOT", detail: `configuration unreadable (${errored.join(", ")}) \u2014 the expected version cannot be determined` };
|
|
45008
|
+
}
|
|
45009
|
+
const specs = [...new Set(configs.flatMap((c) => [...c.specs]).map((s) => splitNpmSpec(s).spec).filter((s) => s !== null))];
|
|
45010
|
+
if (specs.length === 0) {
|
|
45011
|
+
return { id, verdict: "PASS", detail: `no ${pkg} spec declared in opencode config \u2014 nothing to be fresh` };
|
|
45012
|
+
}
|
|
45013
|
+
const expected = /* @__PURE__ */ new Map();
|
|
45014
|
+
const fetcher = deps.fetcher ?? defaultRegistryFetcher;
|
|
45015
|
+
const timeoutMs = deps.timeoutMs ?? REGISTRY_TIMEOUT_MS2;
|
|
45016
|
+
for (const spec of specs) {
|
|
45017
|
+
if (isExactVersion2(spec)) {
|
|
45018
|
+
expected.set(spec, spec);
|
|
45019
|
+
continue;
|
|
45020
|
+
}
|
|
45021
|
+
try {
|
|
45022
|
+
expected.set(spec, await fetchRegistryVersion(fetcher, npmTagUrl(pkg, spec, deps.registryBase), timeoutMs));
|
|
45023
|
+
} catch (err) {
|
|
45024
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
45025
|
+
return { id, verdict: "CANNOT", detail: `registry unreachable for spec '${pkg}@${spec}': ${message} \u2014 border never reads silence as fresh` };
|
|
45026
|
+
}
|
|
45027
|
+
}
|
|
45028
|
+
if (cacheDirs.length === 0) {
|
|
45029
|
+
return { id, verdict: "PASS", detail: `nothing cached to compare (see cache-version); spec resolves to ${[...new Set(expected.values())].join(", ")}` };
|
|
45030
|
+
}
|
|
45031
|
+
const wanted = new Set(expected.values());
|
|
45032
|
+
const judged = cacheDirs.filter((d) => d.packageVersion !== null);
|
|
45033
|
+
const stale = judged.filter((d) => !wanted.has(d.packageVersion));
|
|
45034
|
+
if (judged.length > 0 && stale.length > 0) {
|
|
45035
|
+
return {
|
|
45036
|
+
id,
|
|
45037
|
+
verdict: "FAIL",
|
|
45038
|
+
detail: `cache holds ${stale.map((d) => `${d.tag}->${d.packageVersion}`).join(",")}, spec expects ${[...wanted].join(",")}`
|
|
45039
|
+
};
|
|
45040
|
+
}
|
|
45041
|
+
return { id, verdict: "PASS", detail: `cache matches spec: ${[...wanted].join(",")}` };
|
|
45042
|
+
}
|
|
45043
|
+
|
|
45044
|
+
// src/opencode/inspect.ts
|
|
45045
|
+
function aggregateVerdict(aspects) {
|
|
45046
|
+
if (aspects.some((a) => a.verdict === "FAIL")) return "FAIL";
|
|
45047
|
+
if (aspects.some((a) => a.verdict === "CANNOT")) return "CANNOT";
|
|
45048
|
+
return "PASS";
|
|
45049
|
+
}
|
|
45050
|
+
function inspectExit(verdict) {
|
|
45051
|
+
switch (verdict) {
|
|
45052
|
+
case "FAIL":
|
|
45053
|
+
return EXIT_BLOCKED;
|
|
45054
|
+
case "CANNOT":
|
|
45055
|
+
return EXIT_ERROR;
|
|
45056
|
+
case "PASS":
|
|
45057
|
+
return EXIT_PASS;
|
|
45058
|
+
}
|
|
45059
|
+
}
|
|
45060
|
+
function readPackagedPlugin(path4) {
|
|
45061
|
+
try {
|
|
45062
|
+
return readFileSync19(path4);
|
|
45063
|
+
} catch {
|
|
45064
|
+
return null;
|
|
45065
|
+
}
|
|
45066
|
+
}
|
|
45067
|
+
function gatherFacts(env2, cwd, deps) {
|
|
45068
|
+
const pkg = deps.package ?? BORDER_PACKAGE;
|
|
45069
|
+
const pluginDrop = routeAPluginPath(env2);
|
|
45070
|
+
const cacheRoot = deps.cacheRoot ?? opencodeCacheRoot(env2);
|
|
45071
|
+
const globalPath = deps.globalConfigPath ?? join31(opencodeConfigRoot(env2), "opencode", "opencode.jsonc");
|
|
45072
|
+
const routeAInstalled = existsSync21(pluginDrop);
|
|
45073
|
+
return {
|
|
45074
|
+
pkg,
|
|
45075
|
+
env: env2,
|
|
45076
|
+
cacheRoot,
|
|
45077
|
+
cacheDirs: listCacheDirs(cacheRoot, pkg),
|
|
45078
|
+
configs: [readPluginDeclarations(globalPath, pkg), readPluginDeclarations(join31(cwd, "opencode.jsonc"), pkg)],
|
|
45079
|
+
packagedPlugin: readPackagedPlugin(packagedAssetPath(import.meta.url, "border.ts")),
|
|
45080
|
+
routeAPlugin: pluginDrop,
|
|
45081
|
+
routeAInstalled,
|
|
45082
|
+
routeABytes: routeAInstalled ? readPackagedPlugin(pluginDrop) : null
|
|
45083
|
+
};
|
|
45084
|
+
}
|
|
45085
|
+
async function runInspectCore(ctx, deps = {}) {
|
|
45086
|
+
const facts = gatherFacts(ctx.env, ctx.cwd, deps);
|
|
45087
|
+
const aspects = [
|
|
45088
|
+
verifyCacheVersion(facts),
|
|
45089
|
+
await verifySpecFreshness(facts, {
|
|
45090
|
+
...deps.registryFetcher !== void 0 ? { fetcher: deps.registryFetcher } : {},
|
|
45091
|
+
...deps.registryBase !== void 0 ? { registryBase: deps.registryBase } : {}
|
|
45092
|
+
}),
|
|
45093
|
+
await verifyIdentity({
|
|
45094
|
+
...deps.runner !== void 0 ? { runner: deps.runner } : {},
|
|
45095
|
+
distEntry: deps.distEntry ?? packagedDistEntry(import.meta.url),
|
|
45096
|
+
env: facts.env
|
|
45097
|
+
}),
|
|
45098
|
+
verifyRouteAParity(facts),
|
|
45099
|
+
verifyDualRoute(facts),
|
|
45100
|
+
verifyScope()
|
|
45101
|
+
];
|
|
45102
|
+
const verdict = aggregateVerdict(aspects);
|
|
45103
|
+
const report = { schemaVersion: 1, scope: "v1", verdict, aspects };
|
|
45104
|
+
if (ctx.flags.json) {
|
|
45105
|
+
ctx.stdout(JSON.stringify(report));
|
|
45106
|
+
} else {
|
|
45107
|
+
ctx.stdout(`border opencode inspect [${report.scope}] ${verdict}`);
|
|
45108
|
+
for (const a of aspects) {
|
|
45109
|
+
ctx.stdout(` ${a.verdict.padEnd(6)} ${a.id}: ${a.detail}`);
|
|
45110
|
+
}
|
|
45111
|
+
}
|
|
45112
|
+
return inspectExit(verdict);
|
|
45113
|
+
}
|
|
45114
|
+
|
|
45115
|
+
// src/commands/opencode.ts
|
|
45116
|
+
function resolvePluginAsset(moduleUrl, fileName) {
|
|
45117
|
+
const here = dirname16(fileURLToPath6(moduleUrl));
|
|
45118
|
+
const candidates = [
|
|
45119
|
+
join32(here, "..", "plugin", fileName),
|
|
45120
|
+
// dist mode (bundled): <root>/plugin/<f>
|
|
45121
|
+
join32(here, "..", "..", "plugin", fileName)
|
|
45122
|
+
// src mode: <root>/plugin/<f>
|
|
45123
|
+
];
|
|
45124
|
+
return candidates.find((p) => existsSync22(p)) ?? candidates[1];
|
|
45125
|
+
}
|
|
45126
|
+
function targetsFor(env2) {
|
|
45127
|
+
const root = join32(opencodeConfigRoot(env2), "opencode");
|
|
45128
|
+
return [
|
|
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 }
|
|
45131
|
+
];
|
|
44522
45132
|
}
|
|
44523
45133
|
function carriesMarker(bytes, marker) {
|
|
44524
45134
|
return firstLine(bytes).startsWith(marker);
|
|
@@ -44530,7 +45140,7 @@ function fail(ctx, message) {
|
|
|
44530
45140
|
function readPackagedAsset(ctx, fileName) {
|
|
44531
45141
|
const source = resolvePluginAsset(import.meta.url, fileName);
|
|
44532
45142
|
try {
|
|
44533
|
-
return
|
|
45143
|
+
return readFileSync20(source);
|
|
44534
45144
|
} catch {
|
|
44535
45145
|
ctx.stderr(`border: opencode: packaged asset is missing (${source})`);
|
|
44536
45146
|
ctx.stderr("border: opencode: the border install looks broken \u2014 `npm i -g border-customs` or a fresh checkout should carry plugin/.");
|
|
@@ -44539,7 +45149,7 @@ function readPackagedAsset(ctx, fileName) {
|
|
|
44539
45149
|
}
|
|
44540
45150
|
function readTarget(destination) {
|
|
44541
45151
|
try {
|
|
44542
|
-
return
|
|
45152
|
+
return readFileSync20(destination);
|
|
44543
45153
|
} catch {
|
|
44544
45154
|
return null;
|
|
44545
45155
|
}
|
|
@@ -44606,7 +45216,7 @@ function opencodeInstall(ctx) {
|
|
|
44606
45216
|
ctx.stdout(`${target.destination}: up to date`);
|
|
44607
45217
|
continue;
|
|
44608
45218
|
}
|
|
44609
|
-
mkdirSync12(
|
|
45219
|
+
mkdirSync12(dirname16(target.destination), { recursive: true });
|
|
44610
45220
|
writeFileSync8(target.destination, packaged);
|
|
44611
45221
|
ctx.stdout(`${target.destination}: ${state === "outdated" ? "updated" : "installed"}`);
|
|
44612
45222
|
}
|
|
@@ -44620,8 +45230,8 @@ function opencodeStatus(ctx) {
|
|
|
44620
45230
|
for (const { target, packaged } of prepared) {
|
|
44621
45231
|
const current = readTarget(target.destination);
|
|
44622
45232
|
const state = classify(target, current, packaged);
|
|
44623
|
-
const installedSha = current === null ? "-" :
|
|
44624
|
-
ctx.stdout(`${target.destination}: ${state} installed=${installedSha} packaged=${
|
|
45233
|
+
const installedSha = current === null ? "-" : sha256Hex3(current);
|
|
45234
|
+
ctx.stdout(`${target.destination}: ${state} installed=${installedSha} packaged=${sha256Hex3(packaged)}`);
|
|
44625
45235
|
}
|
|
44626
45236
|
return EXIT_PASS;
|
|
44627
45237
|
}
|
|
@@ -44641,30 +45251,34 @@ function opencodeUninstall(ctx) {
|
|
|
44641
45251
|
`refusing to remove ${target.destination} \u2014 the file is not ours (its first line lacks the marker '${target.marker}'); nothing was removed anywhere`
|
|
44642
45252
|
);
|
|
44643
45253
|
}
|
|
44644
|
-
|
|
45254
|
+
rmSync14(target.destination);
|
|
44645
45255
|
ctx.stdout(`${target.destination}: removed`);
|
|
44646
45256
|
}
|
|
44647
45257
|
ctx.stdout("note: restart opencode for the tool and command to disappear.");
|
|
44648
45258
|
return EXIT_PASS;
|
|
44649
45259
|
}
|
|
44650
45260
|
function runOpencode(ctx) {
|
|
44651
|
-
const [sub, ...
|
|
44652
|
-
|
|
44653
|
-
|
|
44654
|
-
`opencode ${sub ?? "<none>"}: unexpected argument '${rest[0] ?? ""}' \u2014 usage: border opencode install | status | uninstall`
|
|
44655
|
-
);
|
|
44656
|
-
}
|
|
45261
|
+
const [sub, ...restAll] = ctx.positionals;
|
|
45262
|
+
const rest = restAll.filter((arg) => arg !== "--json");
|
|
45263
|
+
const USAGE = "usage: border opencode install | status | uninstall | inspect";
|
|
44657
45264
|
switch (sub) {
|
|
44658
45265
|
case "install":
|
|
44659
|
-
return opencodeInstall(ctx);
|
|
44660
45266
|
case "status":
|
|
44661
|
-
return opencodeStatus(ctx);
|
|
44662
45267
|
case "uninstall":
|
|
44663
|
-
|
|
45268
|
+
case "inspect": {
|
|
45269
|
+
if (rest.length > 0) {
|
|
45270
|
+
throw new UnknownArgError(`opencode ${sub}: unexpected argument '${rest[0] ?? ""}' \u2014 ${USAGE}`);
|
|
45271
|
+
}
|
|
45272
|
+
const table2 = {
|
|
45273
|
+
install: opencodeInstall,
|
|
45274
|
+
status: opencodeStatus,
|
|
45275
|
+
uninstall: opencodeUninstall,
|
|
45276
|
+
inspect: runInspectCore
|
|
45277
|
+
};
|
|
45278
|
+
return table2[sub](ctx);
|
|
45279
|
+
}
|
|
44664
45280
|
default:
|
|
44665
|
-
throw new UnknownArgError(
|
|
44666
|
-
`opencode: unknown subcommand '${sub ?? "<none>"}' \u2014 usage: border opencode install | status | uninstall`
|
|
44667
|
-
);
|
|
45281
|
+
throw new UnknownArgError(`opencode: unknown subcommand '${sub ?? "<none>"}' \u2014 ${USAGE}`);
|
|
44668
45282
|
}
|
|
44669
45283
|
}
|
|
44670
45284
|
|
|
@@ -44766,9 +45380,9 @@ function confirmRemoteBranch(i) {
|
|
|
44766
45380
|
|
|
44767
45381
|
// src/push/landing.ts
|
|
44768
45382
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
44769
|
-
import { mkdtempSync as
|
|
44770
|
-
import { tmpdir as
|
|
44771
|
-
import { dirname 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";
|
|
44772
45386
|
var LANDINGS_REF_PREFIX = "refs/border-landings/";
|
|
44773
45387
|
async function runLandingVerification(o) {
|
|
44774
45388
|
if (o.legs.length === 0) return null;
|
|
@@ -44791,6 +45405,12 @@ async function runLandingVerification(o) {
|
|
|
44791
45405
|
);
|
|
44792
45406
|
return EXIT_ERROR;
|
|
44793
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
|
+
}
|
|
44794
45414
|
const blocking = findings.filter((f) => isBlocking(f.severity)).length;
|
|
44795
45415
|
const verdict = blocking > 0 ? "blocked" : "clean";
|
|
44796
45416
|
sink(buildLandingRecord({ key: o.key, target: leg.target.target, ref: `refs/heads/${leg.branch}`, remoteSha: sha, verdict, blocking, rulesHash }));
|
|
@@ -44818,7 +45438,7 @@ function gitPlain(repoDir, env2, args) {
|
|
|
44818
45438
|
for (const [name, value] of Object.entries(env2)) {
|
|
44819
45439
|
if (!name.startsWith("GIT_")) clean[name] = value;
|
|
44820
45440
|
}
|
|
44821
|
-
clean.GIT_CEILING_DIRECTORIES =
|
|
45441
|
+
clean.GIT_CEILING_DIRECTORIES = dirname17(resolve18(repoDir));
|
|
44822
45442
|
clean.GIT_OPTIONAL_LOCKS = "0";
|
|
44823
45443
|
clean.GIT_TERMINAL_PROMPT = "0";
|
|
44824
45444
|
const r = spawnSync18("git", ["-C", resolve18(repoDir), ...args], { encoding: "utf8", env: clean, timeout: 55e3, maxBuffer: 32 * 1024 * 1024 });
|
|
@@ -44843,11 +45463,11 @@ async function defaultScanRemoteTip(o, remote, branch, remoteSha) {
|
|
|
44843
45463
|
ownedTempRef = landingRef;
|
|
44844
45464
|
rev = git.run(["rev-parse", landingRef]).trim();
|
|
44845
45465
|
}
|
|
44846
|
-
const treeRoot =
|
|
45466
|
+
const treeRoot = mkdtempSync7(join33(tmpdir5(), "border-landing-tree-"));
|
|
44847
45467
|
try {
|
|
44848
45468
|
return await scanTipTree(git, treeRoot, rev, o.env);
|
|
44849
45469
|
} finally {
|
|
44850
|
-
|
|
45470
|
+
rmSync15(treeRoot, { recursive: true, force: true });
|
|
44851
45471
|
}
|
|
44852
45472
|
} finally {
|
|
44853
45473
|
if (ownedTempRef !== null) {
|
|
@@ -45027,6 +45647,9 @@ async function executeYesPush(ctx, loaded) {
|
|
|
45027
45647
|
key: state.key,
|
|
45028
45648
|
legs: legs.map((leg) => ({ target: leg, branch })),
|
|
45029
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,
|
|
45030
45653
|
out: ctx.stdout,
|
|
45031
45654
|
err: ctx.stderr
|
|
45032
45655
|
});
|
|
@@ -45049,17 +45672,17 @@ async function executeYesPush(ctx, loaded) {
|
|
|
45049
45672
|
}
|
|
45050
45673
|
|
|
45051
45674
|
// src/roundtrip/orchestrate.ts
|
|
45052
|
-
import { mkdtempSync as
|
|
45675
|
+
import { mkdtempSync as mkdtempSync8, rmSync as rmSync16, writeFileSync as writeFileSync9 } from "node:fs";
|
|
45053
45676
|
import { randomBytes } from "node:crypto";
|
|
45054
|
-
import { tmpdir as
|
|
45055
|
-
import { join as
|
|
45677
|
+
import { tmpdir as tmpdir6 } from "node:os";
|
|
45678
|
+
import { join as join34 } from "node:path";
|
|
45056
45679
|
|
|
45057
45680
|
// src/scan/fetch.ts
|
|
45058
|
-
import { createHash as
|
|
45681
|
+
import { createHash as createHash19 } from "node:crypto";
|
|
45059
45682
|
import { basename as basename5 } from "node:path";
|
|
45060
45683
|
var SCAN_MAX_ARTIFACT_BYTES = 200 * 1024 * 1024;
|
|
45061
45684
|
var SCAN_REQUEST_TIMEOUT_MS = 6e4;
|
|
45062
|
-
var SCAN_USER_AGENT = "border-customs/0.
|
|
45685
|
+
var SCAN_USER_AGENT = "border-customs/0.8.1 (+https://github.com/gate-corp/border; border scan)";
|
|
45063
45686
|
var CRATES_DOWNLOAD_HOST = "https://static.crates.io";
|
|
45064
45687
|
var defaultScanFetcher = async (url2, init) => {
|
|
45065
45688
|
if (!/^https?:\/\//.test(url2)) throw new EngineRunError(`border scan refuses non-http(s) URL: ${url2}`, null);
|
|
@@ -45113,7 +45736,7 @@ async function fetchJson(fetcher, url2, timeoutMs) {
|
|
|
45113
45736
|
fail2(`metadata response from ${url2} is not valid JSON`);
|
|
45114
45737
|
}
|
|
45115
45738
|
}
|
|
45116
|
-
var
|
|
45739
|
+
var isRecord4 = (v) => typeof v === "object" && v !== null;
|
|
45117
45740
|
function channelBaseUrl(eco) {
|
|
45118
45741
|
const ch = publishChannels().find((c) => c.id === eco);
|
|
45119
45742
|
if (ch === void 0 || ch.defaultUrl.length === 0) {
|
|
@@ -45144,7 +45767,7 @@ async function fetchArtifact(spec, o) {
|
|
|
45144
45767
|
switch (spec.ecosystem) {
|
|
45145
45768
|
case "npm": {
|
|
45146
45769
|
const meta3 = await fetchJson(o.fetcher, npmMetadataUrl(spec.name, spec.version), timeoutMs);
|
|
45147
|
-
const dist =
|
|
45770
|
+
const dist = isRecord4(meta3) && isRecord4(meta3["dist"]) ? meta3["dist"] : {};
|
|
45148
45771
|
const tarball = dist["tarball"];
|
|
45149
45772
|
if (typeof tarball !== "string" || tarball.length === 0) fail2(`npm metadata for ${label} has no dist.tarball`);
|
|
45150
45773
|
if (!/^https?:\/\//.test(tarball)) fail2(`npm dist.tarball for ${label} is not an http(s) URL`);
|
|
@@ -45155,8 +45778,8 @@ async function fetchArtifact(spec, o) {
|
|
|
45155
45778
|
}
|
|
45156
45779
|
case "pypi": {
|
|
45157
45780
|
const meta3 = await fetchJson(o.fetcher, pypiMetadataUrl(spec.name, spec.version), timeoutMs);
|
|
45158
|
-
const urls =
|
|
45159
|
-
const files = urls.filter(
|
|
45781
|
+
const urls = isRecord4(meta3) && Array.isArray(meta3["urls"]) ? meta3["urls"] : [];
|
|
45782
|
+
const files = urls.filter(isRecord4);
|
|
45160
45783
|
const sdists = files.filter((f) => f["packagetype"] === "sdist");
|
|
45161
45784
|
const sdist = sdists.find((f) => typeof f["filename"] === "string" && f["filename"].endsWith(".tar.gz"));
|
|
45162
45785
|
if (sdist === void 0) {
|
|
@@ -45184,8 +45807,8 @@ async function fetchArtifact(spec, o) {
|
|
|
45184
45807
|
}
|
|
45185
45808
|
}
|
|
45186
45809
|
}
|
|
45187
|
-
function
|
|
45188
|
-
return
|
|
45810
|
+
function sha256Hex4(data) {
|
|
45811
|
+
return createHash19("sha256").update(data).digest("hex");
|
|
45189
45812
|
}
|
|
45190
45813
|
|
|
45191
45814
|
// src/scan/spec.ts
|
|
@@ -45228,8 +45851,8 @@ function parseScanSpec(raw) {
|
|
|
45228
45851
|
}
|
|
45229
45852
|
|
|
45230
45853
|
// src/roundtrip/localArtifact.ts
|
|
45231
|
-
import { createReadStream, existsSync as
|
|
45232
|
-
import { createHash as
|
|
45854
|
+
import { createReadStream, existsSync as existsSync23, statSync as statSync3 } from "node:fs";
|
|
45855
|
+
import { createHash as createHash20 } from "node:crypto";
|
|
45233
45856
|
import { basename as basename6, resolve as resolve19 } from "node:path";
|
|
45234
45857
|
var EXT_TABLE = /* @__PURE__ */ new Map([
|
|
45235
45858
|
[".whl", "pypi"],
|
|
@@ -45277,7 +45900,7 @@ var GRAMMAR_HINT = {
|
|
|
45277
45900
|
};
|
|
45278
45901
|
function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
|
|
45279
45902
|
const abs = resolve19(cwd, raw);
|
|
45280
|
-
if (
|
|
45903
|
+
if (existsSync23(abs)) {
|
|
45281
45904
|
const st = statSync3(abs);
|
|
45282
45905
|
if (st.isDirectory()) {
|
|
45283
45906
|
bad3(`'${raw}' resolves to a directory (${abs}) \u2014 local mode needs a single artifact FILE, not a directory`);
|
|
@@ -45312,7 +45935,7 @@ function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
|
|
|
45312
45935
|
return null;
|
|
45313
45936
|
}
|
|
45314
45937
|
async function readLocalArtifact(absPath, cap = SCAN_MAX_ARTIFACT_BYTES) {
|
|
45315
|
-
const hash2 =
|
|
45938
|
+
const hash2 = createHash20("sha256");
|
|
45316
45939
|
const parts = [];
|
|
45317
45940
|
let total = 0;
|
|
45318
45941
|
for await (const chunk of createReadStream(absPath)) {
|
|
@@ -45538,7 +46161,7 @@ var CLAIM_SCANNER_PY = [
|
|
|
45538
46161
|
].join("\n");
|
|
45539
46162
|
|
|
45540
46163
|
// src/roundtrip/manifest.ts
|
|
45541
|
-
import { createHash as
|
|
46164
|
+
import { createHash as createHash21 } from "node:crypto";
|
|
45542
46165
|
var RT_ENGINE = "roundtrip";
|
|
45543
46166
|
var RT_RESIDUE_RULE = "roundtrip-residue-file";
|
|
45544
46167
|
var RT_MODIFY_RULE = "roundtrip-modified-file";
|
|
@@ -45589,7 +46212,7 @@ var PERSISTENCE_PATTERNS = [
|
|
|
45589
46212
|
function isPersistenceSurface(path4) {
|
|
45590
46213
|
return PERSISTENCE_PATTERNS.some((re) => re.test(path4));
|
|
45591
46214
|
}
|
|
45592
|
-
var digestOf = (what) =>
|
|
46215
|
+
var digestOf = (what) => createHash21("sha256").update(what).digest("hex");
|
|
45593
46216
|
function classifyResidue(label, diff, attribution = null) {
|
|
45594
46217
|
const findings = [];
|
|
45595
46218
|
for (const e of diff.added) {
|
|
@@ -45636,7 +46259,7 @@ function classifyResidue(label, diff, attribution = null) {
|
|
|
45636
46259
|
|
|
45637
46260
|
// src/roundtrip/orchestrate.ts
|
|
45638
46261
|
function containerNameFor(label, rand = randomBytes(3).toString("hex")) {
|
|
45639
|
-
return `rt-${
|
|
46262
|
+
return `rt-${sha256Hex4(label).slice(0, 8)}-${rand}`;
|
|
45640
46263
|
}
|
|
45641
46264
|
var shArgv = (name) => {
|
|
45642
46265
|
if (!/^[A-Za-z0-9@._+/-]+$/.test(name)) {
|
|
@@ -45757,7 +46380,7 @@ function runPipeline2(exec, profile, spec, label, hostPath, container, closure,
|
|
|
45757
46380
|
return {
|
|
45758
46381
|
findings: classifyResidue(label, diffManifests(m1, m3), attribution),
|
|
45759
46382
|
installDelta,
|
|
45760
|
-
baselineDigest:
|
|
46383
|
+
baselineDigest: sha256Hex4([...m1.keys()].join("\n"))
|
|
45761
46384
|
};
|
|
45762
46385
|
} finally {
|
|
45763
46386
|
try {
|
|
@@ -45791,12 +46414,12 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
45791
46414
|
ctx.stderr("roundtrip: docker unavailable \u2014 cannot verify");
|
|
45792
46415
|
return EXIT_ERROR;
|
|
45793
46416
|
}
|
|
45794
|
-
const baseDir =
|
|
46417
|
+
const baseDir = mkdtempSync8(join34(tmpdir6(), "border-roundtrip-"));
|
|
45795
46418
|
try {
|
|
45796
46419
|
const localRead = local !== null ? await readLocalArtifact(local.absPath) : null;
|
|
45797
46420
|
const bytes = localRead?.bytes ?? (await fetchArtifact(spec, { fetcher: deps.fetcher ?? defaultScanFetcher })).bytes;
|
|
45798
|
-
const artifactSha256 = localRead !== null ? localRead.sha256 :
|
|
45799
|
-
const hostPath =
|
|
46421
|
+
const artifactSha256 = localRead !== null ? localRead.sha256 : sha256Hex4(bytes);
|
|
46422
|
+
const hostPath = join34(baseDir, "artifact");
|
|
45800
46423
|
writeFileSync9(hostPath, bytes);
|
|
45801
46424
|
const container = containerNameFor(label);
|
|
45802
46425
|
const closure = spec.ecosystem === "pypi" ? resolvePipClosure(exec, profile, hostPath, label, spec) : null;
|
|
@@ -45804,12 +46427,12 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
45804
46427
|
const verdict = computeVerdict(findings);
|
|
45805
46428
|
const report = {
|
|
45806
46429
|
schemaVersion: 1,
|
|
45807
|
-
key:
|
|
46430
|
+
key: sha256Hex4(`roundtrip:${label}:${baselineDigest}:${verdict}`),
|
|
45808
46431
|
head: baselineDigest.slice(0, 40),
|
|
45809
46432
|
dirty: false,
|
|
45810
46433
|
exposureSet: [label],
|
|
45811
46434
|
refSet: [],
|
|
45812
|
-
rulesHash:
|
|
46435
|
+
rulesHash: sha256Hex4(`${profile.image}|${profile.install(profile.artifactPath, spec).join(" ")}|${profile.uninstall(spec).join(" ")}`),
|
|
45813
46436
|
verdict,
|
|
45814
46437
|
counts: countFindings(findings),
|
|
45815
46438
|
findings,
|
|
@@ -45841,7 +46464,7 @@ async function runRoundtripCore(ctx, deps = {}) {
|
|
|
45841
46464
|
}
|
|
45842
46465
|
return exitCodeFromVerdict(verdict);
|
|
45843
46466
|
} finally {
|
|
45844
|
-
|
|
46467
|
+
rmSync16(baseDir, { recursive: true, force: true });
|
|
45845
46468
|
}
|
|
45846
46469
|
}
|
|
45847
46470
|
function parsesRegistrySpec(raw) {
|
|
@@ -45897,9 +46520,9 @@ function dockerAvailableGuard(exec) {
|
|
|
45897
46520
|
var runRoundtrip = (ctx) => runRoundtripCore(ctx);
|
|
45898
46521
|
|
|
45899
46522
|
// src/scan/scan.ts
|
|
45900
|
-
import { mkdtempSync as
|
|
45901
|
-
import { tmpdir as
|
|
45902
|
-
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";
|
|
45903
46526
|
|
|
45904
46527
|
// src/scan/config.ts
|
|
45905
46528
|
function buildScanConfigYaml(eco) {
|
|
@@ -45926,8 +46549,8 @@ function synthesizeScanConfig(eco) {
|
|
|
45926
46549
|
}
|
|
45927
46550
|
|
|
45928
46551
|
// src/scan/materialize.ts
|
|
45929
|
-
import { existsSync as
|
|
45930
|
-
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";
|
|
45931
46554
|
var CRATES_ENVELOPE_FILES = ["Cargo.toml.orig", ".cargo-ok", ".cargo_vcs_info.json"];
|
|
45932
46555
|
var fail3 = (why) => {
|
|
45933
46556
|
throw new Error(`border scan cannot materialize the artifact (${why}) \u2014 refusing to stage an unusable tree (fail-closed)`);
|
|
@@ -45941,15 +46564,15 @@ function extractFlat(archive, destDir) {
|
|
|
45941
46564
|
}
|
|
45942
46565
|
}
|
|
45943
46566
|
function unwrapSingleDir(dir) {
|
|
45944
|
-
const entries =
|
|
46567
|
+
const entries = readdirSync11(dir, { withFileTypes: true });
|
|
45945
46568
|
const dirs = entries.filter((e) => e.isDirectory());
|
|
45946
46569
|
if (entries.length === 1 && dirs.length === 1 && dirs[0] !== void 0) {
|
|
45947
|
-
return
|
|
46570
|
+
return join35(dir, dirs[0].name);
|
|
45948
46571
|
}
|
|
45949
46572
|
return dir;
|
|
45950
46573
|
}
|
|
45951
46574
|
function requireManifest(repoDir, ecosystem, candidates) {
|
|
45952
|
-
if (!candidates.some((rel) =>
|
|
46575
|
+
if (!candidates.some((rel) => existsSync24(join35(repoDir, rel)))) {
|
|
45953
46576
|
fail3(`${ecosystem} package tree has none of [${candidates.join(", ")}] at its root`);
|
|
45954
46577
|
}
|
|
45955
46578
|
}
|
|
@@ -45959,18 +46582,18 @@ function materializePackage(o) {
|
|
|
45959
46582
|
`);
|
|
45960
46583
|
});
|
|
45961
46584
|
const safe = safeArtifactName(o.filename, `${o.ecosystem}-artifact`);
|
|
45962
|
-
const archive =
|
|
46585
|
+
const archive = join35(o.baseDir, safe);
|
|
45963
46586
|
writeFileSync10(archive, o.bytes);
|
|
45964
|
-
const extractDir =
|
|
46587
|
+
const extractDir = join35(o.baseDir, "extract");
|
|
45965
46588
|
mkdirSync13(extractDir, { recursive: true });
|
|
45966
46589
|
extractFlat(archive, extractDir);
|
|
45967
46590
|
let repoDir = unwrapSingleDir(extractDir);
|
|
45968
46591
|
if (o.ecosystem === "rubygems") {
|
|
45969
46592
|
const outer = repoDir;
|
|
45970
46593
|
const dataCandidates = findNativeMissArchives(outer).filter((f) => f.endsWith("data.tar.gz"));
|
|
45971
|
-
const inner = dataCandidates[0] ??
|
|
45972
|
-
if (!
|
|
45973
|
-
const gemTree =
|
|
46594
|
+
const inner = dataCandidates[0] ?? join35(outer, "data.tar.gz");
|
|
46595
|
+
if (!existsSync24(inner)) fail3(".gem archive contains no data.tar.gz");
|
|
46596
|
+
const gemTree = join35(o.baseDir, "gem-tree");
|
|
45974
46597
|
mkdirSync13(gemTree, { recursive: true });
|
|
45975
46598
|
extractFlat(inner, gemTree);
|
|
45976
46599
|
repoDir = unwrapSingleDir(gemTree);
|
|
@@ -45981,11 +46604,11 @@ function materializePackage(o) {
|
|
|
45981
46604
|
} else {
|
|
45982
46605
|
requireManifest(repoDir, o.ecosystem, ["Cargo.toml"]);
|
|
45983
46606
|
for (const file2 of CRATES_ENVELOPE_FILES) {
|
|
45984
|
-
const path4 =
|
|
45985
|
-
if (!
|
|
45986
|
-
const original =
|
|
45987
|
-
|
|
45988
|
-
note(`border scan: crates envelope normalized: ${file2} sha256=${
|
|
46607
|
+
const path4 = join35(repoDir, file2);
|
|
46608
|
+
if (!existsSync24(path4)) continue;
|
|
46609
|
+
const original = readFileSync21(path4);
|
|
46610
|
+
rmSync17(path4, { force: true });
|
|
46611
|
+
note(`border scan: crates envelope normalized: ${file2} sha256=${sha256Hex4(original)}`);
|
|
45989
46612
|
}
|
|
45990
46613
|
}
|
|
45991
46614
|
const env2 = o.env;
|
|
@@ -46011,7 +46634,7 @@ async function runScanCore(ctx, deps = {}) {
|
|
|
46011
46634
|
}
|
|
46012
46635
|
const cfg = synthesizeScanConfig(spec.ecosystem);
|
|
46013
46636
|
const fetcher = deps.fetcher ?? defaultScanFetcher;
|
|
46014
|
-
const baseDir =
|
|
46637
|
+
const baseDir = mkdtempSync9(join36(tmpdir7(), "border-scan-"));
|
|
46015
46638
|
try {
|
|
46016
46639
|
const { bytes, filename } = await fetchArtifact(spec, { fetcher });
|
|
46017
46640
|
const env2 = { ...ctx.env };
|
|
@@ -46022,12 +46645,12 @@ async function runScanCore(ctx, deps = {}) {
|
|
|
46022
46645
|
const verdict = computeVerdict(findings);
|
|
46023
46646
|
const report = {
|
|
46024
46647
|
schemaVersion: 1,
|
|
46025
|
-
key:
|
|
46648
|
+
key: sha256Hex4(`scan:${spec.ecosystem}:${spec.name}@${spec.version}:${head}`),
|
|
46026
46649
|
head,
|
|
46027
46650
|
dirty: false,
|
|
46028
46651
|
exposureSet: [`${spec.ecosystem}:${spec.name}@${spec.version}`],
|
|
46029
46652
|
refSet: [],
|
|
46030
|
-
rulesHash:
|
|
46653
|
+
rulesHash: sha256Hex4(buildScanConfigYaml(spec.ecosystem)),
|
|
46031
46654
|
verdict,
|
|
46032
46655
|
counts: countFindings(findings),
|
|
46033
46656
|
findings,
|
|
@@ -46168,7 +46791,7 @@ function usage() {
|
|
|
46168
46791
|
" llm-ingest record provenance captured by the harness",
|
|
46169
46792
|
" scan inspect a third-party package ([ecosystem:]name@version) for residue",
|
|
46170
46793
|
" roundtrip install+uninstall a package (registry spec or local artifact file) in a throwaway container, report uninstall residue",
|
|
46171
|
-
" opencode install|status|uninstall the official opencode plugin adapter (files under $XDG_CONFIG_HOME/opencode)",
|
|
46794
|
+
" opencode install|status|uninstall|inspect the official opencode plugin adapter (files under $XDG_CONFIG_HOME/opencode; inspect = read-only V1 load-surface audit)",
|
|
46172
46795
|
" exfil read-only exfil-lens scan of <ref|url> [ref...]: tip tree + tag notes (--deep adds reachable-history blobs + commit messages)",
|
|
46173
46796
|
"",
|
|
46174
46797
|
"flags:",
|
|
@@ -46311,7 +46934,7 @@ function isMainModule({ argv1, moduleUrl, realpath }) {
|
|
|
46311
46934
|
return p;
|
|
46312
46935
|
}
|
|
46313
46936
|
};
|
|
46314
|
-
return pathToFileURL(canonical(argv1)).href === pathToFileURL(canonical(
|
|
46937
|
+
return pathToFileURL(canonical(argv1)).href === pathToFileURL(canonical(fileURLToPath7(moduleUrl))).href;
|
|
46315
46938
|
}
|
|
46316
46939
|
if (isMainModule({ argv1: process.argv[1], moduleUrl: import.meta.url, realpath: realpathSync.native })) {
|
|
46317
46940
|
const outcome = run(process.argv.slice(2));
|