Commit Graph

2595 Commits

Author SHA1 Message Date
Marcel
1f4e8a5958 test(auth): userGroup hook redirect + cookie cleanup coverage
Four new tests against the composed handle (with sequence stubbed to return
the head function): backend 401 on a private path redirects to
/login?reason=expired; backend 401 on /login does NOT redirect (no loop);
missing fa_session passes through without a backend call; 200 attaches the
user to event.locals. Closes the hook-coverage gap flagged by Sara S1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:50:41 +02:00
Marcel
d64139d9d1 test(auth): Vitest coverage for logout action
Three tests: happy path POSTs to backend with the session cookie and clears
both fa_session and legacy auth_token; cookies are cleared even when the
backend call rejects (best-effort logout); skips the backend call when no
session cookie is present. Addresses PR #612 / Sara S1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:49:06 +02:00
Marcel
2779502f3b test(auth): Vitest coverage for login action
Six tests covering: load() exposes ?registered and ?reason; action returns 400
on missing email; 401 with INVALID_CREDENTIALS on backend reject; success
re-emits fa_session and deletes legacy auth_token; 500 when backend omits
fa_session in Set-Cookie. Closes the frontend coverage gap on the credential-
handling logic that moved out of the Java side. Addresses PR #612 / Sara S1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:48:16 +02:00
Marcel
9f1e2c9ff5 refactor(auth): hooks.server.ts re-throws redirects via isRedirect()
Replace the duck-typed `status in error && location in error` check with the
official SvelteKit guard. Fragile against minor-version error-shape changes
becomes a one-liner against a typed helper. Addresses PR #612 / Felix F1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:45:46 +02:00
Marcel
dd99c5dd74 refactor(auth): login action imports extractFaSessionId from \$lib/shared/cookies
Drop the inline parser; reuse the now-shared helper. Pure rewire, no behaviour
change. Addresses PR #612 / Felix F2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:44:30 +02:00
Marcel
b607677f30 refactor(auth): extract extractFaSessionId to \$lib/shared/cookies
Move the Set-Cookie parser out of login/+page.server.ts into a shared module
with its own Vitest coverage (single-header, multi-header getSetCookie path,
missing-header, attribute-stripping, prefix-match-rejection). An Undici or
Node upgrade that changes header shape now trips its own test instead of
silently breaking login. Addresses PR #612 / Felix F2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:43:09 +02:00
Marcel
20fe83d889 docs(auth): document XFF trust-the-proxy assumption on resolveClientIp
Pure-comment change: spell out that resolveClientIp's leftmost-X-Forwarded-For
strategy is safe only because Caddy strips client-supplied XFF before
forwarding. Future readers swapping the ingress have a tripwire. Addresses PR
#612 / Nora concern (XFF trust documentation).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:41:39 +02:00
Marcel
c7782d554f test(auth): login response never leaks the password field
Pin the @JsonProperty(WRITE_ONLY) invariant on AppUser.password. If the
annotation is ever dropped — or a new field aliases the hash — the CI run that
ships the regression flags it the next morning rather than waiting for a
security review. Addresses PR #612 / Nora concern (regression test).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:40:41 +02:00
Marcel
ea65611690 fix(auth): logout invalidates session before audit (CWE-613)
Reorder AuthSessionController.logout so HttpSession.invalidate runs before
AuthService.logout, and wrap the audit call in try/catch so an exception (e.g.
the user was deleted between login and logout, making the audit-time
findByEmail throw) cannot leave the session row alive in spring_session.
The user's intent — "log me out" — is honoured even when audit fails.
Addresses PR #612 / Nora B2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:38:57 +02:00
Marcel
17b29edd14 fix(auth): rotate session ID on login to prevent session fixation (CWE-384)
Inject Spring Security's SessionAuthenticationStrategy
(ChangeSessionIdAuthenticationStrategy) into AuthSessionController and invoke
onAuthentication at the credential boundary. The strategy calls
HttpServletRequest.changeSessionId() to invalidate any pre-auth session ID an
attacker may have planted and mint a fresh ID before the SecurityContext is
attached. Addresses PR #612 / Nora B1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:36:33 +02:00
Marcel
3438260090 docs: rewrite seq-auth-flow.puml for the Spring Session model (ADR-020)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m4s
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m6s
CI / fail2ban Regex (pull_request) Successful in 40s
CI / Semgrep Security Scan (pull_request) Successful in 17s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
Removes the cookie-promotion step (auth_token → Authorization: Basic) and
splits the diagram into three labelled phases: Login, Authenticated
request, Logout. Adds the spring_session DB round-trip on every
authenticated request and the alt branch for an expired session
returning 401 → /login?reason=expired.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:59:44 +02:00
Marcel
0bd00a3044 feat(auth): session-expired banner + autofocus + 44px touch target on login
- Amber aria-live banner when ?reason=expired (set by hooks.server.ts
  after the backend rejects an expired fa_session) with a one-line
  explainer about the 8h idle window.
