plugins

SECURITY.md — audit record

A four-axis adversarial review of all 33 plugins (2026-07), and what it found. Axes: authentication/authorization, SSRF/path/parser input, resource-safety/DoS/lifecycle, and correctness/protocol conformance. Each reviewer read every plugin.js and cross-checked findings against NOTES.md/REPORT.md so documented limitations were not reported as bugs.

Headline: the fleet held up well. The dominant pattern — resolve identity with api.auth.getAgent, forward the client’s Authorization on every loopback call, and let the host’s WAC decide — is applied correctly and consistently across the whole data plane. Real defects clustered exactly where a plugin invented its own behavior instead of deferring to WAC (activitypub’s unauthenticated federation surface, sparql’s user-supplied regex). All confirmed findings are fixed, each with a regression test; the suite went 374 → 386 tests.

Fixed

Severity Plugin(s) Finding Fix Commit
High activitypub Unauthenticated inbox fetched an attacker-controlled actor/inbox URL with redirect: follow and no gate → SSRF pivot (e.g. 169.254.169.254); unbounded per-user state growth; non-atomic write gatedFetch (corsproxy’s private-IP guard + DNS resolvesToPublic, fail-closed, manual per-hop redirect re-validation); inbox trimmed to maxInbox, followers deduped; atomic state write; bodies consumed; outbox walk capped 03e4db5
High sparql FILTER(REGEX(...)) compiled a user pattern and ran it on pod-plantable strings with no bound → ReDoS event-loop hang Reject patterns over maxRegexLength (512) and catastrophic nested-quantifier shapes; truncate tested input to maxRegexInput (10k) f1255a7
High caldav, carddav free-busy/query/multiget/PROPFIND walked every member with a full-body buffer each, uncapped → DoS amplification config.maxResources cap (default 10k) truncates the member/href list before the walk; oversized → truncated-but-valid 207 642dec2
Med otp, capability, shortlink Direct writeFileSync of the whole table → a crash mid-write truncates it; loader swallows the parse error into {} (capability: a revoked token silently revives) Atomic write (unique temp file + renameSync) ca52373
Med jmap Email/set create omitted server-set props (blobId/threadId/size) required by RFC 8621 §4.6 Echo them in the create response (blobId = sha256 of stored JSON, threadId = id, size = byte length) e54aba2
Low-Med gitscratch Auth gate required an agent but never checked it against the repo creator → any authed user could push to another’s repo Opt-in config.owned binds a repo to its first materializer (default stays shared) a8847a3
Low rss Atom output missing the RFC 4287 §4.1.1-required <author> Feed-level <author> from config.author/title e54aba2
Low remotestorage Forwarded host content-length over a fetch-decoded stream (latent framing bug) Drop it, matching the webdav sibling e54aba2
Low terminal Query-token compare early-exited on length mismatch (timing side-channel on token length) Constant-time sha256 + timingSafeEqual e54aba2

Accepted risk / deferred (documented, not fixed)

What was checked and found solid

Reporting

This is an experimental research repo, not a production deployment. If you find a security issue, open an issue on the repo (or, for the core server, on JavaScriptSolidServer/JavaScriptSolidServer). None of these plugins should be run on a public deployment without the operator reviewing the accepted-risk items above.