refactor(gateway): run the request guards on core's tw-guard engines - #49
Merged
Merged
Conversation
Core v0.43.0 moved this gateway's guard engines into the shared `tw-guard` crate. Switch to them and drop the copies here: - Hidden text: `tw_guard::hidden::scan_request` with `SMUGGLING`. The audit event's `found` items gain `revealed`, the text the tag characters spell. - Content filter: `tw_guard::content`. Stored rules keep their format (`security.content_filter_patterns`); each compiles through `Rule::new`, a bad one is skipped and the rest run. Rules are keyed by position, so two with the same name both report. The settings validator runs the same compile, so an empty pattern is now refused on save. Presets are core's built-ins grouped as injection / persona / chinese (were basic / strict / chinese). - Output length: `tw_guard::output` with byte counting, as before. The cap now also applies to streams: the frame that crosses it is not sent and the stream ends with an error in the caller's format (a Gemini JSON array ends with an error element and `]`). Cache hits are checked against the cap in force. Bumps tw-dialect, tw-guard and tw-breaker to v0.43.0 and removes `common::regex_util`, which nothing uses any more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fylorn
force-pushed
the
refactor/tw-guard-engines
branch
from
September 24, 2026 09:04
d6a2aed to
076f3df
Compare
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Core v0.43.0 (ThinkWatch-Core#167, #169) moved this gateway's guard engines into the shared
tw-guardcrate. This switches to them and deletes the copies here.Changes
Core pin: tw-dialect, tw-guard, tw-breaker →
v0.43.0(caec54c).Hidden text (
crates/gateway/src/hidden_text.rs): scanning istw_guard::hidden::scan_request(&request, &SMUGGLING). Same semantics (user messages and tool results; log, warn or block; nothing stripped). The audit event'sfounditems gainrevealed, the ASCII the tag characters spell.Content filter (
crates/gateway/src/content_filter.rs): the engine istw_guard::content.security.content_filter_patterns,DenyRuleConfig); admin API shapes unchanged.Rule::new; a bad one is skipped with a warning and the rest run (as before). Rules are keyed by position, so two rules with the same name both report.builtins(), grouped bygroup:injection/persona/chinese(werebasic/strict/chinese). Presets are copied into the rule list, so nothing stored refers to the group ids; only the web i18n keys changed.Output length (
crates/gateway/src/output_guardrails.rs):max_lengthusestw_guard::outputwithUnit::Bytes, so the cap still counts bytes.StreamLimitmeters the client-format bytes next to the tool-call inspection in the pump; the frame that crosses the cap is not sent, and the stream ends like a tool-inspection cut: the converter'sfail, ortw_dialect::convert::error_framefor a passthrough. A Gemini caller withoutalt=ssegets the error as the array's last element, then](the pipeline is SSE inside andJsonArrayFramercloses it). Each Responses WebSocket turn is its own stream, so a turn over the cap fails withresponse.failedand the connection stays.common::regex_utilis removed: nothing uses it any more.Tests
tests/output_limit.rs: stream cut on chat / messages / responses / Gemini SSE, Gemini JSON array ends with an error element, under the cap untouched, whole answer withheld on all four surfaces, bytes not characters, WebSocket turn cut twice on one connection.content_filter_pii.rs: block on all four surfaces streaming and not (upstream untouched), a match inside a tool result, warn/log pass, presets endpoint groups and save round-trip, uncompilable rules refused on save.hidden_text.rs: block on all four surfaces (tool result in each format) streaming and not,offpasses through unstripped,revealedin the audit event.Local: fmt, clippy
--all-targetsand--libwith-D warnings, unit tests, and the integration tests for output_limit, content_filter_pii, hidden_text, gateway_proxy, tool_inspection, gateway_gemini, gateway_responses_ws all pass.🤖 Generated with Claude Code