Skip to content

Security audit remediation - #1

Merged
sowahq merged 11 commits into
mainfrom
security/audit-hardening
Sep 3, 2026
Merged

sowahq merged 11 commits into
mainfrom
security/audit-hardening

Conversation

@sowahq

@sowahq sowahq commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Security audit remediation. Findings came from a multi-dimension audit (auth, crypto, HTTP handlers, cluster) plus govulncheck. Each fix has a regression test where behavior changed; full suite and govulncheck symbol scan are green under the pinned toolchain.

Critical

  • Cluster RPC now requires a verified handshake before any method dispatch. drpcserver ran with no interceptor, so the HMAC Handshake was decorative: any peer reaching cluster.listen could call PutChunk, PutMetadata, Join, or BulkSyncKV (exfiltrating all S3 keys, tokens and SSE key material) unauthenticated. Added a per-connection auth gate keyed on the transport, cleaned up on close.
  • SSE-C no longer reuses the raw customer key with a 32-bit nonce prefix (GCM nonce reuse at ~2^16 objects). Each object now derives a key via HKDF over a per-object salt, matching SSE-S3. Legacy objects without a salt still read.

High

  • Admin API fails closed: with no admin_token set it is denied, unless the operator opts in via allow_unauthenticated_admin. Was open by default.
  • Join is covered by the handshake gate (was unauthenticated layout mutation).
  • Cluster TLS now requires a CA (mutual auth); previously cert+key without a CA silently disabled client verification.
  • Bounded the aws-chunked chunk size before allocation (was make([]byte, size) on an attacker-controlled header before signature check) and bounded peer PutChunk buffering.

Medium

  • S3 secret keys and the SSE keyring are encrypted at rest under a master-derived KEK (were plaintext in Pebble). Transparent unwrap on read; S3 secrets stay plaintext when no master key is configured.
  • UploadPart now verifies the payload against X-Amz-Content-Sha256 (single PUT already did).
  • DeleteObjects capped at 1000 keys, CompleteMultipartUpload at 10000 parts, control XML bodies capped; rejected negative x-amz-decoded-content-length.
  • Metrics endpoint binds to loopback by default.

Low

  • Config file created 0600 (holds secrets).
  • Handshake nonce replay cache within the skew window.
  • POST policy requires an expiration.
  • Internal error strings masked in S3 responses, logged server-side.
  • X-Content-Type-Options: nosniff on all responses, HSTS under TLS.
  • /status detail (go version, disk free, error text) gated behind the admin token.
  • Constant-time SSE-C key MD5 comparison.

Dependencies

  • fasthttp 1.51.0 -> 1.70.0 (authorization bypass via double-encoded paths), fiber 2.52.12 -> 2.52.15 (AutoFormat XSS), klauspost/compress 1.18.0 -> 1.18.7 (s2 OOB read), x/crypto 0.50.0 -> 0.52.0.
  • Pinned toolchain go1.25.13 for the patched standard library (encoding/xml recursion, net/url, crypto/tls, os symlink Root escape).
  • govulncheck symbol scan reports 0 reachable vulnerabilities; remaining module-level hits are in unused packages (x/crypto/ssh, openpgp, x/net/html).

@sowahq
sowahq merged commit 779d675 into main Sep 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant