129 lines
7.5 KiB
Markdown
129 lines
7.5 KiB
Markdown
# 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.
|