@sema-agent/settings-schema 9.0.0 → 10.0.0
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/CHANGELOG.md +69 -0
- package/README.md +7 -0
- package/conformance/VERSION +1 -1
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/meta.json +2 -2
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/expected.json +103 -0
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/input.json +26 -0
- package/conformance/cases/c69-deep-encoded-token-past-every-reading/meta.json +9 -0
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/expected.json +103 -0
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/input.json +26 -0
- package/conformance/cases/c70-rewritten-token-in-front-of-encoded-pem-header/meta.json +9 -0
- package/dist/config-fns.d.ts +8 -5
- package/dist/config-fns.js +8 -5
- package/dist/types.d.ts +11 -8
- package/dist/types.js +16 -9
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,75 @@
|
|
|
6
6
|
> `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
|
|
7
7
|
> 包名是**历史记录**,如实保留不追改。
|
|
8
8
|
|
|
9
|
+
## 10.0.0 — 2026-10-09 — 私钥 PEM 头认串中任意位置(BREAKING:写入校验认串中任意位置的私钥 PEM 头;读侧同套 schema ⇒ 存量私钥串的条目被丢 / 域 throw)
|
|
10
|
+
|
|
11
|
+
> **判档 = major**(README「发版纪律」:校验收紧、让同一份配置读写出不同结果 ⇒ major)。破坏的是**持久数据面**,不是类型面:导出面登记表零 diff(没有新增 / 删除 / 改形的导出),
|
|
12
|
+
> 但读侧与写侧共用同一套 schema,存量配置里已经带着私钥串的部署,升级后读侧丢条目 / 整域 throw(下面「谁受伤」)。硬 breaking:无别名、无开关、无兼容读。
|
|
13
|
+
|
|
14
|
+
### 一、病
|
|
15
|
+
|
|
16
|
+
校验侧探测器 `containsSecretToken`(经 `scanForSecret`)读私钥 PEM 头只认**串首**:头里带空格,按令牌切开的那一遍永远隔不出它;头在串首时靠 `SECRET_LITERAL_RE` 的行首锚分支。
|
|
17
|
+
`"x -----BEGIN PRIVATE KEY-----"` / `"key: -----BEGIN RSA PRIVATE KEY-----"` / `"…\n-----BEGIN OPENSSH PRIVATE KEY-----"` 因此都答 `false` —— 凡走这只探测器的自由文本写口,都能把整把私钥写进配置、
|
|
18
|
+
进域行 / 变更历史 / 发布快照 / `/effective`。展示侧 `redactSecrets` 读的是同一只 `PRIVATE_KEY_PEM_HEADER`,却早已在串中任意位置、任一归一化形上判阳 —— **写与显两只判官对「头在串中」这一格答案不一致**。
|
|
19
|
+
|
|
20
|
+
### 二、改(一处)
|
|
21
|
+
|
|
22
|
+
`scanForSecret` 加一条:私钥 PEM 头(`PRIVATE_KEY_PEM_HEADER`,`-----BEGIN[A-Z0-9 ]*PRIVATE KEY`,大小写不敏感,既有全部变体 —— 裸 `PRIVATE KEY` / `RSA` / `EC` / `DSA` / `OPENSSH` / `ENCRYPTED` / `PGP …`)在串中**任意位置**命中即判阳。
|
|
23
|
+
`containsSecretToken` 对原文与前三层百分号解码都跑 `scanForSecret`,所以编码过的头(`x%20-----BEGIN%20PRIVATE%20KEY`,三层之内)同样命中。**同一只正则**:展示侧在读的就是它,包内没有第二份(`src/` 里非注释行出现私钥头字样的只有那一处定义,有测试钉着)。
|
|
24
|
+
公钥 / 证书头照旧不命中;其余令牌形(`sk-` / `ghp_` / JWT / Bearer / AKIA / AIza …)一字不动。`SECRET_LITERAL_RE` 的源串逐字不变(它是导出的单令牌字面形,下游据它的源串生成表)。
|
|
25
|
+
`redactSecrets` 行为不变(同三形读数仍是整串 `[redacted-secret]`);它头注里「`containsSecretToken` 刻意不在这里放宽」那一句随之改真,`containsSecretToken` / `PRIVATE_KEY_PEM_HEADER` 头注里「只认串首」的说法同改。
|
|
26
|
+
|
|
27
|
+
### 三、一处改全部受益的写口(逐个核过,都在测试里灌同一张表)
|
|
28
|
+
|
|
29
|
+
经 `containsSecretToken` 的写口(源:`src/types.ts`、`src/remote-exec.ts`):
|
|
30
|
+
- `ModelEntry.extraBody` / `compat` / `headers` —— 经 `vetFreeFormJson`(`extraBodyUnsafe` 描述符走查读每个字符串值与每个键;序列化克隆再扫一遍);
|
|
31
|
+
- URL 面 `NON_SECRET_URL`(路径 / 查询 / 片段,`urlQueryFragmentHasSecret`):`ModelEntry.baseUrl`、mcp http `url`、worker `coords.gitApiBaseUrl`、会话镜像引擎 URL、`remoteExec` 的 `apiUrl` / `s3Snapshot.endpoint`、中心凭证 `issuer`;
|
|
32
|
+
- mcp stdio `command` / `args[]`;
|
|
33
|
+
- 插件源 `PLUGIN_ORIGIN`;
|
|
34
|
+
- `remoteExec` ssh 的 `host` / `username`;
|
|
35
|
+
- `WorkerSpec.env` 值;`runtime.execution.coords` 值(含远端 docker 守护进程的 `DOCKER_HOST`)与 `runtime.store.db` 的 `host` / `user` / `database`(车道 / SQL 坐标);
|
|
36
|
+
- 授权覆盖值 `EntitlementOverride.value`(串,或 JSON 序列化后的串)。
|
|
37
|
+
|
|
38
|
+
不受影响:HTTP 头名(字符集本就不收空格,私钥头不可能写进去);`ENV_NAME` 字段(字符集与 AKIA 形判据,不走这只探测器)。
|
|
39
|
+
|
|
40
|
+
### 四、三问
|
|
41
|
+
|
|
42
|
+
- **谁需要**:把凭据误写进自由文本键的部署 —— 例如把整把私钥贴进 `extraBody` 的某个值、mcp 参数、worker `env` 值,或带前缀(`key: …`)/ 换行后贴;这些以前都被放行并落进持久层。
|
|
43
|
+
- **谁受伤**:新写入不会有人受伤(只收紧)。**存量里已经带着这种串的配置**,读写都会变:下一次写回被拒;读侧同一只判官也拒 —— 可降级域(`models` / `mcp` / `plugins` 等)该条按坏条目逐条丢并报 `entry-dropped`(同域其余条目存活),
|
|
44
|
+
`workers` / `entitlement` 等 gate 域沿用既有 fail-closed(整域读拒,拒句点名字段、不带值)。这是读侧的真实后果,不只是「写回被拒」—— 也是本版判 major 的原因。
|
|
45
|
+
- **补偿**:拒句是固定句、不回显值,zod issue 的 `path` 指到字段,运维据此找到并删掉那个值;把私钥挪到 `secretRefs` / `secretEnv`(env-NAME)并**轮换这把密钥**(它已经进过配置)。展示侧本来就把这类串整串扣成 `[redacted-secret]`,所以日志 / 告警里没有泄露面;泄露面是持久层里那份原文。没有开关可以关掉这条拒 —— 刻意不留旋钮(私钥进配置不是可调的偏好)。
|
|
46
|
+
**升级前先清存量**(本包没有现成的清单命令 —— CLI 只有 `migrate-workers`,所以是人工步骤):① 在要升级的配置源(本地 `config.d/*.json`、中心的域行 / 变更历史导出)里不分大小写搜 `-----BEGIN` 后接 `PRIVATE KEY`
|
|
47
|
+
(正则 `-----BEGIN[A-Z0-9 ]*PRIVATE KEY`,另搜百分号编码形 `PRIVATE%20KEY`);`.env` 本来就是放秘密值的地方,里面的值不经这只探测器,不在此列。② 或写几行脚本:用 `@sema-agent/settings-schema/types` 的 `containsSecretToken`
|
|
48
|
+
对每份 JSON 里的每个字符串值与键各跑一遍,命中的就是存量私钥串;要把命中打印出来,过 `redactSecrets`(它把命中的串读成 `[redacted-secret]`,清单里不会再抄一份私钥)。③ 命中的值挪进 `secretEnv` / `secretRefs` 所指的 env-NAME,轮换这把密钥,再升级。
|
|
49
|
+
|
|
50
|
+
### 用户可见变化(谁受影响 / 怎么改 / 不改会怎样)
|
|
51
|
+
|
|
52
|
+
1. **自由文本写口的提交方(管理面 / CLI / 手写 `config.d`)**:带私钥头的串 —— 不论头在串首、前缀文字之后、`key: ` 之后还是换行之后 —— 现在都被拒;公钥 / 证书照旧过。不改会怎样:提交被拒,按拒句修。
|
|
53
|
+
2. **持有存量这类串的部署**:见「谁受伤」与「补偿」。升级前先按「补偿」里的人工步骤清单,把私钥挪进 `secretEnv` 并轮换;不清就升级,读侧会丢条目 / 整域 throw。
|
|
54
|
+
3. **读侧消费方(server 读 `/effective`、`loadLocalConfig`)**:对干净配置零变化。
|
|
55
|
+
4. **共用 `containsSecretToken` 做展示 / 门判的其它仓**:它们对带私钥头的串现在也判阳(更严,方向一致);未在这里逐仓核对其断言 —— 提货时跑自己的套件。
|
|
56
|
+
5. **持有导出面快照的测试 / 脚本**:无变化(导出面零 diff)。
|
|
57
|
+
|
|
58
|
+
### 红先 / 门
|
|
59
|
+
|
|
60
|
+
- 先写钉、对着改前的树跑红,再实现:`test/private-key-anywhere-10.0.0.test.ts` —— 探测器本身(三形 × 全部既有头变体 × 百分号层 × 负控)、二十个写口位置各灌同一张表(中间形拒 / 串首形照旧拒 / 公钥与证书照旧放 / 不含 PEM 的长串照旧放)、
|
|
61
|
+
自由文本写口的拒句不回显值、展示侧同三形读数不变、读侧后果(可降级域逐条丢、gate 域整域读拒)、单源钉、2 MB 对抗串线性钉、一格 🚧 缺口特征钉。写钉时对改前的树 96 格里 33 红;终稿 103 格(后补写口与缺口钉),把 `scanForSecret` 那一行拿掉复跑 37 红 66 绿,实装后 103 绿。
|
|
62
|
+
- 既有钉随之改真(都是借「探测器只认串首」这个缺口触发的格,按缺口登记规矩翻;实装后这两格先红,翻后绿):`test/disclosable-identity-6.3.0.test.ts` 的 R1 前提格(`containsSecretToken(encoded)` 由判阴翻判阳)与 R3 附的冻结 oracle
|
|
63
|
+
(由「与 6.2.0 逐格相同」改为「6.2.0 ∪ 私钥头任意位置」,头的正则在 oracle 里冻一份字面,不读 src 导出)。
|
|
64
|
+
- 一致性语料的变异律随之红了两个靶,按「活着的变异 = 语料缺口」补语料而不是删靶:展示侧脱敏器的「归一化没走完 ⇒ 整串扣」与「私钥头判在原文的归一化形上」这两面墙,此前分别靠
|
|
65
|
+
c43 的五层编码私钥头与 c41 / c43 的编码私钥头压着 —— 探测器认任意位置之后,那几条被探测器那一遍先扣下,两面墙失去杀手。新增 `c69-deep-encoded-token-past-every-reading`(九层编码的 `sk-` 形令牌)与
|
|
66
|
+
`c70-rewritten-token-in-front-of-encoded-pem-header`(`sk-note:` 紧贴编码私钥头、私钥体在换行之后),加冕脚本只加冕这两条;既有金样(`expected.json`)一字未动,只有 c43 的 `meta.json` 里一句已不成立的现在时说明(「五层那条只有『没走完』这一位扣得住」)订正。语料版本戳随包版本。
|
|
67
|
+
- 导出面登记表(`docs/API-SURFACE.md`)重生成:零 diff。
|
|
68
|
+
|
|
69
|
+
### 未闭环(如实)
|
|
70
|
+
|
|
71
|
+
- **写与显对私钥头的读法宽度仍不一致 —— 这是缺陷,不是立场**:`containsSecretToken`(写侧)只读原文 + 前三层宽容百分号解码;**四层及更深**的编码、`+` 代空格(`-----BEGIN+PRIVATE+KEY-----`,form-urlencoded 读法)的头,
|
|
72
|
+
写侧今天判阴(放行),展示侧整串扣。本版只把「头位置」这一维收到任意位置,没动读法宽度;同一个问题两只读法(`percentLayers` 与 `secretScanForms`)作答,收口方向是并成一只读法(写侧读展示侧那一组形),
|
|
73
|
+
那是另一件设计,不在这次改里临时加探测臂。`test/private-key-anywhere-10.0.0.test.ts` 有一格 🚧 特征钉登记它,翻转条件写在钉里。这条缺口对其它令牌形(`sk-` 等)同样存在、早已如此,并非本版引入。
|
|
74
|
+
- **`SECRET_LITERAL_RE` 仍带串首锚的私钥头一支**:它是导出面,下游(例如按它的源串生成匹配表的展示层)据此判串首形;`scanForSecret` 里新加的任意位置判先于它,这一支在探测器内部已是冗余,但改它是导出正则的变更。
|
|
75
|
+
直接用 `SECRET_LITERAL_RE` 整串判定的消费方,串中的私钥头仍看不见 —— 它们应改用 `containsSecretToken`。
|
|
76
|
+
- **存量数据没有迁移器**:升级不动持久层里已有的串;处置是运维删值 + 轮换密钥(见补偿)。本版不提供自动清洗(自动改写配置里的值不是契约层的事)。
|
|
77
|
+
|
|
9
78
|
## 9.0.0 — 2026-10-09 — 能力读数三态读器 · 世代账两键由串改数(BREAKING)
|
|
10
79
|
|
|
11
80
|
> **判档 = major**(README「发版纪律」:导出删除 / 改名、类型 retype ⇒ major)。**硬 breaking:删改直接做,零别名,消费方 tsc 红即通知。** 两件:
|
package/README.md
CHANGED
|
@@ -48,6 +48,13 @@ therefore shows only `zod`. Node built-ins only (`node:crypto`, `node:fs`).
|
|
|
48
48
|
> a zod-3 host (today the config-center product) still dedups to one instance. Moving this package to zod 4 is a major
|
|
49
49
|
> of its own (S-777); 6.2.0 does not touch the dependency or peer range.
|
|
50
50
|
|
|
51
|
+
## 升级指引:9.0.0 → 10.0.0(BREAKING:写入校验认串中任意位置的私钥 PEM 头;读侧同套 schema ⇒ 存量私钥串的条目被丢 / 域 throw;导出面零变化)
|
|
52
|
+
|
|
53
|
+
自由文本写口(`extraBody` / `compat` / `headers`、URL、mcp stdio 参数、插件源、ssh 登录、worker env、车道坐标、覆盖值)此前只在私钥 PEM 头位于**串首**时拒;`"x -----BEGIN PRIVATE KEY-----"` / `"key: -----BEGIN RSA PRIVATE KEY-----"` / 换行之后的头都放行,而展示侧 `redactSecrets` 早就认串中任意位置。10.0.0 起 `containsSecretToken` 在原文与前三层百分号解码上都认串中任意位置的头(与展示侧读同一只 `PRIVATE_KEY_PEM_HEADER`),头的**位置**写与显同判;公钥 / 证书头照旧不拒,其余令牌形不变。⚠️ 读法的**宽度**写侧仍窄于展示侧:四层及更深的百分号编码、`+` 代空格的头,写侧今天仍放行(展示侧整串扣),见 CHANGELOG 10.0.0「未闭环」。**代码动作:无**(导出面零变化)。**存量配置里若已有这种串 —— 这就是本版判 major 的原因**:下一次写回被拒(拒句固定、不回显值,`path` 指到字段);读侧同一只判官也拒 ——
|
|
54
|
+
`models` / `mcp` / `plugins` 等可降级域该条按坏条目逐条丢并报 `entry-dropped`(同域其余条目存活),`workers` / `entitlement` 等 gate 域沿用既有的 fail-closed(整域读拒)。
|
|
55
|
+
**升级前先清存量**(本包没有现成的清单命令,人工步骤):不分大小写搜配置源里的 `-----BEGIN[A-Z0-9 ]*PRIVATE KEY`(另搜 `PRIVATE%20KEY`),或用 `@sema-agent/settings-schema/types` 的 `containsSecretToken` 对每份 JSON 的字符串值与键各跑一遍(打印命中时过 `redactSecrets`,清单里不再抄一份私钥);
|
|
56
|
+
命中的值挪进 `secretEnv` / `secretRefs`(env-NAME)并轮换这把密钥(它已经进过配置)。逐条见 CHANGELOG 10.0.0。
|
|
57
|
+
|
|
51
58
|
## 升级指引:8.3.1 → 9.0.0(BREAKING:两件,硬 breaking —— 删改直接做,零别名)
|
|
52
59
|
|
|
53
60
|
**① 能力读数改三态读器**:`readCenterCapabilities(body)` 删,换 `readCapabilityReading(reply): CapabilityReading`(类型 `CapabilityReading` 同入)。输入改成心跳 **2xx 的整只答复体**(键查找进读器),
|
package/conformance/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
10.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"contributor": "contract",
|
|
3
|
-
"claim": "6.3.0(codex gpt-6-astra r1 F1 部分修,S-792):归一化那一处的两件事压在语料上。① `+` 代空格:扁平无换行的 `-----BEGIN+PRIVATE+KEY-----<base64>-----END+PRIVATE+KEY-----` 全在身份白名单字符集内,6.2.0 起它过身份与 refusal 两谓词 ⇒ 被丢条目的 id 必须不带(`secretScanForms` 把 `+` 读作空格的形交 PEM 头判,身份否决随之);URLSearchParams 形(`+` 代空格 + `%0A`)作 roles 坏键 ⇒ 坐标段整段扣、error 不带私钥。② 解码界上 fail-closed:四层 / 五层百分号编码作 roles 坏键 ⇒ 三层走完仍能再解,整串扣(codex r2 起展示侧的边界读法对末层再解一层,四层那条因此也被 PEM 头判扣下;五层那条只有「没走完」这一位扣得住 —— 变异门的 `unresolved` 靶靠它)。正控:`o3-mini+high` 作 id 逐字带出(`+` 在真实 id 里照旧可披露)。",
|
|
3
|
+
"claim": "6.3.0(codex gpt-6-astra r1 F1 部分修,S-792):归一化那一处的两件事压在语料上。① `+` 代空格:扁平无换行的 `-----BEGIN+PRIVATE+KEY-----<base64>-----END+PRIVATE+KEY-----` 全在身份白名单字符集内,6.2.0 起它过身份与 refusal 两谓词 ⇒ 被丢条目的 id 必须不带(`secretScanForms` 把 `+` 读作空格的形交 PEM 头判,身份否决随之);URLSearchParams 形(`+` 代空格 + `%0A`)作 roles 坏键 ⇒ 坐标段整段扣、error 不带私钥。② 解码界上 fail-closed:四层 / 五层百分号编码作 roles 坏键 ⇒ 三层走完仍能再解,整串扣(codex r2 起展示侧的边界读法对末层再解一层,四层那条因此也被 PEM 头判扣下;五层那条只有「没走完」这一位扣得住 —— 变异门的 `unresolved` 靶靠它)。正控:`o3-mini+high` 作 id 逐字带出(`+` 在真实 id 里照旧可披露)。 【10.0.0 订正】探测器改认串中任意位置的私钥头之后,它对每个归一化形再读三层,五层那条也被探测器那一遍扣下,「没走完」这一位不再独自承重于本条;`unresolved` 靶的语料杀手改为 c69。",
|
|
4
4
|
"firstRedOn": [],
|
|
5
5
|
"secrets": [
|
|
6
6
|
"MC4CAQAwBQYDK2VwBCIEIFAKEC43FLATPLUSIDBODY0000000000000000000000",
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
"MC4CAQAwBQYDK2VwBCIEIFAKEC43DEEPENCODEDROLEKEYBODY00000000000000",
|
|
9
9
|
"MC4CAQAwBQYDK2VwBCIEIFAKEC43FIVELAYERROLEKEYBODY0000000000000000"
|
|
10
10
|
],
|
|
11
|
-
"note": "车PE(2026-09-27)红先:加本条时 6.3.0 修前(A 已修、B 未修)读腿在三腿上报告警差 + 秘密扫描命中。变异门为 `secretScanForms` 的 `+` 读法与 `redactSecrets` 的 `unresolved` 一位各登记一靶,本条杀。firstRedOn 为空:historical 阶梯证据真跑于本条入库之前,那一栏只收 historical 真跑。 codex r2 收敛(车PE 同日):边界读法上线后四层那条不再单靠 `unresolved`,追加五层一条(`FAKEC43FIVELAYER…`)使该靶仍有语料杀。"
|
|
11
|
+
"note": "车PE(2026-09-27)红先:加本条时 6.3.0 修前(A 已修、B 未修)读腿在三腿上报告警差 + 秘密扫描命中。变异门为 `secretScanForms` 的 `+` 读法与 `redactSecrets` 的 `unresolved` 一位各登记一靶,本条杀。firstRedOn 为空:historical 阶梯证据真跑于本条入库之前,那一栏只收 historical 真跑。 codex r2 收敛(车PE 同日):边界读法上线后四层那条不再单靠 `unresolved`,追加五层一条(`FAKEC43FIVELAYER…`)使该靶仍有语料杀。 【10.0.0】`unresolved` 靶改由 c69 杀,本条对该靶不再红。"
|
|
12
12
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"domains": {
|
|
3
|
+
"outcome": "effective",
|
|
4
|
+
"effective": {
|
|
5
|
+
"collab": {
|
|
6
|
+
"templates": []
|
|
7
|
+
},
|
|
8
|
+
"entitlement": {
|
|
9
|
+
"budgetGroups": [],
|
|
10
|
+
"groupBindings": [],
|
|
11
|
+
"subjects": [],
|
|
12
|
+
"tiers": []
|
|
13
|
+
},
|
|
14
|
+
"execution": {
|
|
15
|
+
"allowedLanes": [
|
|
16
|
+
"host"
|
|
17
|
+
],
|
|
18
|
+
"required": false
|
|
19
|
+
},
|
|
20
|
+
"governance": {},
|
|
21
|
+
"hosts": {
|
|
22
|
+
"hosts": []
|
|
23
|
+
},
|
|
24
|
+
"limits": {},
|
|
25
|
+
"mcp": {
|
|
26
|
+
"servers": []
|
|
27
|
+
},
|
|
28
|
+
"models": {
|
|
29
|
+
"models": [
|
|
30
|
+
{
|
|
31
|
+
"api": "openai-completions",
|
|
32
|
+
"apiKeyEnv": "OPENAI_API_KEY",
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"id": "keep-id",
|
|
35
|
+
"name": "keep",
|
|
36
|
+
"provider": "openai",
|
|
37
|
+
"quotaWeight": 1,
|
|
38
|
+
"reasoning": false,
|
|
39
|
+
"tier": "standard",
|
|
40
|
+
"vision": false
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"roles": {
|
|
44
|
+
"summarize": {
|
|
45
|
+
"model": "keep"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"tierGroups": []
|
|
49
|
+
},
|
|
50
|
+
"plugins": {
|
|
51
|
+
"plugins": []
|
|
52
|
+
},
|
|
53
|
+
"projects": {
|
|
54
|
+
"projects": {}
|
|
55
|
+
},
|
|
56
|
+
"prompts": {
|
|
57
|
+
"bindings": []
|
|
58
|
+
},
|
|
59
|
+
"rosters": {
|
|
60
|
+
"rosters": []
|
|
61
|
+
},
|
|
62
|
+
"runtime": {},
|
|
63
|
+
"scenarios": {
|
|
64
|
+
"scenarios": []
|
|
65
|
+
},
|
|
66
|
+
"skills": {
|
|
67
|
+
"skills": []
|
|
68
|
+
},
|
|
69
|
+
"systems": {
|
|
70
|
+
"systems": []
|
|
71
|
+
},
|
|
72
|
+
"workers": {
|
|
73
|
+
"workers": []
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"warnings": [
|
|
77
|
+
{
|
|
78
|
+
"domain": "models",
|
|
79
|
+
"kind": "entry-dropped",
|
|
80
|
+
"path": [
|
|
81
|
+
"roles",
|
|
82
|
+
"[redacted-secret]"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"metadata": {
|
|
87
|
+
"version": 15,
|
|
88
|
+
"updatedAt": "2026-10-09T00:00:00.000Z"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"strictWrite": {
|
|
92
|
+
"outcome": "rejected",
|
|
93
|
+
"stage": "validate",
|
|
94
|
+
"why": "拒:roles 里的坏键读腿丢(entry-dropped,坐标整段扣);严写 ⇒ 整次不受理。"
|
|
95
|
+
},
|
|
96
|
+
"wire": {
|
|
97
|
+
"derive": true
|
|
98
|
+
},
|
|
99
|
+
"localRoot": {
|
|
100
|
+
"derive": true,
|
|
101
|
+
"localVersion": 2824083313848700
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"version": 15,
|
|
4
|
+
"updatedAt": "2026-10-09T00:00:00.000Z"
|
|
5
|
+
},
|
|
6
|
+
"domains": {
|
|
7
|
+
"models": {
|
|
8
|
+
"models": [
|
|
9
|
+
{
|
|
10
|
+
"name": "keep",
|
|
11
|
+
"provider": "openai",
|
|
12
|
+
"id": "keep-id",
|
|
13
|
+
"apiKeyEnv": "OPENAI_API_KEY"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"roles": {
|
|
17
|
+
"summarize": {
|
|
18
|
+
"model": "keep"
|
|
19
|
+
},
|
|
20
|
+
"key=%252525252525252573k-live-FAKEC69NINELAYERROLEKEYTOKEN0000000000": {
|
|
21
|
+
"model": "keep"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contributor": "contract",
|
|
3
|
+
"claim": "10.0.0:展示侧脱敏器的「归一化没走完 ⇒ 整串扣」这一位压在语料上。`sk-` 形令牌的首字母 `s` 被九重百分号编码(`%2525…2573`)作 roles 坏键:探测器的三层解码、边界读法再解一层、探测器对每个形再读三层都够不着它 ⇒ 令牌不被任何定点遍认出,只有「还能再解」这一位把整段坐标扣成 `[redacted-secret]`。此前这一位靠 c43 的五层私钥头压着;10.0.0 起探测器认串中任意位置的私钥头,五层那条也被探测器那一遍扣下,这一位随之没了语料杀手 —— 本条补上。",
|
|
4
|
+
"firstRedOn": [],
|
|
5
|
+
"secrets": [
|
|
6
|
+
"k-live-FAKEC69NINELAYERROLEKEYTOKEN0000000000"
|
|
7
|
+
],
|
|
8
|
+
"note": "变异门靶:`redactSecrets` 去 `scan.unresolved || ` —— 本条杀。不是红先复现:这一位的行为 9.0.0 与 10.0.0 相同(坐标整段扣),本条只是让它在语料里再有一个承重的杀手。firstRedOn 为空:不是 historical 阶梯证据。"
|
|
9
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"domains": {
|
|
3
|
+
"outcome": "effective",
|
|
4
|
+
"effective": {
|
|
5
|
+
"collab": {
|
|
6
|
+
"templates": []
|
|
7
|
+
},
|
|
8
|
+
"entitlement": {
|
|
9
|
+
"budgetGroups": [],
|
|
10
|
+
"groupBindings": [],
|
|
11
|
+
"subjects": [],
|
|
12
|
+
"tiers": []
|
|
13
|
+
},
|
|
14
|
+
"execution": {
|
|
15
|
+
"allowedLanes": [
|
|
16
|
+
"host"
|
|
17
|
+
],
|
|
18
|
+
"required": false
|
|
19
|
+
},
|
|
20
|
+
"governance": {},
|
|
21
|
+
"hosts": {
|
|
22
|
+
"hosts": []
|
|
23
|
+
},
|
|
24
|
+
"limits": {},
|
|
25
|
+
"mcp": {
|
|
26
|
+
"servers": []
|
|
27
|
+
},
|
|
28
|
+
"models": {
|
|
29
|
+
"models": [
|
|
30
|
+
{
|
|
31
|
+
"api": "openai-completions",
|
|
32
|
+
"apiKeyEnv": "OPENAI_API_KEY",
|
|
33
|
+
"enabled": true,
|
|
34
|
+
"id": "keep-id",
|
|
35
|
+
"name": "keep",
|
|
36
|
+
"provider": "openai",
|
|
37
|
+
"quotaWeight": 1,
|
|
38
|
+
"reasoning": false,
|
|
39
|
+
"tier": "standard",
|
|
40
|
+
"vision": false
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"roles": {
|
|
44
|
+
"summarize": {
|
|
45
|
+
"model": "keep"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"tierGroups": []
|
|
49
|
+
},
|
|
50
|
+
"plugins": {
|
|
51
|
+
"plugins": []
|
|
52
|
+
},
|
|
53
|
+
"projects": {
|
|
54
|
+
"projects": {}
|
|
55
|
+
},
|
|
56
|
+
"prompts": {
|
|
57
|
+
"bindings": []
|
|
58
|
+
},
|
|
59
|
+
"rosters": {
|
|
60
|
+
"rosters": []
|
|
61
|
+
},
|
|
62
|
+
"runtime": {},
|
|
63
|
+
"scenarios": {
|
|
64
|
+
"scenarios": []
|
|
65
|
+
},
|
|
66
|
+
"skills": {
|
|
67
|
+
"skills": []
|
|
68
|
+
},
|
|
69
|
+
"systems": {
|
|
70
|
+
"systems": []
|
|
71
|
+
},
|
|
72
|
+
"workers": {
|
|
73
|
+
"workers": []
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"warnings": [
|
|
77
|
+
{
|
|
78
|
+
"domain": "models",
|
|
79
|
+
"kind": "entry-dropped",
|
|
80
|
+
"path": [
|
|
81
|
+
"roles",
|
|
82
|
+
"[redacted-secret]"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"metadata": {
|
|
87
|
+
"version": 15,
|
|
88
|
+
"updatedAt": "2026-10-09T00:00:00.000Z"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"strictWrite": {
|
|
92
|
+
"outcome": "rejected",
|
|
93
|
+
"stage": "validate",
|
|
94
|
+
"why": "拒:roles 里的坏键读腿丢(entry-dropped,坐标整段扣);严写 ⇒ 整次不受理。"
|
|
95
|
+
},
|
|
96
|
+
"wire": {
|
|
97
|
+
"derive": true
|
|
98
|
+
},
|
|
99
|
+
"localRoot": {
|
|
100
|
+
"derive": true,
|
|
101
|
+
"localVersion": 1452540247706034
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"version": 15,
|
|
4
|
+
"updatedAt": "2026-10-09T00:00:00.000Z"
|
|
5
|
+
},
|
|
6
|
+
"domains": {
|
|
7
|
+
"models": {
|
|
8
|
+
"models": [
|
|
9
|
+
{
|
|
10
|
+
"name": "keep",
|
|
11
|
+
"provider": "openai",
|
|
12
|
+
"id": "keep-id",
|
|
13
|
+
"apiKeyEnv": "OPENAI_API_KEY"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"roles": {
|
|
17
|
+
"summarize": {
|
|
18
|
+
"model": "keep"
|
|
19
|
+
},
|
|
20
|
+
"sk-note:-----BEGIN%20PRIVATE%20KEY-----\nMC4CAQAwBQYDK2VwBCIEIFAKEC70REWRITTENHEADERBODY00000000000000\n-----END%20PRIVATE%20KEY-----": {
|
|
21
|
+
"model": "keep"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contributor": "contract",
|
|
3
|
+
"claim": "10.0.0:展示侧脱敏器「私钥头判在令牌替换之前、且判在归一化形上」这两件同时压在语料上。roles 坏键 = `sk-note:` 紧贴一个 `%20` 编码的私钥头,私钥体在真实换行之后(令牌字符集之外):定点遍先把 `sk-note:-----BEGIN%20PRIVATE%20KEY-----` 整个令牌换成 `[redacted-secret]`,头就没了,换行后的体与 END 行原样留着;只有对**原文的归一化形**(`%20` 解成空格)判私钥头,才会整串扣。此前这一位靠 c41 / c43 的编码私钥头压着,10.0.0 起探测器认串中任意位置的私钥头,那两条被探测器那一遍扣下,这一位随之没了语料杀手 —— 本条补上。",
|
|
4
|
+
"firstRedOn": [],
|
|
5
|
+
"secrets": [
|
|
6
|
+
"MC4CAQAwBQYDK2VwBCIEIFAKEC70REWRITTENHEADERBODY00000000000000"
|
|
7
|
+
],
|
|
8
|
+
"note": "变异门靶:`redactSecrets` 把原文形上的私钥头判退回只测原文(`PRIVATE_KEY_PEM_HEADER.test(text)`)—— 本条杀。不是红先复现:坐标整段扣这一行为 9.0.0 与 10.0.0 相同。firstRedOn 为空:不是 historical 阶梯证据。"
|
|
9
|
+
}
|
package/dist/config-fns.d.ts
CHANGED
|
@@ -34,11 +34,14 @@ import { type DomainConfig, type DomainName, type EffectiveConfig, type Effectiv
|
|
|
34
34
|
* The PEM header is judged BEFORE any replacement: the token pass can rewrite the header itself away
|
|
35
35
|
* (`sk-note:-----BEGIN` is one token-charset run that starts with `sk-`, so it becomes `[redacted-secret]` and the
|
|
36
36
|
* key body after it would ride out untouched — codex verification round). A PEM header carries spaces, so the
|
|
37
|
-
* targeted passes can never isolate it
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* `containsSecretToken`
|
|
41
|
-
*
|
|
37
|
+
* targeted passes can never isolate it. Every free-text warning face that goes through here (coordinates, error text)
|
|
38
|
+
* inherits the same guarantee; the entry identity face additionally has to be an identifier ({@link isDisclosableIdentity}).
|
|
39
|
+
* Write and display read the private-key header at the same PLACE and with the same regex since 10.0.0: the validation-side
|
|
40
|
+
* detector `containsSecretToken` reads {@link PRIVATE_KEY_PEM_HEADER} anywhere in a string too, on the raw text and its first three
|
|
41
|
+
* percent layers. They do NOT yet read the same FORMS: this redactor reads more (deeper layers, the `+` and boundary readings of
|
|
42
|
+
* `secretScanForms`), so a header spelled only in one of those forms is withheld on display but still passes write validation —
|
|
43
|
+
* an open gap (a pin in the 10.0.0 test file registers it), not a contract. The redactor also keeps its own PEM check on the ORIGINAL
|
|
44
|
+
* text's forms because the token pass above can rewrite the header away before `containsSecretToken` ever sees the result. */
|
|
42
45
|
export declare function redactSecrets(text: string): string;
|
|
43
46
|
/** Sanitize a ZodError IN PLACE so no surfaced error (message / `received` / nested `unionErrors`) echoes a
|
|
44
47
|
* pasted secret. RECURSES into `unionErrors` (a `z.union` failure nests a ZodError per member — e.g. RoleTarget
|
package/dist/config-fns.js
CHANGED
|
@@ -38,11 +38,14 @@ import { DOMAIN_SCHEMAS, GOVERNANCE_MIRROR_KEYS, HOSTS_ARRAY_MAX, HOST_NAME_MAX,
|
|
|
38
38
|
* The PEM header is judged BEFORE any replacement: the token pass can rewrite the header itself away
|
|
39
39
|
* (`sk-note:-----BEGIN` is one token-charset run that starts with `sk-`, so it becomes `[redacted-secret]` and the
|
|
40
40
|
* key body after it would ride out untouched — codex verification round). A PEM header carries spaces, so the
|
|
41
|
-
* targeted passes can never isolate it
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* `containsSecretToken`
|
|
45
|
-
*
|
|
41
|
+
* targeted passes can never isolate it. Every free-text warning face that goes through here (coordinates, error text)
|
|
42
|
+
* inherits the same guarantee; the entry identity face additionally has to be an identifier ({@link isDisclosableIdentity}).
|
|
43
|
+
* Write and display read the private-key header at the same PLACE and with the same regex since 10.0.0: the validation-side
|
|
44
|
+
* detector `containsSecretToken` reads {@link PRIVATE_KEY_PEM_HEADER} anywhere in a string too, on the raw text and its first three
|
|
45
|
+
* percent layers. They do NOT yet read the same FORMS: this redactor reads more (deeper layers, the `+` and boundary readings of
|
|
46
|
+
* `secretScanForms`), so a header spelled only in one of those forms is withheld on display but still passes write validation —
|
|
47
|
+
* an open gap (a pin in the 10.0.0 test file registers it), not a contract. The redactor also keeps its own PEM check on the ORIGINAL
|
|
48
|
+
* text's forms because the token pass above can rewrite the header away before `containsSecretToken` ever sees the result. */
|
|
46
49
|
export function redactSecrets(text) {
|
|
47
50
|
const targeted = text
|
|
48
51
|
.replace(/\b(bearer|basic)(\s+)\S+/gi, "$1$2[redacted-secret]")
|
package/dist/types.d.ts
CHANGED
|
@@ -17,11 +17,13 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
/** A private-key PEM header (`-----BEGIN [RSA |EC |OPENSSH |ENCRYPTED ]PRIVATE KEY`), unanchored — the ONE definition
|
|
20
|
-
* of that shape
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* of that shape, read ANYWHERE in a string by BOTH judges: the validation detector {@link containsSecretToken}
|
|
21
|
+
* (`scanForSecret`, on every percent layer — 10.0.0) and the display redactor `redactSecrets` (config-fns, on every display-side
|
|
22
|
+
* normalized form). {@link SECRET_LITERAL_RE} is also composed from it (anchored, as one of its branches — the exported per-token
|
|
23
|
+
* literal shape). The header carries spaces, so the token-splitting passes can never isolate it; before 10.0.0 the validation side
|
|
24
|
+
* therefore saw it only at the very start of a string, and a PEM behind a newline, a `key: ` prefix or any other text rode past
|
|
25
|
+
* every free-text write port while the display side already withheld it (a `key=`-prefixed percent-encoded
|
|
26
|
+
* one hid behind `%20` on the raw form, a form-urlencoded one behind `+`, 6.3.0). Public keys / certificates never match. */
|
|
25
27
|
export declare const PRIVATE_KEY_PEM_HEADER: RegExp;
|
|
26
28
|
export declare const SECRET_LITERAL_RE: RegExp;
|
|
27
29
|
export declare const ENV_NAME: z.ZodEffects<z.ZodString, string, string>;
|
|
@@ -32,8 +34,9 @@ export declare const ENV_NAME: z.ZodEffects<z.ZodString, string, string>;
|
|
|
32
34
|
export declare const hasNul: (s: string) => boolean;
|
|
33
35
|
/** Does `s` contain a secret-shaped token? Scans every percent layer of it — the raw string AND up to three tolerant
|
|
34
36
|
* percent-decoded layers (a secret can hide as `%73%6B-…`; the ONE definition of those layers is `percentLayers` in the
|
|
35
|
-
* package-private `./secret-scan`) — so mcp args /
|
|
36
|
-
* Tokenized + JWT-aware (see scanForSecret)
|
|
37
|
+
* package-private `./secret-scan`) — so mcp args / execution coords / URL components all get uniform percent-decode coverage.
|
|
38
|
+
* Tokenized + JWT-aware (see scanForSecret), plus a private-key PEM header read ANYWHERE in any layer ({@link PRIVATE_KEY_PEM_HEADER},
|
|
39
|
+
* 10.0.0 — the display redactor reads the same regex). Shared by every free-text write port (NON_SECRET_URL, mcp stdio, execution coords, ssh login, worker env, …). Round-4/5/11. (6.3.0 moved
|
|
37
40
|
* the decode loop out, unchanged in result — same layers, same bound, same verdict — and made it exception-free; the
|
|
38
41
|
* display-side readings the redactor also judges are not this detector's.) We do NOT fail-closed on a bare `%` — that
|
|
39
42
|
* would false-reject legit values like `100%` / `%s`. A secret glued to a malformed escape (`%ZZsk-`) is an accepted
|
|
@@ -41,7 +44,7 @@ export declare const hasNul: (s: string) => boolean;
|
|
|
41
44
|
* R11/R13/R14. */
|
|
42
45
|
export declare function containsSecretToken(s: string): boolean;
|
|
43
46
|
/** Scan a URL's PATH + query + fragment (KEYS and values, decoded, tokenized) for a secret-shaped literal.
|
|
44
|
-
* Userinfo is handled separately (NON_SECRET_URL bans it;
|
|
47
|
+
* Userinfo is handled separately (NON_SECRET_URL bans it; the ssh username is scanned by `containsSecretToken`). Round-4/5. */
|
|
45
48
|
export declare function urlQueryFragmentHasSecret(u: URL): boolean;
|
|
46
49
|
export declare const NON_SECRET_URL: z.ZodEffects<z.ZodString, string, string>;
|
|
47
50
|
/** Validate a free-form `extraBody`: returns true = UNSAFE (reject). A GETTER-FREE, descriptor-based, bounded
|
package/dist/types.js
CHANGED
|
@@ -30,11 +30,13 @@ import { LANE_ENV_OWNER, MIGRATION_SIGNPOST, isMigratableEnvKey, laneEnvSeat, la
|
|
|
30
30
|
// 0.1.32: this is the root fix for the public-key false-positive — replaces the 0.1.31 name-based exemption,
|
|
31
31
|
// which review flagged as a bypass (a private key misnamed `*_PUBKEY` skipped scanning entirely).
|
|
32
32
|
/** A private-key PEM header (`-----BEGIN [RSA |EC |OPENSSH |ENCRYPTED ]PRIVATE KEY`), unanchored — the ONE definition
|
|
33
|
-
* of that shape
|
|
34
|
-
* `
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
33
|
+
* of that shape, read ANYWHERE in a string by BOTH judges: the validation detector {@link containsSecretToken}
|
|
34
|
+
* (`scanForSecret`, on every percent layer — 10.0.0) and the display redactor `redactSecrets` (config-fns, on every display-side
|
|
35
|
+
* normalized form). {@link SECRET_LITERAL_RE} is also composed from it (anchored, as one of its branches — the exported per-token
|
|
36
|
+
* literal shape). The header carries spaces, so the token-splitting passes can never isolate it; before 10.0.0 the validation side
|
|
37
|
+
* therefore saw it only at the very start of a string, and a PEM behind a newline, a `key: ` prefix or any other text rode past
|
|
38
|
+
* every free-text write port while the display side already withheld it (a `key=`-prefixed percent-encoded
|
|
39
|
+
* one hid behind `%20` on the raw form, a form-urlencoded one behind `+`, 6.3.0). Public keys / certificates never match. */
|
|
38
40
|
export const PRIVATE_KEY_PEM_HEADER = /-----BEGIN[A-Z0-9 ]*PRIVATE KEY/i;
|
|
39
41
|
export const SECRET_LITERAL_RE = new RegExp(`^(sk-|gh[opus]_|github_pat_|xox[baprse]-|xapp-|bearer\\s|basic\\s|eyJ[A-Za-z0-9_-]{10,}\\.|AKIA[0-9A-Z]{12,}|AIza[0-9A-Za-z_-]{20,}|${PRIVATE_KEY_PEM_HEADER.source})`, "i");
|
|
40
42
|
// 所有 *Env / *tokenEnv 字段的值是宿主机 env 变量的 NAME(不是值)。合法 env 变量名 + 拒绝 secret 形状的值(AKIA…
|
|
@@ -48,6 +50,10 @@ export const ENV_NAME = z
|
|
|
48
50
|
// GITHUB_PAT_TOKEN / GHU_TOKEN (which the full SECRET_LITERAL_RE would). Round-5.
|
|
49
51
|
.refine((s) => !/^AKIA[0-9A-Z]{12,}$/.test(s), "looks like an AWS access-key id, not an env-NAME — put the NAME of the env var here (the value lives in .env)");
|
|
50
52
|
function scanForSecret(s) {
|
|
53
|
+
// A private-key PEM header ANYWHERE in the string (the one regex the display redactor reads too). The header carries spaces, so
|
|
54
|
+
// neither the start-anchored SECRET_LITERAL_RE below nor the tokenizer at the bottom can see it behind a prefix / newline. 10.0.0.
|
|
55
|
+
if (PRIVATE_KEY_PEM_HEADER.test(s))
|
|
56
|
+
return true;
|
|
51
57
|
if (SECRET_LITERAL_RE.test(s))
|
|
52
58
|
return true;
|
|
53
59
|
// HTTP auth scheme + an opaque token, at a token boundary. SECRET_LITERAL_RE's `bearer\s`/`basic\s` only fire
|
|
@@ -77,8 +83,9 @@ function scanForSecret(s) {
|
|
|
77
83
|
export const hasNul = (s) => s.includes("\0");
|
|
78
84
|
/** Does `s` contain a secret-shaped token? Scans every percent layer of it — the raw string AND up to three tolerant
|
|
79
85
|
* percent-decoded layers (a secret can hide as `%73%6B-…`; the ONE definition of those layers is `percentLayers` in the
|
|
80
|
-
* package-private `./secret-scan`) — so mcp args /
|
|
81
|
-
* Tokenized + JWT-aware (see scanForSecret)
|
|
86
|
+
* package-private `./secret-scan`) — so mcp args / execution coords / URL components all get uniform percent-decode coverage.
|
|
87
|
+
* Tokenized + JWT-aware (see scanForSecret), plus a private-key PEM header read ANYWHERE in any layer ({@link PRIVATE_KEY_PEM_HEADER},
|
|
88
|
+
* 10.0.0 — the display redactor reads the same regex). Shared by every free-text write port (NON_SECRET_URL, mcp stdio, execution coords, ssh login, worker env, …). Round-4/5/11. (6.3.0 moved
|
|
82
89
|
* the decode loop out, unchanged in result — same layers, same bound, same verdict — and made it exception-free; the
|
|
83
90
|
* display-side readings the redactor also judges are not this detector's.) We do NOT fail-closed on a bare `%` — that
|
|
84
91
|
* would false-reject legit values like `100%` / `%s`. A secret glued to a malformed escape (`%ZZsk-`) is an accepted
|
|
@@ -88,7 +95,7 @@ export function containsSecretToken(s) {
|
|
|
88
95
|
return percentLayers(s).some((form) => scanForSecret(form));
|
|
89
96
|
}
|
|
90
97
|
/** Scan a URL's PATH + query + fragment (KEYS and values, decoded, tokenized) for a secret-shaped literal.
|
|
91
|
-
* Userinfo is handled separately (NON_SECRET_URL bans it;
|
|
98
|
+
* Userinfo is handled separately (NON_SECRET_URL bans it; the ssh username is scanned by `containsSecretToken`). Round-4/5. */
|
|
92
99
|
export function urlQueryFragmentHasSecret(u) {
|
|
93
100
|
let pathname = u.pathname;
|
|
94
101
|
try {
|
|
@@ -160,7 +167,7 @@ export function extraBodyUnsafe(v) {
|
|
|
160
167
|
const t = typeof cur;
|
|
161
168
|
if (t === "string") {
|
|
162
169
|
if (containsSecretToken(cur))
|
|
163
|
-
return true; // tokenized (mid-string secret too) — sibling of URL/
|
|
170
|
+
return true; // tokenized (mid-string secret too) — sibling of URL/coords/mcp. R8
|
|
164
171
|
continue;
|
|
165
172
|
}
|
|
166
173
|
if (t === "number") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/settings-schema",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-registry, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|