- autofocus on email so users returning after a session-expired kick
  can immediately retype credentials.
- min-h-[44px] on the submit button hits the iOS HIG / WCAG 2.1 AAA
  touch target minimum — relevant for the reader cohort on phones.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:58:30 +02:00
Marcel
d301825e50 feat(auth): remove auth_token cookie injection from Vite dev proxy
With the Spring Session model the browser forwards fa_session itself —
the proxy no longer needs to translate auth_token → Authorization: Basic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:55:30 +02:00
Marcel
6193e28587 feat(auth): hooks forward fa_session cookie instead of injecting Basic auth
userGroup: GET /api/users/me with Cookie: fa_session=<id>. On 401, drop
the stale cookie and redirect to /login?reason=expired (unless already
on a public path) so the user sees an explainer instead of a silent kick.

handleFetch: forward fa_session as a Cookie header on every API call
except the public auth endpoints. Drops the old auth_token injection.

Also adds a one-off cleanup of any lingering auth_token cookie from
pre-migration sessions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:54:42 +02:00
Marcel
bfdf64975c feat(auth): rewrite logout action to call /api/auth/logout then clear fa_session
The backend POST invalidates the spring_session row and writes the
LOGOUT audit entry; the client cookie is deleted unconditionally so a
network blip during logout still logs the user out locally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:53:20 +02:00
Marcel
ea800e5e2a feat(auth): rewrite login action to POST /api/auth/login and forward fa_session
Replaces the Basic-credentials-in-cookie flow with the Spring Session model:
1. POST {email, password} as JSON to /api/auth/login
2. Map 401 → INVALID_CREDENTIALS (or SESSION_EXPIRED if the backend returns it)
3. Parse Set-Cookie for fa_session=<opaque> and re-emit to the browser
4. Drop the legacy auth_token cookie

