# 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 32–64+ random bytes. - **No rate limiting / brute-force protection** – Login endpoints have no throttling or lockouts. --- ## Database/Migrations Review - Migrations are paired and reversible (001–003 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.