Result retention controls - #15
Merged
Merged
Conversation
The NEJM AI paper (Wiest et al. 2025, doi:10.1056/AIdbp2400537) established the approach this app builds on, so it belongs in the citation metadata — as a `references:` entry, with the software itself still the primary citation. Not `preferred-citation`: that would make GitHub's "Cite this repository" button emit the paper instead of the software, attributing this codebase to a study that evaluated a different one. The reference's `notes:`, the README and the docs landing page all say the same thing in prose, since most people never open the .cff — this is a follow-up implementation, and the accuracy reported in the paper does not describe this tool.
A security review of the request cache turned up three ways a document
stayed reachable longer than it should, and one place it reached a disk:
- nginx spools request bodies over client_body_buffer_size (16k!) and
responses over the proxy buffers to /tmp, which in the frontend container
is the writable layer. Every upload and every anonymize response was
therefore written to disk, contradicting "nothing touches disk" in
DATA_RETENTION.md. /tmp is now a tmpfs, the body buffer is 1 MB and
/api/ responses stream through unbuffered.
- The cache TTL was refreshed on every read, so "≤15 minutes" was idle
time, not maximum age, and eviction only ran inside put/get — an idle
process held its last documents indefinitely. The TTL is now absolute
from creation, a lifespan task sweeps every minute, and shutdown clears
the cache.
- The request id is the only credential for a cached document (POST
/anonymize with {request_id, overrides} returns the full source text),
and it was written to the INFO log on every request. It is now in
FORBIDDEN_FIELDS; the endpoints log ref=log_reference(id) instead.
Also: DELETE /anonymize/{id}, called when a document is closed, reset or
the tab unloads, so the server-side copy ends when the user is done rather
than at the end of a TTL; and the export path discards the entry its own
cache-miss run created, which no client could ever reference.
Since results now expire on a fixed schedule, the UI has to say so. The
header carries a countdown that doubles as the extend button — a press
buys a full extension window from that moment, repeatably, so stepping
away from the desk does not cost a re-run — and the result view warns
separately in the last minute. The lifetime is batch-wide: it counts down
to whichever document expires first and one press extends them all.
The durations are RESULT_CACHE_TTL_MINUTES (15),
RESULT_CACHE_EXTENSION_MINUTES (60), RESULT_CACHE_MAX_LIFETIME_MINUTES
(720) and RESULT_CACHE_MAX_ENTRIES (100). The defaults suit a research
prototype; the ceiling is what makes the retention statement true, and
setting it equal to the TTL turns extending off. Misconfiguration fails
toward less retention: a ceiling below the TTL shortens the first window.
Durations read as "1 Std. 5 Min." / "45 Min." / "47 Sek." via Intl unit
formatting — m:ss showed "60:00" after an extension and implied a
precision nobody needs at that range.
The changelog is read by users and by the admin setting the app up, and the 0.1.0 section had grown to roughly 75 lines of sub-headed bullets while the unreleased entries carried the rationale and migration notes that belong on a docs/ page. Both are condensed: one bullet per change, naming the setting or route that moved and linking to the page that explains it. The retention entry went from ten lines to five, 0.1.0 from four subsections to eleven bullets. Nothing was dropped that a user or an operator would act on. The unreleased entries become 0.1.1, dated today, with the compare links. Version bumped in package.json, pyproject.toml, CITATION.cff, the APP_VERSION default, both lockfiles, the THIRD_PARTY_NOTICES header and the DEIDENTIFIER_IMAGE_TAG examples in the deployment docs. AGENTS.md already said to keep the changelog short, and that clearly was not enough to prevent the drift, so the rule is now checkable rather than a sentiment: one bullet, one or two sentences, no sub-bullets, no rationale, no migration notes, and a release section that fits on a screen (~20 lines). Condensing is step 3 of Releasing, including the older sections, rather than optional cleanup. Step 2 was also wrong — it called `uv lock` conditional on dependency changes, but both lockfiles carry the project version, so every bump needs them, along with the two version strings that live in prose.
| first = client.post("/api/v1/anonymize", json={"text": SAMPLE_TEXT}).json() | ||
| request_id = first["request_id"] | ||
|
|
||
| assert client.delete(f"/api/v1/anonymize/{request_id}").status_code == 204 |
| def test_delete_of_an_unknown_id_reveals_nothing(client): | ||
| """Same answer either way — whether an id exists is not something an | ||
| unrelated caller should be able to probe.""" | ||
| assert client.delete("/api/v1/anonymize/no-such-id").status_code == 204 |
| finally: | ||
| sweeper.cancel() | ||
| with suppress(asyncio.CancelledError): | ||
| await sweeper |
| """Whatever the UI does, a document leaves memory at the configured | ||
| maximum. Set to 30 minutes here so the test does not have to simulate a | ||
| 12-hour day.""" | ||
| import backend.src.utils.cache as cache_module |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.