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
68 changes: 68 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,74 @@ target.

## [Unreleased]

Secrets and privacy recognises far more: the built-in redaction catalog
grows from 30 rules to 120 in 20 groups, and the API keys saved
for the providers are recognised by value, whatever their format. The
console shows the rules' names and descriptions as thinkwatch-core
writes them, in both languages. The thinkwatch-core crates move from
v0.83.1 to v0.85.0.

No new setting, column or route; the redaction policy's shape is
unchanged.

### Read before upgrading

- **New credential rules ship on.** Under the On state of Secrets and
privacy (`security.redact` mode `enforce`), values they find start
being replaced with placeholders: among them credentials in request
headers, command lines, URLs and bot webhooks, and secrets assigned in
`.env`-style configuration (`DB_PASSWORD=…`). Under Monitor, the
factory state, they are only recorded. A rule that gets in the way can
be switched off on its own in the console.
- **Some keys are reported under a rule of their own.** `sk-or-v1-`
(OpenRouter), `sk-lf-` (Langfuse), `sk-svcacct-`, `sk-admin-` and
`sk-api-` keys were reported as `openai-api-key`; they now have their
own rule ids, and so do audit events and the
`gateway_redaction_found_total` metric's `kind` label, which follows
the new groups (`ai-services`, `dev-platforms`, `cloud`, …) instead of
`api-keys` and `personal`.

### Added

- **Expanded redaction rules** (thinkwatch-core v0.85.0): about 50 more
prefixed credentials (AI services, code hosting, cloud, payments and
collaboration), credentials by position (request headers, command
lines, URL parameters, bot webhooks, configuration assignments),
cryptocurrency recovery phrases and private keys, and personal
information: identity documents, foreign ID numbers, IBAN, phone
numbers, addresses and coordinates, vehicles, device identifiers and
values by field name. Of the new personal-information rules only the
two that can be checked digit by digit ship on (passport
machine-readable zone, IBAN), alongside the resident ID and bank card
numbers as before.
- **Saved upstream keys are recognised by value.** The API keys in each
provider's credential headers (the `Bearer` scheme left out) and a
Bedrock provider's secret access key are matched wherever they appear
in a request, as written or JSON-escaped; values shorter than 12
characters are not. The list follows the providers: it is rebuilt when
they load, when one is created, changed or deleted, and on the other
instances through the router reload. The same keys count as
credentials for the dangerous-commands rule "credential sent to an
unknown host", each allowed to its own provider's host, and the
console's trial recognises them too.
- **A saved key's audit event names its provider, never the value.** The
`gateway.redaction_*` event of the rule `thinkwatch-saved-secret`
carries `sources` (`[{"kind": "upstream-key", "name": <provider>}]`)
and no masked values; the security log shows it as the provider's API
key.

### Changed

- **Rule names and descriptions come from thinkwatch-core.** The security
page shows each built-in rule's name, reason and description and each
group's name as the rule view gives them, in English or Chinese; the
console no longer keeps its own name tables. Security-log rows and
trial hits name the rule by looking its id up in the rule view. The
rule search also matches prefixes, field names and descriptions.
- **The conversion of the old PII pattern list** switches on or off only
the four personal-information rules that list could express; the new
personal-information rules keep their factory setting.

## [3.10.0] — 2026-10-25

Upstreams that fail for the moment before any of the answer reached the
Expand Down
21 changes: 11 additions & 10 deletions Cargo.lock

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

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ opt-level = 3
# never re-exported through a local shim. And the reverse: something only
# this side uses (the at-rest crypto, IMDSv2 credentials, the gateway error)
# lives here, not in core.
tw-bedrock = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.83.1" }
tw-breaker = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.83.1" }
tw-dialect = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.83.1" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.83.1" }
tw-bedrock = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" }
tw-breaker = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" }
tw-dialect = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" }

# Web framework
axum = { version = "0.8", features = ["macros", "ws"] }
Expand Down
8 changes: 5 additions & 3 deletions crates/common/src/guard_policy/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -508,11 +508,13 @@ pub fn redact(patterns: Option<&Value>) -> RedactPolicy {
}
}
}
// Personal data is redacted only where the list asked for it. The
// credential rules are not personal data and stay on.
// The personal data the old list could express (the seeded patterns'
// rules) is redacted only where the list asked for it. Credential
// rules are not personal data and stay on; personal rules the old
// list never knew run as they ship.
for b in tw_guard::redact::rules::BUILTINS
.iter()
.filter(|b| b.kind == tw_guard::redact::rules::Kind::Personal)
.filter(|b| b.kind.is_personal() && SEEDED_PII.iter().any(|(_, _, id)| *id == b.id))
{
match (covered.contains(b.id), b.on_by_default) {
(true, false) => policy.enable.push(b.id.to_string()),
Expand Down
1 change: 1 addition & 0 deletions crates/common/src/guard_policy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use tw_guard::redact::rules::RuleSet;
use crate::dynamic_config::DynamicConfig;

pub mod legacy;
pub mod saved;

/// The settings key a guard's policy is stored under.
pub fn key(guard: Guard) -> &'static str {
Expand Down
Loading
Loading