app to track my HSA eligible receipts
Find a file
Jean-Michel Tremblay 866bc175fb
All checks were successful
Build and Test / build-and-test (push) Successful in 38s
Security hardening: server-side session expiry + nosniff on files
1. Enforce the 12h session lifetime server-side in requireAuth (reject a
   session older than the TTL even if the sealed cookie is intact), so a
   leaked cookie value can't be replayed indefinitely. Shared sessionTTL
   const drives both the cookie MaxAge and the check.
2. Send X-Content-Type-Options: nosniff when serving user-uploaded
   receipt/attachment bytes, so the browser won't sniff past the declared
   (upload-time allowlisted) MIME type.

Update SPEC §2 and §6 accordingly; tests cover stale-session rejection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:56:38 -04:00
.forgejo/workflows CI: auto-deploy on release tags, drop manual deploy workflow 2026-06-19 15:45:50 -04:00
cmd/hsa Add scheduled metadata-only DB backups (spec item 11) 2026-06-19 07:12:50 -04:00
deploy CI: auto-deploy on release tags, drop manual deploy workflow 2026-06-19 15:45:50 -04:00
internal Security hardening: server-side session expiry + nosniff on files 2026-06-20 20:56:38 -04:00
scripts Static portable build + env-file launcher for deployment 2026-06-19 08:32:19 -04:00
.env.example Backups: ROOT/dbbackup, hsa_sqlite_backup_YYYY_MM_DD.db naming 2026-06-19 07:34:31 -04:00
.gitignore Initial commit: HSA receipt tracker 2026-06-17 21:40:12 -04:00
call_claude.sh Initial commit: HSA receipt tracker 2026-06-17 21:40:12 -04:00
CHANGELOG.md AI tab: full-width auto-growing note editors 2026-06-20 20:17:37 -04:00
config.json Initial commit: HSA receipt tracker 2026-06-17 21:40:12 -04:00
DESIGN.md AI classifier correction notes + misread review (AI tab) 2026-06-20 16:04:30 -04:00
go.mod Normalize receipt image orientation from EXIF; add changelog (0.0.1) 2026-06-19 20:26:03 -04:00
go.sum Normalize receipt image orientation from EXIF; add changelog (0.0.1) 2026-06-19 20:26:03 -04:00
plan.md Initial commit: HSA receipt tracker 2026-06-17 21:40:12 -04:00
README.md AI classifier correction notes + misread review (AI tab) 2026-06-20 16:04:30 -04:00
secret.md Initial commit: HSA receipt tracker 2026-06-17 21:40:12 -04:00
secret.sh Initial commit: HSA receipt tracker 2026-06-17 21:40:12 -04:00
SPEC.md Security hardening: server-side session expiry + nosniff on files 2026-06-20 20:56:38 -04:00

HSA Receipt Tracker

A small, mobile-first web app for two household users to capture and archive HSA-eligible receipts (photo or PDF) for future reimbursement and tax substantiation, with optional AI auto-fill of the amount/date/category/patient.

  • What it does (current behavior): SPEC.md — the source of truth.
  • Why it's built this way (history & rationale): DESIGN.md.
  • Version log: CHANGELOG.md.

Running

It's a single static Go binary (CGO_ENABLED=0, pure-Go SQLite). Configure via environment (see .env.example); ./scripts/build.sh builds it and ./scripts/run.sh runs it locally. Deployment notes: deploy/INSTALL.md.

AI classifier correction notes

When an API key is configured, each upload is read by the model to pre-fill the form. You can steer it with correction notes — free-text rules appended to the classifier prompt — managed under the AI tab. When the model misreads a receipt, that upload is recorded; the AI tab lets you review misreads one by one and attribute which note fixed each.

Notes live only in the database (private, never committed, included in /export/db backups). On first run the table is seeded once with these default notes (no PII), which you can edit or delete:

  1. Amounts that use a comma as the decimal separator (e.g. "12,50") mean 12.50, not 1250.
  2. When both a service/visit date and a separate statement, print, or due date appear, use the service date.
  3. "Patient Pay", "You Paid", "Amount Due", and "Patient Responsibility" are the amount actually paid — prefer them over subtotals or insurance-covered amounts.

These defaults are defined in code (internal/storage/ai_notes.go); this list is the human-readable copy. They are only seeded when the notes table is empty, so a deleted default does not come back on restart.