Surveillance of the public surfaces
How the project watches its application, the application’s API and this notebook, and why each piece is built the way it is. Addresses, thresholds and resource names are left out.
Each piece answers one question.
| Question | What answers it |
|---|---|
| Who read patient data, from where, and what happened? | an access trail, one row per request |
| Did someone outside get patient data, or scan the API, just now? | alerts, by email and text message |
| Who visited this week? | a visitor digest in the weekly admin report |
| What happened months ago? | request logs kept well past the platform’s default |
| Can a search engine list the application? | noindex, three ways |
| Who reads this notebook? | aggregate page counts, without cookies |
The access trail
Every request to the patient-data API writes one row: who made it (the account, recorded even when access is refused), what kind of credential, if any, was presented, the method and path, the time, where the request came from, and the outcome with its status code. It is read through an owner-only admin tool. Four choices shaped it.
- Paths, never query strings. A query string carries whatever a client puts in it, and the trail should not keep a copy.
- The client address is the hop the hosting platform’s own front end appends. Earlier hops are whatever the client claims, so a forged address lands there, not in the record.
- Every request, with no retention limit. Nothing is sampled, refused requests are recorded too, and the trail lives apart from the debug logs, so routine debug cleanup cannot touch it.
- It fails open, loudly. If a row cannot be written, the request still gets its normal answer, and the failure is logged as an error that the daily error digest reads. Failing closed would let a logging bug take the patient-data service down.
The alerts
The alerts read the hosting platform’s own request log, which the application’s code cannot alter. That independence is the point: an alert built on the application’s own logging would share any flaw in the application it is meant to catch.
- Patient data served outside an allowlist (critical): a successful patient-data response to any address outside a short list of the owner’s own. It fires when the owner reads from his phone or away from home. That is deliberate: it cannot tell him from a stranger, so it asks.
- A burst of refusals (warning): more refused requests in a short window than normal traffic has ever produced. A real scan sends far more.
Each condition is defined once in code, and the weekly digest recomputes both with the same predicates, so “what would have fired” has one answer. A test pins each log filter to its code twin, and the filters were run against past request logs before they were switched on. Each alert has a fire test: one writes a synthetic request from an address reserved for documentation, the other sends real requests to a path that does not exist. Both reached the owner’s phone and inbox within minutes. The notification channels were set up by hand with the owner, so his phone number lives in the cloud console, not in the repository.
The visitor digest
A section of the weekly admin report, appended verbatim rather than paraphrased by a language model. It counts requests by source (the owner, browsers, declared crawlers, scanners, other automation), refusals by path and the access trail’s rows, and it recomputes both alert conditions over the week. If the logs cannot be read, the section says so. An unreadable log and a quiet week must never look alike.
Long-term request logs
The request logs of the application and its API are also routed to a dedicated log bucket kept far longer than the platform’s default. Nothing in a request decides whether it is kept: a filter that a request can match on purpose is a way to opt out of the record.
Noindex
The application and its API ask search engines not to list them three ways: a robots.txt that disallows everything, a response header, and a meta tag in the page.
This notebook
The notebook counts page loads in aggregate, with a counter that sets no cookies and skips local previews.
Cost and removal
At the project’s volume all of this costs very little. Taking it out is a short, documented list of commands, and a deleted log bucket stays recoverable for a while. The access trail is data, so it is dropped only on the owner’s word, after a backup.