Skip to main content

max / makenotwork

Log CSP violations that the default filter was discarding RUST_LOG is unset in production, so the process ran on the fallback filter, which named only makenotwork, tower_http and sqlx. CSP reports are emitted on their own csp_violation target, which matched no directive, so every report the endpoint accepted was dropped. Nothing outside would show it: the endpoint answers 204 whatever happens, and the existing tests assert status codes only. Verified against prod before the fix by posting a report and finding zero csp_violation lines in six hours of journal. Hoist the fallback filter into a DEFAULT_LOG_FILTER const so a test can put a violation through it and assert the event survives.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-31 02:55 UTC
Signed with PGP, not checked
Commit: 2632dafe36a63006e7033c36e7eceb433ef9a053
Parent: 143c5c7
3 files changed, +70 insertions, -1 deletion
@@ -6,6 +6,16 @@
6 6 //! maintainer wiki; the risk history is in the gitignored `docs/audit_review.md`.
7 7 //! <!-- wiki: mnw-server-overview -->
8 8
9 + /// Tracing filter used when `RUST_LOG` is unset, which is how production runs.
10 + ///
11 + /// `csp_violation` is listed explicitly because it is its own target rather
12 + /// than a module under the crate path, so `makenotwork=...` does not reach it.
13 + /// It lives here rather than in `main.rs` so a test can assert an event on that
14 + /// target actually survives the filter; the endpoint answers 204 either way, so
15 + /// nothing else would notice the reports being dropped.
16 + pub const DEFAULT_LOG_FILTER: &str =
17 + "makenotwork=debug,tower_http=debug,sqlx=info,csp_violation=warn";
18 +
9 19 pub mod access_gate;
10 20 pub mod auth;
11 21 pub mod background;
@@ -31,7 +31,7 @@
31 31 tracing_subscriber::registry()
32 32 .with(
33 33 tracing_subscriber::EnvFilter::try_from_default_env()
34 - .unwrap_or_else(|_| "makenotwork=debug,tower_http=debug,sqlx=info".into()),
34 + .unwrap_or_else(|_| makenotwork::DEFAULT_LOG_FILTER.into()),
35 35 )
36 36 .with(if cfg!(debug_assertions) {
37 37 tracing_subscriber::fmt::layer().boxed()
@@ -132,6 +132,65 @@
132 132 );
133 133 }
134 134
135 + /// The endpoint answers 204 whether or not anything is recorded, so every
136 + /// other test here passes with the reports going nowhere. That is exactly
137 + /// what happened in production: `RUST_LOG` is unset, the default filter
138 + /// named only the crate path, and `csp_violation` matched no directive, so
139 + /// the deployed control discarded every report it accepted.
140 + #[tokio::test]
141 + async fn default_filter_actually_records_a_violation() {
142 + use std::io::Write;
143 + use std::sync::{Arc, Mutex};
144 + use tracing_subscriber::layer::SubscriberExt;
145 +
146 + #[derive(Clone)]
147 + struct Buffer(Arc<Mutex<Vec<u8>>>);
148 + impl Write for Buffer {
149 + fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
150 + self.0.lock().unwrap().extend_from_slice(buf);
151 + Ok(buf.len())
152 + }
153 + fn flush(&mut self) -> std::io::Result<()> {
154 + Ok(())
155 + }
156 + }
157 + impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for Buffer {
158 + type Writer = Self;
159 + fn make_writer(&'a self) -> Self::Writer {
160 + self.clone()
161 + }
162 + }
163 +
164 + let buffer = Buffer(Arc::new(Mutex::new(Vec::new())));
165 + let subscriber = tracing_subscriber::registry()
166 + .with(tracing_subscriber::EnvFilter::new(
167 + crate::DEFAULT_LOG_FILTER,
168 + ))
169 + .with(
170 + tracing_subscriber::fmt::layer()
171 + .with_ansi(false)
172 + .with_writer(buffer.clone()),
173 + );
174 +
175 + let body = br#"{"csp-report":{"document-uri":"https://makenot.work/","violated-directive":"style-src","blocked-uri":"inline"}}"#;
176 + tracing::subscriber::with_default(subscriber, || {
177 + log_violation(
178 + &serde_json::from_slice::<Value>(body).unwrap()["csp-report"],
179 + "report-uri",
180 + );
181 + });
182 +
183 + let logged = String::from_utf8(buffer.0.lock().unwrap().clone()).unwrap();
184 + assert!(
185 + logged.contains("CSP violation reported"),
186 + "the default filter dropped the violation; nothing would reach prod logs. got: {logged:?}"
187 + );
188 + assert!(
189 + logged.contains("style-src"),
190 + "directive missing: {logged:?}"
191 + );
192 + }
193 +
135 194 #[test]
136 195 fn truncates_long_fields_on_a_char_boundary() {
137 196 let long = "\u{e9}".repeat(MAX_FIELD_LEN);