11 KiB
Codebase Concerns
Analysis Date: 2026-03-11
Tech Debt
Date parsing without validation:
- Issue: In
UpdateBudgethandler, date parsing errors are silently ignored with blank identifiers - Files:
backend/internal/api/handlers.go:322-323 - Impact: Invalid dates passed to the server may silently become zero values, leading to incorrect budget date ranges. The API accepts malformed dates without reporting errors.
- Fix approach: Check errors from
time.Parse()and return HTTP 400 with a descriptive error message before updating the budget.
Silent error suppression in API client:
- Issue: In the fetch error handler, JSON parse errors are silently swallowed with
.catch(() => ({})) - Files:
frontend/src/lib/api.ts:14 - Impact: If the server returns non-JSON error responses (e.g., plain text HTML error pages), the error message is lost and users see a generic error. Network/server issues become indistinguishable from API errors.
- Fix approach: Log the raw response or preserve error details. Return a meaningful error message when JSON parsing fails.
Hardcoded default database URL with plaintext credentials:
- Issue: Default database URL includes plaintext username and password in code
- Files:
backend/cmd/server/main.go:33 - Impact: Credentials appear in logs, version control history, and error messages. The
sslmode=disableallows unencrypted database connections by default. - Fix approach: Remove the default fallback, require
DATABASE_URLto be explicitly set in production. Usesslmode=requireand implement proper secrets management.
Placeholder default session secret:
- Issue: Session secret defaults to
"change-me-in-production"if not set via environment - Files:
backend/cmd/server/main.go:34 - Impact: If
SESSION_SECRETis not configured in deployment, all sessions use a hardcoded insecure secret, allowing anyone with the code to forge tokens. - Fix approach: Fail startup with a clear error if
SESSION_SECRETis not set. Make it required for production builds.
Security Considerations
Hardcoded CORS whitelist for localhost only:
- Risk: CORS only allows
http://localhost:5173andhttp://localhost:8080, but this is production code that will be deployed. The hardcoded origins require code changes to deploy. - Files:
backend/internal/api/router.go:20-21 - Current mitigation: None (will break in production unless modified)
- Recommendations: Make CORS origins configurable via environment variables. Use a secure default (e.g.,
AllowedOrigins: []string{}or read from env). Document required configuration.
Insufficient cookie security flags:
- Risk: Session cookies lack the
Secureflag, which is critical for HTTPS environments - Files:
backend/internal/auth/auth.go:91-98 - Current mitigation:
HttpOnlyandSameSite=Laxare set, butSecureflag is missing - Recommendations: Add
Secure: truetoSetSessionCookie(). Make this configurable for development vs. production.
No CSRF protection:
- Risk: API endpoints accept POST/PUT/DELETE from any origin that passes CORS check. No CSRF tokens in use.
- Files:
backend/internal/api/router.go(all mutation endpoints) - Current mitigation: SameSite=Lax provides some protection for browser-based attacks, but is insufficient for API security
- Recommendations: Implement CSRF token validation for state-changing operations, or use SameSite=Strict if browser support permits.
Unencrypted database connections by default:
- Risk:
sslmode=disablein default DATABASE_URL allows plaintext communication with PostgreSQL - Files:
backend/cmd/server/main.go:33 - Current mitigation: None
- Recommendations: Change default to
sslmode=require. Document that production must use encrypted connections.
No input validation on budget/category names:
- Risk: User-provided text fields (name, notes) have no length limits or content validation
- Files:
backend/internal/api/handlers.go(CreateCategory, CreateBudget, CreateBudgetItem accept raw strings) - Current mitigation: Database has TEXT columns but no constraints
- Recommendations: Add max length validation (e.g., 255 chars for names, 5000 for notes) before persisting. Return 422 for validation failures.
No rate limiting:
- Risk: No rate limiting on authentication endpoints. Brute force attacks on login/register are not mitigated.
- Files:
backend/internal/api/router.go(auth routes have no middleware) - Current mitigation: None
- Recommendations: Implement rate limiting middleware per IP or per email address for auth endpoints.
Performance Bottlenecks
No pagination on category/budget listing:
- Problem:
ListCategoriesandListBudgetsfetch all records without limits - Files:
backend/internal/db/queries.go:105-124, 163-181 - Cause: Simple SELECT without LIMIT. With thousands of categories/budgets, this becomes slow.
- Improvement path: Add LIMIT/OFFSET parameters, return paginated results. Frontend should request by page.
N+1 query problem in budget copy:
- Problem:
CopyBudgetItemscallsGetBudget()twice (for verification), then does one bulk INSERT. If called frequently, verification queries add unnecessary overhead. - Files:
backend/internal/db/queries.go:304-319 - Cause: Two separate queries to verify ownership before the copy operation
- Improvement path: Combine budget verification with the INSERT in a single transaction. Use CHECK constraints or triggers instead of application-level verification.
Totals computed in application code every time:
- Problem:
computeTotals()is called for everyGetBudgetWithItems()call, doing arithmetic on all items in memory - Files:
backend/internal/db/queries.go:269-301 - Cause: No caching, no database-side aggregation
- Improvement path: Pre-compute totals and store in
budgettable, or use PostgreSQL aggregation functions (GROUP BY, SUM) in the query.
Fragile Areas
Date parsing without type safety:
- Files:
backend/internal/api/handlers.go:260-269, 322-323 - Why fragile: Dates are parsed as strings and silently fail. Different handlers handle errors differently (one validates, one ignores).
- Safe modification: Extract date parsing into a helper function that validates and returns errors. Use typed request structs with custom JSON unmarshalers.
- Test coverage: No tests visible for invalid date inputs.
Budget item deletion cascades (foreign key constraint):
- Files:
backend/migrations/001_initial.sql(budget_items.category_id has ON DELETE RESTRICT) - Why fragile: Categories cannot be deleted if they have budget items referencing them. Users see opaque "foreign key violation" errors.
- Safe modification: Change to ON DELETE SET NULL if nullability is acceptable, or implement soft deletes. Add validation to prevent orphaned categories.
- Test coverage: No tests for category deletion scenarios.
Async state management in useBudgets hook:
- Files:
frontend/src/hooks/useBudgets.ts - Why fragile:
selectBudget()sets loading=true but fetchList doesn't. Race conditions if budget is selected while list is being fetched. No error boundaries. - Safe modification: Unify loading states, cancel pending requests when switching budgets, handle errors explicitly.
- Test coverage: No tests for concurrent operations.
JSON error handling in API client:
- Files:
frontend/src/lib/api.ts:14 - Why fragile:
.catch(() => ({}))returns empty object on any error. Downstream code treats empty object as valid response. - Safe modification: Check
res.okbefore calling.json(). Return typed error with details preserved. - Test coverage: No tests for malformed response bodies.
Missing Critical Features
No input validation framework:
- Problem: Handlers validate data individually with ad-hoc checks (empty email/password)
- Blocks: Harder to enforce consistent validation across endpoints. SQL injection is mitigated by parameterized queries, but semantic validation is manual.
- Impact: Each new endpoint requires defensive programming. Easy to miss validation cases.
No password strength requirements:
- Problem: Registration accepts any non-empty password
- Blocks: Users can set weak passwords; no minimum length, complexity checks
- Impact: Security of accounts depends entirely on user discipline.
OIDC not implemented:
- Problem: OIDC endpoints exist but return "not implemented"
- Blocks: Alternative authentication methods unavailable
- Impact: Feature is advertised in code but non-functional.
No email verification:
- Problem: Users can register with any email address; no verification step
- Blocks: Typos create invalid accounts. Email communication impossible.
- Impact: Poor UX for password resets or account recovery.
No transaction support in budget copy:
- Problem:
CopyBudgetItemsis not atomic. If INSERT fails partway, database is left in inconsistent state. - Blocks: Reliable bulk operations
- Impact: Data corruption possible during concurrent operations.
Test Coverage Gaps
No backend unit tests:
- What's not tested: API handlers, database queries, auth logic, business logic
- Files:
backend/internal/api/handlers.go,backend/internal/db/queries.go,backend/internal/auth/auth.go - Risk: Regressions in critical paths go undetected. Date parsing bugs, permission checks, decimal arithmetic errors are not caught.
- Priority: High
No frontend component tests:
- What's not tested: useBudgets hook, useAuth hook, form validation, error handling
- Files:
frontend/src/hooks/,frontend/src/pages/ - Risk: User interactions fail silently. State management bugs are caught only by manual testing.
- Priority: High
No integration tests:
- What's not tested: Complete workflows (register → create budget → add items → view totals)
- Files: All
- Risk: Multiple components may work individually but fail together. Database constraints aren't exercised.
- Priority: Medium
No E2E tests:
- What's not tested: Full user flows in a real browser
- Files: All
- Risk: Frontend-specific issues (layout, accessibility, form submission) are missed.
- Priority: Medium
Dependencies at Risk
No version pinning in Go modules:
- Risk: Transitive dependencies can break (jwt library major version bumps, pgx changes)
- Impact: Uncontrolled upgrades break API compatibility
- Migration plan: Use
go.modwith explicit versions, rungo mod tidyin CI. Test with vulnerable versions detected bygovulncheck.
Frontend uses bun as package manager:
- Risk: If bun project stalls or has unresolved bugs, switching to npm/pnpm requires rewriting lockfiles
- Impact: Vendor lock-in to a newer, less battle-tested tool
- Migration plan: Keep package.json syntax compatible. Use
bun add/bun removeinstead of editing package.json manually.
Concerns audit: 2026-03-11