@sema-agent/settings-schema 6.1.0 → 6.3.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 +265 -0
- package/README.md +40 -9
- package/conformance/VERSION +1 -1
- package/conformance/cases/c41-s792-encoded-pem-identity/expected.json +166 -0
- package/conformance/cases/c41-s792-encoded-pem-identity/input.json +62 -0
- package/conformance/cases/c41-s792-encoded-pem-identity/meta.json +12 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/expected.json +120 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/input.json +25 -0
- package/conformance/cases/c42-s792-unknown-key-secret-redacted/meta.json +11 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/expected.json +137 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/input.json +44 -0
- package/conformance/cases/c43-s792-form-and-deep-encoded-pem/meta.json +12 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/expected.json +115 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/input.json +30 -0
- package/conformance/cases/c44-s792-utf8-escape-glued-token/meta.json +10 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/expected.json +185 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/input.json +78 -0
- package/conformance/cases/c45-s792-identity-bounds-and-decode-depth/meta.json +11 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/expected.json +88 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/input.json +18 -0
- package/conformance/cases/c46-s811-host-name-url-userinfo-characterization/meta.json +6 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/expected.json +98 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/input.json +26 -0
- package/conformance/cases/c47-s811-roles-key-url-userinfo-characterization/meta.json +6 -0
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/expected.json +0 -8
- package/conformance/cases/cli01-model-name-nfc-vs-nfd/meta.json +2 -1
- package/conformance/cases/cli02-control-and-zero-width-chars/expected.json +0 -8
- package/conformance/cases/cli02-control-and-zero-width-chars/meta.json +2 -1
- package/dist/config-fns.d.ts +62 -14
- package/dist/config-fns.js +105 -30
- package/dist/cross-session.d.ts +8 -5
- package/dist/cross-session.js +8 -5
- package/dist/fleet.d.ts +163 -0
- package/dist/fleet.js +126 -5
- package/dist/secret-scan.d.ts +39 -0
- package/dist/secret-scan.js +110 -0
- package/dist/types.d.ts +79 -72
- package/dist/types.js +17 -31
- package/package.json +2 -2
package/dist/types.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import { MAX_EXTRA_BODY_DEPTH } from "./hash.js"; // reconcile the extraBody depth cap with the hash cap (R18)
|
|
20
20
|
import { DispatchPolicy } from "./fleet.js"; // WorkerSpec.fleet.dispatch (scheduling framework D3)
|
|
21
|
+
import { carriesUserinfo, percentLayers } from "./secret-scan.js"; // package-private normalization + the userinfo judgement (not in the barrel)
|
|
21
22
|
// COARSE, best-effort literal-secret SHAPE detector (NOT exhaustive — the real boundary is the env-NAME schema
|
|
22
23
|
// on structured fields). Rejected in free-form value fields (worker `env`, model `extraBody`), URL components,
|
|
23
24
|
// AND as an env-NAME value (an AWS key id is a valid env-NAME charset yet is a secret VALUE, not a NAME). Case-
|
|
@@ -28,9 +29,10 @@ import { DispatchPolicy } from "./fleet.js"; // WorkerSpec.fleet.dispatch (sched
|
|
|
28
29
|
// which review flagged as a bypass (a private key misnamed `*_PUBKEY` skipped scanning entirely).
|
|
29
30
|
/** A private-key PEM header (`-----BEGIN [RSA |EC |OPENSSH |ENCRYPTED ]PRIVATE KEY`), unanchored — the ONE definition
|
|
30
31
|
* of that shape: {@link SECRET_LITERAL_RE} is composed from it (anchored, as one of its branches), and
|
|
31
|
-
* `redactSecrets` (config-fns) tests it ANYWHERE in a string. It carries spaces,
|
|
32
|
-
* {@link containsSecretToken} only sees it at the very start of a string; a PEM behind
|
|
33
|
-
* slipped past (S-712 codex review [high])
|
|
32
|
+
* `redactSecrets` (config-fns) tests it ANYWHERE in a string, on every display-side normalized form. It carries spaces,
|
|
33
|
+
* so the token-splitting detector {@link containsSecretToken} only sees it at the very start of a string; a PEM behind
|
|
34
|
+
* a newline or a `key: ` prefix slipped past (S-712 codex review [high]), and a `key=`-prefixed percent-encoded one
|
|
35
|
+
* hid it behind `%20` on the raw form, a form-urlencoded one behind `+` (6.3.0). Public keys / certificates never match. */
|
|
34
36
|
export const PRIVATE_KEY_PEM_HEADER = /-----BEGIN[A-Z0-9 ]*PRIVATE KEY/i;
|
|
35
37
|
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");
|
|
36
38
|
// 所有 *Env / *tokenEnv 字段的值是宿主机 env 变量的 NAME(不是值)。合法 env 变量名 + 拒绝 secret 形状的值(AKIA…
|
|
@@ -71,33 +73,17 @@ function scanForSecret(s) {
|
|
|
71
73
|
* fs path — so the worker dies at exec/boot, not at config edit. A NUL is never legitimate in a command / path
|
|
72
74
|
* / host / image, so reject it on every exec-bound string field (mcp stdio + the whole remoteExec contract). R20. */
|
|
73
75
|
export const hasNul = (s) => s.includes("\0");
|
|
74
|
-
/** Does `s` contain a secret-shaped token? Scans the raw string AND
|
|
75
|
-
* as `%73%6B
|
|
76
|
-
*
|
|
76
|
+
/** Does `s` contain a secret-shaped token? Scans every percent layer of it — the raw string AND up to three tolerant
|
|
77
|
+
* percent-decoded layers (a secret can hide as `%73%6B-…`; the ONE definition of those layers is `percentLayers` in the
|
|
78
|
+
* package-private `./secret-scan`) — so mcp args / dockerHost / URL components all get uniform percent-decode coverage.
|
|
79
|
+
* Tokenized + JWT-aware (see scanForSecret). Shared by NON_SECRET_URL + dockerHost + mcp stdio. Round-4/5/11. (6.3.0 moved
|
|
80
|
+
* the decode loop out, unchanged in result — same layers, same bound, same verdict — and made it exception-free; the
|
|
81
|
+
* display-side readings the redactor also judges are not this detector's.) We do NOT fail-closed on a bare `%` — that
|
|
82
|
+
* would false-reject legit values like `100%` / `%s`. A secret glued to a malformed escape (`%ZZsk-`) is an accepted
|
|
83
|
+
* coarse-heuristic gap (no consumer reconstructs it; the real guarantee is the env-NAME boundary on structured fields).
|
|
84
|
+
* R11/R13/R14. */
|
|
77
85
|
export function containsSecretToken(s) {
|
|
78
|
-
|
|
79
|
-
// → `sk-…`). TOLERANT decode: each valid %HH is decoded, a malformed escape (`%ZZ`, `%` at end) is left as-is
|
|
80
|
-
// so one bad escape elsewhere can't abort the whole decode (R13's all-or-nothing decodeURIComponent did). We
|
|
81
|
-
// do NOT fail-closed on a bare `%` — that would false-reject legit values like `100%` / `%s`. A secret glued
|
|
82
|
-
// to a malformed escape (`%ZZsk-`) is an accepted coarse-heuristic gap (no consumer reconstructs it; the real
|
|
83
|
-
// guarantee is the env-NAME boundary on structured fields). Bounded → deterministic. R11/R13/R14. */
|
|
84
|
-
let cur = s;
|
|
85
|
-
for (let i = 0; i < 4; i++) {
|
|
86
|
-
if (scanForSecret(cur))
|
|
87
|
-
return true;
|
|
88
|
-
const next = cur.replace(/%[0-9A-Fa-f]{2}/g, (m) => {
|
|
89
|
-
try {
|
|
90
|
-
return decodeURIComponent(m);
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
return m;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
if (next === cur)
|
|
97
|
-
break;
|
|
98
|
-
cur = next;
|
|
99
|
-
}
|
|
100
|
-
return false;
|
|
86
|
+
return percentLayers(s).some((form) => scanForSecret(form));
|
|
101
87
|
}
|
|
102
88
|
/** Scan a URL's PATH + query + fragment (KEYS and values, decoded, tokenized) for a secret-shaped literal.
|
|
103
89
|
* Userinfo is handled separately (NON_SECRET_URL bans it; dockerHost scans the ssh user). Round-4/5. */
|
|
@@ -141,7 +127,7 @@ export const NON_SECRET_URL = z
|
|
|
141
127
|
.refine((s) => {
|
|
142
128
|
try {
|
|
143
129
|
const u = new URL(s);
|
|
144
|
-
if (!(u.protocol === "http:" || u.protocol === "https:") || !u.host || u
|
|
130
|
+
if (!(u.protocol === "http:" || u.protocol === "https:") || !u.host || carriesUserinfo(u))
|
|
145
131
|
return false;
|
|
146
132
|
return !urlQueryFragmentHasSecret(u);
|
|
147
133
|
}
|
|
@@ -2053,7 +2039,7 @@ export const PLUGIN_ORIGIN = z
|
|
|
2053
2039
|
return true; // 已被上一条拒;避免对非 URL 再报一条噪声
|
|
2054
2040
|
try {
|
|
2055
2041
|
const u = new URL(s);
|
|
2056
|
-
return !u
|
|
2042
|
+
return !carriesUserinfo(u) && !urlQueryFragmentHasSecret(u);
|
|
2057
2043
|
}
|
|
2058
2044
|
catch {
|
|
2059
2045
|
return false; // claims to be https but does not parse → reject
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/settings-schema",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.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",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"publish:npm": "npm publish --access public --@sema-agent:registry=https://registry.npmjs.org/ --\"//registry.npmjs.org/:_authToken=${NPM_TOKEN}\""
|
|
129
129
|
},
|
|
130
130
|
"devDependencies": {
|
|
131
|
-
"@sema-agent/core": "
|
|
131
|
+
"@sema-agent/core": "7.33.1",
|
|
132
132
|
"@types/node": "^22.10.0",
|
|
133
133
|
"esbuild": "^0.21.5",
|
|
134
134
|
"typescript": "^5.7.0",
|