diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3acf1ba..3efbc4a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,16 +51,14 @@ jobs: run: | curl -sL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz \ | tar xz -C /usr/local/bin gitleaks - gitleaks detect --source . --exit-code 1 --report-format sarif --report-path gitleaks.sarif || true - continue-on-error: true # findings are reported, do not block the pipeline + gitleaks detect --source . --exit-code 1 --report-format sarif --report-path gitleaks.sarif # ── CVE in Rust dependencies ──────────────────────────────────────────── - name: Install cargo-audit run: cargo install cargo-audit --locked - name: cargo audit — RustSec advisory database - run: cargo audit - continue-on-error: true # warn only; known CVEs should not block CI until triaged + run: cargo audit --ignore RUSTSEC-2025-0119 # known: number_prefix via indicatif # ── Licenses, banned crates, supply chain policy ──────────────────────── - name: cargo deny — licenses and banned crates @@ -79,7 +77,7 @@ jobs: format: sarif output: trivy-fs.sarif severity: HIGH,CRITICAL - exit-code: 0 # warn only; change to 1 to block the pipeline + exit-code: 1 # block pipeline on HIGH/CRITICAL vulnerabilities - name: Upload Trivy fs results to GitHub Security tab uses: github/codeql-action/upload-sarif@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8044868..620a964 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -127,6 +127,17 @@ jobs: cache-from: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:astra cache-to: type=registry,ref=${{ env.NORA }}/${{ env.IMAGE_NAME }}-cache:astra,mode=max + # ── Smoke test ────────────────────────────────────────────────────────── + - name: Smoke test — verify alpine image starts and responds + run: | + docker run --rm -d --name nora-smoke -p 5555:5000 \ + ${{ env.NORA }}/${{ env.IMAGE_NAME }}:latest + for i in $(seq 1 10); do + curl -sf http://localhost:5555/health && break || sleep 2 + done + curl -sf http://localhost:5555/health + docker stop nora-smoke + scan: name: Scan (${{ matrix.name }}) runs-on: [self-hosted, nora] diff --git a/deny.toml b/deny.toml index 43c685e..8700a8c 100644 --- a/deny.toml +++ b/deny.toml @@ -5,7 +5,7 @@ # Vulnerability database (RustSec) db-urls = ["https://github.com/rustsec/advisory-db"] ignore = [ - "RUSTSEC-2025-0119", # number_prefix unmaintained, transitive via indicatif; no fix available + "RUSTSEC-2025-0119", # number_prefix unmaintained via indicatif; no fix available. Review by 2026-06-15 ] [licenses] diff --git a/nora-registry/src/error.rs b/nora-registry/src/error.rs index 1076656..8a1a83e 100644 --- a/nora-registry/src/error.rs +++ b/nora-registry/src/error.rs @@ -1,7 +1,6 @@ // Copyright (c) 2026 Volkov Pavel | DevITWay // SPDX-License-Identifier: MIT -#![allow(dead_code)] //! Application error handling with HTTP response conversion //! //! Provides a unified error type that can be converted to HTTP responses @@ -18,6 +17,7 @@ use thiserror::Error; use crate::storage::StorageError; use crate::validation::ValidationError; +#[allow(dead_code)] // Wiring into handlers planned for v0.3 /// Application-level errors with HTTP response conversion #[derive(Debug, Error)] pub enum AppError { @@ -40,6 +40,7 @@ pub enum AppError { Validation(#[from] ValidationError), } +#[allow(dead_code)] /// JSON error response body #[derive(Serialize)] struct ErrorResponse { @@ -74,6 +75,7 @@ impl IntoResponse for AppError { } } +#[allow(dead_code)] impl AppError { /// Create a not found error pub fn not_found(msg: impl Into) -> Self { diff --git a/nora-registry/src/openapi.rs b/nora-registry/src/openapi.rs index 1be2ce9..ade11b0 100644 --- a/nora-registry/src/openapi.rs +++ b/nora-registry/src/openapi.rs @@ -5,7 +5,8 @@ //! //! Functions in this module are stubs used only for generating OpenAPI documentation. -#![allow(dead_code)] + +#![allow(dead_code)] // utoipa doc stubs — not called at runtime, used by derive macros use axum::Router; use std::sync::Arc; diff --git a/nora-registry/src/secrets/mod.rs b/nora-registry/src/secrets/mod.rs index caeb177..0bba218 100644 --- a/nora-registry/src/secrets/mod.rs +++ b/nora-registry/src/secrets/mod.rs @@ -1,7 +1,6 @@ // Copyright (c) 2026 Volkov Pavel | DevITWay // SPDX-License-Identifier: MIT -#![allow(dead_code)] // Foundational code for future S3/Vault integration //! Secrets management for NORA //! @@ -34,6 +33,7 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; use thiserror::Error; +#[allow(dead_code)] // Variants used by provider impls; external error handling planned for v0.4 /// Secrets provider error #[derive(Debug, Error)] pub enum SecretsError { @@ -56,9 +56,11 @@ pub enum SecretsError { #[async_trait] pub trait SecretsProvider: Send + Sync { /// Get a secret by key (required) + #[allow(dead_code)] async fn get_secret(&self, key: &str) -> Result; /// Get a secret by key (optional, returns None if not found) + #[allow(dead_code)] async fn get_secret_optional(&self, key: &str) -> Option { self.get_secret(key).await.ok() } diff --git a/nora-registry/src/secrets/protected.rs b/nora-registry/src/secrets/protected.rs index e87dd99..9582c21 100644 --- a/nora-registry/src/secrets/protected.rs +++ b/nora-registry/src/secrets/protected.rs @@ -13,12 +13,14 @@ use zeroize::{Zeroize, Zeroizing}; /// - Implements Zeroize: memory is overwritten with zeros when dropped /// - Debug shows `***REDACTED***` instead of actual value /// - Clone creates a new protected copy +#[allow(dead_code)] // Used internally by SecretsProvider impls; external callers planned for v0.4 #[derive(Clone, Zeroize)] #[zeroize(drop)] pub struct ProtectedString { inner: String, } +#[allow(dead_code)] impl ProtectedString { /// Create a new protected string pub fn new(value: String) -> Self { @@ -68,6 +70,7 @@ impl From<&str> for ProtectedString { } /// S3 credentials with protected secrets +#[allow(dead_code)] // S3 storage backend planned for v0.4 #[derive(Clone, Zeroize)] #[zeroize(drop)] pub struct S3Credentials { @@ -77,6 +80,7 @@ pub struct S3Credentials { pub region: Option, } +#[allow(dead_code)] impl S3Credentials { pub fn new(access_key_id: String, secret_access_key: String) -> Self { Self { diff --git a/nora-registry/src/validation.rs b/nora-registry/src/validation.rs index 52210de..2364ff8 100644 --- a/nora-registry/src/validation.rs +++ b/nora-registry/src/validation.rs @@ -1,7 +1,6 @@ // Copyright (c) 2026 Volkov Pavel | DevITWay // SPDX-License-Identifier: MIT -#![allow(dead_code)] //! Input validation for artifact registry paths and identifiers //! //! Provides security validation to prevent path traversal attacks and @@ -309,63 +308,6 @@ pub fn validate_docker_reference(reference: &str) -> Result<(), ValidationError> Ok(()) } -/// Validate Maven artifact path. -/// -/// Maven paths follow the pattern: groupId/artifactId/version/filename -/// Example: `org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar` -pub fn validate_maven_path(path: &str) -> Result<(), ValidationError> { - validate_storage_key(path) -} - -/// Validate npm package name. -pub fn validate_npm_name(name: &str) -> Result<(), ValidationError> { - if name.is_empty() { - return Err(ValidationError::EmptyInput); - } - - if name.len() > 214 { - return Err(ValidationError::TooLong { - max: 214, - actual: name.len(), - }); - } - - // Check for path traversal - if name.contains("..") { - return Err(ValidationError::PathTraversal); - } - - Ok(()) -} - -/// Validate Cargo crate name. -pub fn validate_crate_name(name: &str) -> Result<(), ValidationError> { - if name.is_empty() { - return Err(ValidationError::EmptyInput); - } - - if name.len() > 64 { - return Err(ValidationError::TooLong { - max: 64, - actual: name.len(), - }); - } - - // Check for path traversal - if name.contains("..") || name.contains('/') { - return Err(ValidationError::PathTraversal); - } - - // Crate names: alphanumeric, underscores, hyphens - for c in name.chars() { - if !matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-') { - return Err(ValidationError::ForbiddenCharacter(c)); - } - } - - Ok(()) -} - #[cfg(test)] mod tests { use super::*;