Skip to content

Security: ashmht/judgement-tracker

Security

SECURITY.md

Security

Threat model (honest version)

This is a fully-offline, single-device app with no accounts, no server, and no network calls (except font loading, removable — see DEPLOYMENT.md step 4). There is no data worth stealing beyond card-game scores, and nothing is transmitted anywhere. The realistic risks are therefore:

  1. Corrupted or tampered persisted data crashing the app — interrupted writes, backup restores, or manual tampering via device backup tools. The failure mode to prevent is a crash loop on launch that permanently bricks the app.
  2. Hostile input — pathological player names (unbounded length, control characters) bloating storage or corrupting the UI.
  3. Webview compromise — script injection into the WKWebView.
  4. Supply chain — vulnerable npm dependencies.

Controls in place

Risk Control Where
Corrupt storage → crash loop Every persisted key is parsed with safeJsonParse (never throws) and shape-validated; corrupt raw data is quarantined under a corrupt: key for post-mortem, and that key alone starts fresh src/lib/validation.js, load path in src/hooks/useJudgementGame.js
Tampered data with impossible values Validators repair (clamp negatives, cap correctBids ≤ roundsPlayed) or reject structurally-unusable games validateStats, validateGame
Prototype pollution via stored JSON __proto__/constructor/prototype keys dropped during validation validateStats, validateMasteredTips
Pathological player names Sanitized: control/zero-width chars stripped, whitespace collapsed, 40-char cap, 30-player roster cap sanitizePlayerName, addPlayer
Out-of-range bid values Clamped to integers in [0, cardCount] at the entry point clampBidValue, updateBid/updateTricks
Script injection (XSS) No dangerouslySetInnerHTML, no eval, React auto-escaping throughout; CSP forbids remote scripts, connections, frames, and objects index.html CSP meta tag
Supply chain npm audit --omit=dev --audit-level=high gates CI on runtime dependencies (the only code shipped to users); lockfile committed .github/workflows/ci.yml
Diagnostics leaking data The logger records keys and error messages, never full storage values; the diagnostics export goes only to the user's own clipboard, on their explicit tap src/lib/logger.js, ErrorBoundary

A note on dependency advisories

The audit gate is scoped to runtime dependencies (--omit=dev) on purpose. The shipped bundle contains only React and the Capacitor runtime; the build and test tooling (Vite, esbuild, the Capacitor CLI) never runs on a user's device. Their advisories — e.g. the esbuild dev-server request issue — apply to a developer's local machine, not to the App Store binary, so they are tracked but not treated as release blockers.

Non-goals (deliberately)

  • Encryption at rest: the data is card-game scores on the user's own device. iOS file-level protection already applies to the app container. Adding app-layer encryption would add key-management complexity with no meaningful benefit.
  • Authentication: single-device, shared-around-the-table by design.
  • Jailbreak detection / anti-tamper: a user modifying their own scorepad harms only their own leaderboard.

Reporting

If you find a vulnerability, open a GitHub issue (there is no sensitive data category here that warrants private disclosure).

When the model changes

Two future changes would expand this threat model, and each has a marked integration point:

  • Enabling crash reporting (Sentry): data leaves the device. Add the ingest domain to the CSP connect-src, and update the App Store privacy declaration from "Data Not Collected". Hook: setErrorReporter in src/main.jsx.
  • Any sync/multiplayer feature: introduces a server and real authn/authz questions. Revisit this document entirely before building.

There aren't any published security advisories