72 lines
7.4 KiB
Markdown
72 lines
7.4 KiB
Markdown
# 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.
|