ODS Platform · Security Review

docstore / OWASP Top 10

Commit 6759be6
Branch dev
Date 2026-03-19
Reviewer Security Agent
OWASP Score 7 out of 10 Concerns
6 Pass
3 Warn
0 Fail
2 N/A
OWASP Top 10 Checks
A01
Injection
Pass
  • All SQL via sqlx parameterized bindings ($1, $2…) — no string interpolation in any query
  • begin_tenant_tx sets app.current_tenant_id via bound set_config() — not format strings
  • No Command::new with user-supplied input anywhere in codebase
  • RLS tenant context applied per-transaction, never concatenated
A02
Broken Authentication
Warn
  • JWT validated with RS256 signature — algorithm pinned, alg:none bypass impossible
  • validate_exp, validate_nbf, issuer and audience all enforced in decode_jwt()
  • Clock-skew leeway configurable (default 30 s via JWT_LEEWAY_SECS)
  • auth.rs:42 — raw JWT library error forwarded to client via format!("Invalid token: {e}"). Leaks "ExpiredSignature" or "InvalidIssuer" details. Log server-side; return generic "Invalid token" to caller.
A03
Sensitive Data Exposure
Pass
  • storage_key excluded from DocumentResponse — confirmed at domain/document.rs:45–80
  • Internal / Database errors return generic message — error.rs:38–41 hides all details
  • No hardcoded secrets; test RSA key pair in middleware.rs clearly labeled test-only
  • .gitignore covers .env, .env.*, *.log, *.pem, *.key
  • ·Document struct derives Serialize (includes storage_key) — API correctly uses DocumentResponse; low risk but track
A04
XML External Entities
N/A
  • JSON-only API. No XML parsing libraries present. Not applicable.
A05
Broken Access Control
Pass
  • RLS policies on all 6 tables — documents, folders, document_versions, tags, document_tags, audit_log
  • tenant_id set via set_config() in every transaction — RLS cannot be bypassed without a transaction
  • All endpoints call auth.require_roles() before any data access; no endpoint is unguarded
  • Audit restricted to tenant-admin / super-admin; writes require editor+; reads require viewer+
  • ·/health and /ready unauthenticated — by design for liveness probes; no tenant data exposed
A06
Security Misconfiguration
Warn
  • Distroless container (cc-debian12:nonroot), USER nonroot:nonroot — minimal attack surface
  • No debug mode or verbose error details in production code paths
  • max_body_size configured (default 1 MiB) but NOT applied in main.rs — no web::JsonConfig or web::PayloadConfig registered; actix-web default 256 KB applies instead. Config is dead code.
  • ·CORS at Traefik gateway per ADR-001 — cors_allowed_origins parsed for awareness; gateway config must be verified before production
A07
Cross-Site Scripting
N/A
  • API-only service. No HTML rendering, no templating engine. Not applicable.
A08
Insecure Deserialization
Pass
  • Standard serde_json deserialization — no unsafe or custom paths
  • Metadata validated: max nesting depth 5, max key count 50 — domain/document.rs:100–133
  • Status field validated against enum values; invalid input returns 400
  • ·Body size limit: actix default 256 KB applies (intended 1 MiB config not wired — see A06)
A09
Known Vulnerabilities
Warn
  • cargo-audit not installed — automated advisory scan could not be run
  • cargo audit not in CI pipeline (ci-deploy.yml) — no automated dependency scanning
  • Cargo.lock committed — enables reproducible builds and future advisory scanning
  • ·Key deps reviewed manually: actix-web 4, sqlx 0.8, jsonwebtoken 9, rdkafka 0.36 — no known critical advisories at review date
A10
Insufficient Logging
Pass
  • Structured JSON logging via tracing-subscriber with env-filter
  • correlation_id propagated from X-Correlation-Id header or generated as UUID through all service calls
  • Audit log records who/when/what for all mutations; write failures are fatal — enforced by type system
  • Internal errors return generic "Internal server error" — no stack traces leaked to client
Automated Scans
Cargo Audit
Not available — cargo-audit not installed.
Install: cargo install cargo-audit
Add to CI after lint step: cargo audit
Manual review found no known critical advisories for actix-web 4, sqlx 0.8, jsonwebtoken 9, rdkafka 0.36 at review date. Manual review is not a substitute for automated scanning.
Secrets Scan
✓ CLEAN
No hardcoded credentials, API keys, or tokens found in source files.
Test RSA key pair in middleware.rs is clearly labeled test-only with explicit security notice. .gitignore excludes .env, *.pem, *.key.
Unsafe Blocks
1 location — test code only
src/config.rs — lines 83, 90, 91, 163–165, 178
unsafe { env::set_var() / env::remove_var() }
Required in Rust 2024 edition. Inside #[cfg(test)] only. Thread-safe via Mutex<()> (ENV_LOCK). No unsafe in production code paths.
Remediation Priority
# Finding Effort Impact Action
1 Add cargo audit to CI pipeline Low High cargo install cargo-audit && cargo audit — add after lint step in ci-deploy.yml
2 Sanitize JWT error in Unauthorized response Low Medium src/api/auth.rs:42 — log {e} server-side via tracing::warn; return generic "Invalid token" to client
3 Wire max_body_size into actix middleware Low Medium main.rs — add web::JsonConfig::default().limit(config.max_body_size) and web::PayloadConfig::default().limit(...)
4 Verify Traefik gateway CORS & rate-limiting config Medium High Required before production per ADR-001. Confirm Traefik middleware applied to all docstore routes.
Security Strengths