border-customs 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -11
- package/dist/assets/package-lock.json +2 -2
- package/dist/index.js +324 -146
- package/package.json +1 -1
- package/plugin/border.ts +47 -10
package/README.md
CHANGED
|
@@ -40,6 +40,10 @@ source checkout. Every check and push record carries `engineBuild` — the ident
|
|
|
40
40
|
`border --version` prints. A `.dirty` suffix means the verdict came from uncommitted code, and
|
|
41
41
|
records from one repo carrying different `engineBuild` values mean the audit trail is mixed:
|
|
42
42
|
both are findings an auditor should chase, which is exactly why the identity is on the record face.
|
|
43
|
+
Freshness telemetry (the `global-resolution` WARN rung, drift-watch rc 3, and heartbeat lines) is
|
|
44
|
+
read-only information. Pinned consumption governs the install and execution surfaces: running an
|
|
45
|
+
older, deliberately chosen version is a legitimate steady state, not a defect. The decision to
|
|
46
|
+
update always stays with the human operator. (Owner-approved clarification, 2026-11-06.)
|
|
43
47
|
|
|
44
48
|
## The problem it solves
|
|
45
49
|
|
|
@@ -59,7 +63,11 @@ pushed" includes the six things people forget:
|
|
|
59
63
|
|
|
60
64
|
- **Untracked and dirty files are inputs too.** History scanning alone misses the `.env`
|
|
61
65
|
that was never committed but will ride along in a `git add -A`. border fingerprints the
|
|
62
|
-
full `git status --porcelain` digest and scans the working tree directly.
|
|
66
|
+
full `git status --porcelain` digest and scans the working tree directly. One carve-out is
|
|
67
|
+
provably unpushable and demoted to INFO (never silently dropped): paths that are
|
|
68
|
+
`.gitignore`'d **and** untracked — `git add` honours `.gitignore`, so they can only reach a
|
|
69
|
+
commit through a deliberate `git add -f`, which moves them into the tracked surface where
|
|
70
|
+
the full gate applies. Blocking on them would train operators to mute the gate.
|
|
63
71
|
- **Deleted-but-pushed blobs.** A secret removed in a later commit still ships in the
|
|
64
72
|
history of the ref being pushed. The history leg scans everything the push would
|
|
65
73
|
transmit, not just HEAD.
|
|
@@ -129,9 +137,13 @@ it, and the two registry-facing legs (pre-flight and publish) both have to agree
|
|
|
129
137
|
(`.omo/**`, `**/transcripts/**`, `*.session.jsonl`, `opencode.json(c)`, `.opencode/**`,
|
|
130
138
|
env files except `.env.example`/`.env.sample`, junk like `probe*`/`*.rej`/
|
|
131
139
|
`node_modules/**`, plus your `rules.pathPatterns` through the same compiler). Identity
|
|
132
|
-
allow-listing over commit authors, merge committers, and annotated-tag taggers,
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
allow-listing over commit authors, merge committers, and annotated-tag taggers, split by
|
|
141
|
+
*exposure class* against each configured remote's **live** `git ls-remote` (never a stale
|
|
142
|
+
local tracking ref): objects leaving the machine for the first time are CRITICAL
|
|
143
|
+
`identity-not-allowlisted`; offenders the remote already carries everywhere are recorded as
|
|
144
|
+
INFO `identity-already-public` without remediation — border never advises rewriting
|
|
145
|
+
published history. Offenders plus an unreachable remote exit 2 (the exposure class is
|
|
146
|
+
never guessed); no configured remote means first-push semantics (all fresh). Oversized-file, checked-in-binary, and notebook-output rules
|
|
135
147
|
round it out.
|
|
136
148
|
7. **Landing-grade tip lens.** The exact machinery `border push` uses to verify a landing
|
|
137
149
|
(`scanTipTree`: the tip tree materialized from git objects into a throwaway directory,
|
|
@@ -811,7 +823,7 @@ plugin entry ships as `exports["./server"]`):
|
|
|
811
823
|
```jsonc
|
|
812
824
|
{
|
|
813
825
|
// pin the exact version — an @latest channel hits the registry on every cold start
|
|
814
|
-
"plugin": ["border-customs@0.8.
|
|
826
|
+
"plugin": ["border-customs@0.8.5"]
|
|
815
827
|
}
|
|
816
828
|
```
|
|
817
829
|
|
|
@@ -849,7 +861,12 @@ argv-only (no shell; 300 s cap, 64 KiB per stream) restricted to the closed comm
|
|
|
849
861
|
`check`, `push`, `status`, `llm-request`, `llm-ingest`, `scan`, `roundtrip`, `exfil`, `--help`, and
|
|
850
862
|
renders the exit-code contract unchanged: 0 pass, 1 gate-blocked or partial push, 2 the gate
|
|
851
863
|
could not answer — which since 0.5.1 also covers "no border CLI found or verified": the tool
|
|
852
|
-
answers exit 2 cannot-answer and spawns nothing else.
|
|
864
|
+
answers exit 2 cannot-answer and spawns nothing else. Every result carries a
|
|
865
|
+
`face: <resolved candidate> version=<--version stamp>` line: the identity handshake proves *which
|
|
866
|
+
binary*, the stamp line makes *which version* observable on every call (a PATH-fallback to an older
|
|
867
|
+
published CLI is loud, never silent). `BORDER_REQUIRE_PKG_VERSION=1` escalates that to a gate: the
|
|
868
|
+
resolved CLI must report the plugin package's own version or the call is refused as cannot-answer
|
|
869
|
+
with nothing spawned. The tool REFUSES `--yes` — a real push
|
|
853
870
|
is the human gate, terminal-only, and a bare `border push` from the session is DRY-RUN by the
|
|
854
871
|
CLI's own contract anyway. `/border` self-registers via the plugin `config` hook with `??=`, so
|
|
855
872
|
a user file command of the same name — including a Route-A `commands/border.md` — stays
|
|
@@ -864,16 +881,24 @@ fails); plugin load failures are silent, so judge by the HTTP responses, never b
|
|
|
864
881
|
code.
|
|
865
882
|
|
|
866
883
|
`border opencode inspect [--json]` audits this machine's **V1 load surface** as one gate-owned
|
|
867
|
-
command:
|
|
884
|
+
command: seven read-only aspects — cache self-consistency (package.json vs plugin marker line),
|
|
868
885
|
spec freshness (exact pin offline; dist-tag against the registry), the 0.5.1 identity handshake
|
|
869
886
|
replayed against the CLI resolver's candidate order, Route A byte parity, Route A∧B dual-registration
|
|
870
|
-
collision,
|
|
871
|
-
|
|
887
|
+
collision, the scope declaration, and global-resolution (which CLI build answers on which face —
|
|
888
|
+
`BORDER_BIN`, packaged dist, PATH — and whether the active face lags the registry's latest) —
|
|
889
|
+
aggregated fail-closed: any FAIL exits 1, else any CANNOT exits 2, else 0. The seventh aspect's lag
|
|
890
|
+
rung is **WARN — informational**: lag under pinned consumption is a legitimate steady state, so WARN
|
|
891
|
+
never moves the aggregate or any purge trigger; registry-unreachable for this aspect stays named.
|
|
892
|
+
`BORDER_INSPECT_OFFLINE=1` skips the freshness leg explicitly on air-gapped machines — the aspect
|
|
893
|
+
then reports WARN "skipped", never fresh (an unevaluated face is never reported as fresh).
|
|
894
|
+
The certificate boundary is printed, not implied: `--json` carries
|
|
872
895
|
`scope:"v1"`, and V2 load surfaces (project-local `.opencode/plugins/`, npm-prefix layouts, lazy
|
|
873
896
|
activation) are a DECLARED BLIND SPOT pending V2 stabilization — a PASS is never evidence that
|
|
874
897
|
"the whole machine is clean". The tool never writes: `--purge` lives in the drift-watch cron shell,
|
|
875
|
-
which is now a thin consumer of `inspect --json` with
|
|
876
|
-
|
|
898
|
+
which is now a thin consumer of `inspect --json` with rc contract FRESH 0 / DRIFT 1 / NOT-CACHED 0 /
|
|
899
|
+
CANNOT 2 / **GLOBAL-WARN or GLOBAL-UNREACHABLE 3** (warn-only by construction: the cron purge guard
|
|
900
|
+
acts on rc 1 only, and rc 3 can never reach it), plus an optional `--heartbeat <path>` that appends
|
|
901
|
+
one `ISO / verdict / rc / bin` line per run.
|
|
877
902
|
|
|
878
903
|
## What this is not
|
|
879
904
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "border-customs",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "border-customs",
|
|
9
|
-
"version": "0.8.
|
|
9
|
+
"version": "0.8.5",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@secretlint/core": "13.0.5",
|
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
44
44
|
var define_BORDER_BUILD_default;
|
|
45
45
|
var init_define_BORDER_BUILD = __esm({
|
|
46
46
|
"<define:__BORDER_BUILD__>"() {
|
|
47
|
-
define_BORDER_BUILD_default = { version: "0.8.
|
|
47
|
+
define_BORDER_BUILD_default = { version: "0.8.5", commit: "56c5aa3959e18fa143e49904608f4564798ff0a6", dirty: false, builtAt: "2026-11-08T08:34:50.133Z" };
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
50
|
|
|
@@ -30368,8 +30368,8 @@ function sandboxBindingJoin(images) {
|
|
|
30368
30368
|
return [...new Set(images)].sort().join("\n");
|
|
30369
30369
|
}
|
|
30370
30370
|
function sandboxRun(o) {
|
|
30371
|
-
const
|
|
30372
|
-
const script = `${
|
|
30371
|
+
const probe2 = o.laneProbes.map((p) => `{ printf '%s ' '${p}'; ${p} --version 2>&1 | head -n 1; }`).join("\n");
|
|
30372
|
+
const script = `${probe2} > /audit/out/toolchain.txt
|
|
30373
30373
|
${o.laneScript}`;
|
|
30374
30374
|
const r = runSandboxedBuild({
|
|
30375
30375
|
spec: o.spec,
|
|
@@ -37531,8 +37531,8 @@ function pypiArtifactCoherenceFindings(o) {
|
|
|
37531
37531
|
for (const initRel of o.files.filter((f) => /(?:^|\/)__init__\.py$/.test(f)).sort()) {
|
|
37532
37532
|
const body = o.read(initRel);
|
|
37533
37533
|
if (body === null) continue;
|
|
37534
|
-
const
|
|
37535
|
-
if (
|
|
37534
|
+
const probe2 = probeDunderVersion(body);
|
|
37535
|
+
if (probe2 !== null) sources.push({ label: `__version__ literal in ${initRel}`, path: initRel, probe: probe2 });
|
|
37536
37536
|
}
|
|
37537
37537
|
const hits = [];
|
|
37538
37538
|
analyze(sources, `${o.archiveName}!`, hits);
|
|
@@ -37978,6 +37978,29 @@ init_define_BORDER_BUILD();
|
|
|
37978
37978
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
37979
37979
|
import { dirname as dirname6, resolve as resolve6 } from "node:path";
|
|
37980
37980
|
|
|
37981
|
+
// src/lsremote.ts
|
|
37982
|
+
init_define_BORDER_BUILD();
|
|
37983
|
+
var PushStateError = class extends Error {
|
|
37984
|
+
exitCode = 2;
|
|
37985
|
+
name = "PushStateError";
|
|
37986
|
+
};
|
|
37987
|
+
var SHA_SHAPE = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/;
|
|
37988
|
+
function parseLsRemote(text, targetId) {
|
|
37989
|
+
const out = /* @__PURE__ */ new Map();
|
|
37990
|
+
for (const line of text.split("\n")) {
|
|
37991
|
+
if (line.trim() === "") continue;
|
|
37992
|
+
const fields = line.split(" ");
|
|
37993
|
+
const sha = fields[0];
|
|
37994
|
+
const ref = fields[1];
|
|
37995
|
+
const shapeOk = fields.length === 2 && sha !== void 0 && SHA_SHAPE.test(sha) && (ref === "HEAD" || ref !== void 0 && ref.startsWith("refs/"));
|
|
37996
|
+
if (!shapeOk) {
|
|
37997
|
+
throw new PushStateError(`target '${targetId}': malformed 'git ls-remote' output line '${line.slice(0, 72)}' \u2014 refusing to interpret (fail closed)`);
|
|
37998
|
+
}
|
|
37999
|
+
if (ref !== "HEAD") out.set(ref, sha);
|
|
38000
|
+
}
|
|
38001
|
+
return out;
|
|
38002
|
+
}
|
|
38003
|
+
|
|
37981
38004
|
// src/rules/allowlist.ts
|
|
37982
38005
|
init_define_BORDER_BUILD();
|
|
37983
38006
|
var BOT_EMAIL_PATTERN = "*[bot]@users.noreply.github.com";
|
|
@@ -38055,7 +38078,6 @@ function runGit2(repoDir, args) {
|
|
|
38055
38078
|
}
|
|
38056
38079
|
var LOG_FIELDS = 6;
|
|
38057
38080
|
var LOG_FORMAT = "--format=%H%x1f%an%x1f%ae%x1f%cn%x1f%ce%x1f%P";
|
|
38058
|
-
var REV_BATCH = 200;
|
|
38059
38081
|
function collectCommits(s, revs) {
|
|
38060
38082
|
const out = runGit2(s.repoDir, ["log", LOG_FORMAT, ...revs]);
|
|
38061
38083
|
for (const line of out.split("\n")) {
|
|
@@ -38094,30 +38116,66 @@ function revExists(repoDir, rev) {
|
|
|
38094
38116
|
});
|
|
38095
38117
|
return r.status === 0;
|
|
38096
38118
|
}
|
|
38097
|
-
function
|
|
38098
|
-
|
|
38099
|
-
const short = ref.replace(/^refs\/(?:heads|tags)\//, "");
|
|
38100
|
-
if (short !== ref) candidates.push(`refs/remotes/${remoteName}/${short}`);
|
|
38101
|
-
return candidates.find((c) => revExists(repoDir, c)) ?? null;
|
|
38119
|
+
function revParse(repoDir, rev) {
|
|
38120
|
+
return runGit2(repoDir, ["rev-parse", rev]).trim();
|
|
38102
38121
|
}
|
|
38103
|
-
|
|
38104
|
-
|
|
38105
|
-
|
|
38106
|
-
|
|
38107
|
-
if (
|
|
38108
|
-
|
|
38109
|
-
|
|
38110
|
-
|
|
38111
|
-
|
|
38112
|
-
|
|
38113
|
-
|
|
38114
|
-
|
|
38122
|
+
var LS_REMOTE_TIMEOUT_MS = 3e4;
|
|
38123
|
+
function urlForMessage(url2) {
|
|
38124
|
+
try {
|
|
38125
|
+
const u = new URL(url2);
|
|
38126
|
+
if (u.protocol === "file:") return url2;
|
|
38127
|
+
return `${u.protocol}//${u.host}/***`;
|
|
38128
|
+
} catch {
|
|
38129
|
+
return "***";
|
|
38130
|
+
}
|
|
38131
|
+
}
|
|
38132
|
+
function lsRemoteFetch(repoDir, url2, env2) {
|
|
38133
|
+
const r = spawnSync6("git", ["ls-remote", url2], {
|
|
38134
|
+
cwd: repoDir,
|
|
38135
|
+
encoding: "utf8",
|
|
38136
|
+
env: { ...process.env, ...env2, GIT_CEILING_DIRECTORIES: dirname6(resolve6(repoDir)) },
|
|
38137
|
+
timeout: LS_REMOTE_TIMEOUT_MS
|
|
38138
|
+
});
|
|
38139
|
+
if (r.error !== void 0 || r.status !== 0) {
|
|
38140
|
+
const why = r.error !== void 0 ? r.error.message : (r.stderr ?? "").trim().slice(-200);
|
|
38141
|
+
throw new Error(
|
|
38142
|
+
`identity: remote ${urlForMessage(url2)} is unreachable via git ls-remote (${why}) \u2014 the exposure class of the offending identities cannot be decided and border never guesses it; restore connectivity or scope the check and retry`
|
|
38143
|
+
);
|
|
38144
|
+
}
|
|
38145
|
+
return parseLsRemote(r.stdout ?? "", urlForMessage(url2));
|
|
38146
|
+
}
|
|
38147
|
+
function remoteTruth(o, walkShas) {
|
|
38148
|
+
const truth = { published: /* @__PURE__ */ new Set(), publishedTags: /* @__PURE__ */ new Set(), remoteLabels: [] };
|
|
38149
|
+
const resolvable = o.refSet.filter((ref) => revExists(o.repoDir, ref));
|
|
38150
|
+
const urls = [...new Set(o.cfg.targets.git.remotes.map((r) => r.url).filter((u) => u !== void 0 && u.length > 0))];
|
|
38151
|
+
if (resolvable.length === 0 || urls.length === 0) return truth;
|
|
38152
|
+
const env2 = o.env ?? process.env;
|
|
38153
|
+
const fetch2 = o.lsRemote ?? ((url2) => lsRemoteFetch(o.repoDir, url2, env2));
|
|
38154
|
+
for (const url2 of urls) {
|
|
38155
|
+
const map2 = fetch2(url2);
|
|
38156
|
+
truth.remoteLabels.push(urlForMessage(url2));
|
|
38157
|
+
for (const ref of resolvable) {
|
|
38158
|
+
const ep = map2.get(ref) ?? map2.get(`${ref}^{}`);
|
|
38159
|
+
if (ep === void 0) continue;
|
|
38160
|
+
if (!/^[0-9a-f]{40}$|^[0-9a-f]{64}$/.test(ep)) {
|
|
38161
|
+
throw new Error(`identity: remote ${urlForMessage(url2)} reports a malformed endpoint sha for ${ref} \u2014 refusing to interpret (fail closed)`);
|
|
38162
|
+
}
|
|
38163
|
+
if (ref.startsWith("refs/tags/")) {
|
|
38164
|
+
const localObj = revParse(o.repoDir, ref);
|
|
38165
|
+
const localPeel = revParse(o.repoDir, `${ref}^{}`);
|
|
38166
|
+
if (map2.get(ref) === localObj || map2.get(`${ref}^{}`) === localPeel) truth.publishedTags.add(ref);
|
|
38167
|
+
continue;
|
|
38115
38168
|
}
|
|
38116
|
-
|
|
38117
|
-
|
|
38169
|
+
if (!revExists(o.repoDir, ep)) {
|
|
38170
|
+
throw new Error(
|
|
38171
|
+
`identity: remote ${urlForMessage(url2)} points ${ref} at ${ep.slice(0, 12)}, which is not an object in this repository \u2014 the transmitted set cannot be computed; run 'git fetch ${urlForMessage(url2)}' and retry (fail closed)`
|
|
38172
|
+
);
|
|
38118
38173
|
}
|
|
38174
|
+
const fresh = new Set(runGit2(o.repoDir, ["rev-list", `${ep}..${ref}`]).split("\n").filter((s) => s !== ""));
|
|
38175
|
+
for (const sha of walkShas) if (!fresh.has(sha)) truth.published.add(sha);
|
|
38119
38176
|
}
|
|
38120
38177
|
}
|
|
38178
|
+
return truth;
|
|
38121
38179
|
}
|
|
38122
38180
|
function scanTags(repoDir, authors) {
|
|
38123
38181
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -38155,35 +38213,61 @@ function mergeEntries(target, extra) {
|
|
|
38155
38213
|
}
|
|
38156
38214
|
}
|
|
38157
38215
|
}
|
|
38216
|
+
var IDENTITY_PUBLIC_RULE = "identity-already-public";
|
|
38217
|
+
function locationsSuffix(objects) {
|
|
38218
|
+
const locations = objects.map(([obj, roles]) => `${roles} of ${obj}`);
|
|
38219
|
+
const shownList = locations.slice(0, MAX_LISTED_LOCATIONS);
|
|
38220
|
+
const more = locations.length - shownList.length;
|
|
38221
|
+
return { shown: shownList.join("; "), suffix: more > 0 ? ` (+${String(more)} more)` : "" };
|
|
38222
|
+
}
|
|
38158
38223
|
function scanIdentity(o) {
|
|
38159
38224
|
const authors = o.cfg.rules.authors;
|
|
38160
38225
|
const s = { repoDir: o.repoDir, authors, entries: /* @__PURE__ */ new Map(), seen: /* @__PURE__ */ new Set() };
|
|
38161
|
-
if (o.refSet.length > 0)
|
|
38162
|
-
collectCommits(s, o.refSet);
|
|
38163
|
-
scanTransmitted(s, o.refSet, o.cfg.targets.git.remotes);
|
|
38164
|
-
}
|
|
38226
|
+
if (o.refSet.length > 0) collectCommits(s, o.refSet);
|
|
38165
38227
|
mergeEntries(s.entries, scanTags(o.repoDir, authors));
|
|
38228
|
+
const truth = s.entries.size > 0 ? remoteTruth(o, s.seen) : { published: /* @__PURE__ */ new Set(), publishedTags: /* @__PURE__ */ new Set(), remoteLabels: [] };
|
|
38166
38229
|
const findings = [];
|
|
38167
38230
|
for (const entry of s.entries.values()) {
|
|
38168
|
-
const
|
|
38169
|
-
const
|
|
38170
|
-
const
|
|
38171
|
-
|
|
38231
|
+
const freshObjects = [];
|
|
38232
|
+
const publicObjects = [];
|
|
38233
|
+
for (const [obj, roles] of entry.objects) {
|
|
38234
|
+
const isCommit = /^[0-9a-f]{40}$/.test(obj);
|
|
38235
|
+
const published = isCommit ? truth.published.has(obj) : truth.publishedTags.has(obj);
|
|
38236
|
+
(published ? publicObjects : freshObjects).push([obj, roles]);
|
|
38237
|
+
}
|
|
38172
38238
|
const identity = `${entry.name} <${entry.email}>`;
|
|
38173
|
-
const firstCommit = [...entry.objects.entries()].find(([obj]) => /^[0-9a-f]{40}$/.test(obj));
|
|
38174
38239
|
const { valueDigest, snippet } = redact(identity);
|
|
38175
|
-
const
|
|
38176
|
-
|
|
38177
|
-
|
|
38178
|
-
|
|
38179
|
-
|
|
38180
|
-
|
|
38181
|
-
|
|
38182
|
-
|
|
38183
|
-
|
|
38184
|
-
|
|
38185
|
-
|
|
38186
|
-
|
|
38240
|
+
const firstCommit = [...freshObjects, ...publicObjects].find(([obj]) => /^[0-9a-f]{40}$/.test(obj));
|
|
38241
|
+
if (freshObjects.length > 0) {
|
|
38242
|
+
const { shown, suffix } = locationsSuffix(freshObjects);
|
|
38243
|
+
const raw = `'${identity}' is not allowlisted in rules.authors \u2014 push would expose this git identity forever: ${shown}${suffix}. Remediation: use git filter-repo manually (out of scope for border; border never rewrites history).`;
|
|
38244
|
+
const finding = {
|
|
38245
|
+
rule: IDENTITY_RULE,
|
|
38246
|
+
severity: "CRITICAL",
|
|
38247
|
+
target: IDENTITY_TARGET,
|
|
38248
|
+
engine: IDENTITY_ENGINE,
|
|
38249
|
+
message: o.sanitizer?.sanitize(raw) ?? raw,
|
|
38250
|
+
valueDigest,
|
|
38251
|
+
snippet
|
|
38252
|
+
};
|
|
38253
|
+
if (firstCommit !== void 0) finding.commit = firstCommit[0];
|
|
38254
|
+
findings.push(finding);
|
|
38255
|
+
}
|
|
38256
|
+
if (publicObjects.length > 0) {
|
|
38257
|
+
const { shown, suffix } = locationsSuffix(publicObjects);
|
|
38258
|
+
const raw = `'${identity}' is not allowlisted in rules.authors \u2014 but every object carrying it is already public on the configured remote(s) (${truth.remoteLabels.join(", ") || "remotes probed"}): ${shown}${suffix}. Nothing new leaves this machine with this push; recorded as fact for housekeeping (add it to rules.authors, or keep it out of new commits). border detects, never erases \u2014 rewriting published history is not offered.`;
|
|
38259
|
+
const publishedFinding = {
|
|
38260
|
+
rule: IDENTITY_PUBLIC_RULE,
|
|
38261
|
+
severity: "INFO",
|
|
38262
|
+
target: IDENTITY_TARGET,
|
|
38263
|
+
engine: IDENTITY_ENGINE,
|
|
38264
|
+
message: o.sanitizer?.sanitize(raw) ?? raw,
|
|
38265
|
+
valueDigest: redact(`${identity}:already-public`).valueDigest,
|
|
38266
|
+
snippet
|
|
38267
|
+
};
|
|
38268
|
+
if (firstCommit !== void 0) publishedFinding.commit = firstCommit[0];
|
|
38269
|
+
findings.push(publishedFinding);
|
|
38270
|
+
}
|
|
38187
38271
|
}
|
|
38188
38272
|
return findings;
|
|
38189
38273
|
}
|
|
@@ -39404,12 +39488,12 @@ function pruneRunDirs(repoDir, key8, keep = KEEP_RUN_DIRS_PER_KEY8) {
|
|
|
39404
39488
|
async function computeFingerprint(repoDir, cfg, configDigest, effectiveTargets, o = {}) {
|
|
39405
39489
|
const envOpt = o.env !== void 0 ? { env: o.env } : {};
|
|
39406
39490
|
const ctx = await gatherContext(repoDir, envOpt);
|
|
39407
|
-
const
|
|
39491
|
+
const probe2 = await probeEngines(cfg, {
|
|
39408
39492
|
...envOpt,
|
|
39409
39493
|
...o.requireOverride !== void 0 ? { requireOverride: o.requireOverride } : {}
|
|
39410
39494
|
});
|
|
39411
39495
|
const rulesHash = await computeCheckRulesHash({
|
|
39412
|
-
engineVersions:
|
|
39496
|
+
engineVersions: probe2.engineVersions,
|
|
39413
39497
|
configDigest,
|
|
39414
39498
|
...envOpt
|
|
39415
39499
|
});
|
|
@@ -39425,7 +39509,7 @@ async function computeFingerprint(repoDir, cfg, configDigest, effectiveTargets,
|
|
|
39425
39509
|
rulesHash,
|
|
39426
39510
|
exposureSet: [...exposureSet(cfg, { cwd: repoDir })]
|
|
39427
39511
|
};
|
|
39428
|
-
return { ctx, fp, probeDegraded:
|
|
39512
|
+
return { ctx, fp, probeDegraded: probe2.degraded };
|
|
39429
39513
|
}
|
|
39430
39514
|
async function consultSkipLedger(q) {
|
|
39431
39515
|
let ctx;
|
|
@@ -40156,26 +40240,6 @@ function gitTargetId(remote, index) {
|
|
|
40156
40240
|
}
|
|
40157
40241
|
|
|
40158
40242
|
// src/pushstate.ts
|
|
40159
|
-
var PushStateError = class extends Error {
|
|
40160
|
-
exitCode = 2;
|
|
40161
|
-
name = "PushStateError";
|
|
40162
|
-
};
|
|
40163
|
-
var SHA_SHAPE = /^[0-9a-f]{40}$|^[0-9a-f]{64}$/;
|
|
40164
|
-
function parseLsRemote(text, targetId) {
|
|
40165
|
-
const out = /* @__PURE__ */ new Map();
|
|
40166
|
-
for (const line of text.split("\n")) {
|
|
40167
|
-
if (line.trim() === "") continue;
|
|
40168
|
-
const fields = line.split(" ");
|
|
40169
|
-
const sha = fields[0];
|
|
40170
|
-
const ref = fields[1];
|
|
40171
|
-
const shapeOk = fields.length === 2 && sha !== void 0 && SHA_SHAPE.test(sha) && (ref === "HEAD" || ref !== void 0 && ref.startsWith("refs/"));
|
|
40172
|
-
if (!shapeOk) {
|
|
40173
|
-
throw new PushStateError(`target '${targetId}': malformed 'git ls-remote' output line '${line.slice(0, 72)}' \u2014 refusing to interpret (fail closed)`);
|
|
40174
|
-
}
|
|
40175
|
-
if (ref !== "HEAD") out.set(ref, sha);
|
|
40176
|
-
}
|
|
40177
|
-
return out;
|
|
40178
|
-
}
|
|
40179
40243
|
function localRefCommits(repoDir, refSet, env2) {
|
|
40180
40244
|
const commits = /* @__PURE__ */ new Map();
|
|
40181
40245
|
if (refSet.length === 0) return commits;
|
|
@@ -42332,6 +42396,46 @@ function resolveBuildConsent(o) {
|
|
|
42332
42396
|
return { allowed: false, reason: WITHHELD_REASON };
|
|
42333
42397
|
}
|
|
42334
42398
|
|
|
42399
|
+
// src/check/ignoredSurface.ts
|
|
42400
|
+
init_define_BORDER_BUILD();
|
|
42401
|
+
var IGNORED_SURFACE_NOTE = " \u2014 path is .gitignore'd and untracked: git add honours .gitignore, so it cannot reach a push; informational surface, never blocking";
|
|
42402
|
+
function nulList(stdout) {
|
|
42403
|
+
return new Set(stdout.split("\0").filter((p) => p !== ""));
|
|
42404
|
+
}
|
|
42405
|
+
function ignoredHit(rel, ignored) {
|
|
42406
|
+
if (ignored.has(rel)) return true;
|
|
42407
|
+
let cut = rel.indexOf("/");
|
|
42408
|
+
while (cut > 0) {
|
|
42409
|
+
if (ignored.has(`${rel.slice(0, cut)}/`)) return true;
|
|
42410
|
+
cut = rel.indexOf("/", cut + 1);
|
|
42411
|
+
}
|
|
42412
|
+
return false;
|
|
42413
|
+
}
|
|
42414
|
+
function relFacetPath(f, repoDir) {
|
|
42415
|
+
if (f.path === void 0 || f.commit !== void 0) return null;
|
|
42416
|
+
if (f.target !== "git" && f.target !== "tree") return null;
|
|
42417
|
+
const outer = f.path.split("!")[0] ?? f.path;
|
|
42418
|
+
const prefix = `${repoDir}/`;
|
|
42419
|
+
return outer.startsWith(prefix) ? outer.slice(prefix.length) : outer;
|
|
42420
|
+
}
|
|
42421
|
+
function demoteIgnoredUntracked(findings, repoDir, o = {}) {
|
|
42422
|
+
const candidates = findings.filter((f) => f.severity !== "INFO" && relFacetPath(f, repoDir) !== null);
|
|
42423
|
+
if (candidates.length === 0) return 0;
|
|
42424
|
+
const tracked = nulList(runGitChecked(repoDir, ["ls-files", "-z"], o));
|
|
42425
|
+
const ignored = nulList(runGitChecked(repoDir, ["ls-files", "--others", "--ignored", "--exclude-standard", "-z"], o));
|
|
42426
|
+
if (ignored.size === 0) return 0;
|
|
42427
|
+
let demoted = 0;
|
|
42428
|
+
for (const f of findings) {
|
|
42429
|
+
if (f.severity === "INFO") continue;
|
|
42430
|
+
const rel = relFacetPath(f, repoDir);
|
|
42431
|
+
if (rel === null || !ignoredHit(rel, ignored) || tracked.has(rel)) continue;
|
|
42432
|
+
f.severity = "INFO";
|
|
42433
|
+
f.message = `${f.message}${IGNORED_SURFACE_NOTE}`;
|
|
42434
|
+
demoted += 1;
|
|
42435
|
+
}
|
|
42436
|
+
return demoted;
|
|
42437
|
+
}
|
|
42438
|
+
|
|
42335
42439
|
// src/check/exfilTreeScan.ts
|
|
42336
42440
|
init_define_BORDER_BUILD();
|
|
42337
42441
|
import { readFileSync as readFileSync17, statSync as statSync2 } from "node:fs";
|
|
@@ -42583,7 +42687,7 @@ function revExists2(run2, rev) {
|
|
|
42583
42687
|
return false;
|
|
42584
42688
|
}
|
|
42585
42689
|
}
|
|
42586
|
-
function
|
|
42690
|
+
function trackingEndpoint(run2, remoteName, ref) {
|
|
42587
42691
|
const candidates = [`refs/remotes/${remoteName}/${ref}`];
|
|
42588
42692
|
const short = ref.replace(/^refs\/(?:heads|tags)\//, "");
|
|
42589
42693
|
if (short !== ref) candidates.push(`refs/remotes/${remoteName}/${short}`);
|
|
@@ -42606,7 +42710,7 @@ function willPublishShas(run2, refSet, remotes) {
|
|
|
42606
42710
|
for (const remote of remotes) {
|
|
42607
42711
|
const name = remote.name;
|
|
42608
42712
|
if (name === void 0) continue;
|
|
42609
|
-
const endpoint =
|
|
42713
|
+
const endpoint = trackingEndpoint(run2, name, ref);
|
|
42610
42714
|
if (endpoint !== null) ranges.push(`${endpoint}..${ref}`);
|
|
42611
42715
|
}
|
|
42612
42716
|
if (ranges.length === 0) collect([ref]);
|
|
@@ -43136,13 +43240,13 @@ async function runPipeline(o, ctx, lockWarning) {
|
|
|
43136
43240
|
const envOpt = o.env !== void 0 ? { env: o.env } : {};
|
|
43137
43241
|
const findings = [];
|
|
43138
43242
|
const guard = trackedBorderStateFinding(repoDir, ctx.headSha, o.env);
|
|
43139
|
-
const
|
|
43243
|
+
const probe2 = await probeEngines(o.cfg, {
|
|
43140
43244
|
...envOpt,
|
|
43141
43245
|
...o.requireOverride !== void 0 ? { requireOverride: o.requireOverride } : {}
|
|
43142
43246
|
});
|
|
43143
|
-
findings.push(...
|
|
43247
|
+
findings.push(...probe2.findings);
|
|
43144
43248
|
if (guard !== null) findings.push(guard);
|
|
43145
|
-
const broken = new Set(
|
|
43249
|
+
const broken = new Set(probe2.findings.map((f) => f.engine));
|
|
43146
43250
|
const sanitizer = new TextSanitizer();
|
|
43147
43251
|
findings.push(...filterBorderStateFindings(detectHostileConfig({ repoDir, target: "git", ...envOpt }), repoDir));
|
|
43148
43252
|
if (!broken.has("gitleaks")) {
|
|
@@ -43187,6 +43291,7 @@ async function runPipeline(o, ctx, lockWarning) {
|
|
|
43187
43291
|
findings.push(f);
|
|
43188
43292
|
}
|
|
43189
43293
|
}
|
|
43294
|
+
demoteIgnoredUntracked(findings, repoDir, o.env !== void 0 ? { env: o.env } : {});
|
|
43190
43295
|
const ledgerArtifacts = [];
|
|
43191
43296
|
const buildToolchains = [];
|
|
43192
43297
|
for (const channel of publishChannels()) {
|
|
@@ -43235,7 +43340,7 @@ async function runPipeline(o, ctx, lockWarning) {
|
|
|
43235
43340
|
}
|
|
43236
43341
|
}
|
|
43237
43342
|
const rulesHash = await computeCheckRulesHash({
|
|
43238
|
-
engineVersions:
|
|
43343
|
+
engineVersions: probe2.engineVersions,
|
|
43239
43344
|
configDigest: o.configDigest,
|
|
43240
43345
|
...o.env !== void 0 ? { env: o.env } : {}
|
|
43241
43346
|
});
|
|
@@ -43295,7 +43400,7 @@ async function runPipeline(o, ctx, lockWarning) {
|
|
|
43295
43400
|
return {
|
|
43296
43401
|
report,
|
|
43297
43402
|
ctx,
|
|
43298
|
-
degraded:
|
|
43403
|
+
degraded: probe2.degraded,
|
|
43299
43404
|
sanitizedSummary: sanitizeSummary(report, sanitizer),
|
|
43300
43405
|
lockWarning,
|
|
43301
43406
|
artifacts: ledgerArtifacts.length > 0 ? ledgerArtifacts : null,
|
|
@@ -43326,12 +43431,12 @@ async function runCheck(ctx) {
|
|
|
43326
43431
|
effective = { kind: "loaded", config: load.explicit.config, warnings: [], source: load.explicit.source, security: load.explicit.security };
|
|
43327
43432
|
} else {
|
|
43328
43433
|
if (ctx.flags.requireEngine !== void 0 && ctx.flags.requireEngine.length > 0) {
|
|
43329
|
-
const
|
|
43434
|
+
const probe2 = await probeEngines(NO_TARGETS_PROBE_CFG, {
|
|
43330
43435
|
requireOverride: ctx.flags.requireEngine,
|
|
43331
43436
|
...env2 !== void 0 ? { env: env2 } : {}
|
|
43332
43437
|
});
|
|
43333
|
-
if (
|
|
43334
|
-
for (const finding of
|
|
43438
|
+
if (probe2.degraded) {
|
|
43439
|
+
for (const finding of probe2.findings) ctx.stderr(`border: ${finding.message}`);
|
|
43335
43440
|
return EXIT_ERROR;
|
|
43336
43441
|
}
|
|
43337
43442
|
}
|
|
@@ -43849,13 +43954,13 @@ async function runLlmRequest(ctx) {
|
|
|
43849
43954
|
|
|
43850
43955
|
// src/commands/opencode.ts
|
|
43851
43956
|
init_define_BORDER_BUILD();
|
|
43852
|
-
import { existsSync as
|
|
43957
|
+
import { existsSync as existsSync24, lstatSync, mkdirSync as mkdirSync12, readFileSync as readFileSync23, rmSync as rmSync14, writeFileSync as writeFileSync8 } from "node:fs";
|
|
43853
43958
|
import { dirname as dirname17, join as join36 } from "node:path";
|
|
43854
43959
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
43855
43960
|
|
|
43856
43961
|
// src/opencode/inspect.ts
|
|
43857
43962
|
init_define_BORDER_BUILD();
|
|
43858
|
-
import { existsSync as
|
|
43963
|
+
import { existsSync as existsSync23, readFileSync as readFileSync22 } from "node:fs";
|
|
43859
43964
|
import { join as join35 } from "node:path";
|
|
43860
43965
|
|
|
43861
43966
|
// src/opencode/aspects/cacheVersion.ts
|
|
@@ -44105,6 +44210,10 @@ function verifyDualRoute(facts) {
|
|
|
44105
44210
|
return { id, verdict: "PASS", detail: "neither route registered \u2014 no border plugin load claimed on the V1 surface" };
|
|
44106
44211
|
}
|
|
44107
44212
|
|
|
44213
|
+
// src/opencode/aspects/globalResolution.ts
|
|
44214
|
+
init_define_BORDER_BUILD();
|
|
44215
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
44216
|
+
|
|
44108
44217
|
// src/opencode/aspects/identity.ts
|
|
44109
44218
|
init_define_BORDER_BUILD();
|
|
44110
44219
|
import { execFile } from "node:child_process";
|
|
@@ -44161,11 +44270,11 @@ async function verifyIdentity(deps) {
|
|
|
44161
44270
|
const reasons = [];
|
|
44162
44271
|
let anySpawned = false;
|
|
44163
44272
|
for (const candidate of candidates) {
|
|
44164
|
-
const
|
|
44165
|
-
if (passesHandshake(
|
|
44273
|
+
const probe2 = await runner(candidate, HANDSHAKE_ARGV);
|
|
44274
|
+
if (passesHandshake(probe2)) {
|
|
44166
44275
|
return { id, verdict: "PASS", detail: `${candidate.label} passed the identity handshake` };
|
|
44167
44276
|
}
|
|
44168
|
-
if (
|
|
44277
|
+
if (probe2.status !== null) {
|
|
44169
44278
|
anySpawned = true;
|
|
44170
44279
|
reasons.push(`${candidate.label}: spawned but --help did not answer as border (impostor?)`);
|
|
44171
44280
|
} else {
|
|
@@ -44178,51 +44287,6 @@ async function verifyIdentity(deps) {
|
|
|
44178
44287
|
return { id, verdict: "CANNOT", detail: `no border binary reachable \u2014 tried: ${reasons.join("; ")}` };
|
|
44179
44288
|
}
|
|
44180
44289
|
|
|
44181
|
-
// src/opencode/aspects/routeA.ts
|
|
44182
|
-
init_define_BORDER_BUILD();
|
|
44183
|
-
function verifyRouteAParity(facts) {
|
|
44184
|
-
const id = "route-a-parity";
|
|
44185
|
-
const { routeAPlugin, routeAInstalled } = facts;
|
|
44186
|
-
if (!routeAInstalled) {
|
|
44187
|
-
return { id, verdict: "PASS", detail: `${routeAPlugin}: absent \u2014 Route A not installed, nothing to be stale` };
|
|
44188
|
-
}
|
|
44189
|
-
if (facts.packagedPlugin === null) {
|
|
44190
|
-
return { id, verdict: "CANNOT", detail: "the packaged plugin asset is unreadable \u2014 the border install itself looks broken, parity cannot be judged" };
|
|
44191
|
-
}
|
|
44192
|
-
const installed = facts.routeABytes;
|
|
44193
|
-
if (installed === null) {
|
|
44194
|
-
return { id, verdict: "CANNOT", detail: `${routeAPlugin}: present but unreadable \u2014 parity cannot be judged` };
|
|
44195
|
-
}
|
|
44196
|
-
const installedSha = sha256Hex3(installed);
|
|
44197
|
-
const packagedSha = sha256Hex3(facts.packagedPlugin);
|
|
44198
|
-
if (installedSha === packagedSha) {
|
|
44199
|
-
return { id, verdict: "PASS", detail: `${routeAPlugin}: up-to-date installed=${installedSha} packaged=${packagedSha}` };
|
|
44200
|
-
}
|
|
44201
|
-
const ours = firstLine(installed).startsWith(PLUGIN_MARKER);
|
|
44202
|
-
const shape = ours ? "outdated (marker ours, bytes differ)" : "foreign (first line lacks the marker)";
|
|
44203
|
-
return { id, verdict: "FAIL", detail: `${routeAPlugin}: ${shape} installed=${installedSha} packaged=${packagedSha}` };
|
|
44204
|
-
}
|
|
44205
|
-
|
|
44206
|
-
// src/opencode/aspects/scope.ts
|
|
44207
|
-
init_define_BORDER_BUILD();
|
|
44208
|
-
var SCOPE_ID = "scope";
|
|
44209
|
-
var SCOPE_NAME = "v1-load-surface";
|
|
44210
|
-
var SCOPE_UNCOVERED = [
|
|
44211
|
-
"V2 project-local .opencode/plugins/",
|
|
44212
|
-
"V2 npm-prefix/node_modules installs",
|
|
44213
|
-
"V2 lazy activation state"
|
|
44214
|
-
];
|
|
44215
|
-
function verifyScope() {
|
|
44216
|
-
return {
|
|
44217
|
-
id: SCOPE_ID,
|
|
44218
|
-
verdict: "PASS",
|
|
44219
|
-
detail: `verdict scope = ${SCOPE_NAME}; NOT covered (declared blind, separate ledger item): ${SCOPE_UNCOVERED.join(", ")}`
|
|
44220
|
-
};
|
|
44221
|
-
}
|
|
44222
|
-
|
|
44223
|
-
// src/opencode/aspects/specFreshness.ts
|
|
44224
|
-
init_define_BORDER_BUILD();
|
|
44225
|
-
|
|
44226
44290
|
// src/opencode/registry.ts
|
|
44227
44291
|
init_define_BORDER_BUILD();
|
|
44228
44292
|
var isRecord3 = (v) => typeof v === "object" && v !== null;
|
|
@@ -44276,7 +44340,110 @@ async function fetchRegistryVersion(fetcher, url2, timeoutMs) {
|
|
|
44276
44340
|
return parsed["version"];
|
|
44277
44341
|
}
|
|
44278
44342
|
|
|
44343
|
+
// src/opencode/aspects/globalResolution.ts
|
|
44344
|
+
var VERSION_ARGV = ["--version"];
|
|
44345
|
+
function parseVersionStamp(stdout) {
|
|
44346
|
+
const m = /^border\s+(\d[^\s]*)/m.exec(stdout);
|
|
44347
|
+
return m?.[1] ?? null;
|
|
44348
|
+
}
|
|
44349
|
+
async function probe(runner, target) {
|
|
44350
|
+
const out = await runner(target, VERSION_ARGV);
|
|
44351
|
+
if (out.status === null) return "unspawnable";
|
|
44352
|
+
return parseVersionStamp(out.stdout) ?? "no version stamp";
|
|
44353
|
+
}
|
|
44354
|
+
async function verifyGlobalResolution(deps) {
|
|
44355
|
+
const id = "global-resolution";
|
|
44356
|
+
if ((deps.env["BORDER_INSPECT_OFFLINE"] ?? "") === "1") {
|
|
44357
|
+
return { id, verdict: "WARN", detail: "skipped (BORDER_INSPECT_OFFLINE=1) \u2014 freshness not evaluated; an unevaluated face is never reported as fresh" };
|
|
44358
|
+
}
|
|
44359
|
+
const runner = deps.runner ?? defaultInspectRunner;
|
|
44360
|
+
const override = deps.env["BORDER_BIN"];
|
|
44361
|
+
const hasOverride = override !== void 0 && override.length > 0;
|
|
44362
|
+
const faces = [];
|
|
44363
|
+
let active = null;
|
|
44364
|
+
if (hasOverride) {
|
|
44365
|
+
const stamp = await probe(runner, { bin: override, prefix: [], label: `BORDER_BIN=${override}` });
|
|
44366
|
+
faces.push({ label: `env BORDER_BIN=${override}`, stamp });
|
|
44367
|
+
active = stamp;
|
|
44368
|
+
} else {
|
|
44369
|
+
if (existsSync22(deps.distEntry)) {
|
|
44370
|
+
const packaged = { bin: deps.distEntry, prefix: [], label: "packaged dist" };
|
|
44371
|
+
let stamp = await probe(runner, packaged);
|
|
44372
|
+
if (stamp === "unspawnable") {
|
|
44373
|
+
stamp = await probe(runner, { bin: "node", prefix: [deps.distEntry], label: "packaged dist" });
|
|
44374
|
+
}
|
|
44375
|
+
faces.push({ label: "packaged dist", stamp });
|
|
44376
|
+
}
|
|
44377
|
+
const pathStamp = await probe(runner, { bin: "border", prefix: [], label: "border on PATH" });
|
|
44378
|
+
faces.push({ label: "border on PATH", stamp: pathStamp });
|
|
44379
|
+
active = pathStamp === "unspawnable" ? null : pathStamp;
|
|
44380
|
+
}
|
|
44381
|
+
const faceLine = faces.map((f) => `${f.label}: ${f.stamp}`).join("; ");
|
|
44382
|
+
const fetcher = deps.fetcher ?? defaultRegistryFetcher;
|
|
44383
|
+
let latest;
|
|
44384
|
+
try {
|
|
44385
|
+
latest = await fetchRegistryVersion(fetcher, npmTagUrl(deps.pkg, "latest", deps.registryBase), deps.timeoutMs ?? REGISTRY_TIMEOUT_MS2);
|
|
44386
|
+
} catch (err) {
|
|
44387
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
44388
|
+
return { id, verdict: "CANNOT", detail: `registry unreachable for global-resolution: ${message}; ${faceLine}` };
|
|
44389
|
+
}
|
|
44390
|
+
if (active === null) {
|
|
44391
|
+
return { id, verdict: "PASS", detail: `no active CLI face (no BORDER_BIN, no PATH border); registry latest ${latest}; ${faceLine}` };
|
|
44392
|
+
}
|
|
44393
|
+
const activeCore = active.split("+")[0];
|
|
44394
|
+
if (activeCore === latest) {
|
|
44395
|
+
return { id, verdict: "PASS", detail: `active face matches registry latest ${latest}; ${faceLine}` };
|
|
44396
|
+
}
|
|
44397
|
+
if (active === "unspawnable" || active === "no version stamp") {
|
|
44398
|
+
return { id, verdict: "WARN", detail: `active CLI face carries no version stamp (registry latest ${latest}) \u2014 informational: identity may be fine, age is unknown; ${faceLine}` };
|
|
44399
|
+
}
|
|
44400
|
+
return { id, verdict: "WARN", detail: `active CLI face is ${active}, registry latest is ${latest} \u2014 informational only: pinned consumption is a legitimate steady state, the decision to update stays with the human; ${faceLine}` };
|
|
44401
|
+
}
|
|
44402
|
+
|
|
44403
|
+
// src/opencode/aspects/routeA.ts
|
|
44404
|
+
init_define_BORDER_BUILD();
|
|
44405
|
+
function verifyRouteAParity(facts) {
|
|
44406
|
+
const id = "route-a-parity";
|
|
44407
|
+
const { routeAPlugin, routeAInstalled } = facts;
|
|
44408
|
+
if (!routeAInstalled) {
|
|
44409
|
+
return { id, verdict: "PASS", detail: `${routeAPlugin}: absent \u2014 Route A not installed, nothing to be stale` };
|
|
44410
|
+
}
|
|
44411
|
+
if (facts.packagedPlugin === null) {
|
|
44412
|
+
return { id, verdict: "CANNOT", detail: "the packaged plugin asset is unreadable \u2014 the border install itself looks broken, parity cannot be judged" };
|
|
44413
|
+
}
|
|
44414
|
+
const installed = facts.routeABytes;
|
|
44415
|
+
if (installed === null) {
|
|
44416
|
+
return { id, verdict: "CANNOT", detail: `${routeAPlugin}: present but unreadable \u2014 parity cannot be judged` };
|
|
44417
|
+
}
|
|
44418
|
+
const installedSha = sha256Hex3(installed);
|
|
44419
|
+
const packagedSha = sha256Hex3(facts.packagedPlugin);
|
|
44420
|
+
if (installedSha === packagedSha) {
|
|
44421
|
+
return { id, verdict: "PASS", detail: `${routeAPlugin}: up-to-date installed=${installedSha} packaged=${packagedSha}` };
|
|
44422
|
+
}
|
|
44423
|
+
const ours = firstLine(installed).startsWith(PLUGIN_MARKER);
|
|
44424
|
+
const shape = ours ? "outdated (marker ours, bytes differ)" : "foreign (first line lacks the marker)";
|
|
44425
|
+
return { id, verdict: "FAIL", detail: `${routeAPlugin}: ${shape} installed=${installedSha} packaged=${packagedSha}` };
|
|
44426
|
+
}
|
|
44427
|
+
|
|
44428
|
+
// src/opencode/aspects/scope.ts
|
|
44429
|
+
init_define_BORDER_BUILD();
|
|
44430
|
+
var SCOPE_ID = "scope";
|
|
44431
|
+
var SCOPE_NAME = "v1-load-surface";
|
|
44432
|
+
var SCOPE_UNCOVERED = [
|
|
44433
|
+
"V2 project-local .opencode/plugins/",
|
|
44434
|
+
"V2 npm-prefix/node_modules installs",
|
|
44435
|
+
"V2 lazy activation state"
|
|
44436
|
+
];
|
|
44437
|
+
function verifyScope() {
|
|
44438
|
+
return {
|
|
44439
|
+
id: SCOPE_ID,
|
|
44440
|
+
verdict: "PASS",
|
|
44441
|
+
detail: `verdict scope = ${SCOPE_NAME}; NOT covered (declared blind, separate ledger item): ${SCOPE_UNCOVERED.join(", ")}`
|
|
44442
|
+
};
|
|
44443
|
+
}
|
|
44444
|
+
|
|
44279
44445
|
// src/opencode/aspects/specFreshness.ts
|
|
44446
|
+
init_define_BORDER_BUILD();
|
|
44280
44447
|
var isExactVersion2 = (spec) => /^\d+\.\d+\.\d+([-+].*)?$/.test(spec);
|
|
44281
44448
|
async function verifySpecFreshness(facts, deps = {}) {
|
|
44282
44449
|
const id = "spec-freshness";
|
|
@@ -44333,6 +44500,7 @@ function inspectExit(verdict) {
|
|
|
44333
44500
|
case "CANNOT":
|
|
44334
44501
|
return EXIT_ERROR;
|
|
44335
44502
|
case "PASS":
|
|
44503
|
+
case "WARN":
|
|
44336
44504
|
return EXIT_PASS;
|
|
44337
44505
|
}
|
|
44338
44506
|
}
|
|
@@ -44348,7 +44516,7 @@ function gatherFacts(env2, cwd, deps) {
|
|
|
44348
44516
|
const pluginDrop = routeAPluginPath(env2);
|
|
44349
44517
|
const cacheRoot = deps.cacheRoot ?? opencodeCacheRoot(env2);
|
|
44350
44518
|
const globalPath = deps.globalConfigPath ?? join35(opencodeConfigRoot(env2), "opencode", "opencode.jsonc");
|
|
44351
|
-
const routeAInstalled =
|
|
44519
|
+
const routeAInstalled = existsSync23(pluginDrop);
|
|
44352
44520
|
return {
|
|
44353
44521
|
pkg,
|
|
44354
44522
|
env: env2,
|
|
@@ -44363,6 +44531,7 @@ function gatherFacts(env2, cwd, deps) {
|
|
|
44363
44531
|
}
|
|
44364
44532
|
async function runInspectCore(ctx, deps = {}) {
|
|
44365
44533
|
const facts = gatherFacts(ctx.env, ctx.cwd, deps);
|
|
44534
|
+
const distEntry = deps.distEntry ?? packagedDistEntry(import.meta.url);
|
|
44366
44535
|
const aspects = [
|
|
44367
44536
|
verifyCacheVersion(facts),
|
|
44368
44537
|
await verifySpecFreshness(facts, {
|
|
@@ -44371,12 +44540,20 @@ async function runInspectCore(ctx, deps = {}) {
|
|
|
44371
44540
|
}),
|
|
44372
44541
|
await verifyIdentity({
|
|
44373
44542
|
...deps.runner !== void 0 ? { runner: deps.runner } : {},
|
|
44374
|
-
distEntry
|
|
44543
|
+
distEntry,
|
|
44375
44544
|
env: facts.env
|
|
44376
44545
|
}),
|
|
44377
44546
|
verifyRouteAParity(facts),
|
|
44378
44547
|
verifyDualRoute(facts),
|
|
44379
|
-
verifyScope()
|
|
44548
|
+
verifyScope(),
|
|
44549
|
+
await verifyGlobalResolution({
|
|
44550
|
+
...deps.runner !== void 0 ? { runner: deps.runner } : {},
|
|
44551
|
+
distEntry,
|
|
44552
|
+
env: facts.env,
|
|
44553
|
+
pkg: facts.pkg,
|
|
44554
|
+
...deps.registryFetcher !== void 0 ? { fetcher: deps.registryFetcher } : {},
|
|
44555
|
+
...deps.registryBase !== void 0 ? { registryBase: deps.registryBase } : {}
|
|
44556
|
+
})
|
|
44380
44557
|
];
|
|
44381
44558
|
const verdict = aggregateVerdict(aspects);
|
|
44382
44559
|
const report = { schemaVersion: 1, scope: "v1", verdict, aspects };
|
|
@@ -44400,7 +44577,7 @@ function resolvePluginAsset(moduleUrl, fileName) {
|
|
|
44400
44577
|
join36(here, "..", "..", "plugin", fileName)
|
|
44401
44578
|
// src mode: <root>/plugin/<f>
|
|
44402
44579
|
];
|
|
44403
|
-
return candidates.find((p) =>
|
|
44580
|
+
return candidates.find((p) => existsSync24(p)) ?? candidates[1];
|
|
44404
44581
|
}
|
|
44405
44582
|
function targetsFor(env2) {
|
|
44406
44583
|
const root = join36(opencodeConfigRoot(env2), "opencode");
|
|
@@ -44972,7 +45149,7 @@ import { createHash as createHash20 } from "node:crypto";
|
|
|
44972
45149
|
import { basename as basename5 } from "node:path";
|
|
44973
45150
|
var SCAN_MAX_ARTIFACT_BYTES = 200 * 1024 * 1024;
|
|
44974
45151
|
var SCAN_REQUEST_TIMEOUT_MS = 6e4;
|
|
44975
|
-
var SCAN_USER_AGENT = "border-customs/0.8.
|
|
45152
|
+
var SCAN_USER_AGENT = "border-customs/0.8.5 (+https://github.com/TachikomaGundam/Border-Customs; border scan)";
|
|
44976
45153
|
var CRATES_DOWNLOAD_HOST = "https://static.crates.io";
|
|
44977
45154
|
var defaultScanFetcher = async (url2, init) => {
|
|
44978
45155
|
if (!/^https?:\/\//.test(url2)) throw new EngineRunError(`border scan refuses non-http(s) URL: ${url2}`, null);
|
|
@@ -45143,7 +45320,7 @@ function parseScanSpec(raw) {
|
|
|
45143
45320
|
|
|
45144
45321
|
// src/roundtrip/localArtifact.ts
|
|
45145
45322
|
init_define_BORDER_BUILD();
|
|
45146
|
-
import { createReadStream, existsSync as
|
|
45323
|
+
import { createReadStream, existsSync as existsSync25, statSync as statSync3 } from "node:fs";
|
|
45147
45324
|
import { createHash as createHash21 } from "node:crypto";
|
|
45148
45325
|
import { basename as basename6, resolve as resolve20 } from "node:path";
|
|
45149
45326
|
var EXT_TABLE = /* @__PURE__ */ new Map([
|
|
@@ -45192,7 +45369,7 @@ var GRAMMAR_HINT = {
|
|
|
45192
45369
|
};
|
|
45193
45370
|
function classifyLocalInput(raw, cwd, parsesAsRegistrySpec) {
|
|
45194
45371
|
const abs = resolve20(cwd, raw);
|
|
45195
|
-
if (
|
|
45372
|
+
if (existsSync25(abs)) {
|
|
45196
45373
|
const st = statSync3(abs);
|
|
45197
45374
|
if (st.isDirectory()) {
|
|
45198
45375
|
bad3(`'${raw}' resolves to a directory (${abs}) \u2014 local mode needs a single artifact FILE, not a directory`);
|
|
@@ -45742,13 +45919,13 @@ async function recordRoundtripProof(ctx, artifactSha256, rows, verdict) {
|
|
|
45742
45919
|
return;
|
|
45743
45920
|
}
|
|
45744
45921
|
const repoDir = resolveRepoDir(ctx.cwd, { ...env2 !== void 0 ? { env: env2 } : {} });
|
|
45745
|
-
const
|
|
45746
|
-
if (
|
|
45922
|
+
const probe2 = await probeEngines(effective.config, { ...env2 !== void 0 ? { env: env2 } : {} });
|
|
45923
|
+
if (probe2.degraded) {
|
|
45747
45924
|
notice("engine probe degraded \u2014 cannot mint the rulesHash this proof must match; proof NOT recorded");
|
|
45748
45925
|
return;
|
|
45749
45926
|
}
|
|
45750
45927
|
const rulesHash = await computeCheckRulesHash({
|
|
45751
|
-
engineVersions:
|
|
45928
|
+
engineVersions: probe2.engineVersions,
|
|
45752
45929
|
configDigest: computeConfigDigest(effective),
|
|
45753
45930
|
...env2 !== void 0 ? { env: env2 } : {}
|
|
45754
45931
|
});
|
|
@@ -45773,7 +45950,7 @@ var runRoundtrip = (ctx) => runRoundtripCore(ctx);
|
|
|
45773
45950
|
init_define_BORDER_BUILD();
|
|
45774
45951
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
45775
45952
|
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
45776
|
-
import { existsSync as
|
|
45953
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync13, readFileSync as readFileSync24, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
|
|
45777
45954
|
import { dirname as dirname19, join as join39 } from "node:path";
|
|
45778
45955
|
var realIo = {
|
|
45779
45956
|
read: (p) => readFileSync24(p, "utf8"),
|
|
@@ -45785,7 +45962,7 @@ var realIo = {
|
|
|
45785
45962
|
mkdir0700: (p) => {
|
|
45786
45963
|
let cur = p;
|
|
45787
45964
|
const stack = [];
|
|
45788
|
-
while (!
|
|
45965
|
+
while (!existsSync26(cur) && cur !== "/" && cur !== ".") {
|
|
45789
45966
|
stack.push(cur);
|
|
45790
45967
|
cur = dirname19(cur);
|
|
45791
45968
|
}
|
|
@@ -45793,7 +45970,7 @@ var realIo = {
|
|
|
45793
45970
|
mkdirSync13(dir, { mode: 448 });
|
|
45794
45971
|
}
|
|
45795
45972
|
},
|
|
45796
|
-
exists: (p) =>
|
|
45973
|
+
exists: (p) => existsSync26(p),
|
|
45797
45974
|
remove: (p) => unlinkSync2(p),
|
|
45798
45975
|
remotes: (cwd) => {
|
|
45799
45976
|
const r = spawnSync20("git", ["-C", cwd, "remote", "-v"], { encoding: "utf8" });
|
|
@@ -45973,7 +46150,7 @@ function synthesizeScanConfig(eco) {
|
|
|
45973
46150
|
|
|
45974
46151
|
// src/scan/materialize.ts
|
|
45975
46152
|
init_define_BORDER_BUILD();
|
|
45976
|
-
import { existsSync as
|
|
46153
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync14, readdirSync as readdirSync13, readFileSync as readFileSync25, rmSync as rmSync17, writeFileSync as writeFileSync11 } from "node:fs";
|
|
45977
46154
|
import { join as join40 } from "node:path";
|
|
45978
46155
|
var CRATES_ENVELOPE_FILES = ["Cargo.toml.orig", ".cargo-ok", ".cargo_vcs_info.json"];
|
|
45979
46156
|
var fail3 = (why) => {
|
|
@@ -45996,7 +46173,7 @@ function unwrapSingleDir(dir) {
|
|
|
45996
46173
|
return dir;
|
|
45997
46174
|
}
|
|
45998
46175
|
function requireManifest(repoDir, ecosystem, candidates) {
|
|
45999
|
-
if (!candidates.some((rel) =>
|
|
46176
|
+
if (!candidates.some((rel) => existsSync27(join40(repoDir, rel)))) {
|
|
46000
46177
|
fail3(`${ecosystem} package tree has none of [${candidates.join(", ")}] at its root`);
|
|
46001
46178
|
}
|
|
46002
46179
|
}
|
|
@@ -46016,7 +46193,7 @@ function materializePackage(o) {
|
|
|
46016
46193
|
const outer = repoDir;
|
|
46017
46194
|
const dataCandidates = findNativeMissArchives(outer).filter((f) => f.endsWith("data.tar.gz"));
|
|
46018
46195
|
const inner = dataCandidates[0] ?? join40(outer, "data.tar.gz");
|
|
46019
|
-
if (!
|
|
46196
|
+
if (!existsSync27(inner)) fail3(".gem archive contains no data.tar.gz");
|
|
46020
46197
|
const gemTree = join40(o.baseDir, "gem-tree");
|
|
46021
46198
|
mkdirSync14(gemTree, { recursive: true });
|
|
46022
46199
|
extractFlat(inner, gemTree);
|
|
@@ -46029,7 +46206,7 @@ function materializePackage(o) {
|
|
|
46029
46206
|
requireManifest(repoDir, o.ecosystem, ["Cargo.toml"]);
|
|
46030
46207
|
for (const file2 of CRATES_ENVELOPE_FILES) {
|
|
46031
46208
|
const path4 = join40(repoDir, file2);
|
|
46032
|
-
if (!
|
|
46209
|
+
if (!existsSync27(path4)) continue;
|
|
46033
46210
|
const original = readFileSync25(path4);
|
|
46034
46211
|
rmSync17(path4, { force: true });
|
|
46035
46212
|
note(`border scan: crates envelope normalized: ${file2} sha256=${sha256Hex4(original)}`);
|
|
@@ -46142,6 +46319,7 @@ function runStatus(ctx) {
|
|
|
46142
46319
|
return EXIT_ERROR;
|
|
46143
46320
|
}
|
|
46144
46321
|
for (const w of ledger.warnings) ctx.stderr(`border: WARNING ${w}`);
|
|
46322
|
+
ctx.stdout(`engine: ${engineBuild()}`);
|
|
46145
46323
|
try {
|
|
46146
46324
|
const drift = remoteTrackingDrift(repoDir);
|
|
46147
46325
|
const names = drift.configured.length > 0 ? drift.configured.join(", ") : "(none configured \u2014 nothing can ever be pushed)";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "border-customs",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"//": "publint is a runtime dep (exact pin): the npm-stage engine is invoked via bin resolution against the installed layout, so consumers get the publint-fail leg out of the box; the dev suite resolves the same node_modules/.bin/publint — no devDependencies entry is needed for it to stay green.",
|
|
5
5
|
"description": "Fail-closed git push gate: secret scanning + supply-chain checks before any remote leaves the machine",
|
|
6
6
|
"type": "module",
|
package/plugin/border.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// border-opencode-plugin v0.8.
|
|
1
|
+
// border-opencode-plugin v0.8.5
|
|
2
2
|
// Official opencode plugin adapter for the border fail-closed push gate.
|
|
3
3
|
// Registers ONE agent tool, `border`, that drives the border CLI — argv-only
|
|
4
4
|
// (node:child_process execFile, never a shell), top-level commands restricted
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
// cannot-answer, never a fallback to some other binary.
|
|
47
47
|
|
|
48
48
|
import { execFile } from "node:child_process";
|
|
49
|
-
import { existsSync } from "node:fs";
|
|
49
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
50
50
|
import { fileURLToPath } from "node:url";
|
|
51
51
|
import { z } from "zod";
|
|
52
52
|
|
|
@@ -209,25 +209,47 @@ function isBorderHelp(result: CliResult): boolean {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
type Resolution =
|
|
212
|
-
| { readonly ok: true; readonly resolution: BinResolution }
|
|
212
|
+
| { readonly ok: true; readonly resolution: BinResolution; readonly stamp: string }
|
|
213
213
|
| { readonly ok: false; readonly reasons: readonly string[] };
|
|
214
214
|
|
|
215
|
+
/** Twin of parseVersionStamp in src/opencode/aspects/globalResolution.ts; the
|
|
216
|
+
* digit anchor keeps the banner (`border — fail-closed…`) from parsing as a
|
|
217
|
+
* version. Duplicated on purpose: this file must stay import-free (Route A
|
|
218
|
+
* drops it as a single standalone file). */
|
|
219
|
+
function parseVersion(stdout: string): string | null {
|
|
220
|
+
const m = /^border\s+(\d[^\s]*)/m.exec(stdout);
|
|
221
|
+
return m?.[1] ?? null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** The version this plugin module was packaged with (the ./package.json export).
|
|
225
|
+
* Route A drops (bare file in plugins/) have no sibling package.json: null. */
|
|
226
|
+
function packagedVersion(): string | null {
|
|
227
|
+
try {
|
|
228
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8")) as { version?: unknown };
|
|
229
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
215
235
|
/** Positive handshakes are cached per BORDER_BIN key for the host process's
|
|
216
|
-
* life; failures are never cached, so a mid-session install
|
|
217
|
-
* next call. */
|
|
218
|
-
let verified: { readonly key: string; readonly resolution: BinResolution } | null = null;
|
|
236
|
+
* life (stamp included); failures are never cached, so a mid-session install
|
|
237
|
+
* recovers on the next call. */
|
|
238
|
+
let verified: { readonly key: string; readonly resolution: BinResolution; readonly stamp: string } | null = null;
|
|
219
239
|
|
|
220
240
|
async function resolveVerified(): Promise<Resolution> {
|
|
221
241
|
const key = process.env["BORDER_BIN"] ?? "";
|
|
222
242
|
if (verified !== null && verified.key === key) {
|
|
223
|
-
return { ok: true, resolution: verified.resolution };
|
|
243
|
+
return { ok: true, resolution: verified.resolution, stamp: verified.stamp };
|
|
224
244
|
}
|
|
225
245
|
const reasons: string[] = [];
|
|
226
246
|
for (const candidate of candidateList()) {
|
|
227
247
|
const probe = await runCli(candidate, ["--help"], PROBE_TIMEOUT_MS);
|
|
228
248
|
if (isBorderHelp(probe)) {
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
const version = await runCli(candidate, ["--version"], PROBE_TIMEOUT_MS);
|
|
250
|
+
const stamp = version.status === 0 ? (parseVersion(version.stdout) ?? "unknown") : "unknown";
|
|
251
|
+
verified = { key, resolution: candidate, stamp };
|
|
252
|
+
return { ok: true, resolution: candidate, stamp };
|
|
231
253
|
}
|
|
232
254
|
reasons.push(
|
|
233
255
|
probe.status === 0
|
|
@@ -367,11 +389,26 @@ async function buildToolResult(argv: readonly string[]): Promise<string> {
|
|
|
367
389
|
"--- stderr ---\n(empty)",
|
|
368
390
|
].join("\n");
|
|
369
391
|
}
|
|
392
|
+
const shell = `$ border ${argv.join(" ")}`;
|
|
393
|
+
if ((process.env["BORDER_REQUIRE_PKG_VERSION"] ?? "") === "1") {
|
|
394
|
+
const own = packagedVersion();
|
|
395
|
+
const stampCore = resolution.stamp.split("+")[0];
|
|
396
|
+
if (own === null || own !== stampCore) {
|
|
397
|
+
return [
|
|
398
|
+
shell,
|
|
399
|
+
"exit: 2",
|
|
400
|
+
`note: cannot-answer — BORDER_REQUIRE_PKG_VERSION=1 demands the resolved CLI report this package's own version (${own ?? "unreadable"}); the resolved face (${resolution.resolution.label}) reported '${resolution.stamp}'. Nothing was run.`,
|
|
401
|
+
"--- stdout ---\n(empty)",
|
|
402
|
+
"--- stderr ---\n(empty)",
|
|
403
|
+
].join("\n");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
370
406
|
const result = await runCli(resolution.resolution, argv, TIMEOUT_MS);
|
|
371
407
|
if (result.spawnFailed) verified = null;
|
|
372
408
|
const sections = [
|
|
373
|
-
|
|
409
|
+
shell,
|
|
374
410
|
`exit: ${String(result.status)}`,
|
|
411
|
+
`face: ${resolution.resolution.label} version=${resolution.stamp}`,
|
|
375
412
|
];
|
|
376
413
|
if (result.note.length > 0) sections.push(`note: ${result.note}`);
|
|
377
414
|
sections.push(`--- stdout ---\n${result.stdout.length === 0 ? "(empty)" : result.stdout}`);
|