Files
web-hosts/domains/coppertone.tech/docs/audits/20251120-160733-security-audit.md
2025-12-26 13:38:04 +01:00

7.4 KiB
Raw Blame History

Copper Tone Technologies Security/Quality Audit

Date: 2025-11-20
Auditor: Codex (manual review per docs/AUDITOR.md guidance)
Scope: Backend Go services, database migrations, frontend auth/XSS posture, infra (Containerfiles, podman-compose), CI workflows.
Method: Static code/config review only. Automated scans (npm audit, govulncheck, trivy, lint/tests) not run because this session uses a restricted, offline sandbox.


Executive Summary

  • Overall risk: High. Users can self-assign ADMIN at registration, downstream services do not enforce ownership/role checks, and the Stripe webhook has no signature verification. These allow total compromise of data and payments.
  • Transport security gaps: All services hardcode sslmode=disable to Postgres; no way to require TLS in production.
  • Auth inconsistencies: JWT claims differ between issuer and consumers (userId vs user_id), enabling logic bugs while RBAC already trusts client-chosen roles.
  • Frontend XSS exposure: Markdown is rendered and injected via v-html with no sanitization.
  • DevOps hygiene: Compose ships default secrets; containers run as root (scratch images) without CA certs; no backend .env.example.

Critical Findings

  • Privilege escalation at signup handleRegisterEmailPassword and handleRegisterBlockchain accept a user-supplied role and insert it directly (backend/functions/auth-service/main.go:168-263). Any new user can register as ADMIN, then call other services with administrative privileges.
  • No authorization / data-owner checks Work Management and Payment services allow any authenticated token to list/read/update/delete all projects, tasks, work orders, invoices, and payments; no scoping to the caller or role gating beyond minimal wrappers (backend/functions/work-management-service/main.go:69-360, payment-service/main.go:97-454). Combined with self-assigned admin, this is full data disclosure/modification risk.
  • Stripe webhook unauthenticated /webhooks/stripe has TODOs for signature verification and status updates but currently accepts any POST and returns 200 (backend/functions/payment-service/main.go:682-694). An attacker can spoof payment events.
  • Database TLS disabled All services build sslmode=disable into the connection string with no override (auth-service/main.go:126-147, work-management-service/main.go:91-114, payment-service/main.go:121-144). Production connections would be unencrypted and unauthenticated.

High Findings

  • JWT claim mismatch / brittle RBAC Auth service issues tokens with userId while other services expect user_id (auth-service/main.go:590-612 vs work-management-service/main.go:172-178, payment-service/main.go:201-207). Context user IDs become nil, and RBAC trusts whatever roles are in the token (already user-controlled). Inconsistent claims increase the chance of bypasses/bugs.
  • Replayable blockchain login verifyEthereumSignature trusts arbitrary message provided by the client and stores no nonce/timestamp (auth-service/main.go:241-320). A captured signature can be replayed indefinitely to log in.
  • Over-permissive CORS All services send Access-Control-Allow-Origin: * with broad methods/headers (auth-service/main.go:151-164, work-management-service/main.go:116-130, payment-service/main.go:146-159). In production this enables credential theft if tokens are ever stored/sent in browsers across origins.
  • Default secrets in compose podman-compose.yml ships your_super_secret_jwt_key and DB_PASSWORD=password baked into service env, suitable only for dev but likely to be deployed as-is; no backend .env.example enumerating required secrets.

Medium Findings

  • Payment/auth amounts handled as floats API structs use float64 for money (payment-service/main.go:21-68); conversions to cents rely on floating arithmetic (main.go:641-665). Risk of rounding errors; prefer fixed-point integers.
  • Task/status updates trust clients fully No server-side validation of allowed status transitions or ownership (e.g., anyone can mark tasks completed or change invoice status).
  • Container hardening Final images run as root (FROM scratch) with no USER directive and no CA certificates; TLS outbound would fail, and root runtime expands attack surface.
  • CI image builds may fail Workflows call podman build on GitHub-hosted runners without ensuring podman availability/privileged mode; not a security bug but a reliability gap.

Low Findings

  • Frontend XSS vector via Markdown ServiceDetailView.vue and ArticleDetailView.vue render Markdown to HTML and inject via v-html without sanitization (frontend/src/views/ServiceDetailView.vue:8-18, ArticleDetailView.vue:8-22). Safe only if content is fully trusted and static; otherwise add sanitization.
  • JWT secret length not enforced Auth service only checks non-empty (auth-service/main.go:93-99); mandate 3264+ random bytes.
  • No rate limiting / brute-force protection Login endpoints have no throttling or lockouts.

Database/Migrations Review

  • Migrations are paired and reversible (001003 up/down present). Types and constraints look sane (ENUMs for status/roles, FK indexes present). No plaintext secrets stored.
  • Missing: encryption requirements for sensitive columns (if any added later), and no migrations enforce audited timestamp updates for all tables beyond current set.

Environment & Compliance

  • Missing backend .env.example; secrets and required vars are not documented.
  • .gitignore properly excludes .env*. No evidence of secrets committed.
  • No HTTPS/TLS enforcement discussed for services; production should terminate TLS and set HSTS.
  • Accessibility, GDPR, PCI: not evaluated in depth; Stripe used for payments (good), but webhook is insecure.

Recommendations / Next Steps

  1. Fix auth/RBAC: Remove client-controlled roles from registration; default to least-privileged and add an admin-only path to elevate. Align JWT claims (userId vs user_id) and validate roles server-side per endpoint. Add ownership checks for projects/tasks/invoices/payments.
  2. Secure transport & secrets: Make sslmode configurable with require/verify-full for production; document vars in a backend .env.example; replace compose defaults with ${VAR} placeholders.
  3. Stripe webhook: Verify signatures (Stripe-Signature), parse events, and update payment status safely.
  4. CORS & HTTP hardening: Restrict origins/methods/headers in prod; add request timeouts and rate limiting on auth endpoints.
  5. Blockchain login: Require server-issued nonce/timestamp, bind signatures to that nonce, and expire after single use.
  6. Frontend sanitization: Sanitize rendered Markdown (e.g., DOMPurify) or ensure content is immutable/trusted.
  7. Container/CI: Add non-root USER, CA certificates, and health probes; ensure CI runners support podman or switch to docker/buildx.
  8. Monetary precision: Store/accept currency values as integers of smallest unit; validate currency codes and ranges.
  9. Automated scans (run outside this sandbox): npm audit/npm audit --production, go mod verify, go vet, govulncheck, golangci-lint, trivy image, and full test suites.

Testing Performed

  • Not run (sandboxed, offline). All findings from static analysis of repository files only.