diff --git a/.github/workflows/security-deep.yml b/.github/workflows/security-deep.yml index 0f52e9af..ed59046d 100644 --- a/.github/workflows/security-deep.yml +++ b/.github/workflows/security-deep.yml @@ -120,23 +120,16 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: persist-credentials: false - - name: Create reports directory - run: mkdir -p reports/trivy - - - name: Install Trivy CLI - run: | - curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | \ - sh -s -- -b /usr/local/bin v0.65.0 - trivy --version - - name: Run full Trivy filesystem, dependency, and IaC scan - run: | - # --exit-code 0: reporting-only scan; do NOT fail the step on vulnerabilities - trivy fs . \ - --scanners vuln,misconfig \ - --severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL \ - --format sarif \ - --output reports/trivy/trivy-full.sarif + uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 + with: + scan-type: fs + scan-ref: . + scanners: vuln,misconfig + severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL + format: sarif + output: reports/trivy/trivy-full.sarif + exit-code: '0' - name: Verify SARIF file exists id: verify-sarif run: | diff --git a/Cargo.lock b/Cargo.lock index b7a40dca..69f52cd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,6 +193,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "audit-sqlite" version = "0.1.0" @@ -1121,6 +1132,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1689,7 +1709,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi", + "hermit-abi 0.5.2", "libc", ] @@ -2468,6 +2488,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "atty", "audit-sqlite", "auth-sqlite", "axum 0.8.9", diff --git a/apps/rook/src/main.rs b/apps/rook/src/main.rs index 10546895..6b3c2fbe 100644 --- a/apps/rook/src/main.rs +++ b/apps/rook/src/main.rs @@ -174,13 +174,22 @@ async fn announce_bootstrap_if_needed(container: &di::RookContainer) -> anyhow:: match setup_token { Some(token) => { // Sanitize: replace control/non-printable chars to prevent log injection - let sanitized: String = token.chars().map(|c| { - if c.is_ascii_control() || c == '"' || c == '\\' || c == '\n' || c == '\r' || c == '\t' { - '?' - } else { - c - } - }).collect(); + let sanitized: String = token + .chars() + .map(|c| { + if c.is_ascii_control() + || c == '"' + || c == '\\' + || c == '\n' + || c == '\r' + || c == '\t' + { + '?' + } else { + c + } + }) + .collect(); let preview = if sanitized.len() > 8 { format!("{}…", &sanitized[..8]) } else { @@ -189,7 +198,9 @@ async fn announce_bootstrap_if_needed(container: &di::RookContainer) -> anyhow:: tracing::warn!(setup_token_preview = %preview, setup_token_len = token.len(), "rook is in bootstrap mode; set the admin password before using the server"); // Only print full token to interactive TTY; otherwise show preview only if atty::is(atty::Stream::Stderr) { - eprintln!("rook bootstrap mode: use setup token {token} to set the admin password"); + eprintln!( + "rook bootstrap mode: use setup token {sanitized} to set the admin password" + ); } else { eprintln!("rook bootstrap mode: use setup token {preview}… (len={}) to set the admin password", token.len()); } diff --git a/crates/application/rook-usecases/src/route_request.rs b/crates/application/rook-usecases/src/route_request.rs index 82cc49cd..e559f16f 100644 --- a/crates/application/rook-usecases/src/route_request.rs +++ b/crates/application/rook-usecases/src/route_request.rs @@ -119,7 +119,8 @@ impl RouteRequest { req: CompletionRequest, ) -> Result>, CortexError> { - self.execute_stream_with_format(req, ApiFormat::OpenAI).await + self.execute_stream_with_format(req, ApiFormat::OpenAI) + .await } pub async fn execute_stream_with_format( diff --git a/crates/domain/rook-core/src/ports.rs b/crates/domain/rook-core/src/ports.rs index 11a40324..7d5b11eb 100644 --- a/crates/domain/rook-core/src/ports.rs +++ b/crates/domain/rook-core/src/ports.rs @@ -13,11 +13,11 @@ use chrono::{DateTime, Utc}; use shared_kernel::{CacheKey, ConnectionId, CortexResult, ModelId, ProviderId}; use super::{ - ApiKeyId, ApiKeyRecord, ApiKeyRepositoryError, ApiKeySubject, NewSession, NewUser, - PasswordHash, ProviderConnection, RepositoryError, Session, SessionId, User, UserId, + ApiFormat, AuditEntry, CompletionRequest, CompletionResponse, HealthStatus, StreamChunk, }; use super::{ - ApiFormat, AuditEntry, CompletionRequest, CompletionResponse, HealthStatus, StreamChunk, + ApiKeyId, ApiKeyRecord, ApiKeyRepositoryError, ApiKeySubject, NewSession, NewUser, + PasswordHash, ProviderConnection, RepositoryError, Session, SessionId, User, UserId, }; /// --------------------------------------------------------------------------- diff --git a/crates/infrastructure/transport-axum/src/format_registry.rs b/crates/infrastructure/transport-axum/src/format_registry.rs index 52f97d1a..4c3ec80d 100644 --- a/crates/infrastructure/transport-axum/src/format_registry.rs +++ b/crates/infrastructure/transport-axum/src/format_registry.rs @@ -167,9 +167,7 @@ impl FormatTranslatorPort for FormatRegistry { } fn missing_translator(kind: &str, from: ApiFormat, to: ApiFormat) -> CortexError { - CortexError::invalid_request(format!( - "missing {kind} translator for {from:?} -> {to:?}" - )) + CortexError::invalid_request(format!("missing {kind} translator for {from:?} -> {to:?}")) } // --------------------------------------------------------------------------- diff --git a/crates/infrastructure/transport-axum/tests/format_translation_integration.rs b/crates/infrastructure/transport-axum/tests/format_translation_integration.rs index 2af06b3c..a76f0844 100644 --- a/crates/infrastructure/transport-axum/tests/format_translation_integration.rs +++ b/crates/infrastructure/transport-axum/tests/format_translation_integration.rs @@ -202,7 +202,6 @@ fn anthropic_response_has_correct_structure() { assert_eq!(json["usage"]["output_tokens"], 5); } - // --------------------------------------------------------------------------- // Registry-routed multi-format use case integration // --------------------------------------------------------------------------- @@ -210,8 +209,8 @@ fn anthropic_response_has_correct_structure() { use async_trait::async_trait; use futures::stream; use rook_core::{ - ApiFormat, AuditEntry, AuditPort, CacheKey, CachePort, CompletionRequest, - FormatTranslatorPort, HealthStatus, ProviderPort, RequestMetadata, RouterPort, StreamChunk, + ApiFormat, AuditEntry, AuditPort, CacheKey, CachePort, CompletionRequest, FormatTranslatorPort, + HealthStatus, ProviderPort, RequestMetadata, RouterPort, StreamChunk, }; use rook_usecases::RouteRequest; use std::{sync::Arc, time::Duration}; @@ -248,7 +247,10 @@ impl ProviderPort for RegistryTestProvider { } } - async fn complete(&self, req: &CompletionRequest) -> shared_kernel::CortexResult { + async fn complete( + &self, + req: &CompletionRequest, + ) -> shared_kernel::CortexResult { Ok(CompletionResponse { id: req.id.clone(), provider: self.id.clone(), @@ -267,7 +269,9 @@ impl ProviderPort for RegistryTestProvider { async fn stream( &self, _req: &CompletionRequest, - ) -> shared_kernel::CortexResult>> { + ) -> shared_kernel::CortexResult< + futures::stream::BoxStream<'static, shared_kernel::CortexResult>, + > { Ok(Box::pin(stream::empty())) } } @@ -278,7 +282,10 @@ struct RegistryTestRouter { #[async_trait] impl RouterPort for RegistryTestRouter { - async fn select(&self, _req: &CompletionRequest) -> shared_kernel::CortexResult> { + async fn select( + &self, + _req: &CompletionRequest, + ) -> shared_kernel::CortexResult> { Ok(self.provider.clone()) } @@ -293,7 +300,10 @@ struct NoopCache; #[async_trait] impl CachePort for NoopCache { - async fn get(&self, _key: &CacheKey) -> shared_kernel::CortexResult> { + async fn get( + &self, + _key: &CacheKey, + ) -> shared_kernel::CortexResult> { Ok(None) }