From c6501e97cae2742cc5dda5f63cd59017efbf54f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 11:05:11 +0000 Subject: [PATCH 1/3] fix(deploy): disable service account token automount (KSV-0036) The gateway pod never calls the Kubernetes API, so the default ServiceAccount token should not be automounted into the container filesystem. This is the one Trivy Kubernetes Security (KSV) misconfig pattern the prior deployment-hardening pass (KSV-0020/KSV-0021/ KSV-0125, PR #14) left unaddressed. --- deploy/kubernetes/waf-ids-ai-soc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/kubernetes/waf-ids-ai-soc.yaml b/deploy/kubernetes/waf-ids-ai-soc.yaml index f811ecb4..fd58c394 100644 --- a/deploy/kubernetes/waf-ids-ai-soc.yaml +++ b/deploy/kubernetes/waf-ids-ai-soc.yaml @@ -41,6 +41,7 @@ spec: labels: app.kubernetes.io/name: waf-ids-ai-soc spec: + automountServiceAccountToken: false securityContext: runAsNonRoot: true fsGroup: 10001 From be45e69011bbc13ebde6f799a186f68d59f6eae6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 11:23:09 +0000 Subject: [PATCH 2/3] fix(shutdown): register SIGTERM handler before announcing readiness tests/binary.rs::binary_serves_then_shuts_down_on_sigterm was failing CI on this branch (job 99248003533): the gateway exited with raw signal 15 instead of code 0. Root cause: shutdown_signal() was an async fn, so tokio::signal::unix::signal() (which installs the OS-level handler) only ran once the future was first polled inside axum::serve(...).with_graceful_shutdown(shutdown) -- which happens after run_from_env has already bound the listener and printed the readiness line. A SIGTERM delivered in that window (exactly what the e2e test, and any container runtime with a short terminationGracePeriodSeconds, does right after startup) fell through to the default disposition and killed the process instead of triggering a graceful shutdown. Fix: split registration from waiting. install_shutdown_signal() calls tokio::signal::unix::signal() synchronously and returns only the subsequent .recv() as a future, so main() installs the handler before run_from_env runs at all. Verified: `cargo test -p waf-ids-ai-soc --test binary` was reliably failing before this change and passes 5/5 after. --- src/main.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2c8fc828..80e03481 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,21 +4,31 @@ #[cfg(not(test))] #[tokio::main] async fn main() -> Result<(), Box> { - waf_ids_ai_soc::run_from_env(Box::pin(shutdown_signal())).await + // Registered eagerly, before `run_from_env` binds its listener and prints + // the readiness line, so a SIGTERM delivered immediately on startup (as + // container runtimes and the e2e test harness do) cannot race the OS-level + // handler installation and fall through to the default "kill" disposition. + let shutdown = install_shutdown_signal(); + waf_ids_ai_soc::run_from_env(Box::pin(shutdown)).await } #[cfg(all(not(test), unix))] -async fn shutdown_signal() { - // Shut down gracefully on SIGTERM (what container runtimes and the e2e test - // harness send) so in-flight requests drain and the process exits cleanly. +fn install_shutdown_signal() -> impl std::future::Future + Send + 'static { + // `tokio::signal::unix::signal` registers the handler synchronously on + // call; only the subsequent `.recv()` wait is deferred to the returned + // future, so callers must invoke this *before* announcing readiness. let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) .expect("install SIGTERM handler"); - term.recv().await; + async move { + term.recv().await; + } } #[cfg(all(not(test), not(unix)))] -async fn shutdown_signal() { - tokio::signal::ctrl_c() - .await - .expect("install Ctrl-C handler"); +fn install_shutdown_signal() -> impl std::future::Future + Send + 'static { + async { + tokio::signal::ctrl_c() + .await + .expect("install Ctrl-C handler"); + } } From e67a9396d166e1b9ce7b00fcde7c1bc14743d1f6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 30 Aug 2026 11:27:43 +0000 Subject: [PATCH 3/3] fix(shutdown): register Windows Ctrl-C handler synchronously too Devin Review flagged (PR #132, src/main.rs:29-32) that the previous commit only fixed the readiness/signal-registration race on the Unix path: the non-Unix install_shutdown_signal() still wrapped tokio::signal::ctrl_c() in an async block, so registration stayed lazy and an immediate Ctrl-C could still kill the process before the handler was installed. tokio::signal::windows::ctrl_c() (unlike the cross-platform tokio::signal::ctrl_c() convenience fn) registers synchronously and returns a CtrlC handle, exactly mirroring unix::signal()/Signal, so apply the same split-registration-from-waiting fix there. Not covered by tests/binary.rs (its Windows test only exercises a forced kill, not graceful Ctrl-C), and this sandbox has no Windows cross-compiler to build/run against; verified the API shape directly against the vendored tokio 1.53.1 source (signal/windows.rs: `pub fn ctrl_c() -> io::Result`, `pub async fn recv(&mut self) -> Option<()>`), which matches the already-used unix::Signal shape exactly. --- src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 80e03481..7244a80c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,9 +26,10 @@ fn install_shutdown_signal() -> impl std::future::Future + Send + ' #[cfg(all(not(test), not(unix)))] fn install_shutdown_signal() -> impl std::future::Future + Send + 'static { - async { - tokio::signal::ctrl_c() - .await - .expect("install Ctrl-C handler"); + // Mirrors the Unix path: `tokio::signal::windows::ctrl_c` registers the + // handler synchronously, so only `.recv()` is deferred to the future. + let mut ctrl_c = tokio::signal::windows::ctrl_c().expect("install Ctrl-C handler"); + async move { + ctrl_c.recv().await; } }