# ActaLog — Code Maturity Assessment **Framework**: Trail of Bits Code Maturity Evaluation v0.1.0 (adapted for web application) **Date**: 2026-04-03 (base assessment) · **Last reviewed**: 2026-07-04 (v1.3.3) **Version Assessed**: 1.2.1 (base) — see "Remediation status" below for changes through 1.3.3 **Deployment Context**: Single-instance, ~3 users --- ## Remediation status — updated 2026-07-04 (v1.3.3) The base assessment below reflects v1.2.1 (2026-04-03). Several of its top gaps have since been closed; this section tracks the delta so the scorecard is not read as current state. **Resolved since the base assessment:** - ✅ **CORS enforcement** — allowlist now actually enforced (v1.2.3). - ✅ **Security response headers** — CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy middleware added (v1.2.4). - ✅ **Password policy** — 12-char minimum + complexity (v1.2.3). - ✅ **File upload hardening** — avatar uploads validated by magic bytes, not client `Content-Type` (v1.2.4). - ✅ **CI toolchain drift** — Node 24 + npm pinned to 11.16.0 in CI and Docker so `npm ci` stays in lockstep with the lockfile (v1.3.3). **In progress (this release — CI/supply-chain hardening):** - 🔶 Go lint regressions now blocking on PRs (`only-new-issues`); `govulncheck`, `npm audit`, and a Trivy image scan added as blocking CI gates; GitHub Actions pinned to commit SHAs and Docker base images pinned to digests. **Still open (tracked, not yet addressed):** - ⬜ **Repository-layer `context.Context`** — 0 of 373 repo functions accept a context, so DB calls have no timeout/cancellation (`noctx` lint disabled with 851 violations noted). Needs a dedicated refactor branch. - ⬜ **Single JWT signing secret** — no key rotation (`kid` + multi-key) path. - ⬜ **Large files** — `backup_service.go` (2.2k), `scheduling_handler.go` (1.8k), `data_quality_service.go` (1.5k) remain decomposition candidates. - ⬜ **Accumulated lint debt** — ~605 golangci-lint findings (errcheck 265, gosec 187, revive 58, staticcheck 51, gocyclo 27, …) grandfathered; blocked from growing via `only-new-issues`. (Note: `.golangci.yml` was a broken half-v1/v2 config that silently failed `config verify` in CI — fixed to valid v2 this release, which is why exclusions now apply and the count dropped from ~1,800.) --- ## Executive Summary **Overall Maturity Score: 2.8 / 4.0 (Moderate–Satisfactory)** ActaLog is a well-structured, production-deployed CrossFit tracking PWA with strong architectural discipline, impressive audit trail coverage, and solid test coverage for a project of its age. The main gaps are concentrated in security headers, CORS enforcement, password policy, and documentation drift. **Top 3 Strengths:** 1. Comprehensive audit event taxonomy — 80+ typed events, full CRUD coverage across all entities 2. Strong CI matrix — unit + integration tests against SQLite, PostgreSQL, and MariaDB on every push 3. Clean Architecture with zero cross-layer violations — domain layer has no dependencies **Top 3 Critical Gaps (at time of assessment — 2026-04-03):** 1. CORS enforcement is intentionally disabled (`pkg/middleware/cors.go:31-33` — allows any origin even when not in the allowed list) *(deferred)* 2. No security response headers (no CSP, HSTS, X-Frame-Options, X-Content-Type-Options) *(deferred)* 3. Password minimum was only 8 characters with no complexity requirements — **fixed 2026-04-03** (raised to 12 chars + complexity) --- ## Maturity Scorecard | # | Category | Rating | Score | Key Finding | |---|----------|--------|-------|-------------| | 1 | Arithmetic | Satisfactory | 3 | 1RM formulas well-tested; guard clauses on all zero inputs | | 2 | Auditing | Satisfactory | 3 | 80+ typed events, full service integration; no monitoring/alerting defined | | 3 | Auth / Access Controls | Moderate | 2 | Lockout implemented; CORS broken; no security headers; password policy fixed 2026-04-03 | | 4 | Complexity Management | Moderate | 2 | Clean Architecture excellent; several files >1000 lines; lint upgraded to v2 2026-04-03 | | 5 | Decentralization (Single Points of Failure) | Moderate | 2 | Single JWT secret, admin-only unlocking, no secret rotation docs | | 6 | Documentation | Satisfactory | 3 | Extensive docs; testing doc version drift fixed 2026-04-03; swagger present | | 7 | Transaction Ordering / Concurrency Risks | Moderate | 2 | Rate limiter in-memory only (not distributed); no request deduplication | | 8 | Low-Level / Unsafe Code | Strong | 4 | Only `syscall` for signal handling; all SQL uses parameterized queries | | 9 | Testing & Verification | Satisfactory | 3 | 81.6% service coverage; CI matrix across 3 DBs; no fuzz tests; frontend tests added to CI 2026-04-03 | --- ## Detailed Analysis ### 1. ARITHMETIC — Satisfactory (3/4) **Evidence:** - `pkg/prmath/one_rm.go`: Hybrid Epley/Wathan formula selection by rep range with named formula constants - `pkg/prmath/one_rm_test.go`: Table-driven tests covering all formula branches, zero-input guards, and boundary at reps=1/10/11 - Guard clauses: `if weight <= 0 || reps <= 0 { return 0, "" }` prevents division-by-zero - `CalculateAllFormulas`: Brzycki protected against `reps >= 37` which would produce division-by-zero (`36/(37-reps)`) - `CompareToBaseline`: Zero-baseline guard present **Gaps:** - No overflow protection for extremely large weight values (float64 is safe in practice but undocumented) - Repository arithmetic (counting queries, percentages in `benchmark_service.go`) is untested at boundary conditions - No precision rounding documented — floating point results passed directly to users without rounding spec --- ### 2. AUDITING — Satisfactory (3/4) **Evidence:** - `internal/domain/audit_log.go`: ~80 typed event constants spanning auth, CRUD, scheduling, subscriptions - `internal/service/audit_log_service.go`: Dedicated service with `LogEvent()`, `LogLoginSuccess()`, etc. - Auth handler (`internal/handler/auth_handler.go:82-184`): Login attempts, failures, and outcomes all logged with IP+UA - AuditLog injected as dependency in `movement_service`, `wod_service`, `subscription_service`, `organization_service` - `AuditLogFilters` supports date range, user, event type — good for incident investigation - `audit_log_service.go` at 100% test coverage **Gaps:** - No alerting, dashboards, or automated anomaly detection (accepted risk at current deployment scale) - `notification_service.go` only 82.6% covered — notification audit events may have gaps - No documented retention policy, though `DeleteOlderThan` exists in the repository - `rate_limit_exceeded` event constant defined in domain but not verified to be emitted by the rate limiter middleware --- ### 3. AUTHENTICATION / ACCESS CONTROLS — Moderate (2/4) **Strengths:** - `pkg/auth/password.go`: bcrypt at cost 12 ✅ - `pkg/auth/jwt.go`: HMAC-SHA256, explicit algorithm check (`if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok`) - `pkg/middleware/auth.go`: `AdminOnly` and `CoachOrAdmin` middleware for role separation - Account lockout: `internal/service/user_service.go:297` — locks after `maxLoginAttempts` failures with configurable duration - `FailedLoginAttempts` field not exposed in JSON responses (`json:"-"`) - Refresh token revocation implemented (`RevokeToken` handler) - Forgot password always returns success regardless of email existence (prevents user enumeration) **Critical Issues:** **CORS disabled** (`pkg/middleware/cors.go:31-33`): The `else` branch for disallowed origins *also* sets `Access-Control-Allow-Origin` to the requesting origin. This nullifies the allowed origins list entirely. The code correctly detects whether an origin is allowed, then ignores its own result: ```go // pkg/middleware/cors.go:25-33 // Always set CORS headers (for debugging - will improve later) if origin != "" { if allowed { w.Header().Set("Access-Control-Allow-Origin", origin) } else { // For now, allow all origins in development ← THIS SHIPS TO PRODUCTION w.Header().Set("Access-Control-Allow-Origin", origin) } } ``` **No security response headers**: Missing `Content-Security-Policy`, `X-Frame-Options`, `X-Content-Type-Options`, `Strict-Transport-Security`, `Referrer-Policy`. These are browser-enforced protections against XSS and clickjacking regardless of user count. **Weak password policy** *(fixed 2026-04-03)*: Was 8-character minimum only. Now enforces 12-character minimum with uppercase, lowercase, and digit requirements in `Register`, `ResetPassword`, and `ChangePassword`. UI hints updated across all four views. **X-Forwarded-For unsanitized** *(fixed 2026-04-03)*: Was taking the full header value without splitting on comma. Now takes the leftmost (original client) IP from `X-Forwarded-For` and strips the port from `RemoteAddr` via `net.SplitHostPort` (`pkg/middleware/rate_limit.go`). --- ### 4. COMPLEXITY MANAGEMENT — Moderate (2/4) **Strengths:** - Clean Architecture: domain → service → handler with zero upward dependencies - 21 domain entities, each in its own file - Handler structs inject only what they need (constructor injection) - `internal/service/test_helpers.go` and `internal/handler/mocks_test.go` centralize test infrastructure **Issues:** - `cmd/actalog/main.go`: **1,118 lines** — route registration, middleware stack, static file serving, migration runner, and scheduler startup all in one file - `internal/service/backup_service.go`: **2,249 lines** / 38 functions - `internal/service/scheduling_service.go`: **1,450 lines** / 55 functions - `internal/service/data_quality_service.go`: **1,554 lines** - `golangci-lint` runs with `continue-on-error: true` in CI (`.github/workflows/ci.yml:35`) — lint failures are silently ignored and never block merges - `interface{}` / `any` used extensively in audit log details — loses type safety in structured event data --- ### 5. DECENTRALIZATION / SINGLE POINTS OF FAILURE — Moderate (2/4) *(For a web app: single-point admin controls, key/secret management, centralized state, upgrade risk.)* **Strengths:** - Database abstraction allows swapping SQLite/Postgres/MySQL without code changes - Docker multi-database testing matrix validates portability - `db_versions/` snapshots enable rollback testing **Issues:** - **Single JWT secret**: Compromise of `JWT_SECRET` invalidates all active sessions — no key rotation mechanism or multiple-key support documented - **Admin account bootstrap**: First registered user becomes admin with no documented recovery procedure if that account is compromised or deleted - **In-memory rate limiter**: State lost on restart; acceptable for single instance but fragile - **Backup service**: `backup_service.go` stores backups locally — no documented offsite backup strategy - **No secret rotation runbook**: `JWT_SECRET`, `DB_PASSWORD`, `SMTP_PASSWORD` have no rotation procedure in docs --- ### 6. DOCUMENTATION — Satisfactory (3/4) **Strengths:** - `docs/` contains: ARCHITECTURE, CHANGELOG, DATABASE_SCHEMA, DEPLOYMENT, DOCKER, REQUIREMENTS, TESTING, USER_PERMISSIONS, ROADMAP, LOGGING, GDPR compliance docs - Swagger/OpenAPI generated from handler annotations (`docs/swagger.yaml`, `docs/swagger.json`) - `CLAUDE.md` has excellent operational runbook (port cleanup, DB testing matrix, credential scanning) - `CHANGELOG.md` follows Keep-a-Changelog format with dedicated Security section (supply chain incident documented in v1.2.1) - Architecture diagram uses Mermaid (machine-readable, renderable in GitHub) **Gaps:** - `docs/TESTING.md` says version `0.17.0-beta` but current codebase is `1.2.1` — significant version drift in the key testing document - Coverage targets stated in TESTING.md (`>90%` for service layer) are not met for `user_service.go` (61.9%), `import_service.go` (60.8%) with no tracking issue - No incident response playbook (what to do when JWT secret is compromised, account breach, etc.) - API docs not verified to be in sync with actual routes — no CI step validates swagger accuracy against live routes --- ### 7. TRANSACTION ORDERING / CONCURRENCY RISKS — Moderate (2/4) *(Adapted from MEV: covers race conditions, idempotency, concurrent request handling.)* **Strengths:** - Rate limiter uses `sync.RWMutex` correctly (`pkg/middleware/rate_limit.go:33`) - Logger uses mutex for concurrent file writes (`pkg/logger/logger.go:45`) - `go test -race` available via `make test` **Issues:** - **No request idempotency keys** for mutations (POST workout, POST reservation) — duplicate submissions possible on network retry, though low risk at 3-user scale - `consistency_repository.go` runs health checks but no distributed locking for concurrent migrations (benign at single-instance scale) - `user_workout_repository.go` UPSERT-style operations not verified to be atomic across all 3 DB backends in tests *Note: Distributed rate limiting and scheduler deduplication gaps are not applicable for single-instance deployment.* --- ### 8. LOW-LEVEL / UNSAFE CODE — Strong (4/4) **Evidence:** - Only `syscall` usage is `signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)` — standard graceful shutdown pattern - No `unsafe` package usage anywhere in application code - All SQL uses parameterized queries (`db.Query(query, args...)`, `db.Exec(query, args...)`) - `fmt.Sprintf` in SQL is used only for: placeholder generation (`?,?,?`), driver-specific boolean expressions via `getBoolValue()`, and static table names — never for user input - The `whereClause` builder in `internal/repository/user_repository.go:699` appends string constants (e.g., `" AND name LIKE ?"`) and passes values via the `args` slice — correct parameterized pattern - The `DELETE IN (?,?,?)` pattern in `internal/repository/class_session_repository.go:700` builds placeholder count from `[]int64` length — correct batch delete pattern - Multi-DB placeholder rebinding via `rebindQuery()` correctly handles `?` vs `$1` difference - No CGO in application code (sqlite3 driver requires it, but that is a dependency, not owned code) --- ### 9. TESTING & VERIFICATION — Satisfactory (3/4) **Strengths:** - **81.6% overall service coverage** documented in `docs/TESTING.md` - 12 services at **100% coverage**, 3 more at >80% - CI runs unit tests + integration tests against all 3 DB backends (SQLite, PostgreSQL, MariaDB) on every push and PR - `ci-failure-notify.yml` auto-creates GitHub issues on CI failure — good operational hygiene - 54 frontend test files (Vue components tested with Vitest) - Handler-layer tests present (74 test files in `internal/handler/`) - `internal/service/test_helpers.go` (2,443 lines) — substantial mock infrastructure **Gaps:** - **Lint gate** *(partially fixed 2026-04-03)*: golangci-lint upgraded to v2.11.4; `continue-on-error: true` remains (851 `noctx` violations require architectural refactor before enabling hard gate) - **No fuzz testing** despite arithmetic operations that would benefit (1RM with extreme inputs) - **No coverage enforcement** in CI — coverage can silently drop without failing the build - `user_service.go` at **61.9%** — the most security-critical service has the lowest coverage - **Frontend tests added to CI 2026-04-03** — `web-test` job now runs `npm run test:run` on every push (was build-only) - Repository layer coverage listed as "pending" in TESTING.md - No contract/schema validation tests for REST API responses --- ## Improvement Roadmap ### CRITICAL — Fix before next release (30 min – 2 hrs each) | # | Issue | Location | Status | |---|-------|----------|--------| | C1 | Fix CORS middleware — remove `else` branch that grants any origin | `pkg/middleware/cors.go:31-33` | Deferred | | C2 | Add security response headers middleware (CSP, X-Frame-Options, HSTS, X-Content-Type-Options) | New `pkg/middleware/security_headers.go` | Deferred | | C3 | Fix X-Forwarded-For IP parsing — split on comma | `pkg/middleware/rate_limit.go` | ✅ Fixed 2026-04-03 | ### HIGH — Within 1–2 months | # | Issue | Location | Status | |---|-------|----------|--------| | H1 | Strengthen password policy to ≥12 chars + complexity | `internal/service/user_service.go` | ✅ Fixed 2026-04-03 | | H2 | Enable golangci-lint as hard gate (remove `continue-on-error`) | `.github/workflows/ci.yml` | Deferred (851 noctx violations) | | H3 | Add frontend tests to CI (`npm run test:run`) | `.github/workflows/ci.yml` | ✅ Fixed 2026-04-03 | | H4 | Increase `user_service.go` coverage from 61.9% to ≥85% | `internal/service/user_service_test.go` | Open | | H5 | Document JWT secret rotation runbook | `docs/DEPLOYMENT.md` | Open | | H6 | Update `docs/TESTING.md` version from `0.17.0-beta` to `1.2.1` | `docs/TESTING.md` | ✅ Fixed 2026-04-03 | ### MEDIUM — Within 2–4 months | # | Issue | Location | Effort | |---|-------|----------|--------| | M1 | Break up `cmd/actalog/main.go` (1,118 lines) into route sub-packages | `cmd/actalog/` | 1 week | | M2 | Add coverage threshold enforcement to CI | CI pipeline | 3 hrs | | M3 | Add fuzz tests for 1RM arithmetic edge cases | `pkg/prmath/one_rm_test.go` | 4 hrs | | M4 | Document offsite backup strategy | `docs/DEPLOYMENT.md` | 2 hrs | | M5 | Add swagger accuracy validation step to CI | `.github/workflows/ci.yml` | 3 hrs | ### LOW / NOT APPLICABLE at current scale | # | Item | Reason | |---|------|--------| | - | Distributed rate limiting | Single instance — in-memory is sufficient | | - | Idempotency keys | 3 users, low concurrent write probability | | - | Scheduler deduplication | Single process, no risk | | - | Incident response playbook | Manual audit log review is feasible at this scale | | - | Fuzz testing (high-pri) | No external attackers supplying arithmetic inputs | --- ## Notes on Methodology This assessment used the Trail of Bits 9-category framework adapted for a traditional web application. Categories originally targeting smart contract / DeFi concerns (MEV, decentralization, low-level EVM manipulation) were reinterpreted as: - **MEV / Transaction Ordering** → concurrency, race conditions, request idempotency - **Decentralization** → single points of failure, key management, admin bootstrap - **Low-Level Manipulation** → unsafe Go code, raw SQL construction, CGO usage Ratings follow the Trail of Bits scale: - **Missing (0)**: Not present - **Weak (1)**: Several significant improvements needed - **Moderate (2)**: Adequate, can be improved - **Satisfactory (3)**: Above average, minor improvements possible - **Strong (4)**: Exceptional --- ## Resolution status (added 2026-04-28) This scorecard reflects `main` on 2026-04-03. The "Auth Lifecycle" Moderate (2) rating was driven primarily by password policy and CORS issues; both shipped fixes in v1.2.3, and the response-header coverage gap closed in v1.2.4. The body above is preserved as a dated snapshot — re-run the assessment after the next major batch of changes for an updated rating rather than editing this document in place.