load() now also exposes ?reason= so the page can show the
session-expired banner (Task 21 wires it into the .svelte file).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:52:30 +02:00
Marcel
cfff594732 feat(auth): frontend ErrorCode + i18n for INVALID_CREDENTIALS and SESSION_EXPIRED
Mirrors the backend ErrorCode additions from commit 393a3c25.
Adds error_session_expired_explainer for the login-page banner that
will surface when ?reason=expired.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 20:51:06 +02:00
Marcel
0fa330a357 test(auth): integration tests for full session lifecycle and idle-timeout
Also switches pom.xml to spring-boot-starter-session-jdbc (Spring Boot 4.x
split the session auto-config into a separate starter; spring-session-jdbc
alone does not register JdbcSessionAutoConfiguration).
Adds SpringSessionConfig#cookieSerializer bean to configure fa_session name
and SameSite=Strict (spring.session.cookie.* properties are no longer
supported by the Boot 4.x auto-configuration layer).
Cleans up application.yaml / application-dev.yaml: removes store-type: jdbc
and the unsupported cookie.* keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:50:22 +02:00
Marcel
a6c85e3658 feat(auth): delete AuthTokenCookieFilter and its test (ADR-020)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:27:53 +02:00
Marcel
e0aca0f883 feat(auth): AuthSessionController — POST /api/auth/login + /api/auth/logout with Spring Session JDBC
- Expose AuthenticationManager bean in SecurityConfig
- Permit /api/auth/login; return 401 (not 302) for unauthenticated requests
- Remove httpBasic and formLogin from SecurityConfig

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:26:49 +02:00
Marcel
a77b0c1221 feat(auth): AuthService — login/logout with audit logging and timing-safe credential rejection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:21:46 +02:00
Marcel
393a3c25fd feat(auth): add INVALID_CREDENTIALS + SESSION_EXPIRED error codes; LOGIN_SUCCESS/FAILED/LOGOUT audit kinds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:19:01 +02:00
Marcel
8c7a2741b0 feat(auth): configure Spring Session JDBC (fa_session, 8h idle, SameSite=strict)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:18:28 +02:00
Marcel
865c6ed796 feat(auth): add spring-session-jdbc 4.0.3 dependency
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:17:46 +02:00
Marcel
14542b6e33 migration: V67 — recreate spring_session tables (ADR-020)
Re-introduces tables dropped by V2. Canonical DDL from Spring
Session 3.x schema-postgresql.sql.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:16:25 +02:00
Marcel
de7053644b docs(adr): ADR-020 — stateful auth via Spring Session JDBC
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 19:15:51 +02:00
Marcel
f1e0b92f47 style(ocr): normalize cap_drop to block notation in docker-compose.yml
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m3s
CI / OCR Service Tests (pull_request) Successful in 18s
CI / Backend Unit Tests (pull_request) Successful in 3m10s
CI / fail2ban Regex (pull_request) Successful in 44s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m0s
CI / Unit & Component Tests (push) Successful in 3m2s
CI / OCR Service Tests (push) Successful in 18s
CI / Backend Unit Tests (push) Successful in 3m0s
CI / fail2ban Regex (push) Successful in 42s
CI / Semgrep Security Scan (push) Successful in 18s
CI / Compose Bucket Idempotency (push) Successful in 1m1s
Aligns with the block sequence style used in docker-compose.prod.yml and
the rest of the compose file, removing the inline [ALL] inconsistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 18:54:24 +02:00
Marcel
bead6f1811 fix(ocr): handle empty-string HTRMOPO_DIR env var with or-fallback
os.environ.get(key, default) returns "" when the key exists but is blank —
the default is only used when the key is absent. The or-fallback treats both
absence and blank values as "use the default".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 18:53:26 +02:00
Marcel
7769dbc9f4 security(ocr): apply container hardening baseline to docker-compose.prod.yml
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m3s
CI / OCR Service Tests (pull_request) Successful in 18s
CI / Backend Unit Tests (pull_request) Successful in 3m4s
CI / fail2ban Regex (pull_request) Successful in 43s
CI / Semgrep Security Scan (pull_request) Successful in 18s
CI / Compose Bucket Idempotency (pull_request) Successful in 59s
Mirror the CIS Docker §4.1/§4.6 hardening from docker-compose.yml to the
production/staging compose file, which is standalone (not an overlay).

