Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 19 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ categories = ["network-programming", "web-programming::http-server"]

# 二进制走 CalVer,crate 走 SemVer —— 两者是两回事,卖给不同的人。
# 这里是 crate 的版本。
version = "0.72.0"
version = "0.73.0"

[workspace.dependencies]
# ── 内部 crate ───────────────────────────────────────────
Expand Down
37 changes: 37 additions & 0 deletions release-notes/0.73.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
This release gives every rule of every guard its own action, lets content rules check the text of the model's answers as well as requests, and adds a record-only action that reports a match without changing anything.

**Upgrade notes**

- The control-plane protocol version (`CONTROL_API_VERSION`) goes from 47 to 48. ThinkWatch Lite connects only to a core with the same protocol version: ThinkWatch Lite 2026.10.18 includes 0.72.0 (protocol 47) and does not connect to 0.73.0. A server used with it stays on 0.72.0 until the app is updated to a release that includes 0.73.0; `sudo twcore upgrade --version 0.72.0 --restart` switches a server back.
- The request store's schema is unchanged; nothing is cleared.
- Configuration: existing configurations load unchanged and keep their behaviour. New optional keys: `actions` and a per-rule `action` under `security.redact`; `scope`, `case_sensitive` and `replacement` on content rules; the content action `replace`. A configuration that uses any of them does not load in 0.72.x.
- A content rule whose `scope` includes `response` cannot use `action: block` or `match: codepoints`; such a configuration is refused with its own message.
- New message codes: `config.rule_scope_block`, `config.rule_scope_codepoints`, `config.rule_scope_empty`, `security.rule_scope_block`, `security.rule_scope_codepoints`, `security.rule_scope_empty`, `security.redact_action_unknown`, `security.unknown_custom_rule`. `security.content_action_unknown` is renamed `security.content_action_bad` (its sentence now lists `replace`); `security.no_action_of_its_own` is removed.
- Changed in the protocol:
- `RuleAction` gains `placeholder` and `replace`; new type `ContentScope` (`request` or `response`).
- Rule views carry `scope` and `replacement` for content rules, the real `case_sensitive` of content matchers, and `action` / `default_action` for redaction rules.
- `CustomRuleSave` gains `scope` and `replacement`; `case_sensitive` applies to content rules and `action` to redaction rules. `SetBuiltinRuleAction` accepts redaction rules.
- New endpoint `PUT /security/{guard}/rules/enabled` (`SetRulesEnabled`) switches several built-in and custom rules on or off with one configuration write; unknown ids or names are refused and nothing is written.
- `SecurityTestRequest` gains `scope` (try a sample as a request or as an answer) and `replacement`.
- `ContentMatched` gains `direction`; its outcome can be `replaced`. `SecretItem` gains `replaced`. `OutcomeDetail` for a content replacement carries `segments` and `replacement`. `SecurityCounts` gains `content_replaced`.

**Actions per rule.** Every rule now has its own action, and a guard's mode decides whether the actions apply: under `enforce` each rule does what it says, under `observe` every rule only records, under `off` nothing is checked.
- Outbound redaction: `placeholder` (replace with a placeholder and put the value back in the answer, as before) or `record` (report the value and send it unchanged; it never enters the ledger). Where a record-only match overlaps a placeholder match, the placeholder match still applies.
- Tool-call inspection: `cut` or `record`, as before.
- Content filter: `block` (requests only), `strip`, `replace` or `record`. `replace` writes the rule's `replacement` text (default `***`) in place of the match, on requests as well as answers, and is not reversed in the answer.

**Content rules for answers.** A custom content rule's `scope` lists `request`, `response` or both. A rule on `response` checks the text the client receives: streamed answers in every supported format (including Codex freeform tool input), whole answers and the Responses WebSocket. Tool calls are left to tool-call inspection and thinking is not checked. Matching is the same as for requests, with `case_sensitive` (off by default).
- Streamed answers are not buffered. Each text holds back only the characters that could still grow into a match: the longest keyword less one, or a regular expression's longest possible match, at most 256 characters; deleting rules hold back as many again. A match longer than that may have its start sent already.
- Deleting repeats until no match remains, so deleting one match cannot form another. Whole answers always come out clean. A stream gives the same result where a match is nested one level deep; with deeper nesting the start of a match may already have been sent, and the rest is deleted.
- Regular expressions anchored to the start or end of a text or line are checked once, on the model's own text.
- Copies of the answer text in the same stream (closing events that repeat the whole text) are rewritten to match what was sent. When an answer rule can delete or replace, logprobs are removed from streamed answers and from whole answers whose text changed, since they repeat the text word by word.
- Held text is sent when the stream ends, breaks off or carries an upstream error.
- The stored answer is the one the client received.

**Requests.** Deleting and replacing on requests repeat until no match remains; a part still changing after 64 rounds is emptied. Case-insensitive matching uses the same method for requests and answers, so characters whose lowercase form has a different length are matched correctly.

**Redaction fixes.** Overlapping placeholder matches merge into one placeholder, so no part of either is sent in clear. Custom redaction patterns match within one JSON string at a time and can no longer make a request body invalid JSON. Matches on placeholders already in the text are ignored before overlapping matches merge.

**Stored bodies and security log.** Stored bodies and log excerpts put the placeholder back wherever a request value appears as written, so a value the model repeats without its context (a connection-string password, for example) is not stored in clear. In valid JSON only string contents are changed; text that is not valid JSON, such as an event stream, is masked wherever a value appears, numbers included. Excerpts never show part of a value.

**WebSocket.** A frame from an answer that has already ended is filtered by that answer's own filter and never changes the answer in progress, also when a relay rewrites answer ids.
Loading