|
26 | 26 | //! The content filter reads the caller's text where the request's own |
27 | 27 | //! format puts it, and a rule that deletes or replaces text does so there: |
28 | 28 | //! the request goes on as the changed one, decoded again. Outbound redaction |
29 | | -//! then searches the whole request, numbers what it finds once, and every |
30 | | -//! hop — forwarded or converted — goes out through it (see |
31 | | -//! `crate::redaction`). Both are thinkwatch-core's, shared with the |
32 | | -//! desktop gateway. |
| 29 | +//! then searches the text written in the request, numbers what it finds |
| 30 | +//! once, and every hop — forwarded or converted — goes out through it, |
| 31 | +//! searched as the format it is sent in (see `crate::redaction`). Both |
| 32 | +//! are thinkwatch-core's, shared with the desktop gateway. |
33 | 33 |
|
34 | 34 | use std::convert::Infallible; |
35 | 35 |
|
@@ -61,7 +61,7 @@ use crate::guards::Caller; |
61 | 61 | use crate::lifecycle::Completed; |
62 | 62 | use crate::metadata::RequestMetadata; |
63 | 63 | use crate::protocol::UpstreamProtocol; |
64 | | -use crate::redaction::Redaction; |
| 64 | +use crate::redaction::{Redaction, Shape}; |
65 | 65 | use crate::router::RouteEntry; |
66 | 66 | use tw_guard::redact::replace::Ledger; |
67 | 67 |
|
@@ -446,7 +446,11 @@ impl Outbound { |
446 | 446 | if let Some(stripped) = tw_dialect::convert::strip_carried(client, &bytes) { |
447 | 447 | bytes = stripped; |
448 | 448 | } |
449 | | - let (bytes, ledger) = self.redaction.replace(bytes, &self.ledger); |
| 449 | + // Sent in the caller's own format: its written text is where |
| 450 | + // the caller's format puts it. |
| 451 | + let (bytes, ledger) = |
| 452 | + self.redaction |
| 453 | + .replace(bytes, &self.ledger, Shape::Generate(client)); |
450 | 454 | return Ok(Wire { |
451 | 455 | body: bytes, |
452 | 456 | path, |
@@ -479,8 +483,11 @@ impl Outbound { |
479 | 483 | body = serde_json::to_vec(&v).unwrap_or(body); |
480 | 484 | } |
481 | 485 | // The conversion moved the placeholders along with the text; one it |
482 | | - // assembled from two pieces is numbered here. |
483 | | - let (body, ledger) = self.redaction.replace(body, &self.ledger); |
| 486 | + // assembled from two pieces is numbered here. The text is searched |
| 487 | + // where the upstream's format puts it. |
| 488 | + let (body, ledger) = |
| 489 | + self.redaction |
| 490 | + .replace(body, &self.ledger, Shape::Generate(protocol.dialect())); |
484 | 491 | // With none of the caller's own, any breakpoints are the ones the |
485 | 492 | // conversion added for Claude. |
486 | 493 | let auto_cache = decoded.request.cache.is_empty() |
@@ -763,21 +770,24 @@ async fn run( |
763 | 770 | let decoded = |
764 | 771 | tw_dialect::convert::decode(surface.dialect, &raw, path, internal_query(surface.dialect)); |
765 | 772 |
|
766 | | - // 5. Outbound redaction: the whole request is searched and what is |
767 | | - // found numbered once, in the order the caller wrote it. |
| 773 | + // 5. Outbound redaction: the text written in the request — located by |
| 774 | + // the structure of the caller's format, never its ids, types or |
| 775 | + // names — is searched and what is found numbered once, in the order |
| 776 | + // the caller wrote it. |
768 | 777 | // Placeholders are stable per value, so two callers sending the same |
769 | 778 | // structure redact to the same bytes and share a cache slot; each |
770 | 779 | // restores their own values on the way out. |
771 | 780 | // |
772 | 781 | // The audit row keeps what the caller wrote (their at-rest |
773 | 782 | // redaction setting applies on top). |
774 | | - let (findings, ledger) = guards.redaction.look(&body); |
| 783 | + let shape = Shape::Generate(surface.dialect); |
| 784 | + let (findings, ledger) = guards.redaction.look(&body, shape); |
775 | 785 | crate::redaction::record(&state.audit, &caller, &guards.redaction, &findings); |
776 | 786 | let request_for_audit = body.to_vec(); |
777 | 787 | let outbound_body = if ledger.is_empty() { |
778 | 788 | raw |
779 | 789 | } else { |
780 | | - let (replaced, _) = guards.redaction.replace(body.to_vec(), &ledger); |
| 790 | + let (replaced, _) = guards.redaction.replace(body.to_vec(), &ledger, shape); |
781 | 791 | serde_json::from_slice(&replaced).map_err(|_| { |
782 | 792 | ctx.emit(GatewayError::TransformError( |
783 | 793 | "The request could not be read after redaction.".into(), |
@@ -1227,7 +1237,7 @@ mod tests { |
1227 | 1237 | mode: tw_guard::policy::Mode::Off, |
1228 | 1238 | ..Default::default() |
1229 | 1239 | }); |
1230 | | - let (_, ledger) = redaction.look(b"{}"); |
| 1240 | + let (_, ledger) = redaction.look(b"{}", Shape::Generate(surface.dialect)); |
1231 | 1241 | Outbound { |
1232 | 1242 | surface, |
1233 | 1243 | path: path.into(), |
|
0 commit comments