chadtmiller.com/knotless
git that lives in your Atmosphere account
1
10
An author: filter reads kept handles rather than asking each time
Merged Asked for changes
This makes the author: filter read the handle from the index, and otherwise look it up once through a 10-minute Kept cache (authors.json). The direction is right, and the docs page is updated. Two problems in the cache itself.
- The cache is keyed by text anyone can send, and it is never pruned.
author:comes straight from the query string. Every distinct value, even a nonsense one, adds an entry toAUTHORS(Some(None)is stored). Everyputthen rewrites the wholeauthors.jsonwithstore::write. A crawler or attacker varying the filter grows the map and the file without bound, and each new key costs a full file write.AVATARSandCLAIMSare keyed by real DIDs, so they don't have this problem. Keep only values that look like a handle (and drop anything else before it reaches the cache), and/or bound the size. - A failed lookup is kept as "no such account" for ten minutes.
resolve_handle(...).ok()turns a timeout or a 5xx at the handle's host intoSome(None), andputstores it. The host is already known to take seconds to answer, so a slow or failing answer would make a real author's filter match no one until the entry expires. Cache a negative only when the host answered that the handle names no one. On other errors, returnNonefromlookso nothing is kept.
crates/knotless-serve/src/web/items.rsline 113
let (ctx, handle) = (app.ctx.clone(), author.to_string()); | |
let look = async move { | |
let found = knotless::identity::resolve_handle(&ctx, &handle).await; | |
Some(found.ok()) |
found.ok() makes any error, including a timeout or 5xx, into Some(None), which through stores for ten minutes. Only a real "names no one" answer should be kept. Return None for the other errors.
crates/knotless-serve/src/web/items.rsline 115
let found = knotless::identity::resolve_handle(&ctx, &handle).await; | |
Some(found.ok()) | |
}; | |
let found = AUTHORS.through(&app.ctx.env.cache_dir, author, look).await; |
author is user input used as the cache key. Each distinct value adds an entry and rewrites all of authors.json, with no bound. Check that it looks like a handle before it reaches the cache, and/or cap the size.
All checks have passed
8 passed, 2 skipped
Merged as 56e27dc
What changes
An
author:filter on a repository's pulls or issues now finds the account in the handles the index keeps (Index::did_of_handle, by a new index onhandle (handle)). A handle not kept there is looked up at its host once, through the same cache avatars and claims use (authors.json). What was found, an account or none, is kept for ten minutes. Pages asking at once share one lookup and wait five seconds at most.Why
did()inweb/items.rscalledresolve_handleon every request, with no cache. On prod, a crawler loads/chadtmiller.com/knotless/issuesevery minute or two. Each load made a request to a handle's/.well-known/atproto-didthat took 1.5–2.7s, out of a 1.7–3s page.How it was checked
forge.rs's filter test covers both ways: by the author's handle, which the index keeps once the page has listed authors, and byauthor:nobody.invalid, which is looked up and found to name no one. Left to CI.Shortcomings
A handle kept in the index can be out of date for a day, as the handles the pages show are. A filter by a handle that moved to another account can match the old one until the handle is looked up again.
🤖 Generated with Claude Code