- Fix cache volume mount path: ocr-cache:/root/.cache → /app/cache (matches
  the non-root user's HF_HOME/XDG_CACHE_HOME, avoids PermissionError)
- Add HF_HOME, XDG_CACHE_HOME, TORCH_HOME env vars so HuggingFace, ketos,
  and PyTorch all write to the declared writable volumes, not HOME
- Add read_only: true, tmpfs (/tmp:512m), cap_drop: [ALL],
  no-new-privileges:true — matching the dev baseline

Also extend DEPLOYMENT.md §8 upgrade notes to cover all three environments
(dev/production/staging), each with its correct project-namespaced volume name.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:43:18 +02:00
Marcel
74ca5ee35f docs(adr): ADR-019 — container hardening baseline (non-root + read-only)
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m2s
CI / OCR Service Tests (pull_request) Successful in 18s
CI / Backend Unit Tests (pull_request) Successful in 3m11s
CI / fail2ban Regex (pull_request) Successful in 43s
CI / Semgrep Security Scan (pull_request) Successful in 17s
CI / Compose Bucket Idempotency (pull_request) Successful in 59s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:33:06 +02:00
Marcel
38973a014e docs: add XDG_CACHE_HOME/TORCH_HOME to OCR env table and upgrade notes for PR #611
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:32:02 +02:00
Marcel
fc8b4b164b security(ocr): redirect XDG cache and Torch home away from read-only HOME
Prevents PyTorch/Matplotlib/Ketos from writing to /home/ocr which is
on the read-only container filesystem — fixes Nora's blocker. Also
restores the explanatory comment on the ocr_cache volume mount.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:30:39 +02:00
Marcel
eb63df2000 test(ocr): add startup root canary tests for main.py lifespan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:29:47 +02:00
Marcel
53bd574660 test(ocr): replace vacuous startswith assertion with equality check
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 17:26:58 +02:00
Marcel
581ba01d8d security(ocr): log warning on startup when running as root
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m3s
CI / OCR Service Tests (pull_request) Successful in 18s
CI / Backend Unit Tests (pull_request) Successful in 3m10s
CI / fail2ban Regex (pull_request) Successful in 42s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 59s
Adds a canary log line if os.getuid() == 0. Produces an observable
signal in container logs if the USER directive is ever removed from
the Dockerfile, without requiring an external audit tool.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:51:00 +02:00
Marcel
9db42d6cc1 fix(ocr): resolve HTRMOPO_DIR from env var, not ~ expansion
With --no-create-home, os.path.expanduser("~") resolves to "/" causing
kraken get to write to /.local/share/htrmopo. Replace with
os.environ.get("HTRMOPO_DIR", "/app/models/.htrmopo") so the path is
explicit and override-friendly without a home directory.

Adds two tests verifying env-var resolution and ~-free default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:49:21 +02:00
Marcel
ab24786d2a security(ocr): harden compose — fix cache volume path, add read_only + cap_drop
Move ocr_cache mount from /root/.cache to /app/cache (correct path for
non-root user). Add HF_HOME so Hugging Face resolves to the same path.
Add runtime hardening: read_only, tmpfs /tmp (512 MB cap), cap_drop ALL,
no-new-privileges.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:47:18 +02:00
Marcel
1aca4c4a41 security(ocr): add non-root user and set HOME/HF_HOME in Dockerfile
CIS Docker §4.1: run uvicorn as UID 1000 (ocr) instead of root.
Creates /home/ocr and /app/cache with correct ownership so named
volumes inherit ocr:ocr on first Docker mount. Sets HOME and HF_HOME
so ~ expansion and Hugging Face caching resolve under /app, not /root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:46:25 +02:00
Marcel
669eaa7c65 fix(ci): pin semgrep version, add pip cache, harden rule severity
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m2s
CI / OCR Service Tests (pull_request) Successful in 18s
CI / Backend Unit Tests (pull_request) Successful in 2m55s
CI / fail2ban Regex (pull_request) Successful in 42s
CI / Semgrep Security Scan (pull_request) Successful in 18s
CI / Compose Bucket Idempotency (pull_request) Successful in 59s
CI / Unit & Component Tests (push) Successful in 3m3s
CI / OCR Service Tests (push) Successful in 19s
CI / Backend Unit Tests (push) Successful in 2m56s
CI / fail2ban Regex (push) Successful in 40s
CI / Semgrep Security Scan (push) Successful in 17s
CI / Compose Bucket Idempotency (push) Successful in 59s
- Pin semgrep to 1.163.0 to prevent silent upgrades breaking the scan
- Add cache: 'pip' to setup-python@v5 for faster CI runs
- Promote all three XXE Semgrep rules from WARNING to ERROR to match
  the --error CI flag intent
- Update SAX/StAX rule messages to reference XxeSafeXmlParser and
  the OWASP XXE prevention cheat sheet
- Remove stale issue reference from regression test comment
- Document XML metacharacter constraint on buildValidOds test helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 16:18:03 +02:00
Marcel
f15ea031d1 ci(security): add Semgrep XXE rule and CI scan job
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m2s
CI / OCR Service Tests (pull_request) Successful in 18s
CI / Backend Unit Tests (pull_request) Successful in 3m3s
CI / fail2ban Regex (pull_request) Successful in 40s
CI / Semgrep Security Scan (pull_request) Successful in 1m11s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m1s
Add .semgrep/security.yml with rules for DocumentBuilderFactory,
SAXParserFactory, and XMLInputFactory without XXE hardening (CWE-611).
Add semgrep-scan CI job — runs in parallel with backend-unit-tests,
local rules only, --error flag fails the build on any match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 14:48:46 +02:00
Marcel
25a39fca9c security(import): harden DocumentBuilderFactory against XXE in MassImportService
Extract XxeSafeXmlParser with all 6 OWASP-recommended features
(disallow-doctype-decl, external-general-entities, external-parameter-entities,
load-external-dtd, XInclude, expandEntityReferences). Make readOds()
package-private; add failing-then-passing regression test and valid-ODS guard test.

POI 5.5.0 does not mitigate this: the vulnerable parser is a custom
DocumentBuilderFactory call in readOds(), not inside POI's internal ODS reader.
The hardening is defence-in-depth, not redundant with POI defaults.

Closes #528

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 14:48:03 +02:00
Marcel
e398133907 security(deps): bump Spring Boot 4.0.0 → 4.0.6 and OWASP sanitizer 20240325.1 → 20260101.1
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m6s
CI / OCR Service Tests (pull_request) Successful in 17s
CI / Backend Unit Tests (pull_request) Successful in 3m8s
CI / fail2ban Regex (pull_request) Successful in 41s
CI / Compose Bucket Idempotency (pull_request) Successful in 58s
CI / Unit & Component Tests (push) Successful in 3m5s
CI / OCR Service Tests (push) Successful in 18s
CI / Backend Unit Tests (push) Successful in 2m57s
CI / fail2ban Regex (push) Successful in 39s
CI / Compose Bucket Idempotency (push) Successful in 1m0s
Clears 2 CRITICAL CVEs (CVE-2026-40976, CVE-2026-22732) and 17 HIGH CVEs
in Netty, Jetty, Spring Security, and Spring Boot itself. Also fixes
CVE-2025-66021 in the OWASP HTML sanitizer used by GeschichteService.

JaCoCo threshold ratcheted to 0.77 (actual measured coverage; previous
0.88 gate was never enforced since CI ran clean test not clean verify).
CI backend job changed to ./mvnw clean verify so the gate runs on every
push going forward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:55:12 +02:00
Marcel
186535f8c9 test(security): add ActuatorSecurityTest to guard auth boundaries
Tests that /actuator/health is accessible without credentials and
/actuator/env requires authentication — permanent regression guards
against CVE-2026-40976-class Actuator filter chain bypass bugs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:45:28 +02:00
Marcel
de19d17b00 docs(adr): add ADR-018 for GlitchTip frontend error tracking via @sentry/sveltekit
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 3m4s
CI / OCR Service Tests (pull_request) Successful in 18s
CI / Backend Unit Tests (pull_request) Successful in 2m39s
CI / fail2ban Regex (pull_request) Successful in 40s
CI / Compose Bucket Idempotency (pull_request) Successful in 59s
CI / Unit & Component Tests (push) Successful in 5m46s
CI / OCR Service Tests (push) Successful in 45s
CI / Backend Unit Tests (push) Failing after 10m32s
CI / fail2ban Regex (push) Successful in 3m7s
CI / Compose Bucket Idempotency (push) Successful in 2m26s
Documents the decision to use the Sentry SDK with self-hosted GlitchTip,
sendDefaultPii:false rationale, errorId surfacing to users, and alternatives
considered (Sentry SaaS rejected for data-minimisation reasons).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 10:27:46 +02:00
Marcel
b2e31c3c1b refactor(observability): lower trace sample rate, add DSN comment, improve status visibility
- Lower tracesSampleRate from 1.0 to 0.1 in both hooks (errors still captured
  at 100%; trace volume reduced for self-hosted GlitchTip on shared VPS)
- Add comment explaining VITE_SENTRY_DSN is a write-only ingest key, safe in
  client bundle — prevents accidental rotation as if it were a password
- Restore HTTP status code prominence: text-4xl font-bold (was text-xs text-ink-3)
- Add min-w-[44px] to copy button for WCAG 2.2 minimum touch target

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 10:27:01 +02:00
Marcel
9e23620072 refactor(observability): add hooks.server.ts to coverage include in vite.config.ts
The handleError callback in hooks.server.ts is now gated by the 80% branch
coverage threshold along with the rest of the server-side logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 10:26:03 +02:00
Marcel
af42113fca test(observability): add hooks.client.test.ts unit tests for handleError callback
Two tests matching the existing hooks.server.test.ts coverage: returns
Sentry lastEventId as errorId; falls back to crypto.randomUUID when
lastEventId returns undefined.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 10:25:30 +02:00
Marcel
c779ec59f9 feat(observability): guard navigator.clipboard and handle rejection in copyId
Adds availability guard (navigator.clipboard may be undefined in non-HTTPS
contexts) and a rejection handler so clipboard-denied errors are silently
caught rather than becoming unhandled promise rejections. Tests cover the
success feedback and the silent-failure path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 10:24:35 +02:00
Marcel
2023ea2931 docs(c4): add GlitchTip as external error-tracking system to L1 context diagram
All checks were successful
CI / Unit & Component Tests (pull_request) Successful in 3m3s
CI / OCR Service Tests (pull_request) Successful in 17s
CI / Backend Unit Tests (pull_request) Successful in 2m43s
CI / fail2ban Regex (pull_request) Successful in 41s
CI / Compose Bucket Idempotency (pull_request) Successful in 58s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 09:46:17 +02:00
Marcel
59b18039ed refactor(observability): remove console.log from tags proxy and enforce no-console lint rule
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 09:45:49 +02:00