fix live leaderbord details

This commit is contained in:
larssand
2026-08-28 21:12:43 +02:00
parent 3cf68cf34a
commit c6262aceab
15 changed files with 293 additions and 34 deletions

View File

@@ -0,0 +1,128 @@
# Security and Performance Audit
Audit date: 2026-08-28
## Scope
This review covers the Node/Express backend, SQLite persistence, browser runtime, decoder WebSocket processing, overlay refresh path, dependency tree, and bundled runtime assets. It is a source-level review plus the repository test and npm vulnerability scans. It is not a penetration test of a deployed network.
## Verification baseline
- `npm audit --omit=dev`: 0 known vulnerabilities across 105 production dependencies
- `npm test`: 74 tests passing after the changes in this audit
- `npm run verify`: Express and SQLite runtime verified
- Runtime target: Node.js 22-26
## Changes applied during this audit
### Overlay rendering
The overlay previously rebuilt its complete HTML tree from three independent loops: backend hydration every 800 ms, live refresh every 250 ms, and the one-second session timer. This caused repeated leaderboard calculation, HTML generation, DOM replacement, image handling, and event-listener registration.
The live 250 ms path now updates only:
- elapsed/remaining clock nodes
- predicted-lap text
- predicted-lap progress width and status color
Full overlay rendering remains event-driven for passings and state changes, and follows backend hydration for remote overlay clients.
### Backend synchronization
The backend state poll now runs only while the current view is `Overlay`. It no longer hydrates the full application state every 800 ms while an operator edits Settings, Race Setup, Drivers, Cars, or other administration views.
### Decoder messages
Only recognized decoder passings that mutate race state now trigger state persistence and a view render. Status, keepalive, malformed, ignored, and duplicate channel messages no longer serialize the complete state or rebuild the active page.
### Cross-origin requests
The backend no longer returns wildcard CORS headers. Requests with an `Origin` header are accepted from the server's own origin or from origins explicitly listed in `RC_ALLOWED_ORIGINS`. Other origins receive `403`, including simple cross-origin POST requests that do not use a browser preflight.
Responses also include:
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: no-referrer`
## Security findings
### Critical: administration API has no authentication
All users that can reach port 8081 can currently read or replace application state and operate managed AMMC. The most sensitive endpoints are:
- `GET/POST /api/state`
- `GET/POST /api/ammc/config`
- `POST /api/ammc/start`
- `POST /api/ammc/stop`
- `POST /api/passings`
- `POST /api/export/pdf`
AMMC configuration includes an executable path and arguments. Because the backend can start that executable, access to these endpoints must be treated as administrative host access.
Required deployment rule: do not expose port 8081 directly to the public internet. Keep it on a trusted race LAN, behind a VPN, or behind an authenticated reverse proxy.
Recommended code change: add authenticated operator sessions or an admin API token, protect every mutating/admin endpoint, and keep public timing endpoints separate.
### High: public overlay token does not protect the state API
`PUBLIC_OVERLAY_TOKEN` gates the public overlay HTML route, but the overlay still hydrates through the general `/api/state` endpoint. That endpoint contains full operational state and is not token-protected or sanitized.
Therefore `PUBLIC_OVERLAY_TOKEN` is only a route gate, not complete data/API security. Before publishing an overlay outside the race LAN, add a read-only, sanitized public-overlay state endpoint and make public overlays use only that endpoint.
### Medium: monolithic state import increases stored-XSS impact
Most dynamic UI values are escaped before insertion into HTML. However, imported JSON and unrestricted `/api/state` replacement can place attacker-controlled identifiers and configuration into many UI paths. Authentication and strict import schema validation should be the primary controls. A Content Security Policy should be added after inline styles have been removed or explicitly accounted for.
### Medium: large request bodies and no rate limiting
The global JSON limit is 20 MB and administrative endpoints have no request-rate control. This is acceptable only on a trusted LAN. Add per-route limits and rate limiting before any internet-facing deployment.
### Medium: bundled AMMC binaries are trusted artifacts
The repository includes large platform binaries and an archive. Installation currently verifies presence, not cryptographic hashes or signatures. Record vendor versions and expected SHA-256 hashes, then verify them during installation or release packaging.
### Low: health endpoint exposes the database path
`/api/health` returns the absolute SQLite path. This is useful for local diagnostics but unnecessary on a public deployment. Remove it or expose detailed health data only to authenticated operators.
## Performance findings
### High: monolithic state serialization and SQLite rewrite
`saveState()` serializes the complete application state into localStorage. Backend synchronization stores the same state as one large JSON document. `/api/passings` also parses and rewrites the state document to keep remote overlays current.
Cost grows with all historical sessions, results, passings, training records, and audio events. This is the main long-term scaling limit.
Recommended design:
1. Keep configuration and small UI state in `app_state`.
2. Store sessions, competitors, results, passings, training laps, and audit events in normalized SQLite tables.
3. Update only the active session result on a passing.
4. Publish active-session deltas through WebSocket or Server-Sent Events instead of polling full state.
### Medium: leaderboard and track-record calculations repeat on each passing
A valid passing builds the leaderboard before and after mutation, scans prior class results for the track record, and can build it again during rendering. This is acceptable for roughly 10-12 cars but will become visible with larger endurance histories.
Recommended design: cache active-session leaderboard inputs and per-class track records; invalidate only the affected competitor and class.
### Medium: full view replacement on each valid passing
Timing and overlay views still replace substantial HTML after each valid passing. The 250 ms overlay animation path is now incremental, but passing-driven rendering remains full-page. Move leaderboard rows to keyed incremental updates if profiling shows frame drops during larger races.
### Low: source delivery is unbundled and unminified
ES modules are appropriate for the current local deployment. The size of `app.js` affects maintainability more than runtime by itself, but production releases would benefit from bundling, minification, hashed assets, and long-lived cache headers. Development currently uses `no-store` intentionally to support live file reload.
### Distribution size
The AMMC archive and multi-platform binaries dominate repository and deployment size. Build platform-specific release archives instead of syncing all AMMC targets to each timing computer.
## Recommended implementation order
1. Add admin authentication and a sanitized public overlay API.
2. Replace full-state overlay polling with server-pushed active-session deltas.
3. Normalize active race results and training history into SQLite tables.
4. Add request limits, rate limiting, import schema validation, and CSP.
5. Add AMMC checksum verification and platform-specific release packaging.
6. Profile a simulated 12-car race and an endurance dataset before deeper leaderboard optimization.