From 89175bfe9c6e121ee496b370669d7f2dee77be40 Mon Sep 17 00:00:00 2001 From: CueCrux-Myles Date: Thu, 30 Jul 2026 09:38:03 +0100 Subject: [PATCH 1/4] fix(mcp): fail closed for OAuth-only auth Use one authentication-posture predicate for transport requests, discovery metadata, and daemon bind validation. Add missing and unknown bearer regressions for OAuth-only deployments.\n\nagent:codex-work --- crates/corecruxd/src/main.rs | 29 ++++-- crates/crux-mcp/AGENTS.md | 5 +- crates/crux-mcp/src/agent.rs | 20 ++++ crates/crux-mcp/src/agent_card.rs | 47 +++++++-- crates/crux-mcp/src/lib.rs | 6 +- crates/crux-mcp/src/server.rs | 163 ++++++++++++++++++++++++++---- docs/THREAT_MODEL.md | 6 +- docs/agent/CODEMAP.md | 2 +- llms-full.txt | 8 +- 9 files changed, 240 insertions(+), 46 deletions(-) diff --git a/crates/corecruxd/src/main.rs b/crates/corecruxd/src/main.rs index fba58311..0d2b176a 100644 --- a/crates/corecruxd/src/main.rs +++ b/crates/corecruxd/src/main.rs @@ -370,10 +370,12 @@ async fn main() -> Result<(), Box> { replication_auth_bearer_configured(), ) .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + let mcp_auth_configured = + crux_mcp::agent::mcp_authentication_configured(&mcp_agent_registry, crux_mcp::oauth::introspection_enabled()); validate_mcp_bind_posture( config.mcp_enabled, config.mcp_addr, - mcp_agent_registry.is_empty(), + mcp_auth_configured, insecure_dev_auth_bind_allowed(), ) .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; @@ -2248,15 +2250,15 @@ fn validate_network_auth_posture( fn validate_mcp_bind_posture( mcp_enabled: bool, mcp_addr: SocketAddr, - agent_registry_empty: bool, + authentication_configured: bool, allow_insecure_dev_auth_bind: bool, ) -> Result<(), String> { - if !mcp_enabled || mcp_addr.ip().is_loopback() || !agent_registry_empty || allow_insecure_dev_auth_bind { + if !mcp_enabled || mcp_addr.ip().is_loopback() || authentication_configured || allow_insecure_dev_auth_bind { return Ok(()); } Err(format!( - "MCP may not bind to non-loopback address ({mcp_addr}) without CRUX_AGENT_TOKEN/CRUX_AGENT_TOKENS or CORECRUXD_ALLOW_INSECURE_DEV_AUTH_BIND=1" + "MCP may not bind to non-loopback address ({mcp_addr}) without CRUX_AGENT_TOKEN/CRUX_AGENT_TOKENS, OAuth introspection, or CORECRUXD_ALLOW_INSECURE_DEV_AUTH_BIND=1" )) } @@ -3289,10 +3291,10 @@ mod tests { let err = validate_mcp_bind_posture( true, SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 14801), - true, + false, false, ) - .expect_err("non-loopback MCP without tokens should fail"); + .expect_err("non-loopback MCP without authentication should fail"); assert!(err.contains("CRUX_AGENT_TOKEN")); } @@ -3301,10 +3303,21 @@ mod tests { validate_mcp_bind_posture( true, SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 14801), + true, false, + ) + .expect("configured MCP authentication should allow non-loopback bind"); + } + + #[test] + fn mcp_non_loopback_with_oauth_is_ok() { + validate_mcp_bind_posture( + true, + SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 14801), + true, false, ) - .expect("configured MCP tokens should allow non-loopback bind"); + .expect("configured OAuth introspection should allow non-loopback bind"); } #[test] @@ -3312,7 +3325,7 @@ mod tests { validate_mcp_bind_posture( false, SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 14801), - true, + false, false, ) .expect("disabled MCP should skip validation"); diff --git a/crates/crux-mcp/AGENTS.md b/crates/crux-mcp/AGENTS.md index f425af65..a1be97e9 100644 --- a/crates/crux-mcp/AGENTS.md +++ b/crates/crux-mcp/AGENTS.md @@ -4,8 +4,9 @@ Agent-facing MCP server: JSON-RPC 2.0 transport, tool dispatch, and an axum server speaking MCP Streamable HTTP. Hosted inside `corecruxd` (port 14801). ~42 tool modules, -~33k LOC — index only. Agents authenticate via `CRUX_AGENT_TOKEN` (validated in `agent` -before any dispatch). +~33k LOC — index only. Agents authenticate through registered bearer tokens or +hosted-client OAuth introspection; `agent::mcp_authentication_configured` is the shared +fail-closed predicate used before dispatch, in discovery, and for bind validation. ## Where to start - `src/tools/mod.rs` — `ToolDefinition` + `list_tools` / `list_tools_local_surface`: diff --git a/crates/crux-mcp/src/agent.rs b/crates/crux-mcp/src/agent.rs index 35cc2e7e..be3321ff 100644 --- a/crates/crux-mcp/src/agent.rs +++ b/crates/crux-mcp/src/agent.rs @@ -193,6 +193,15 @@ impl AgentRegistry { } } +/// Return whether the MCP transport has any authentication rail configured. +/// +/// This predicate is shared by request authentication, discovery metadata, and +/// daemon bind validation so OAuth-only deployments cannot accidentally be +/// treated as anonymous merely because the static-token registry is empty. +pub fn mcp_authentication_configured(agent_registry: &AgentRegistry, oauth_introspection_enabled: bool) -> bool { + !agent_registry.is_empty() || oauth_introspection_enabled +} + fn is_safe_agent_name(name: &str) -> bool { !name.is_empty() && name.len() <= MAX_AGENT_NAME_BYTES @@ -254,6 +263,17 @@ mod tests { assert_eq!(AgentRegistry::from_single_token(TOKEN_A).len(), 1); } + #[test] + fn mcp_authentication_configured_covers_both_authentication_rails() { + let empty = AgentRegistry::empty(); + let static_bearer = AgentRegistry::from_single_token(TOKEN_A); + + assert!(!mcp_authentication_configured(&empty, false)); + assert!(mcp_authentication_configured(&static_bearer, false)); + assert!(mcp_authentication_configured(&empty, true)); + assert!(mcp_authentication_configured(&static_bearer, true)); + } + fn clear_agent_token_env() { std::env::remove_var("CRUX_AGENT_TOKEN"); std::env::remove_var("CRUX_AGENT_TOKENS"); diff --git a/crates/crux-mcp/src/agent_card.rs b/crates/crux-mcp/src/agent_card.rs index 5384277d..edad141e 100644 --- a/crates/crux-mcp/src/agent_card.rs +++ b/crates/crux-mcp/src/agent_card.rs @@ -75,22 +75,28 @@ pub struct AgentSkill { /// /// [`CORE_FLOOR`]: crate::tools::surface::CORE_FLOOR pub fn build_agent_card(ctx: &McpContext) -> AgentCard { - let auth_required = !ctx.agent_registry.is_empty(); - let oauth = crate::oauth::introspection_enabled(); + build_agent_card_with_auth_posture(ctx, crate::oauth::introspection_enabled()) +} + +pub(crate) fn build_agent_card_with_auth_posture(ctx: &McpContext, oauth: bool) -> AgentCard { + let static_bearer = !ctx.agent_registry.is_empty(); + let auth_required = crate::agent::mcp_authentication_configured(&ctx.agent_registry, oauth); let mut modes = Vec::new(); - if auth_required { + if static_bearer { modes.push("bearer-token".to_string()); } if oauth { modes.push("oauth2".to_string()); } - let scheme = if !auth_required { - "none" - } else if oauth { + let scheme = if static_bearer && oauth { "oauth2+bearer" - } else { + } else if oauth { + "oauth2" + } else if static_bearer { "bearer" + } else { + "none" } .to_string(); @@ -152,7 +158,7 @@ mod tests { #[test] fn card_no_auth_when_registry_empty() { let ctx = McpContext::new_default("node-x"); - let card = build_agent_card(&ctx); + let card = build_agent_card_with_auth_posture(&ctx, false); assert_eq!(card.schema, AGENT_CARD_SCHEMA); assert_eq!(card.provider, "CueCrux"); assert!(card.name.contains("node-x")); @@ -168,16 +174,35 @@ mod tests { fn card_requires_bearer_when_registry_nonempty() { let mut ctx = McpContext::new_default("node-y"); ctx.agent_registry = AgentRegistry::from_single_token("crux_at_0123456789abcdef01234567"); - let card = build_agent_card(&ctx); + let card = build_agent_card_with_auth_posture(&ctx, false); assert!(card.authentication.required); assert_eq!(card.authentication.scheme, "bearer"); assert!(card.authentication.modes.contains(&"bearer-token".to_string())); } + #[test] + fn card_requires_oauth_when_registry_empty() { + let ctx = McpContext::new_default("node-oauth"); + let card = build_agent_card_with_auth_posture(&ctx, true); + assert!(card.authentication.required); + assert_eq!(card.authentication.scheme, "oauth2"); + assert_eq!(card.authentication.modes, vec!["oauth2"]); + } + + #[test] + fn card_advertises_both_authentication_rails() { + let mut ctx = McpContext::new_default("node-both"); + ctx.agent_registry = AgentRegistry::from_single_token("crux_at_0123456789abcdef01234567"); + let card = build_agent_card_with_auth_posture(&ctx, true); + assert!(card.authentication.required); + assert_eq!(card.authentication.scheme, "oauth2+bearer"); + assert_eq!(card.authentication.modes, vec!["bearer-token", "oauth2"]); + } + #[test] fn card_advertises_http_when_base_url_set() { let ctx = McpContext::new_default("n").with_daemon_base_url("http://127.0.0.1:14800"); - let card = build_agent_card(&ctx); + let card = build_agent_card_with_auth_posture(&ctx, false); assert!(card.access.protocols.contains(&"http".to_string())); assert_eq!(card.access.base_url.as_deref(), Some("http://127.0.0.1:14800")); } @@ -185,7 +210,7 @@ mod tests { #[test] fn card_serializes_camelcase_and_omits_empty_tags() { let ctx = McpContext::new_default("n"); - let v = serde_json::to_value(build_agent_card(&ctx)).unwrap(); + let v = serde_json::to_value(build_agent_card_with_auth_posture(&ctx, false)).unwrap(); assert!(v.get("schema").is_some()); assert!(v["access"].get("wellKnown").is_some(), "camelCase wellKnown"); assert!(v.get("authentication").is_some()); diff --git a/crates/crux-mcp/src/lib.rs b/crates/crux-mcp/src/lib.rs index 666bd53b..c4a555e8 100644 --- a/crates/crux-mcp/src/lib.rs +++ b/crates/crux-mcp/src/lib.rs @@ -23,8 +23,10 @@ //! //! ## Authentication //! -//! Agents authenticate via `CRUX_AGENT_TOKEN` (Bearer token). The token is -//! validated in the [`agent`] module before any tool dispatch. +//! Agents authenticate with registered bearer tokens (`CRUX_AGENT_TOKEN` / +//! `CRUX_AGENT_TOKENS`) or hosted-client OAuth bearer introspection. When +//! either rail is configured, missing and unknown credentials fail closed +//! before any tool dispatch. #![deny(clippy::unwrap_used)] diff --git a/crates/crux-mcp/src/server.rs b/crates/crux-mcp/src/server.rs index 294ef1e4..b5eb6364 100644 --- a/crates/crux-mcp/src/server.rs +++ b/crates/crux-mcp/src/server.rs @@ -28,6 +28,15 @@ use crate::sse::{RegisterError, Registration}; const MCP_SESSION_HEADER: &str = "mcp-session-id"; const MCP_SESSION_ID_MAX_LEN: usize = 128; +/// HTTP transport state captures the authentication posture once at router +/// construction. Tests can inject OAuth-only posture without mutating the +/// process-wide introspector, while production uses the actual configured +/// introspector. +struct McpHttpState { + ctx: McpContext, + oauth_introspection_enabled: bool, +} + /// True when the request is a `tools/call` for `cuecrux_session` carrying a /// non-empty `intent` — the trigger that reshapes the `dynamic` surface and so /// warrants a `tools/list_changed` push (M3.5). @@ -46,7 +55,14 @@ fn is_cuecrux_session_with_intent(req: &JsonRpcRequest) -> bool { /// Build the axum router with MCP endpoints. pub fn router(ctx: McpContext) -> axum::Router { - let state = Arc::new(ctx); + router_with_auth_posture(ctx, crate::oauth::introspection_enabled()) +} + +fn router_with_auth_posture(ctx: McpContext, oauth_introspection_enabled: bool) -> axum::Router { + let state = Arc::new(McpHttpState { + ctx, + oauth_introspection_enabled, + }); axum::Router::new() .route("/mcp", post(handle_mcp_post)) .route("/mcp", get(handle_mcp_get)) @@ -74,11 +90,18 @@ async fn handle_oauth_protected_resource() -> Response { /// daemon. **Launch default ON** — served unless `CRUX_AGENT_CARD=0`; the card /// describes the service only (no caller passport, no private facts), so it is /// safe to expose out of the box. -async fn handle_agent_card(State(ctx): State>) -> Response { +async fn handle_agent_card(State(state): State>) -> Response { if !agent_card_enabled() { return StatusCode::NOT_FOUND.into_response(); } - (StatusCode::OK, Json(crate::agent_card::build_agent_card(&ctx))).into_response() + ( + StatusCode::OK, + Json(crate::agent_card::build_agent_card_with_auth_posture( + &state.ctx, + state.oauth_introspection_enabled, + )), + ) + .into_response() } /// agent-card M6: discovery-endpoint flag (`CRUX_AGENT_CARD`). Launch default @@ -91,8 +114,9 @@ pub(crate) fn agent_card_enabled() -> bool { } /// `POST /mcp` — JSON-RPC 2.0 endpoint (MCP Streamable HTTP). -async fn handle_mcp_post(State(ctx): State>, headers: HeaderMap, body: String) -> Response { - let outcome = match authenticate_agent(&ctx, &headers).await { +async fn handle_mcp_post(State(state): State>, headers: HeaderMap, body: String) -> Response { + let ctx = &state.ctx; + let outcome = match authenticate_agent(ctx, &headers, state.oauth_introspection_enabled).await { Ok(outcome) => outcome, Err(problem) => return problem.into_response(), }; @@ -224,7 +248,8 @@ async fn handle_mcp_post(State(ctx): State>, headers: HeaderMap, /// - `Accept: text/event-stream` → open a server→client SSE stream for /// notifications (M3.5: `tools/list_changed`), keyed by `Mcp-Session-Id`. /// - otherwise → static server-info discovery (unchanged). -async fn handle_mcp_get(State(ctx): State>, req: axum::extract::Request) -> Response { +async fn handle_mcp_get(State(state): State>, req: axum::extract::Request) -> Response { + let ctx = &state.ctx; let headers = req.headers(); let peer_ip = req.extensions().get::>().map(|ci| ci.0.ip()); let wants_sse = headers @@ -239,7 +264,7 @@ async fn handle_mcp_get(State(ctx): State>, req: axum::extract:: // and therefore never saw the `WWW-Authenticate` challenge that tells it // where the Authorization Server is. Discovery has to be reachable, but the // challenge is the thing a probing client actually needs. - let auth = match authenticate_agent(&ctx, headers).await { + let auth = match authenticate_agent(ctx, headers, state.oauth_introspection_enabled).await { Ok(outcome) => outcome, Err(problem) => return problem.into_response(), }; @@ -392,12 +417,18 @@ impl AuthOutcome { /// Authenticate an MCP request. Order: (1) registered static agent token /// (unchanged legacy path), (2) hosted-client OAuth bearer via introspection /// (opt-in; only when configured), (3) reject unless this is a no-auth daemon. -async fn authenticate_agent(ctx: &McpContext, headers: &HeaderMap) -> Result { +async fn authenticate_agent( + ctx: &McpContext, + headers: &HeaderMap, + oauth_introspection_enabled: bool, +) -> Result { + let authentication_configured = + crate::agent::mcp_authentication_configured(&ctx.agent_registry, oauth_introspection_enabled); let Some(token) = bearer_token(headers) else { - return if ctx.agent_registry.is_empty() { - Ok(AuthOutcome::Anonymous) - } else { + return if authentication_configured { Err(UnauthorizedAgent(AuthDenial::MissingToken)) + } else { + Ok(AuthOutcome::Anonymous) }; }; @@ -413,7 +444,7 @@ async fn authenticate_agent(ctx: &McpContext, headers: &HeaderMap) -> Result Result axum::Router { - router(McpContext::new_default("test-node")) + router_with_auth_posture(McpContext::new_default("test-node"), false) } fn test_app_with_ctx(ctx: McpContext) -> axum::Router { - router(ctx) + router_with_auth_posture(ctx, false) + } + + fn test_app_oauth_only() -> axum::Router { + router_with_auth_posture(McpContext::new_default("test-node"), true) } #[tokio::test] @@ -820,6 +855,100 @@ mod tests { ); } + #[tokio::test] + async fn oauth_only_get_banner_requires_bearer() { + let resp = test_app_oauth_only() + .oneshot( + Request::builder() + .method("GET") + .uri("/mcp") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!( + resp.status(), + StatusCode::UNAUTHORIZED, + "OAuth-only MCP must not expose authenticated discovery anonymously" + ); + } + + #[tokio::test] + async fn oauth_only_sse_requires_bearer() { + let session_id = "oauth-only-missing-bearer"; + let resp = test_app_oauth_only() + .oneshot( + Request::builder() + .method("GET") + .uri("/mcp") + .header("accept", "text/event-stream") + .header("mcp-session-id", session_id) + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::UNAUTHORIZED); + assert!(!crate::sse::is_registered(session_id)); + } + + #[tokio::test] + async fn oauth_only_post_requires_bearer() { + let body = serde_json::to_string(&json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "store_fact", + "arguments": { + "entity": "test:oauth-only", + "key": "must-not-write", + "value": "blocked" + } + } + })) + .unwrap(); + let resp = test_app_oauth_only() + .oneshot( + Request::builder() + .method("POST") + .uri("/mcp") + .header("content-type", "application/json") + .body(Body::from(body)) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!( + resp.status(), + StatusCode::UNAUTHORIZED, + "OAuth-only MCP must challenge before the full dispatcher" + ); + } + + #[tokio::test] + async fn oauth_only_unknown_bearer_is_invalid_token() { + let resp = test_app_oauth_only() + .oneshot( + Request::builder() + .method("GET") + .uri("/mcp") + .header("authorization", "Bearer unknown-oauth-token") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(resp.status(), StatusCode::UNAUTHORIZED); + let body_bytes = resp.into_body().collect().await.unwrap().to_bytes(); + let body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap(); + assert_eq!( + body["error"], "invalid_token", + "OAuth availability or token validation failures must never fall back to anonymous" + ); + } + /// ...but it must still answer an AUTHENTICATED GET, and must stay open on a /// no-auth daemon (covered by `get_returns_server_info`). #[tokio::test] diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index ceab9f4b..75e2e656 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -94,8 +94,10 @@ for the Crux Daemon. (`CORECRUXD_REPLICATION_AUTH_BEARER`). 3. Prometheus metrics (`/metrics`) and health endpoints (`/healthz`, `/readyz`) are unauthenticated. Restrict access at the network level if exposing beyond localhost. -4. MCP Streamable HTTP server-info discovery can remain public, but SSE stream - creation requires configured bearer auth when agent tokens are present. +4. MCP Streamable HTTP requests fail closed whenever either registered bearer + tokens or hosted-client OAuth introspection is configured. This includes + server-info GET, SSE stream creation, and JSON-RPC POST; only an MCP daemon + with neither authentication rail configured permits anonymous access. 5. Public `/v1/version` is redacted. Full operational version details live at `/v1/admin/version` behind `admin:read`. diff --git a/docs/agent/CODEMAP.md b/docs/agent/CODEMAP.md index 248c3602..51aafdf4 100644 --- a/docs/agent/CODEMAP.md +++ b/docs/agent/CODEMAP.md @@ -44,7 +44,7 @@ SPA — `dist/` only, not a cargo member — and is excluded below.) |---|---|---|---|---| | `corecruxd` | The Crux Daemon binary (HTTP / gRPC / MCP host, signing, auth) | *(binary)* — owns `build_segment_seal_receipt`, `sign_segment_seal_material` (`src/grpc.rs`) | — | 83.8k | | `corecruxctl` | CoreCrux CLI: `verify-store`, `replay`, `gaps` | *(binary)* — `verify_store`, `replay`, `gaps` modules (`src/`) | — | 33.1k | -| `crux-mcp` | Agent-facing MCP server (JSON-RPC 2.0 + axum Streamable-HTTP); ~42 tool modules | `dispatch`, `router`/`with_rcx_router`/`with_agent_passports`, `crc_v1` | corecruxd | 33.1k | +| `crux-mcp` | Agent-facing MCP server (JSON-RPC 2.0 + axum Streamable-HTTP); static-bearer/OAuth fail-closed auth; ~42 tool modules | `dispatch`, `router`/`with_rcx_router`/`with_agent_passports`, `mcp_authentication_configured`, `crc_v1` | corecruxd | 33.1k | | `crux-observe` | Self-observation layer: ops events + bootstrap docs → memory facts | `Redactor`/`redact_line`, `bootstrap_entity`/`ops_entity`, `self_observe_enabled` | crux-mcp, corecruxd | 2.7k | | `crux-observe-api` | Wire types for the agent audit-chain data contract | `NodeKind`, `RiskClass`, `StepStatus`, `ReasoningRef` | corecruxd | 0.8k | | `crux-integrations` | Declarative manifest contract for daemon integration packs | `IntegrationManifest`, `IntegrationEntry`, `EntryKind`, `SafetyPolicy` | corecruxctl, crux-mcp, corecruxd | 2.5k | diff --git a/llms-full.txt b/llms-full.txt index d6b83cec..d27989ff 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -196,7 +196,7 @@ SPA — `dist/` only, not a cargo member — and is excluded below.) |---|---|---|---|---| | `corecruxd` | The Crux Daemon binary (HTTP / gRPC / MCP host, signing, auth) | *(binary)* — owns `build_segment_seal_receipt`, `sign_segment_seal_material` (`src/grpc.rs`) | — | 83.8k | | `corecruxctl` | CoreCrux CLI: `verify-store`, `replay`, `gaps` | *(binary)* — `verify_store`, `replay`, `gaps` modules (`src/`) | — | 33.1k | -| `crux-mcp` | Agent-facing MCP server (JSON-RPC 2.0 + axum Streamable-HTTP); ~42 tool modules | `dispatch`, `router`/`with_rcx_router`/`with_agent_passports`, `crc_v1` | corecruxd | 33.1k | +| `crux-mcp` | Agent-facing MCP server (JSON-RPC 2.0 + axum Streamable-HTTP); static-bearer/OAuth fail-closed auth; ~42 tool modules | `dispatch`, `router`/`with_rcx_router`/`with_agent_passports`, `mcp_authentication_configured`, `crc_v1` | corecruxd | 33.1k | | `crux-observe` | Self-observation layer: ops events + bootstrap docs → memory facts | `Redactor`/`redact_line`, `bootstrap_entity`/`ops_entity`, `self_observe_enabled` | crux-mcp, corecruxd | 2.7k | | `crux-observe-api` | Wire types for the agent audit-chain data contract | `NodeKind`, `RiskClass`, `StepStatus`, `ReasoningRef` | corecruxd | 0.8k | | `crux-integrations` | Declarative manifest contract for daemon integration packs | `IntegrationManifest`, `IntegrationEntry`, `EntryKind`, `SafetyPolicy` | corecruxctl, crux-mcp, corecruxd | 2.5k | @@ -833,8 +833,10 @@ for the Crux Daemon. (`CORECRUXD_REPLICATION_AUTH_BEARER`). 3. Prometheus metrics (`/metrics`) and health endpoints (`/healthz`, `/readyz`) are unauthenticated. Restrict access at the network level if exposing beyond localhost. -4. MCP Streamable HTTP server-info discovery can remain public, but SSE stream - creation requires configured bearer auth when agent tokens are present. +4. MCP Streamable HTTP requests fail closed whenever either registered bearer + tokens or hosted-client OAuth introspection is configured. This includes + server-info GET, SSE stream creation, and JSON-RPC POST; only an MCP daemon + with neither authentication rail configured permits anonymous access. 5. Public `/v1/version` is redacted. Full operational version details live at `/v1/admin/version` behind `admin:read`. From 6b9d988588a71168b7ee3409ade8515d222029b4 Mon Sep 17 00:00:00 2001 From: CueCrux-Myles Date: Fri, 31 Jul 2026 07:32:14 +0100 Subject: [PATCH 2/4] fix(security): harden daemon browser responses Apply one console-compatible browser security policy to direct daemon routes and the desktop proxy. Deny foreign framing without breaking same-origin Studio and 3D tiles, replace hostile upstream policy headers, and regression-test all response classes plus daemon/proxy parity. Co-Authored-By: OpenAI Codex --- crates/corecruxd/src/console.rs | 187 ++++++++++++++++++++++++- shells/desktop/connection/src/proxy.rs | 122 +++++++++++++++- 2 files changed, 298 insertions(+), 11 deletions(-) diff --git a/crates/corecruxd/src/console.rs b/crates/corecruxd/src/console.rs index 0dcc9808..2243290c 100644 --- a/crates/corecruxd/src/console.rs +++ b/crates/corecruxd/src/console.rs @@ -8,8 +8,9 @@ use std::borrow::Cow; use std::path::{Path, PathBuf}; -use axum::extract::Path as AxumPath; -use axum::http::{header, HeaderValue, Method}; +use axum::extract::{Path as AxumPath, Request}; +use axum::http::{header, HeaderName, HeaderValue, Method}; +use axum::middleware::Next; use axum::response::{Html, IntoResponse, Redirect, Response}; use axum::routing::get; use axum::Router; @@ -54,6 +55,25 @@ const CONSOLE_DEV_PATH_ENV: &str = "CORECRUXD_CONSOLE_DEV_PATH"; // after trimming) the production defaults below apply. const CONSOLE_ALLOWED_ORIGINS_ENV: &str = "CORECRUXD_CONSOLE_ALLOWED_ORIGINS"; +// Browser hardening for every document and static asset served by the daemon. +// The console intentionally uses same-origin frames for its 3D view and Studio +// web tiles, so frame-src/frame-ancestors and X-Frame-Options permit only that +// same origin rather than denying framing outright. Inline script/style remain +// necessary for the embedded, no-build shell and `/activate`; eval is not. +const CONTENT_SECURITY_POLICY: &str = "default-src 'self'; base-uri 'none'; object-src 'none'; frame-src 'self'; frame-ancestors 'self'; form-action 'self'; connect-src 'self'; img-src 'self' data: blob:; font-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; worker-src 'self'; manifest-src 'self'"; +const BROWSER_SECURITY_HEADERS: [(&str, &str); 7] = [ + ("content-security-policy", CONTENT_SECURITY_POLICY), + ("x-content-type-options", "nosniff"), + ("x-frame-options", "SAMEORIGIN"), + ("referrer-policy", "no-referrer"), + ( + "permissions-policy", + "camera=(), microphone=(), geolocation=(), payment=(), usb=(), serial=()", + ), + ("cross-origin-opener-policy", "same-origin"), + ("cross-origin-resource-policy", "same-origin"), +]; + // Default allowlist when `CORECRUXD_CONSOLE_ALLOWED_ORIGINS` is unset: the // public console origin plus the two Tailnet-facing origins the daemon is // reachable on (host `crux` / its Tailscale IP). Matches the M5 plan intent. @@ -315,6 +335,19 @@ fn console_cors_layer() -> CorsLayer { .allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE, header::ACCEPT]) } +/// Replace any route-local browser policy with the daemon's canonical policy. +/// This layer sits outside CORS so its headers also cover CORS-generated +/// preflight responses. +async fn add_browser_security_headers(request: Request, next: Next) -> Response { + let mut response = next.run(request).await; + for (name, value) in BROWSER_SECURITY_HEADERS { + response + .headers_mut() + .insert(HeaderName::from_static(name), HeaderValue::from_static(value)); + } + response +} + pub fn routes(enabled: bool) -> Router { if !enabled { return Router::new(); @@ -329,6 +362,7 @@ pub fn routes(enabled: bool) -> Router { // Device-grant approval page (ExecPlan crux-unified-login-rails, M3). .route("/activate", get(serve_activate)) .layer(console_cors_layer()) + .layer(axum::middleware::from_fn(add_browser_security_headers)) } /// `/activate` — operator approval page for the device-authorization grant. @@ -585,11 +619,14 @@ fn resolve_dev_html_path(base: &Path) -> PathBuf { #[cfg(test)] mod tests { use super::{ - resolve_console_body, CONSOLE_DEV_PATH_ENV, CONSOLE_V2_API_JS, CONSOLE_V2_HTML, CONSOLE_V2_ICON_SVG, - CONSOLE_V2_LINKGRAPH_MJS, CONSOLE_V2_MANIFEST, CONSOLE_V2_PAGES_JS, CONSOLE_V2_RENDER_JS, CONSOLE_V2_SW_JS, + resolve_console_body, BROWSER_SECURITY_HEADERS, CONSOLE_DEV_PATH_ENV, CONSOLE_V2_API_JS, CONSOLE_V2_HTML, + CONSOLE_V2_ICON_SVG, CONSOLE_V2_LINKGRAPH_MJS, CONSOLE_V2_MANIFEST, CONSOLE_V2_PAGES_JS, CONSOLE_V2_RENDER_JS, + CONSOLE_V2_SW_JS, CONTENT_SECURITY_POLICY, }; use std::sync::Mutex; + const DESKTOP_PROXY_SOURCE: &str = include_str!("../../../shells/desktop/connection/src/proxy.rs"); + // The dev-path / v2 flag env vars are process-global; serialise tests that // mutate either of them. Recover a poisoned lock so one failing env test // does not cascade into the others. @@ -598,6 +635,148 @@ mod tests { ENV_LOCK.lock().unwrap_or_else(|poison| poison.into_inner()) } + fn assert_browser_security_headers(headers: &axum::http::HeaderMap) { + for (name, expected) in BROWSER_SECURITY_HEADERS { + assert_eq!( + headers.get_all(name).iter().count(), + 1, + "{name} must occur exactly once" + ); + assert_eq!( + headers.get(name).and_then(|value| value.to_str().ok()), + Some(expected), + "{name} must match the canonical browser policy" + ); + } + assert!(CONTENT_SECURITY_POLICY.contains("frame-src 'self'")); + assert!(CONTENT_SECURITY_POLICY.contains("frame-ancestors 'self'")); + assert!(!CONTENT_SECURITY_POLICY.contains("'unsafe-eval'")); + assert!(!CONTENT_SECURITY_POLICY.contains("'wasm-unsafe-eval'")); + } + + #[tokio::test] + async fn browser_security_headers_cover_all_console_responses() { + use tower::ServiceExt; + + let _guard = env_lock(); + std::env::remove_var(CONSOLE_DEV_PATH_ENV); + std::env::remove_var(super::CONSOLE_ALLOWED_ORIGINS_ENV); + + for (method, uri, expected_status) in [ + (axum::http::Method::GET, "/", axum::http::StatusCode::SEE_OTHER), + (axum::http::Method::GET, "/console", axum::http::StatusCode::OK), + (axum::http::Method::HEAD, "/console", axum::http::StatusCode::OK), + ( + axum::http::Method::GET, + "/console-assets/CueCrux-Arc-Loop.png", + axum::http::StatusCode::OK, + ), + ( + axum::http::Method::GET, + "/console-assets/missing.png", + axum::http::StatusCode::NOT_FOUND, + ), + (axum::http::Method::GET, "/console-v2/sw.js", axum::http::StatusCode::OK), + ( + axum::http::Method::GET, + "/console-v2/missing.js", + axum::http::StatusCode::NOT_FOUND, + ), + ( + axum::http::Method::GET, + "/console-v2/%2E%2E", + axum::http::StatusCode::BAD_REQUEST, + ), + ( + axum::http::Method::GET, + "/console-3d/index.html", + axum::http::StatusCode::OK, + ), + ( + axum::http::Method::GET, + "/console-3d/missing.js", + axum::http::StatusCode::NOT_FOUND, + ), + (axum::http::Method::GET, "/activate", axum::http::StatusCode::OK), + ] { + let response = super::routes(true) + .oneshot( + axum::http::Request::builder() + .method(method) + .uri(uri) + .body(axum::body::Body::empty()) + .expect("build request"), + ) + .await + .expect("router response"); + assert_eq!(response.status(), expected_status, "unexpected status for {uri}"); + assert_browser_security_headers(response.headers()); + } + + let preflight = super::routes(true) + .oneshot( + axum::http::Request::builder() + .method("OPTIONS") + .uri("/console") + .header(axum::http::header::ORIGIN, "https://crux.cuecrux.com") + .header(axum::http::header::ACCESS_CONTROL_REQUEST_METHOD, "GET") + .header(axum::http::header::ACCESS_CONTROL_REQUEST_HEADERS, "authorization") + .body(axum::body::Body::empty()) + .expect("build preflight request"), + ) + .await + .expect("preflight response"); + assert_eq!(preflight.status(), axum::http::StatusCode::OK); + assert_browser_security_headers(preflight.headers()); + assert_eq!( + preflight + .headers() + .get(axum::http::header::ACCESS_CONTROL_ALLOW_ORIGIN) + .and_then(|value| value.to_str().ok()), + Some("https://crux.cuecrux.com"), + "security middleware must preserve CORS preflight headers" + ); + } + + #[test] + fn desktop_proxy_browser_security_policy_matches_daemon() { + let normalized_desktop = DESKTOP_PROXY_SOURCE + .chars() + .filter(|character| !character.is_whitespace()) + .collect::() + .to_ascii_lowercase(); + let normalized_csp = CONTENT_SECURITY_POLICY + .chars() + .filter(|character| !character.is_whitespace()) + .collect::() + .to_ascii_lowercase(); + assert!( + normalized_desktop.contains(&format!("constcontent_security_policy:&str=\"{normalized_csp}\";")), + "desktop proxy CSP must match the daemon policy byte-for-byte" + ); + assert!( + normalized_desktop.contains("constbrowser_security_headers:[(&str,&str);7]"), + "desktop proxy must expose the same seven canonical headers" + ); + assert!( + normalized_desktop.contains("(\"content-security-policy\",content_security_policy)"), + "desktop proxy must source CSP from its parity-checked constant" + ); + for (name, value) in BROWSER_SECURITY_HEADERS.into_iter().skip(1) { + let normalized_value = value + .chars() + .filter(|character| !character.is_whitespace()) + .collect::() + .to_ascii_lowercase(); + let compact = format!("(\"{name}\",\"{normalized_value}\")"); + let compact_with_trailing_comma = format!("(\"{name}\",\"{normalized_value}\",)"); + assert!( + normalized_desktop.contains(&compact) || normalized_desktop.contains(&compact_with_trailing_comma), + "desktop proxy header {name} must match the daemon value" + ); + } + } + #[test] fn console_serves_v2_shell_unconditionally() { // The legacy console is removed; `/console` always serves the v2 shell diff --git a/shells/desktop/connection/src/proxy.rs b/shells/desktop/connection/src/proxy.rs index 15bb5ba6..080cd24f 100644 --- a/shells/desktop/connection/src/proxy.rs +++ b/shells/desktop/connection/src/proxy.rs @@ -25,7 +25,22 @@ const SESSION_HANDSHAKE_PATH: &str = "/__crux_session"; const PROXY_COOKIE_NAME: &[u8] = b"__crux_proxy"; const CONSOLE_PATH: &str = "/console"; -const CSP: &str = "default-src 'self'; base-uri 'none'; object-src 'none'; frame-src 'none'; frame-ancestors 'none'; form-action 'self'; connect-src 'self'; img-src 'self' data: blob:; font-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; worker-src 'self'; manifest-src 'self'"; +// Keep this policy byte-for-byte aligned with corecruxd's console policy. The +// shipped shell embeds same-origin 3D and Studio web tiles, so foreign framing +// is denied while same-origin framing remains available. +const CONTENT_SECURITY_POLICY: &str = "default-src 'self'; base-uri 'none'; object-src 'none'; frame-src 'self'; frame-ancestors 'self'; form-action 'self'; connect-src 'self'; img-src 'self' data: blob:; font-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; worker-src 'self'; manifest-src 'self'"; +const BROWSER_SECURITY_HEADERS: [(&str, &str); 7] = [ + ("Content-Security-Policy", CONTENT_SECURITY_POLICY), + ("X-Content-Type-Options", "nosniff"), + ("X-Frame-Options", "SAMEORIGIN"), + ("Referrer-Policy", "no-referrer"), + ( + "Permissions-Policy", + "camera=(), microphone=(), geolocation=(), payment=(), usb=(), serial=()", + ), + ("Cross-Origin-Opener-Policy", "same-origin"), + ("Cross-Origin-Resource-Policy", "same-origin"), +]; /// A sanitized request passed from the loopback BFF to a native HTTP adapter. pub struct ForwardRequest { @@ -1238,10 +1253,10 @@ fn write_status_line(stream: &mut dyn Write, status: u16) -> io::Result<()> { } fn write_security_headers(stream: &mut dyn Write) -> io::Result<()> { - write!( - stream, - "Content-Security-Policy: {CSP}\r\nX-Content-Type-Options: nosniff\r\nX-Frame-Options: DENY\r\nReferrer-Policy: no-referrer\r\nPermissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), serial=()\r\nCross-Origin-Opener-Policy: same-origin\r\nCross-Origin-Resource-Policy: same-origin\r\n" - ) + for (name, value) in BROWSER_SECURITY_HEADERS { + write!(stream, "{name}: {value}\r\n")?; + } + Ok(()) } fn reason_phrase(status: u16) -> &'static str { @@ -1336,7 +1351,7 @@ mod tests { use super::{ forward_request, handle_session_handshake, render_status_html, validate_browser_origin, ForwardMode, ForwardRequest, ParsedRequest, ProxyControl, ProxyMode, ProxyServer, ProxySessionState, SecretBytes, Shared, - StatusPage, Upstream, UpstreamError, UpstreamResponse, + StatusPage, Upstream, UpstreamError, UpstreamResponse, BROWSER_SECURITY_HEADERS, CONTENT_SECURITY_POLICY, }; use crate::{validate_attach_url, SecretToken}; @@ -1488,6 +1503,89 @@ mod tests { } } + fn assert_browser_security_headers(response: &[u8]) { + let rendered = String::from_utf8_lossy(response); + let header_block = rendered + .split_once("\r\n\r\n") + .map_or(rendered.as_ref(), |(headers, _)| headers); + for (name, value) in BROWSER_SECURITY_HEADERS { + let matches: Vec<_> = header_block + .lines() + .filter(|line| { + line.split_once(':') + .is_some_and(|(candidate, _)| candidate.eq_ignore_ascii_case(name)) + }) + .collect(); + assert_eq!(matches.len(), 1, "{name} must occur exactly once"); + assert_eq!(matches[0], format!("{name}: {value}"), "{name} must be canonical"); + } + assert!(CONTENT_SECURITY_POLICY.contains("frame-src 'self'")); + assert!(CONTENT_SECURITY_POLICY.contains("frame-ancestors 'self'")); + assert!(!CONTENT_SECURITY_POLICY.contains("'unsafe-eval'")); + assert!(!CONTENT_SECURITY_POLICY.contains("'wasm-unsafe-eval'")); + } + + #[test] + fn browser_security_policy_covers_every_proxy_response_class() { + let mode = ForwardMode { + upstream_origin: validate_attach_url("https://daemon.example").unwrap(), + upstream: Arc::new(FakeUpstream::new(UpstreamResponse::new( + 200, + Vec::new(), + Cursor::new(Vec::new()), + ))), + token: Arc::new(token()), + session: established_session(), + active: Arc::new(AtomicBool::new(true)), + clear_browser_state: Arc::new(AtomicBool::new(false)), + }; + let running = AtomicBool::new(true); + + for (method, status) in [("GET", 200), ("HEAD", 200), ("GET", 204), ("GET", 304)] { + let mut response = Vec::new(); + super::write_upstream_response( + &mut response, + UpstreamResponse::new( + status, + vec![ + ( + "Content-Security-Policy".to_string(), + b"default-src *; script-src * 'unsafe-eval'".to_vec(), + ), + ("X-Frame-Options".to_string(), b"ALLOWALL".to_vec()), + ("X-Content-Type-Options".to_string(), b"off".to_vec()), + ("Referrer-Policy".to_string(), b"unsafe-url".to_vec()), + ("Permissions-Policy".to_string(), b"camera=*".to_vec()), + ("Cross-Origin-Opener-Policy".to_string(), b"unsafe-none".to_vec()), + ("Cross-Origin-Resource-Policy".to_string(), b"cross-origin".to_vec()), + ], + Cursor::new(b"body".to_vec()), + ), + method, + &mode, + &running, + ) + .unwrap(); + assert_browser_security_headers(&response); + } + + let mut status_response = Vec::new(); + super::write_status( + &mut status_response, + &StatusPage::new("Connecting", "Browser policy is active."), + ) + .unwrap(); + assert_browser_security_headers(&status_response); + + let mut error_response = Vec::new(); + super::write_safe_error(&mut error_response, 502, "The daemon response was blocked.").unwrap(); + assert_browser_security_headers(&error_response); + + let mut handshake_response = Vec::new(); + super::write_session_established(&mut handshake_response, TEST_SESSION_ID, &AtomicBool::new(false)).unwrap(); + assert_browser_security_headers(&handshake_response); + } + #[test] fn status_page_escapes_untrusted_profile_and_reason() { let status = StatusPage { @@ -1812,6 +1910,16 @@ mod tests { ("Proxy-Connection".to_string(), b"keep-alive".to_vec()), ("WWW-Authenticate".to_string(), b"Basic realm=hostile".to_vec()), ("Content-Type".to_string(), b"text/plain".to_vec()), + ( + "Content-Security-Policy".to_string(), + b"default-src *; script-src * 'unsafe-eval'".to_vec(), + ), + ("X-Content-Type-Options".to_string(), b"off".to_vec()), + ("X-Frame-Options".to_string(), b"ALLOWALL".to_vec()), + ("Referrer-Policy".to_string(), b"unsafe-url".to_vec()), + ("Permissions-Policy".to_string(), b"camera=*".to_vec()), + ("Cross-Origin-Opener-Policy".to_string(), b"unsafe-none".to_vec()), + ("Cross-Origin-Resource-Policy".to_string(), b"cross-origin".to_vec()), ], Cursor::new(reflected), ))); @@ -1844,7 +1952,7 @@ mod tests { let rendered = String::from_utf8_lossy(&browser_response); assert!(rendered.starts_with("HTTP/1.1 200")); assert!(rendered.contains("[REDACTED]")); - assert!(rendered.contains("Content-Security-Policy:")); + assert_browser_security_headers(&browser_response); assert_eq!(rendered.matches("Clear-Site-Data:").count(), 1); assert!(rendered.contains("Clear-Site-Data: \"cache\", \"storage\"")); assert!(!rendered.to_ascii_lowercase().contains("set-cookie")); From 2a956b06f55be36f2653559243d6b2868f990888 Mon Sep 17 00:00:00 2001 From: CueCrux-Myles Date: Fri, 31 Jul 2026 08:38:37 +0100 Subject: [PATCH 3/4] fix(security): remove yanked ZIP crypto path Stop receipt and daemon crates from re-enabling ZIP defaults so the unused aes-crypto graph is absent. Make the CLI Deflate backend explicit with zlib-rs, add a compressed-import round trip, refresh root and fuzz locks, and enforce yanked-package denial in the existing cargo-deny CI job. Co-Authored-By: OpenAI Codex --- .github/workflows/audit.yml | 2 +- Cargo.lock | 107 +------- crates/corecrux-receipts/Cargo.toml | 2 +- crates/corecruxctl/Cargo.toml | 2 +- crates/corecruxctl/src/audit_pack.rs | 20 ++ crates/corecruxd/Cargo.toml | 2 +- deny.toml | 1 + fuzz/Cargo.lock | 389 ++++++++++----------------- 8 files changed, 164 insertions(+), 361 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 651d1512..892be2f1 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -63,7 +63,7 @@ jobs: - name: Check advisory ignore metadata run: bash scripts/check-deny-advisory-ignores.sh - - name: Check cargo-deny policy + - name: Check advisories, yanked dependencies, and cargo-deny policy run: cargo deny check cargo-audit: diff --git a/Cargo.lock b/Cargo.lock index 8601d58c..f2e240a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,17 +27,6 @@ dependencies = [ "inout", ] -[[package]] -name = "aes" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" -dependencies = [ - "cipher", - "cpubits", - "cpufeatures 0.3.0", -] - [[package]] name = "ahash" version = "0.8.12" @@ -408,7 +397,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", - "zeroize", ] [[package]] @@ -432,15 +420,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "bzip2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" -dependencies = [ - "libbz2-rs-sys", -] - [[package]] name = "cast" version = "0.3.0" @@ -1134,12 +1113,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "cpubits" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -1767,12 +1740,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" -[[package]] -name = "deflate64" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" - [[package]] name = "der" version = "0.7.10" @@ -1896,7 +1863,6 @@ dependencies = [ "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", - "zeroize", ] [[package]] @@ -2369,13 +2335,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", "wasip2", "wasip3", - "wasm-bindgen", ] [[package]] @@ -2987,12 +2951,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "libbz2-rs-sys" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" - [[package]] name = "libc" version = "0.2.186" @@ -3068,15 +3026,6 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e20f57f9918e5bd7bc58c22cdd70a6afc7375d4dd9683af5f2b34bd3d2bba619" -[[package]] -name = "lzma-rust2" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47bb1e988e6fb779cf720ad431242d3f03167c1b3f2b1aae7f1a94b2495b36ae" -dependencies = [ - "sha2 0.10.9", -] - [[package]] name = "mach2" version = "0.6.0" @@ -3532,7 +3481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf211e3776eea6aec988552fa118dd746d70e1b1e5e244058d1c98015f3e5872" dependencies = [ "hmac-sha256", - "lzma-rust2 0.15.8", + "lzma-rust2", "ureq 3.3.0", ] @@ -3587,16 +3536,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pbkdf2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" -dependencies = [ - "digest 0.11.3", - "hmac 0.13.0", -] - [[package]] name = "pem" version = "3.0.6" @@ -3845,12 +3784,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppmd-rust" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -4795,17 +4728,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2" version = "0.10.9" @@ -5146,7 +5068,6 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", - "js-sys", "num-conv", "powerfmt", "serde_core", @@ -7083,25 +7004,11 @@ version = "8.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" dependencies = [ - "aes", - "bzip2", - "constant_time_eq", "crc32fast", - "deflate64", "flate2", - "getrandom 0.4.2", - "hmac 0.13.0", "indexmap", - "lzma-rust2 0.16.2", "memchr", - "pbkdf2", - "ppmd-rust", - "sha1", - "time", "typed-path", - "zeroize", - "zopfli", - "zstd", ] [[package]] @@ -7116,18 +7023,6 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - [[package]] name = "zstd" version = "0.13.3" diff --git a/crates/corecrux-receipts/Cargo.toml b/crates/corecrux-receipts/Cargo.toml index 143addc4..e6ff9f19 100644 --- a/crates/corecrux-receipts/Cargo.toml +++ b/crates/corecrux-receipts/Cargo.toml @@ -34,7 +34,7 @@ sha2 = { workspace = true } tar = { workspace = true } thiserror = { workspace = true } zstd = { workspace = true } -zip = { workspace = true, features = ["default"] } +zip = { workspace = true } # C2PA X.509 trust-anchor signer (agent-ux-07 M6). # `p256` provides the local P-256 leaf key (ECDSA-with-SHA256). diff --git a/crates/corecruxctl/Cargo.toml b/crates/corecruxctl/Cargo.toml index 3b118e3b..839bdb7e 100644 --- a/crates/corecruxctl/Cargo.toml +++ b/crates/corecruxctl/Cargo.toml @@ -37,7 +37,7 @@ url = { workspace = true } urlencoding = { workspace = true } uuid = { workspace = true, features = ["v4"] } xxhash-rust = { workspace = true, features = ["xxh64"] } -zip = { workspace = true, default-features = false, features = ["deflate-flate2"] } +zip = { workspace = true, default-features = false, features = ["deflate-flate2-zlib-rs"] } rand = { workspace = true } sha2 = { workspace = true } diff --git a/crates/corecruxctl/src/audit_pack.rs b/crates/corecruxctl/src/audit_pack.rs index 66d15dc8..691de391 100644 --- a/crates/corecruxctl/src/audit_pack.rs +++ b/crates/corecruxctl/src/audit_pack.rs @@ -2373,6 +2373,7 @@ fn build_integrity_scan_report(opts: &AuditPackOptionsV1) -> IntegrityScanReport #[allow(clippy::unwrap_used, clippy::expect_used)] mod tests { use super::*; + use std::io::Write as _; use tempfile::tempdir; fn h(seq: u64, event_id: &str) -> StreamHeaderLineV1 { @@ -2386,6 +2387,25 @@ mod tests { } } + #[test] + fn deflated_receipt_export_entry_round_trips() { + let expected = b"{\"schema\":\"corecrux.receipt_export.v1\"}\n"; + let mut cursor = Cursor::new(Vec::new()); + { + let mut writer = zip::ZipWriter::new(&mut cursor); + let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated); + writer + .start_file("manifest.json", options) + .expect("start deflated manifest entry"); + writer.write_all(expected).expect("write deflated manifest"); + writer.finish().expect("finish deflated receipt export"); + } + + let mut archive = zip::ZipArchive::new(Cursor::new(cursor.into_inner())).expect("open deflated receipt export"); + let actual = read_zip_entry(&mut archive, "manifest.json").expect("read deflated receipt export entry"); + assert_eq!(actual, expected); + } + #[test] fn audit_status_worst_ordering() { assert_eq!( diff --git a/crates/corecruxd/Cargo.toml b/crates/corecruxd/Cargo.toml index 6a4be754..42935b4a 100644 --- a/crates/corecruxd/Cargo.toml +++ b/crates/corecruxd/Cargo.toml @@ -75,7 +75,7 @@ uuid = { workspace = true, features = ["v4"] } url = { workspace = true } zeroize = "1.8" zstd = { workspace = true } -zip = { workspace = true, features = ["default"] } +zip = { workspace = true } corecrux-frame = { path = "../corecrux-frame" } crux-escrow = { path = "../crux-escrow" } diff --git a/deny.toml b/deny.toml index a68f329b..effaf91d 100644 --- a/deny.toml +++ b/deny.toml @@ -8,6 +8,7 @@ no-default-features = false # ── Advisories ────────────────────────────────────────────────────── [advisories] +yanked = "deny" ignore = [ # Resolved: protobuf 2.28.0 eliminated by tonic 0.14 upgrade + prometheus default-features=false. ] diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 2d32b513..001978f2 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -2,31 +2,14 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.6", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.9.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ - "cipher 0.5.2", - "cpubits", - "cpufeatures 0.3.0", + "crypto-common 0.2.2", + "inout", ] [[package]] @@ -110,6 +93,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b25655df2c3cdd83c5e5b293b88acd880332b2ddadd7c30ac43144fdc0033da9" + [[package]] name = "base64ct" version = "1.8.3" @@ -161,7 +150,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", - "zeroize", ] [[package]] @@ -170,15 +158,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bzip2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" -dependencies = [ - "libbz2-rs-sys", -] - [[package]] name = "cc" version = "1.2.64" @@ -197,17 +176,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures 0.2.17", -] - [[package]] name = "chacha20" version = "0.10.0" @@ -215,21 +183,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", + "cipher", "cpufeatures 0.3.0", "rand_core 0.10.1", ] [[package]] name = "chacha20poly1305" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +checksum = "9b89e1c441e926b9c82a8d023f6e1b7ae0adcfaa7d621814e4d60789bac751cb" dependencies = [ "aead", - "chacha20 0.9.1", - "cipher 0.4.4", + "chacha20", + "cipher", "poly1305", - "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "num-traits", ] [[package]] @@ -259,25 +236,15 @@ dependencies = [ "half", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common 0.1.6", - "inout 0.1.4", - "zeroize", -] - [[package]] name = "cipher" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ + "block-buffer 0.12.1", "crypto-common 0.2.2", - "inout 0.2.2", + "inout", ] [[package]] @@ -286,6 +253,18 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "cms" +version = "0.3.0-pre.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986e7713348fbe5322df18082d73a0ad53a2946dbb7a02452cf3622b83b91e7c" +dependencies = [ + "const-oid 0.10.2", + "der 0.8.1", + "spki 0.8.0", + "x509-cert", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -306,7 +285,7 @@ checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "corecrux-frame" -version = "0.3.1" +version = "0.5.53" dependencies = [ "blake3", "thiserror", @@ -329,7 +308,7 @@ dependencies = [ [[package]] name = "corecrux-index" -version = "0.3.1" +version = "0.5.53" dependencies = [ "blake3", "corecrux-frame", @@ -340,19 +319,25 @@ dependencies = [ [[package]] name = "corecrux-receipts" -version = "0.3.1" +version = "0.5.53" dependencies = [ - "base64", + "base64 0.23.0", "blake3", "chacha20poly1305", + "chrono", "ciborium", + "ciborium-ll", + "cms", + "const-oid 0.10.2", "corecrux-types", - "der", + "der 0.7.10", "ed25519-dalek", + "half", "p256", "parking_lot", "rand_core 0.6.4", "rcgen", + "ring", "serde", "serde_bytes", "serde_json", @@ -361,14 +346,16 @@ dependencies = [ "thiserror", "ureq", "url", + "uuid", "x509-cert", + "x509-parser", "zip", "zstd", ] [[package]] name = "corecrux-segment" -version = "0.3.1" +version = "0.5.53" dependencies = [ "blake3", "crc32c", @@ -381,7 +368,7 @@ dependencies = [ [[package]] name = "corecrux-storage" -version = "0.3.1" +version = "0.5.53" dependencies = [ "blake3", "corecrux-frame", @@ -397,7 +384,7 @@ dependencies = [ [[package]] name = "corecrux-types" -version = "0.3.1" +version = "0.5.53" dependencies = [ "anyhow", "blake3", @@ -406,12 +393,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cpubits" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -456,7 +437,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crux-session" -version = "0.3.1" +version = "0.5.53" dependencies = [ "blake3", "ed25519-dalek", @@ -465,6 +446,7 @@ dependencies = [ "serde", "serde_json", "thiserror", + "zeroize", ] [[package]] @@ -486,7 +468,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] @@ -496,7 +477,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.2", "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -541,12 +524,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" -[[package]] -name = "deflate64" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" - [[package]] name = "der" version = "0.7.10" @@ -554,9 +531,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid 0.9.6", + "pem-rfc7468 0.7.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +dependencies = [ + "const-oid 0.10.2", "der_derive", "flagset", - "pem-rfc7468", + "pem-rfc7468 1.0.0", "zeroize", ] @@ -576,9 +564,9 @@ dependencies = [ [[package]] name = "der_derive" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +checksum = "59600e2c2d636fde9b65e99cc6445ac770c63d3628195ff39932b8d6d7409903" dependencies = [ "proc-macro2", "quote", @@ -612,8 +600,6 @@ dependencies = [ "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", - "ctutils", - "zeroize", ] [[package]] @@ -633,12 +619,12 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.10", "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", - "spki", + "spki 0.7.3", ] [[package]] @@ -678,7 +664,7 @@ dependencies = [ "ff", "generic-array", "group", - "pem-rfc7468", + "pem-rfc7468 0.7.0", "pkcs8", "rand_core 0.6.4", "sec1", @@ -740,16 +726,6 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe" -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "miniz_oxide", - "zlib-rs", -] - [[package]] name = "foldhash" version = "0.1.5" @@ -840,13 +816,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", "wasip2", "wasip3", - "wasm-bindgen", ] [[package]] @@ -907,15 +881,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest 0.11.3", -] - [[package]] name = "hybrid-array" version = "0.4.12" @@ -1046,15 +1011,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - [[package]] name = "inout" version = "0.2.2" @@ -1082,9 +1038,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.102" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", @@ -1103,12 +1059,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "libbz2-rs-sys" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" - [[package]] name = "libc" version = "0.2.186" @@ -1154,22 +1104,13 @@ checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "lz4_flex" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e" +checksum = "ecbdfe44b1bd960b68170b417450a628c43f7cf56bb3c5317e61cb230ee7f226" dependencies = [ "twox-hash", ] -[[package]] -name = "lzma-rust2" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce716bf1a316f47a280fc76295f6495b5bea4752bca01c3b3885e101b1c23c02" -dependencies = [ - "sha2 0.11.0", -] - [[package]] name = "memchr" version = "2.8.2" @@ -1182,16 +1123,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - [[package]] name = "nom" version = "7.1.3" @@ -1251,12 +1182,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "p256" version = "0.13.2" @@ -1292,23 +1217,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "pbkdf2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" -dependencies = [ - "digest 0.11.3", - "hmac 0.13.0", -] - [[package]] name = "pem" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -1321,6 +1236,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1339,8 +1263,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", ] [[package]] @@ -1351,12 +1275,11 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "poly1305" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +checksum = "6e2d0073b297041425c7c3df6eb4792d598a15323fe63346852b092eca02904c" dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", + "cpufeatures 0.3.0", "universal-hash", ] @@ -1375,12 +1298,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppmd-rust" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" - [[package]] name = "prettyplease" version = "0.2.37" @@ -1436,7 +1353,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20 0.10.0", + "chacha20", "getrandom 0.4.2", "rand_core 0.10.1", ] @@ -1472,12 +1389,13 @@ dependencies = [ [[package]] name = "rcx-capability-token" -version = "0.3.1" +version = "0.5.53" dependencies = [ "blake3", "crux-session", "ed25519-dalek", "hex", + "serde", ] [[package]] @@ -1495,7 +1413,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -1598,7 +1516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der", + "der 0.7.10", "generic-array", "pkcs8", "subtle", @@ -1665,17 +1583,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2" version = "0.10.9" @@ -1714,12 +1621,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simd-adler32" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" - [[package]] name = "slab" version = "0.4.12" @@ -1739,7 +1640,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.1", ] [[package]] @@ -1814,7 +1725,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469" dependencies = [ "deranged", - "js-sys", "num-conv", "powerfmt", "serde_core", @@ -1932,12 +1842,12 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" dependencies = [ - "crypto-common 0.1.6", - "subtle", + "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -1952,7 +1862,7 @@ version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ - "base64", + "base64 0.22.1", "log", "once_cell", "rustls", @@ -1981,6 +1891,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "version_check" version = "0.9.5" @@ -2013,9 +1933,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -2026,9 +1946,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2036,9 +1956,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -2049,9 +1969,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.125" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] @@ -2320,13 +2240,13 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x509-cert" -version = "0.2.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94" +checksum = "105ef4642d9cb137ef83d623d0e4bf08b8adf69e9918ca904a174adb6d3d038b" dependencies = [ - "const-oid 0.9.6", - "der", - "spki", + "const-oid 0.10.2", + "der 0.8.1", + "spki 0.8.0", "tls_codec", ] @@ -2497,51 +2417,18 @@ version = "8.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" dependencies = [ - "aes", - "bzip2", - "constant_time_eq", "crc32fast", - "deflate64", - "flate2", - "getrandom 0.4.2", - "hmac 0.13.0", "indexmap", - "lzma-rust2", "memchr", - "pbkdf2", - "ppmd-rust", - "sha1", - "time", "typed-path", - "zeroize", - "zopfli", - "zstd", ] -[[package]] -name = "zlib-rs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" - [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - [[package]] name = "zstd" version = "0.13.3" From c45298ebae56d9cb2ccc7acff4d32aa7077cabd8 Mon Sep 17 00:00:00 2001 From: CueCrux-Myles Date: Fri, 7 Aug 2026 21:05:37 +0100 Subject: [PATCH 4/4] fix(deps): regenerate both lockfiles after the cherry-pick `014cd05a` carried its own Cargo.lock from 2026-07-30. Replayed onto a main that has moved 76 commits since, that lock no longer resolves, and CI runs `--locked`, so every cargo job failed identically: error: the lock file Cargo.lock needs to be updated but --locked was passed My local verification missed it by running cargo without `--locked`, which silently regenerated the lock instead of failing. Both workspace and fuzz lockfiles regenerated and re-checked with `--locked`. Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.lock | 17 +++++++++++++++++ fuzz/Cargo.lock | 18 ++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2e240a3..93e093ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1113,6 +1113,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -4728,6 +4734,17 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 001978f2..60373c84 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -207,6 +207,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "num-traits", + "serde", ] [[package]] @@ -285,7 +286,7 @@ checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "corecrux-frame" -version = "0.5.53" +version = "0.5.57" dependencies = [ "blake3", "thiserror", @@ -308,7 +309,7 @@ dependencies = [ [[package]] name = "corecrux-index" -version = "0.5.53" +version = "0.5.57" dependencies = [ "blake3", "corecrux-frame", @@ -319,7 +320,7 @@ dependencies = [ [[package]] name = "corecrux-receipts" -version = "0.5.53" +version = "0.5.57" dependencies = [ "base64 0.23.0", "blake3", @@ -333,6 +334,7 @@ dependencies = [ "der 0.7.10", "ed25519-dalek", "half", + "hex", "p256", "parking_lot", "rand_core 0.6.4", @@ -355,7 +357,7 @@ dependencies = [ [[package]] name = "corecrux-segment" -version = "0.5.53" +version = "0.5.57" dependencies = [ "blake3", "crc32c", @@ -368,7 +370,7 @@ dependencies = [ [[package]] name = "corecrux-storage" -version = "0.5.53" +version = "0.5.57" dependencies = [ "blake3", "corecrux-frame", @@ -384,7 +386,7 @@ dependencies = [ [[package]] name = "corecrux-types" -version = "0.5.53" +version = "0.5.57" dependencies = [ "anyhow", "blake3", @@ -437,7 +439,7 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crux-session" -version = "0.5.53" +version = "0.5.57" dependencies = [ "blake3", "ed25519-dalek", @@ -1389,7 +1391,7 @@ dependencies = [ [[package]] name = "rcx-capability-token" -version = "0.5.53" +version = "0.5.57" dependencies = [ "blake3", "crux-session",