Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
**Vulnerability:** The manual SVG sanitizer `apps/web/src/lib/sanitize-svg.ts` used an insufficient allowlist/blocklist. It allowed `<foreignObject>`, `<use>`, `<animate>`, `<set>`, `<image>`, and `<math>` which could lead to mutation XSS (mXSS) and other attack vectors when rendering untrusted Mermaid or Math blocks.
**Learning:** Manual DOM tree sanitization is notoriously hard to get right because attackers can hide active elements (e.g. `<a>` tags with `javascript:` payloads) inside grandchild trees (like `<math><mtext><a>`) or use SVG-specific tricks (like `<animate href... values="javascript:...">`) which naive list-based cleaners overlook.
**Prevention:** Rather than adding heavy third-party dependencies (like DOMPurify) against explicit project instructions, expanding the `BLOCKED` Set to include these structural injection vectors provides a robust, zero-dependency mitigation. Future expansions should verify nested elements carefully.

## 2026-10-17 - [Missing Security Headers in Axum Backend]
**Vulnerability:** The Axum backend lacked standard security headers (`X-Content-Type-Options`, `X-Frame-Options`, `Strict-Transport-Security`), which are important defense-in-depth measures against MIME-sniffing, clickjacking, and man-in-the-middle attacks.
**Learning:** In a custom Axum backend, relying solely on `tower_http`'s CORS or static cache headers is insufficient for a complete security posture. Explicitly adding security headers is required.
**Prevention:** Create and integrate a dedicated security headers middleware (or layer) using `axum` and `tower` to append these headers to all outgoing responses.
2 changes: 2 additions & 0 deletions apps/server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
let mut app = worker::worker_router(state.clone())
.layer(middleware::from_fn(security::static_cache_headers))
.layer(middleware::from_fn(security::security_headers))
.layer(security::cors_layer())
.layer(TraceLayer::new_for_http());
app = worker::with_static_fallback(app);
Expand Down Expand Up @@ -415,6 +416,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
worker::fence_stale_leader,
))
.layer(middleware::from_fn(security::static_cache_headers))
.layer(middleware::from_fn(security::security_headers))
.layer(security::cors_layer())
.layer(TraceLayer::new_for_http())
.with_state(state);
Expand Down
11 changes: 11 additions & 0 deletions apps/server/src/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ pub fn cors_layer() -> CorsLayer {
])
}

pub async fn security_headers(req: Request<Body>, next: Next) -> Response<Body> {
let mut res = next.run(req).await;

// Ajout des headers de sécurité standards
res.headers_mut().insert(header::X_CONTENT_TYPE_OPTIONS, HeaderValue::from_static("nosniff"));
res.headers_mut().insert("x-frame-options", HeaderValue::from_static("SAMEORIGIN"));
res.headers_mut().insert(header::STRICT_TRANSPORT_SECURITY, HeaderValue::from_static("max-age=31536000; includeSubDomains"));

res
}

/// HTML / routes app : pas de cache (évite page blanche après MAJ Flatpak).
/// Assets fingerprinted `/_astro/*` : cache long.
pub async fn static_cache_headers(req: Request<Body>, next: Next) -> Response<Body> {
Expand Down
Loading