fix(api): lock down unauthenticated write endpoints — delete /event, gate /simulate, restrict webhook sources #64
Labels
No labels
blocked
bug
enhancement
high-priority
low-priority
needs-info
needs-triage
ready-for-agent
ready-for-human
referenced
research
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gabogg/hikcentral#64
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Three write paths accept data without the checks they need:
POST /api/occupancy/event(app/controllers/occupancy_controller.py,receive_counting_event) writes people-counting passages with no authentication at all. Those passages feed live occupancy, the published KPIs, calibration (k, baseline offset), the Trust Index and the operator flux stream. After #53 it only accepts registered cameras, and after #52 onlyIN/OUT(PASSremoved), but anyone who can reach the server can still inject traffic. The payload has no lower bound oncountand no timestamp bounds (backdating into closed cycles). Nothing uses it: no caller inapp/,scripts/ordocs/, and zero requests in the production access log of 2026-09-18 (~453k logged API requests, checked read-only). Counting is 100% the Artemis polling sync.POST /api/occupancy/simulateis admin-only, but it writes synthetic passages into the realpeople_counting_eventstable, where they move published KPIs,kand the Trust Index and can't be told apart from real traffic afterwards. Unused in production (zero requests in the same log).POST /api/event/webhook(app/controllers/webhook_controller.py) is HikCentral's inbound callback for door events. It is unauthenticated, so anyone who can reach the server can post fake door opens, closes and alarms. It can't use bearer auth: HikCentral is the caller and has no user account. HikCentral runs on the same VM and subscribes with the address fromwebhook_manager.get_local_ip(). Production has received 3,453 door transitions through it, so it must keep working.Settled scope
POST /api/occupancy/eventand itsCountingEventWebhookPayloadschema. Move the tests that used it: theINVALID_DIRECTIONerror-code check intests/test_i18n.py, the event call intest_occupancy_api_endpoints_rbac, and the endpoint tests from #53 (test_event_endpoint_rejects_unregistered_camera) and #52 (test_event_endpoint_rejects_pass_direction). Keep the behaviours they covered under the batch ingestion interface where it still applies, and drop the rest. Remove now-unused i18n strings./api/occupancy/simulatebehind a new setting,enable_simulation, off by default (production off). When disabled it returns 403 with a clearerror_code. Kept because it's useful in development and test.POST /api/event/webhookby source address: accept loopback and the address the webhook subscribed with, plus an optional extra allowlist in config for a deployment where HikCentral runs on another host. Anything else gets 403 and a warning log line. Production needs no config change.Door-command authorization (who may unlock or hold doors open) was split out to #72. It's a role policy, not a missing check.
Acceptance criteria
POST /api/occupancy/eventandCountingEventWebhookPayloadremoved; no reference left in app code, frontend or i18n./eventmigrated or removed as above; their still-relevant behaviours remain covered./api/occupancy/simulaterefuses with 403 unlessenable_simulationis on; default off; tests cover both states.node --test).Related: #29, PR #53, PR #52, #72.
Partly addressed by PR #52 (
2b78fdb): thePASSdirection is gone.POST /api/occupancy/eventnow accepts onlyIN/OUT(400INVALID_DIRECTIONotherwise), and the batch input schema enforcesLiteral["IN", "OUT"]. Still open here: authentication,count >= 1, the timestamp bounds, or deleting the endpoint.[security] POST /api/occupancy/event is unauthenticated and accepts unvalidated passagesto fix(occupancy): POST /api/occupancy/event is unauthenticated and accepts unvalidated passagesfix(occupancy): POST /api/occupancy/event is unauthenticated and accepts unvalidated passagesto fix(api): lock down unauthenticated write endpoints — delete /event, gate /simulate, restrict webhook sourcesTriaged 2026-09-24 (grilling session). Scope settled and written into the description above; relabelled
ready-for-agentand retitled.Decisions:
/event: unused (no code caller, zero production requests in the logged period);/simulatecovers manual testing./simulatebehindenable_simulation, default off. Kept, but it can no longer pollute production figures by accident.