border-customs 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -9
- package/dist/assets/exfil-src/rules.ts +1 -1
- package/dist/assets/exfil-src/rulesPath.ts +28 -0
- package/dist/assets/exfil-src/scan.ts +2 -0
- package/dist/assets/exfil-src/severity.ts +7 -3
- package/dist/assets/package-lock.json +2 -2
- package/dist/index.js +546 -435
- package/package.json +1 -1
- package/plugin/border.ts +1 -1
package/README.md
CHANGED
|
@@ -118,7 +118,18 @@ it, and the two registry-facing legs (pre-flight and publish) both have to agree
|
|
|
118
118
|
against the history of every ref *and* against the object set a remote does not already
|
|
119
119
|
have (the transmit set). Oversized-file, checked-in-binary, and notebook-output rules
|
|
120
120
|
round it out.
|
|
121
|
-
7. **
|
|
121
|
+
7. **Landing-grade tip lens.** The exact machinery `border push` uses to verify a landing
|
|
122
|
+
(`scanTipTree`: the tip tree materialized from git objects into a throwaway directory,
|
|
123
|
+
scanned by the native exfil family plus both twins filtered to `exfil-*` ids) also runs
|
|
124
|
+
here, against local HEAD — the tree a push would transmit. This closes the
|
|
125
|
+
disk-versus-tip gap: content that exists only in HEAD (introduced by a merge commit's
|
|
126
|
+
resolution and since deleted from disk, say) is invisible to the history and working-tree
|
|
127
|
+
legs above but cannot hide from the gate. Tip findings merge before the allow-list, so a
|
|
128
|
+
config `allow` pin exempts them by rule+file+digest exactly like every other finding, and
|
|
129
|
+
the post-push landing check honors the *same* pins — a green packet means green landing, in
|
|
130
|
+
both directions. The tip leg's git plumbing is fail-closed: a git failure aborts the run
|
|
131
|
+
(exit 2), never a silent clean.
|
|
132
|
+
8. **Registry pre-flight** (npm, PyPI, crates.io, and RubyGems targets). Three outcomes per
|
|
122
133
|
target: version already published ⇒ CRITICAL `version-exists` ("bump version required");
|
|
123
134
|
name owned by someone outside your `rules.authors` allow-list ⇒ CRITICAL
|
|
124
135
|
`name-foreign-owner`; ambiguous ownership signals ⇒ also CRITICAL, because guessing wrong
|
|
@@ -129,7 +140,7 @@ it, and the two registry-facing legs (pre-flight and publish) both have to agree
|
|
|
129
140
|
one of that API's exact *absent* messages (its 404s are text, not JSON, and an
|
|
130
141
|
unrecognized one is never guessed), a timeout, or unparseable JSON all fail closed as
|
|
131
142
|
exit 2. An unreachable registry blocks the push instead of letting it run blind.
|
|
132
|
-
|
|
143
|
+
9. **Artifact stage.** If registry targets are configured, packages are built **once** into
|
|
133
144
|
`.border/dist/` (`npm pack --ignore-scripts`, `python -m build --no-isolation`,
|
|
134
145
|
`cargo package --allow-dirty --no-verify`, `gem build <name>.gemspec -o .border/dist/`),
|
|
135
146
|
that exact byte-stream is scanned (gitleaks + secretlint over the extracted contents),
|
|
@@ -145,14 +156,16 @@ it, and the two registry-facing legs (pre-flight and publish) both have to agree
|
|
|
145
156
|
re-runs the check's exact `cargo package` into a throwaway directory and refuses to
|
|
146
157
|
publish if the fresh digest differs from the certified `.crate`. `gem push` uploads the
|
|
147
158
|
recorded `.gem` byte-for-byte, so it needs no such re-assertion.
|
|
148
|
-
|
|
159
|
+
10. **Report.** Findings carry `valueDigest` (sha256 of the matched value) and a masked
|
|
149
160
|
snippet: fully blocked for short values, else `first4…last4`. Raw secret bytes never
|
|
150
161
|
leave process memory. A per-run `TextSanitizer` holds the digest-to-value registry and
|
|
151
162
|
replaces every flagged literal in *all* rendered text, including agent-written strings in
|
|
152
163
|
the LLM layer, so a secret cannot reappear inside a message about itself. `report.json`
|
|
153
164
|
(canonical, byte-stable for machines) and `report.md` land under `.border/runs/`.
|
|
154
|
-
|
|
155
|
-
and artifact digests. Degraded and NO-OP runs can never write a PASS.
|
|
165
|
+
11. **Ledger record.** A PASS is appended to `.border/ledger.jsonl` together with the key
|
|
166
|
+
and artifact digests. Degraded and NO-OP runs can never write a PASS. The key folds in a
|
|
167
|
+
*lens identity*, so a PASS recorded under an older, weaker set of legs never certifies a
|
|
168
|
+
push under the current lens — upgrading the gate forces one fresh full check.
|
|
156
169
|
|
|
157
170
|
```bash
|
|
158
171
|
$ border check # full scan, ~seconds on a mid-size repo
|
|
@@ -251,6 +264,7 @@ lives inside. The exfil lens adds five rules over the surfaces about to become p
|
|
|
251
264
|
| `exfil-home-path` | `/home/<user>` and `C:\Users\<user>` shapes |
|
|
252
265
|
| `exfil-cred-location` | `~/.pypirc`, `SSHPASS`, `*.env` credential-location shapes |
|
|
253
266
|
| `exfil-host-profile` | multi-signal host fingerprints (os+version / no-docker / no-passwordless-sudo co-occurrences in one paragraph) |
|
|
267
|
+
| `exfil-machine-binding` | vendor inference-model ids pinned on portable surfaces next to model/default/provider context (L-MACHINE-LOCAL; born 2026-10-02) |
|
|
254
268
|
|
|
255
269
|
The severity a reader **observes** is the (rule × facet × channel) matrix, and its single
|
|
256
270
|
home is [src/exfil/severity.ts](src/exfil/severity.ts) — the check pipeline, the
|
|
@@ -264,11 +278,13 @@ home is [src/exfil/severity.ts](src/exfil/severity.ts) — the check pipeline, t
|
|
|
264
278
|
| `exfil-home-path` | tree | native | MEDIUM |
|
|
265
279
|
| `exfil-cred-location` | tree | native | MEDIUM |
|
|
266
280
|
| `exfil-host-profile` | tree | native | MEDIUM |
|
|
281
|
+
| `exfil-machine-binding` | tree | native | MEDIUM |
|
|
267
282
|
| `exfil-rfc1918` | message | native | HIGH |
|
|
268
283
|
| `exfil-ssh-target` | message | native | HIGH |
|
|
269
284
|
| `exfil-home-path` | message | native | MEDIUM |
|
|
270
285
|
| `exfil-cred-location` | message | native | MEDIUM |
|
|
271
286
|
| `exfil-host-profile` | message | native | MEDIUM |
|
|
287
|
+
| `exfil-machine-binding` | message | native | MEDIUM |
|
|
272
288
|
|
|
273
289
|
Why an observed `CRITICAL` stands where the intent table says HIGH: the twin channels have
|
|
274
290
|
no HIGH rung — every ingested gitleaks finding carries CRITICAL, and secretlint's `error`
|
|
@@ -411,7 +427,10 @@ never from cache:
|
|
|
411
427
|
and exits 1; a rerun of `border push --yes` picks up only the still-PENDING targets.
|
|
412
428
|
- After every git leg that moved, border **verifies the landing**: it re-observes the remote
|
|
413
429
|
tip (`git ls-remote`, then a fetch of exactly that tip when it is not local) and scans the
|
|
414
|
-
landed tree with the same exfil machinery as `border exfil
|
|
430
|
+
landed tree with the same exfil machinery as `border exfil` — the same lens `border check`
|
|
431
|
+
now runs pre-push, and the same config `allow` pins apply: blocking counts are computed
|
|
432
|
+
after the allow-list, and any exemption is echoed loudly (rule, entry index, count), never
|
|
433
|
+
silent. Three honest outcomes: clean
|
|
415
434
|
appends a `t:"landing"` ledger record and preserves the push exit; a blocking hit exits 1
|
|
416
435
|
with `ALREADY PUBLIC; border detects, never erases` — the bytes are out, and border's job
|
|
417
436
|
there is detection, recording the fact, never rewriting history; an unobservable tip exits
|
|
@@ -717,7 +736,7 @@ plugin entry ships as `exports["./server"]`):
|
|
|
717
736
|
```jsonc
|
|
718
737
|
{
|
|
719
738
|
// pin the exact version — an @latest channel hits the registry on every cold start
|
|
720
|
-
"plugin": ["border-customs@0.8.
|
|
739
|
+
"plugin": ["border-customs@0.8.1"]
|
|
721
740
|
}
|
|
722
741
|
```
|
|
723
742
|
|
|
@@ -827,6 +846,13 @@ which is now a thin consumer of `inspect --json` with the rc contract unchanged
|
|
|
827
846
|
|
|
828
847
|
## Changelog
|
|
829
848
|
|
|
849
|
+
### 0.8.1 (2026-10-06)
|
|
850
|
+
- Maintenance release: certificate-chain rebuild off the scrubbed lineage (INCIDENT-20261006
|
|
851
|
+
identity-exposure cleanup). No functional change vs 0.8.0; 0.8.0's tarball is byte-intact but
|
|
852
|
+
its OIDC provenance pin moved with the history rewrite, so a fresh tag + publish restores a
|
|
853
|
+
verifiable chain. Rotation: package.json + lock roots + plugin marker + scan UA + README pins +
|
|
854
|
+
this entry.
|
|
855
|
+
|
|
830
856
|
### 0.8.0 (2026-09-30)
|
|
831
857
|
- Add: **`border opencode inspect [--json]`** — the load-surface self-audit becomes a gate-owned
|
|
832
858
|
surface: six read-only aspects (cache self-consistency, spec freshness vs pin/registry, identity
|
|
@@ -868,7 +894,7 @@ which is now a thin consumer of `inspect --json` with the rc contract unchanged
|
|
|
868
894
|
|
|
869
895
|
### 0.6.0 (2026-09-25)
|
|
870
896
|
- Add: **the exfil lens** — five rules (`exfil-rfc1918`, `exfil-ssh-target`,
|
|
871
|
-
`exfil-home-path`, `exfil-cred-location`, `exfil-host-profile`) that flag
|
|
897
|
+
`exfil-home-path`, `exfil-cred-location`, `exfil-host-profile`, `exfil-machine-binding`) that flag
|
|
872
898
|
exfil-shaped literals on about-to-be-public surfaces, the gap classic secret
|
|
873
899
|
scanners leave open because an internal IP is not a credential. The observed
|
|
874
900
|
severity of every (rule × facet × channel) cell lives in one machine-readable
|
|
@@ -1065,4 +1091,4 @@ border 是一个 fail-closed(失败即拦截)的推送前门禁 CLI:`npm install
|
|
|
1065
1091
|
|
|
1066
1092
|
OpenCode 插件适配层(0.5.0):零手工装载两条路线——主路是在 `opencode.jsonc` 声明 `"plugin": ["border-customs@0.5.0"]`(建议钉具体版本,@latest 每次冷启动都打注册表),opencode 自行下载构件,插件入口走 `exports["./server"]`;插件自带 CLI 解析,顺序为 `BORDER_BIN` 覆盖(独占,坏了就报错、绝不静默回退)> 包内同侧 `dist/index.js`(shebang 直接执行,不可执行则回退 PATH 上的 `node` 承载)> PATH 上的 `border`,免全局安装、免 PATH 配置;0.5.1 起任何候选都必须先过**身份握手**(以 `--help` 应答出 border 横幅与 `usage: border` 才算数),通过之前绝不投递用户 argv,握手不过即 cannot-answer exit 2、不运行任何其他二进制——0.5.0 曾用 `process.execPath` 承载包内 dist,而在插件宿主里 execPath 是 opencode 本体,边检门被静默冒充为宿主(2026-09-22 事故,见 0.5.1 变更日志)。备路是 `border opencode install|status|uninstall` 文件投递,落盘 `$XDG_CONFIG_HOME/opencode` 下的 `plugins/border.ts` 与 `commands/border.md`(无 XDG 回退 HOME/.config):受管文件凭 marker 行识别身份,同名外人文件 exit 2 拒绝、绝不覆盖,卸载只删自己带 marker 的文件,字节相同即 `up to date`。装载面只有一个 `border` 工具(argv-only 生成、无 shell、300 秒与每流 64 KiB 上限,命令闭集 check/push/status/llm-request/llm-ingest/scan/roundtrip/exfil/--help)与一条 `/border` 斜杠命令(config 钩子以 `??=` 自注册,用户同名文件命令保持权威)。边界如实说:allowlist 是 UX/防呆而非安全边界——插件进程与 opencode 进程同权,门禁的牙齿在 CLI 与账本,不在适配层;`push --yes` 按设计留在终端人审,工具端机械拒绝,会话内裸 push 本就是 CLI 合同的 DRY-RUN。装载验证走 opt-in 探针 `BORDER_OPENCODE_PROBE=1`(临时 HOME/XDG 起 `opencode serve` 对照组,断言 `border` 工具 id 与恰好一条 border 命令,重复即 FAIL;插件加载失败是静默的,只认 HTTP 响应不认退出码)。两条路线装载后都需重启 opencode:工具与命令在启动时扫描。
|
|
1067
1093
|
|
|
1068
|
-
外泄透镜(0.6.0):经典密钥扫描器看不见内网 IP、`user@内网主机`、家目录、凭据文件位置、主机画像这类"外泄形状"字面量——它们不是密钥,却如实描述公网仓库身后的私网。0.6.0 加五条规则 `exfil-rfc1918/exfil-ssh-target/exfil-home-path/exfil-cred-location/exfil-host-profile`,每个(规则×分面×通道)格子的**观察严重度**只有单一事实源 `src/exfil/severity.ts`:树面 HIGH 族走双生规则(gitleaks TOML + secretlint pattern,id 逐字节相等、三通道判决对等性有测试钉死),因两引擎都没有 HIGH 档而如实记为 CRITICAL(不硬掰,如实表);树面 MEDIUM 族走原生腿;提交消息面原生独占、id 带 `:message` 后缀,allow 条目按 id 精确匹配、跨面豁免在结构上不可能;tag note 分面归既有引擎腿 `tag-message-secret`,核心绝不重复发。规则源文件并入 rulesHash,改一个匹配器即令全部缓存 PASS 失效。独立只读入口 `border exfil <rev|url>`(默认树尖,`--deep` 补全史 blob 与全部可达提交消息;URL 模式落临时克隆、退出即销毁)。push 之后新增**落地核验**:ls-remote 复读远端尖、必要时精确 fetch 该对象,用同一套机制扫已公开树——干净则追加 `t:"landing"` 账本行并保留原退出码;有阻断发现则 exit 1 并明说 ALREADY PUBLIC;border 只检测、从不抹除;远端不可观测则 exit 2、一条记录都不造(没核验就没事实),已执行的 push 记录原样矗立——账本 append-only
|
|
1094
|
+
外泄透镜(0.6.0):经典密钥扫描器看不见内网 IP、`user@内网主机`、家目录、凭据文件位置、主机画像这类"外泄形状"字面量——它们不是密钥,却如实描述公网仓库身后的私网。0.6.0 加五条规则 `exfil-rfc1918/exfil-ssh-target/exfil-home-path/exfil-cred-location/exfil-host-profile`,每个(规则×分面×通道)格子的**观察严重度**只有单一事实源 `src/exfil/severity.ts`:树面 HIGH 族走双生规则(gitleaks TOML + secretlint pattern,id 逐字节相等、三通道判决对等性有测试钉死),因两引擎都没有 HIGH 档而如实记为 CRITICAL(不硬掰,如实表);树面 MEDIUM 族走原生腿;提交消息面原生独占、id 带 `:message` 后缀,allow 条目按 id 精确匹配、跨面豁免在结构上不可能;tag note 分面归既有引擎腿 `tag-message-secret`,核心绝不重复发。规则源文件并入 rulesHash,改一个匹配器即令全部缓存 PASS 失效。独立只读入口 `border exfil <rev|url>`(默认树尖,`--deep` 补全史 blob 与全部可达提交消息;URL 模式落临时克隆、退出即销毁)。push 之后新增**落地核验**:ls-remote 复读远端尖、必要时精确 fetch 该对象,用同一套机制扫已公开树——干净则追加 `t:"landing"` 账本行并保留原退出码;有阻断发现则 exit 1 并明说 ALREADY PUBLIC;border 只检测、从不抹除;远端不可观测则 exit 2、一条记录都不造(没核验就没事实),已执行的 push 记录原样矗立——账本 append-only,任何情况下不改写历史。2026-10-04 补上透镜对等:check 流水线在原生规则之后、注册表预检之前多跑一条**树尖腿**,用与落地核验完全同一套 scanTipTree 机制预扫本地 HEAD(push 将传输的那棵树),历史与工作面腿看不见的"只在树尖存在"内容(如合并提交解析引入、随后从磁盘删除的文件)从此无处藏;树尖发现汇入 allow 前合并点,按 rule+file+digest 与所有发现同权豁免——落地核验同样消费 cfg allow 并把每一次豁免大声列出(规则、条目号、条数),绝不沉默;绿色交接单与绿色落地双向一致。ledger key 折入透镜身份,旧一代(无树尖腿)的缓存 PASS 不再为新透镜背书,升级后强制一次全新全扫。树尖腿 git 故障即 exit 2,没有静默清白。测试语料遵守夹具纪律:10.200.0.0/16 为预留夹具段(树面全通道静默、双生规则原生镜像该豁免,消息面例外照红),其余泄漏形状一律由 `assembleOctet`/`assembleHost` 运行时拼装,签出字节里没有完整泄漏 token——border 的透镜对自己的仓库绿,红故事照常武装。真值通道 `BORDER_EXFIL_TRUTH=1` 为 opt-in,红锚点带生命周期条款:上游作者清理后按设计转绿、留档不判败。
|
|
@@ -176,4 +176,4 @@ export function matchSshTarget(text: string, o: RuleOptions): RuleHit[] {
|
|
|
176
176
|
// matchHomePath / matchCredLocation / matchHostProfile live in rulesPath.ts
|
|
177
177
|
// and are re-exported here so every consumer keeps importing the five
|
|
178
178
|
// predicates from one module. Type-only imports keep that pair acyclic.
|
|
179
|
-
export { matchCredLocation, matchHomePath, matchHostProfile } from "./rulesPath.ts";
|
|
179
|
+
export { matchCredLocation, matchHomePath, matchHostProfile, matchMachineBinding } from "./rulesPath.ts";
|
|
@@ -110,3 +110,31 @@ export function matchHostProfile(text: string, _o: RuleOptions): RuleHit[] {
|
|
|
110
110
|
flush();
|
|
111
111
|
return hits;
|
|
112
112
|
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------- exfil-machine-binding
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* L-MACHINE-LOCAL (owner ruling 2026-10-01; rule born 2026-10-02 after a
|
|
118
|
+
* vendor model id sat in another repo's portable README+code default for six
|
|
119
|
+
* releases unnoticed). Vendor inference-model identifiers pinned on portable
|
|
120
|
+
* surfaces couple a package to ONE operator's device authorization. Context
|
|
121
|
+
* gate: the line must ALSO speak of model/default/provider — prose mentions
|
|
122
|
+
* of a model name stay green (location, not vocabulary — mirrors the
|
|
123
|
+
* cred-location "position, not value" discipline).
|
|
124
|
+
* Self-stealth: literals are built in fragments so THIS file never trips
|
|
125
|
+
* its own regex.
|
|
126
|
+
*/
|
|
127
|
+
const 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`;
|
|
128
|
+
const BINDING_VENDOR_RE = new RegExp(BINDING_TOKEN, "gi");
|
|
129
|
+
const BINDING_CONTEXT_RE = /(?:model|模型|default|默认|provider)/i;
|
|
130
|
+
|
|
131
|
+
export function matchMachineBinding(text: string, _o: RuleOptions): RuleHit[] {
|
|
132
|
+
const hits: RuleHit[] = [];
|
|
133
|
+
text.split("\n").forEach((body, idx) => {
|
|
134
|
+
if (!BINDING_CONTEXT_RE.test(body)) return;
|
|
135
|
+
for (const m of body.matchAll(BINDING_VENDOR_RE)) {
|
|
136
|
+
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 — device authorization belongs in operator-local config, not portable surfaces (L-MACHINE-LOCAL)." });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return hits;
|
|
140
|
+
}
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
import {
|
|
41
41
|
matchCredLocation,
|
|
42
42
|
matchHomePath,
|
|
43
|
+
matchMachineBinding,
|
|
43
44
|
matchHostProfile,
|
|
44
45
|
matchRfc1918,
|
|
45
46
|
matchSshTarget,
|
|
@@ -99,6 +100,7 @@ const PREDICATES: Readonly<Record<ExfilRuleId, (text: string, o: RuleOptions) =>
|
|
|
99
100
|
"exfil-home-path": matchHomePath,
|
|
100
101
|
"exfil-cred-location": matchCredLocation,
|
|
101
102
|
"exfil-host-profile": matchHostProfile,
|
|
103
|
+
"exfil-machine-binding": matchMachineBinding,
|
|
102
104
|
};
|
|
103
105
|
|
|
104
106
|
/**
|
|
@@ -44,13 +44,14 @@
|
|
|
44
44
|
export const EXFIL_SEVERITIES = ["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"] as const;
|
|
45
45
|
export type ExfilSeverity = (typeof EXFIL_SEVERITIES)[number];
|
|
46
46
|
|
|
47
|
-
/** The
|
|
47
|
+
/** The exfil-lens rule ids, verbatim; twins must ship these ids byte-equal (T2 guard). */
|
|
48
48
|
export const EXFIL_RULE_IDS = [
|
|
49
49
|
"exfil-rfc1918",
|
|
50
50
|
"exfil-ssh-target",
|
|
51
51
|
"exfil-home-path",
|
|
52
52
|
"exfil-cred-location",
|
|
53
53
|
"exfil-host-profile",
|
|
54
|
+
"exfil-machine-binding",
|
|
54
55
|
] as const;
|
|
55
56
|
export type ExfilRuleId = (typeof EXFIL_RULE_IDS)[number];
|
|
56
57
|
|
|
@@ -71,6 +72,7 @@ export const EXFIL_TABLE_SEVERITY: Readonly<Record<ExfilRuleId, ExfilSeverity>>
|
|
|
71
72
|
"exfil-home-path": "MEDIUM",
|
|
72
73
|
"exfil-cred-location": "MEDIUM",
|
|
73
74
|
"exfil-host-profile": "MEDIUM",
|
|
75
|
+
"exfil-machine-binding": "MEDIUM",
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
/** HIGH family: the two identity-carrying location rules; tree face belongs to the twins. */
|
|
@@ -85,7 +87,7 @@ export const EXFIL_HIGH_FAMILY: readonly ExfilRuleId[] = ["exfil-rfc1918", "exfi
|
|
|
85
87
|
*/
|
|
86
88
|
export const EXFIL_SECRET_VALUE_RULES: readonly ExfilRuleId[] = EXFIL_HIGH_FAMILY;
|
|
87
89
|
/** MEDIUM family: privacy-shape rules; native carries them on both scanned facets. */
|
|
88
|
-
export const EXFIL_MEDIUM_FAMILY: readonly ExfilRuleId[] = ["exfil-home-path", "exfil-cred-location", "exfil-host-profile"];
|
|
90
|
+
export const EXFIL_MEDIUM_FAMILY: readonly ExfilRuleId[] = ["exfil-home-path", "exfil-cred-location", "exfil-host-profile", "exfil-machine-binding"];
|
|
89
91
|
|
|
90
92
|
/** null = FORBIDDEN cell: that channel must never emit that rule on that facet. */
|
|
91
93
|
export type ObservedSeverity = ExfilSeverity | null;
|
|
@@ -96,6 +98,7 @@ const TWIN_HIGH: Readonly<Record<ExfilRuleId, ObservedSeverity>> = {
|
|
|
96
98
|
"exfil-home-path": null,
|
|
97
99
|
"exfil-cred-location": null,
|
|
98
100
|
"exfil-host-profile": null,
|
|
101
|
+
"exfil-machine-binding": null,
|
|
99
102
|
};
|
|
100
103
|
const NULL_ROW: Readonly<Record<ExfilRuleId, ObservedSeverity>> = {
|
|
101
104
|
"exfil-rfc1918": null,
|
|
@@ -103,13 +106,14 @@ const NULL_ROW: Readonly<Record<ExfilRuleId, ObservedSeverity>> = {
|
|
|
103
106
|
"exfil-home-path": null,
|
|
104
107
|
"exfil-cred-location": null,
|
|
105
108
|
"exfil-host-profile": null,
|
|
109
|
+
"exfil-machine-binding": null,
|
|
106
110
|
};
|
|
107
111
|
|
|
108
112
|
/** The full (facet × channel × rule) truth table — the plan's 分面契约, machine-readable. */
|
|
109
113
|
export const EXFIL_MATRIX: Readonly<Record<ExfilFacet, Readonly<Record<ExfilChannel, Readonly<Record<ExfilRuleId, ObservedSeverity>>>>>> = {
|
|
110
114
|
tree: {
|
|
111
115
|
// native tree leg = MEDIUM family补发 only (v2 double-emission revoked).
|
|
112
|
-
native: { ...NULL_ROW, "exfil-home-path": "MEDIUM", "exfil-cred-location": "MEDIUM", "exfil-host-profile": "MEDIUM" },
|
|
116
|
+
native: { ...NULL_ROW, "exfil-home-path": "MEDIUM", "exfil-cred-location": "MEDIUM", "exfil-host-profile": "MEDIUM", "exfil-machine-binding": "MEDIUM" },
|
|
113
117
|
// gitleaks.ts:81 hardcodes CRITICAL for every ingested finding.
|
|
114
118
|
"gitleaks-twin": TWIN_HIGH,
|
|
115
119
|
// secretlint.ts:299-309 has no HIGH rung — error maps to CRITICAL as-is.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "border-customs",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
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.1",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@opencode-ai/plugin": "^1.17.4",
|