Skip to content

Security hardening (CSRF, rate-limiting, secure cookies) + dev tooling - #1

Merged
78wesley merged 4 commits into
masterfrom
feat/security-hardening-and-tooling
Jun 22, 2026
Merged

78wesley merged 4 commits into
masterfrom
feat/security-hardening-and-tooling

Conversation

@78wesley

Copy link
Copy Markdown
Owner

Summary

Hardens the app for untrusted networks and adds a lint/type/coverage toolchain.

  • CSRF protection — per-session token via <meta> + client JS (POST forms & HTMX X-CSRFToken header); a Beforeware rejects mismatches, including on /login and /setup. No per-form changes required.
  • Login/setup rate-limiting — 10 attempts / 5 min per client throttles online password guessing.
  • Secure cookies — SUBTRACKER_SECURE_COOKIES=1 → Secure + SameSite=strict (off by default for plain-HTTP dev).
  • Permission constants — dependency-free app/permissions.py with a Perm class; all 53 call sites migrated off string literals so a typo is an import-time error, not a silent allow/deny.
  • Tooling — ruff, mypy (scoped to the FastHTML-free logic/data layer), and coverage, wired into the Tests workflow.
  • Type/lint cleanup — fixed implicit-Optional annotations across the logic layer; mypy is clean.
  • Tests — 43 → 66: CSRF, rate-limit, RBAC HTTP enforcement, subscription lifecycle, import/export round-trips, permission-catalog drift guard.
  • Docs — README security notes updated (previously said these were "not yet implemented").

Verification

ruff check ✅ · mypy ✅ · pytest 66 passed (72% coverage) ✅

🤖 Generated with Claude Code

78wesley and others added 4 commits June 22, 2026 23:27
Add a dev toolchain and wire it into the Tests workflow so every PR is
linted, type-checked, and measured for coverage before images publish.

- ruff: pycodestyle/pyflakes/isort/bugbear/comprehensions/pyupgrade, with
  the codebase's deliberate style (compact `if cond: return` guards,
  FastHTML's repeated get/post handlers, FT component calls) configured out.
- mypy: scoped to the FT-free logic/data layer (FastHTML's dynamically
  generated tags can't be resolved from `import *`); union-attr disabled for
  sqlite-utils' Table|View access pattern.
- coverage via pytest-cov.
- .gitignore the coverage artifacts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Security hardening
- CSRF protection (app/csrf.py): a per-session token, exposed in a
  <meta> tag, is auto-attached by client JS to POST forms (hidden field)
  and HTMX requests (X-CSRFToken header). A Beforeware ahead of the auth
  gate rejects any unsafe request whose token doesn't match the session —
  covering /login and /setup too. No per-form changes required.
- Auth rate limiting (app/ratelimit.py): 10 attempts / 5 min per client
  on /login and /setup throttles online password guessing.
- Secure cookies: SUBTRACKER_SECURE_COOKIES=1 marks the session cookie
  Secure and sets SameSite=strict (off by default for plain-HTTP dev).

Permission constants
- Extract the permission catalog into a dependency-free app/permissions.py
  with a Perm class (single source of truth for names). Migrate all 53
  call sites from string literals to constants so a typo is an
  AttributeError at import time, not a silent allow/deny.

Type/lint cleanup
- Fix implicit-Optional annotations across the logic/data layer and a few
  missing var annotations so mypy is clean; apply ruff's import sorting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expand the suite from 43 to 66 tests:
- CSRF: tokenless/wrong-token POSTs are 403; valid form token and the
  HTMX header path both pass; pages expose the <meta> token.
- Rate limiter: unit behaviour + /login throttling after too many tries.
- RBAC at the HTTP layer: a viewer is denied the new/create routes and
  the admin/users pages; a team admin is allowed — guarding the per-route
  permission checks, not just the resolver.
- Subscription lifecycle: create -> edit -> soft-delete -> restore, and
  permanent-delete (row gone, audit retained).
- Import/export round-trips (CSV + JSON) and bad-row reporting.
- Permission-catalog drift guard: Perm constants match the catalog.

conftest gains CSRF-aware post()/login()/setup_first_admin() helpers and an
autouse fixture that resets the process-global rate limiter between tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The security notes claimed CSRF/rate-limiting/Secure cookies were "not yet
implemented" — update to reflect what now ships, add the
SUBTRACKER_SECURE_COOKIES env var, and document the ruff/mypy/coverage
commands and the new modules in the project layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@78wesley
78wesley merged commit 74600ac into master Jun 22, 2026
1 check passed
@78wesley
78wesley deleted the feat/security-hardening-and-tooling branch June 22, 2026 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant