From 135bff3f86959bbfd0b17f09a6106e33ed9dad8d Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Sat, 20 Jun 2026 16:24:29 +0300 Subject: [PATCH 1/3] chore(security): ignore RUSTSEC-2023-0071 advisory Document and suppress the "Marvin Attack" advisory (timing sidechannel in `rsa` 0.9.x) for both cargo-deny and cargo-audit: - deny.toml: ignore with full rationale - .cargo/audit.toml: mirror the ignore No fix exists (latest `rsa` is 0.10.0-rc; advisory patched:[]). We keep jsonwebtoken's pure-Rust `rust_crypto` backend over `aws_lc_rs` (C FFI), required by no-FFI consumers. RSA is used for JWT verification (public key) only, so the private-key timing attack is not reachable here. Also expose the jwt crypto backend as crate features so consumers can opt into the constant-time aws_lc_rs backend without forking: - rust_crypto (default, pure Rust) / aws_lc_rs (opt-in, C FFI) - backends are mutually exclusive; CI runs an explicit per-backend matrix instead of `--all-features` Tracking: #48 --- .cargo/audit.toml | 14 ++++++++++++++ .github/workflows/ci.yml | 23 +++++++++++++++++++---- Cargo.toml | 22 ++++++++++++++++++++-- deny.toml | 21 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 .cargo/audit.toml create mode 100644 deny.toml diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 0000000..128c905 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,14 @@ +# cargo-audit configuration. +# Run: cargo audit +# +# RUSTSEC-2023-0071 — "Marvin Attack" timing sidechannel in `rsa` 0.9.x +# (rsa -> jsonwebtoken rust_crypto backend -> structured-proxy). +# +# Ignored deliberately; mirror of the rationale in deny.toml. +# See https://github.com/structured-world/structured-proxy/issues/48 +# - No fixed `rsa` release exists (latest 0.10.0-rc; advisory patched:[]). +# - We keep the pure-Rust `rust_crypto` backend over `aws_lc_rs` (C FFI). +# - RSA is used for JWT verification (public key) only; Marvin targets +# private-key timing, not exploitable on the verify path. +[advisories] +ignore = ["RUSTSEC-2023-0071"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4096c97..be40e86 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,21 @@ env: jobs: quality-checks: - name: Quality Checks + name: Quality Checks (${{ matrix.backend.name }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + backend: + # The jsonwebtoken crypto backends are mutually exclusive (enabling + # both panics at runtime), so each is exercised on its own leg instead + # of via `--all-features`. + - name: rust_crypto + # Pure-Rust default backend (RustCrypto / rsa). + flags: "--features redis" + - name: aws_lc_rs + # Opt-in constant-time backend (aws-lc, C FFI); disables the default. + flags: "--no-default-features --features aws_lc_rs,redis" steps: - uses: actions/checkout@v6 with: @@ -30,16 +43,18 @@ jobs: - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Format + if: matrix.backend.name == 'rust_crypto' run: cargo fmt --all -- --check - name: Clippy - run: cargo clippy --all-targets --all-features + run: cargo clippy --all-targets ${{ matrix.backend.flags }} - name: Build - run: cargo build --release + run: cargo build --release ${{ matrix.backend.flags }} - name: Test - run: cargo test --all-features + run: cargo test ${{ matrix.backend.flags }} - name: Publish dry-run + if: matrix.backend.name == 'rust_crypto' run: cargo publish --dry-run diff --git a/Cargo.toml b/Cargo.toml index 0e44084..af4a81c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,8 +64,11 @@ ipnet = "2" # Optional shared rate-limit store for multi-instance deployments. redis = { version = "0.27", features = ["tokio-comp"], optional = true } -# Auth (JWT validation, route policies) -jsonwebtoken = { version = "10", features = ["rust_crypto"] } +# Auth (JWT validation, route policies). +# Crypto backend is selected via this crate's `rust_crypto` / `aws_lc_rs` +# features (see [features] below); `use_pem` is always required for PEM key +# parsing and is therefore enabled unconditionally. +jsonwebtoken = { version = "10", default-features = false, features = ["use_pem"] } reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } globset = "0.4" base64 = "0.22" @@ -78,6 +81,21 @@ clap = { version = "4", features = ["derive"] } envoy-types = "0.7" [features] +default = ["rust_crypto"] + +# JWT crypto backend. Mutually exclusive: enable exactly one. jsonwebtoken +# picks its provider from these features and panics at runtime if both (or +# neither) are on, so do NOT build with `--all-features`. +# +# `rust_crypto` (default): pure-Rust RustCrypto backend. Pulls in `rsa`, which +# carries RUSTSEC-2023-0071 (Marvin Attack). Not exploitable on our verify-only +# path (public key, no private-key ops); see deny.toml / issue #48. +rust_crypto = ["jsonwebtoken/rust_crypto"] +# `aws_lc_rs`: constant-time / FIPS-capable backend via aws-lc (C FFI), +# advisory-free. Opt-in for consumers that allow FFI; enable with +# `default-features = false, features = ["aws_lc_rs"]`. +aws_lc_rs = ["jsonwebtoken/aws_lc_rs"] + # Shared Redis-backed rate-limit store for multi-instance deployments. redis = ["dep:redis"] diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..9dba480 --- /dev/null +++ b/deny.toml @@ -0,0 +1,21 @@ +# cargo-deny configuration. +# Run: cargo deny check advisories +# +# Only the advisories section is configured here; license/bans/sources checks +# fall back to cargo-deny defaults. + +[advisories] +# RUSTSEC-2023-0071 — "Marvin Attack" timing sidechannel in `rsa` 0.9.x. +# Chain: rsa -> jsonwebtoken (rust_crypto backend) -> structured-proxy. +# +# Ignored deliberately. See https://github.com/structured-world/structured-proxy/issues/48 +# 1. No fix exists: latest `rsa` is 0.10.0-rc and the advisory has patched:[] / +# unaffected:[] in the RustSec DB. Nothing to upgrade to. +# 2. We keep jsonwebtoken's pure-Rust `rust_crypto` backend over `aws_lc_rs` +# (C FFI), required by no-FFI consumers (e.g. CoordiNode ADR-013). +# 3. RSA here is used for JWT *verification* (public key) only. Marvin targets +# private-key timing (decrypt/sign), which never runs on the verify path. +# Revisit when RustCrypto ships a constant-time `rsa` stable release. +ignore = [ + { id = "RUSTSEC-2023-0071", reason = "No fixed `rsa` release exists (latest 0.10.0-rc; advisory patched:[]). We deliberately keep jsonwebtoken's pure-Rust `rust_crypto` backend over `aws_lc_rs` (C FFI). RSA is used for JWT verification (public key) only; Marvin targets private-key timing, not exploitable on the verify path. Revisit when RustCrypto ships a constant-time `rsa` stable. Tracking: structured-world/structured-proxy#48" }, +] From 44d178a4c558c3349e531b7b6ca3588221208f3a Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Sat, 20 Jun 2026 16:36:03 +0300 Subject: [PATCH 2/3] ci: add cargo-deny advisories security job Gates CI on RustSec advisories via cargo-deny, which reads the ignore list in deny.toml (RUSTSEC-2023-0071). Operates on Cargo.lock without compiling, so it is unaffected by the mutually-exclusive jwt backends. Part of #48 --- .github/workflows/ci.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be40e86..feab851 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,3 +58,17 @@ jobs: - name: Publish dry-run if: matrix.backend.name == 'rust_crypto' run: cargo publish --dry-run + + security-audit: + name: Security Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + # Scans Cargo.lock for RustSec advisories; honors the ignore list in + # deny.toml. Third-party action: pinned to a commit SHA (dependabot + # keeps it fresh). + - uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2.0.20 + with: + command: check advisories From 0b550fb62a49351ced7d32cc8ab9d0283a9ec664 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Sat, 20 Jun 2026 16:47:16 +0300 Subject: [PATCH 3/3] feat(auth): guard mutually-exclusive jwt backends at compile time Enabling both `rust_crypto` and `aws_lc_rs` (or neither) makes jsonwebtoken fall back to a provider that panics on first use. Turn that runtime failure into a compile_error! so a misconfigured feature set is caught at build time. Also document the backend choice in the crate-level docs. Part of #48 --- src/lib.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 1b176cc..d981eb7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,21 @@ //! structured-proxy --config sid-proxy.yaml //! structured-proxy --config sflow-proxy.yaml //! ``` +//! +//! ## JWT crypto backend +//! +//! Exactly one crypto backend feature must be enabled (they are mutually +//! exclusive): `rust_crypto` (default, pure Rust) or `aws_lc_rs` (opt-in, +//! constant-time / FIPS-capable, links aws-lc via C FFI). Enabling both or +//! neither is rejected at compile time by the guards below. + +// jsonwebtoken selects its provider from these features and would otherwise +// panic at runtime on an invalid combination; turn that into a build error. +#[cfg(all(feature = "rust_crypto", feature = "aws_lc_rs"))] +compile_error!("features `rust_crypto` and `aws_lc_rs` are mutually exclusive; enable exactly one"); + +#[cfg(not(any(feature = "rust_crypto", feature = "aws_lc_rs")))] +compile_error!("exactly one JWT crypto backend must be enabled: `rust_crypto` or `aws_lc_rs`"); pub mod auth; pub mod config;