fix(security): close the remaining triage advisories - #511
Merged
Merged
Conversation
…rd bypass Webhook JavaScript templates (GHSA-mc99-9jf5-22cq, GHSA-fmf9-23m7-xg84): the validator only refused 'constructor' when it was the callee of a member call or the target of an assignment. Reading it into a local, destructuring it, a sequence-expression callee, a call-of-call and a tagged template all walked past the checks and reached the Function constructor in the worker. The validator now refuses reading constructor/__proto__/prototype/caller/ callee through any member access or destructuring pattern, refuses dynamic computed keys (obj[expr]) so a forbidden name cannot be assembled at run time, refuses tagged templates, and only accepts identifier, member and inline-arrow callees. Share password cookie (GHSA-p6c2-mq9r-cx3r): the overview, dashboard and report share procedures and the db share-access validators unlocked a password-protected share whenever a cookie named shared-<type>-<id> existed, whatever its value. The cookie is now an HMAC over the share type, id and current password hash keyed by COOKIE_SECRET, verified with a constant-time compare, so it cannot be forged, cannot be replayed against another share, and expires when the password changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lated context The validator was a denylist: it looked for known-bad shapes and let every other node through, which is how each new escape got in. It is now an allowlist of AST node types. Anything not on the list (tagged templates, sequence expressions, switch, labels, var, ++, delete, computed keys, object methods, TypeScript syntax, ...) is refused by default. The language is deliberately small: object/array literals, spread, property access with literal keys, template strings, ternaries, logical and arithmetic operators, const/let, if, and the allowlisted built-in methods. A template cannot call a function it defined itself: local identifiers are never callable, so there is no recursion, no IIFE and no "store a reference now, call it later" path. Inline arrows only appear as callbacks to the allowlisted array methods. All templates currently saved in production are in the test suite as fixtures. execute() now runs the template in a fresh V8 context via node:vm with eval and new Function disabled, a 250ms timeout and a 1MB output cap. The payload crosses in as JSON and the result crosses out as JSON, so the template never touches a host-realm object. This is defense in depth behind the validator, not a boundary on its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e literal Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (23)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…put in bytes Review follow-ups on the allowlist validator: - An assignment could target a property of an allowed global (Math.round = (x) => Math.round(x)), replacing a built-in for the rest of the run and looping until the timeout. Assignments now have to be rooted at a local variable. - A nested arrow is now only accepted as an argument passed directly to a call. Stored or assigned arrows are never callable, so they had no legitimate use. - The output cap measured UTF-16 code units; it now measures UTF-8 bytes, which is what goes on the wire. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Unauthenticated reads (GHSA-vrrm-p9p4-2gfg, GHSA-c98x-mph7-r6vp, GHSA-r4g5-vgpj-923m): reference.getChartReferences had no access check at all, and event.bots let any anonymous caller through as long as a shareOverview row existed, ignoring public and password. Both now go through hasAnonymousShareAccessToProject, which requires a public share for the project that is either password-free or unlocked with a verified share-access cookie. Members still need project access. ClickHouse escaping (GHSA-7fm7-rprq-g8rm, GHSA-gvwr-5684-wjqc, GHSA-gvr5-22f4-pj6g): conversion.service interpolated event names, dates and the project id raw inside quotes; they are now sqlstring-escaped. The cohort profile filter column is an identifier and cannot be escaped, so profileColumnAccess now enforces the same column allowlist that filter-where.service uses and throws on anything else. sankey.service replaced quote-doubling, which ClickHouse's backslash escapes defeat, with sqlstring.escape in all eight places. Bull Board (GHSA-r627-6vrh-65p9): the queue dashboard can add, retry and clean jobs and was mounted with no auth. It now mounts only when BULLBOARD_USERNAME and BULLBOARD_PASSWORD are set, behind HTTP Basic auth with constant-time comparison, and /healthcheck and /metrics are registered before it so they stay reachable. The Coolify template provides generated credentials; docs updated. Logs (GHSA-xr2x-w49w-hp2c): the migration banner printed full DATABASE_URL and CLICKHOUSE_URL; credentials and credential-like query params are now redacted. The email fallback dumped recipient, subject and template data (including password-reset links); it now logs a one-line warning with a redacted recipient. Unsubscribe tokens (GHSA-cv3v-4j56-hr88 side note): the HMAC secret fell back to a literal default, so links were forgeable. It now requires UNSUBSCRIBE_SECRET or COOKIE_SECRET and fails the request otherwise. The Coolify worker gets COOKIE_SECRET so it signs with the same key the API verifies with. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
lindesvard
force-pushed
the
fix/security-triage-remaining
branch
from
September 22, 2026 20:27
b4a6cfb to
466d543
Compare
Base automatically changed from
fix/security-sandbox-and-share-cookie
to
main
September 22, 2026 20:44
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.
Summary
Closes the five remaining items from the security advisory triage. Stacked on #510 (needs the share-access helper from it); merge #510 first and this PR retargets to
main.Unauthenticated reads (GHSA-vrrm-p9p4-2gfg, GHSA-c98x-mph7-r6vp dup, GHSA-r4g5-vgpj-923m) — Medium
reference.getChartReferenceshad no access check.event.botslet anonymous callers through whenever ashareOverviewrow existed, ignoringpublicand the password.hasAnonymousShareAccessToProject(projectId, cookies, kinds)inshare.service.ts: the project must have a public share that is password-free or unlocked with a verified share-access cookie. Members still need project access. Share pages keep working because their viewers hold exactly that cookie.ClickHouse escaping (GHSA-7fm7-rprq-g8rm, GHSA-gvwr-5684-wjqc, GHSA-gvr5-22f4-pj6g) — Medium
conversion.service: event names, dates and project id were interpolated raw inside quotes. Nowsqlstring.escaped.cohort.serviceprofileColumnAccess: the column is an identifier and cannot be escaped, so it now enforces the same allowlistfilter-where.serviceuses and throws on anything else.sankey.service: quote-doubling replaced withsqlstring.escapein all eight places.grep -rn "replace(/'/g" packages/db/srcis now empty.Bull Board (GHSA-r627-6vrh-65p9) — Medium
BULLBOARD_USERNAMEandBULLBOARD_PASSWORDare set, behind HTTP Basic auth with constant-time compare. Without them the worker logs a warning and serves only/healthcheckand/metrics(registered before the guard).Secrets in logs (GHSA-xr2x-w49w-hp2c) — Low
DATABASE_URL/CLICKHOUSE_URL(userinfo andpassword/sslpassword/userquery params; comma-separated ClickHouse URLs handled).Unsubscribe secret (GHSA-cv3v-4j56-hr88 side note) — Medium
UNSUBSCRIBE_SECRETorCOOKIE_SECRET, read at call time so a missing value fails that request with a clear error.COOKIE_SECRET. Before this, the worker on Coolify was signing unsubscribe links with the default and the API was verifying with the real secret, so those links were already broken there.Test plan
reference.test.ts(anon refused / anon with unlocked share served / member without access refused / member served)basic-auth.test.ts(accept, missing header, wrong user/password/malformed, colon in password)/healthcheckstill returns 200Not in this PR
🤖 Generated with Claude Code