Skip to content

fix(security): JS check memory budget residuals, regexp call time and process isolation, crawl link scheme, subscriber list role, reset links on password change - #742

Merged
fclairamb merged 33 commits into
mainfrom
security/scan-specs-2026-11-02
Nov 2, 2026
Merged

fclairamb merged 33 commits into
mainfrom
security/scan-specs-2026-11-02

Conversation

@fclairamb

@fclairamb fclairamb commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Specs filed from the daily security scans. Each spec is the tracker for its finding.

What changed (specs implemented by this PR)

  • AC2 (medium): specs/todos/2026-11-02-01-js-checker-memory-budget-residuals.md. Even after fix(security): JS check memory budget gaps and regexp time limit, Discord link re-authentication, invitation cap #735, one native call in a JS check can allocate GBs: JSON.stringify, toLocaleString, normalize, String.raw, the URI encoders, and ropes flattened by JSON.parse, by a thrown error (which also stores a huge error string with the result) or hidden deeper than the scan's depth cap. Now charged, with the rest of the amplifying builtins; the thrown error text is capped at 16 KiB. Process isolation is filed as AC7.

  • AC3 (medium): specs/todos/2026-11-02-02-js-checker-regexp-total-time.md. The 2 s regexp limit of fix(security): JS check memory budget gaps and regexp time limit, Discord link re-authentication, invitation cap #735 applies to each match. A global replace chains slow matches into hours of CPU in one call that the timeout cannot stop. Now bounded for the whole run.

  • AC4 (low): specs/todos/2026-11-02-03-crawl-finding-url-scheme.md. Sitemap URLs from robots.txt and sitemap indexes skip the http(s) filter, so a crawled site can plant a javascript: link in a check's findings. The dashboard CSP blocks it today. Now filtered in the crawler, and dash0 renders only http(s) links.

  • AC5 (low): specs/todos/2026-11-02-04-status-page-subscribers-admin-only.md. Viewers can list a status page's subscriber email addresses. The list is now admin-only (breaking for viewer/user API clients).

  • AC6 (low): specs/todos/2026-11-02-05-password-reset-tokens-purged-on-change.md. Outstanding password-reset links survive a password change, a reset and an email change. Reset links are now deleted on each of these.

  • AC7 (medium): specs/todos/2026-11-02-06-js-checker-process-isolation.md. Each JS script now runs in a memory-limited child process of the same binary (heap watchdog, RLIMIT_AS, optional cgroup, kill at timeout); bindings stay served by the parent. A killed child gives an error/timeout result, the parent never crashes. SP_JS_ISOLATION=off, SP_JS_MEMORY_LIMIT_MB, SP_JS_CGROUP documented. About 11-27 ms overhead per run on macOS.

Decided by autopilot

  • AC5 (subscribers admin only), open question "should user members keep the list?": no, admin only (recommended answer).

Earlier scan specs (implemented, in specs/done/2026/10/ and specs/done/2026/11/)

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 33.50694% with 766 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.97%. Comparing base (061eca0) to head (eb2914e).

Files with missing lines Patch % Lines
...erver/internal/checkers/checkjs/memory_builtins.go 10.67% 293 Missing ⚠️
...erver/internal/checkers/checkjs/regexp_deadline.go 8.21% 190 Missing ⚠️
...erver/internal/checkers/checkjs/isolation_child.go 1.80% 109 Missing ⚠️
server/internal/checkers/checkjs/isolation.go 73.89% 71 Missing ⚠️
...nternal/checkers/checkjs/isolation_limits_linux.go 5.88% 48 Missing ⚠️
...erver/internal/checkers/checkjs/memory_bindings.go 37.50% 25 Missing ⚠️
server/internal/checkers/checkjs/isolation_wire.go 69.76% 13 Missing ⚠️
server/internal/handlers/auth/service.go 79.59% 10 Missing ⚠️
server/internal/checkers/checkjs/checker.go 76.66% 7 Missing ⚠️

❌ Your patch check has failed because the patch coverage (33.50%) is below the target coverage (87.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #742      +/-   ##
==========================================
- Coverage   89.79%   88.97%   -0.83%     
==========================================
  Files        1311     1317       +6     
  Lines      133570   134677    +1107     
==========================================
- Hits       119940   119827     -113     
- Misses      13621    14841    +1220     
  Partials        9        9              
Files with missing lines Coverage Δ
server/internal/app/server.go 92.04% <100.00%> (+0.01%) ⬆️
server/internal/checkers/checkcrawl/crawl.go 84.10% <100.00%> (+1.34%) ⬆️
server/internal/checkers/checkjs/memory.go 24.93% <100.00%> (-66.96%) ⬇️
server/internal/checkers/checkjs/memory_strings.go 22.50% <100.00%> (-68.72%) ⬇️
server/internal/checkers/checkjs/regexp.go 75.00% <100.00%> (-25.00%) ⬇️
server/internal/envcheck/envcheck.go 87.67% <100.00%> (+0.71%) ⬆️
server/internal/handlers/auth/email_change.go 89.85% <100.00%> (+0.07%) ⬆️
server/internal/checkers/checkjs/checker.go 81.03% <76.66%> (-11.18%) ⬇️
server/internal/handlers/auth/service.go 90.10% <79.59%> (-0.10%) ⬇️
server/internal/checkers/checkjs/isolation_wire.go 69.76% <69.76%> (ø)
... and 6 more

... and 22 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

fclairamb and others added 18 commits November 2, 2026 07:53
…rd thrown and deep ropes

JSON.stringify (charged as it writes, via a replacer of its own),
Array/TypedArray toLocaleString, String.prototype.normalize, String.raw,
encodeURI, encodeURIComponent and escape now draw from the 64 MiB budget.
JSON.parse, parseInt/parseFloat, the decode/encode functions, unescape and
RegExp/String/Number put the flatten guard in front of their argument.
An uncaught rope (thrown string or Error message) is refused before
err.Error() flattens it, the result's error text is cut at 16 KiB, and a
value too deep or too large for the rope scan is refused instead of passed.

Behaviour change: String, Number and RegExp globals are Proxies, so
"".constructor === String is false; a binding argument or a result nested
more than 64 levels deep or holding over 1 Mi values is refused.

Spec: specs/todos/2026-11-02-01-js-checker-memory-budget-residuals.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cess isolation spec

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'s deadline

A global replace/replaceAll/match, matchAll and every split ran their match
loop natively in one goja call, each match with a fresh 2 s regexp2 limit:
N slow matches ran N x 2 s past the check's timeout (spec 2026-11-02-02,
scan finding AC3).

RegExp.prototype.exec and test now check the run's deadline first. A global
Symbol.replace / Symbol.match runs goja's spec loop (one exec per match) on a
Proxy of the regexp; Symbol.split runs a Go port of goja's split, driven by
exec on a global copy of the pattern (and the per-position loop for a
splitter goja cannot shortcut). Results are unchanged; a run overshoots its
timeout by one match at most and reports `timeout`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….txt and sitemap indexes

A Sitemap: line or a sitemap index <loc> with a javascript:/data: URL was
fetched and its failure stored verbatim as a sitemap_error finding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A viewer or user member (or their PAT) now gets 403 FORBIDDEN on
GET /orgs/:org/status-pages/:uid/subscribers: the rows carry third
parties' email addresses. Removal keeps the write floor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Non-admins see a Permission Denied notice in place of the list (also on
a 403); adding a webhook/Slack delivery stays available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… email change

Reset entries are now indexed per user (password_reset_user:<userUID>:<tokenHash>,
same TTL). purgePasswordResets deletes them all after a reset, a password
change or first-password set, and an email change (self or super admin).
Each entry also stores a hash of the address the link was mailed to; the
reset is refused once the account's email differs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fclairamb
fclairamb marked this pull request as draft November 2, 2026 07:29
@fclairamb fclairamb added the frozen Batch PR under review: takes only its own fixes, new work goes to the next batch label Nov 2, 2026
fclairamb and others added 5 commits November 2, 2026 08:47
A js script now runs in a child of the same binary (SP_JS_CHILD=1, the
hidden subcommand). The child has a heap limit (256 MiB by default,
SP_JS_MEMORY_LIMIT_MB), an RLIMIT_AS/RLIMIT_DATA cap on Linux, an optional
delegated cgroup (SP_JS_CGROUP), and is killed at the timeout plus 1 s.
The bindings that need the check process (http, solidping.*, browser,
rdp, vnc, tcp, udp, websocket, and so the egress guard, allowlists and
repair hold) are served by the parent over the child's stdin/stdout.
A child killed by its limit is an error result naming the limit; one
killed at the timeout is a timeout. SP_JS_ISOLATION=off restores the
in-process run.

Spec: 2026-11-02-06

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…isolation

An empty PluginConfig was dropped by omitempty, so the child ran the plugin
as a plain js check (no warning status). The docs gain a Process isolation
section with the SP_JS_* operator variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…limited child process

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…(batch gate)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@fclairamb fclairamb changed the title fix(security): JS check memory budget residuals and regexp call time, crawl link scheme, subscriber list role, reset links on password change fix(security): JS check memory budget residuals, regexp call time and process isolation, crawl link scheme, subscriber list role, reset links on password change Nov 2, 2026
@fclairamb
fclairamb marked this pull request as ready for review November 2, 2026 07:57
@fclairamb
fclairamb enabled auto-merge (squash) November 2, 2026 08:17
fclairamb and others added 5 commits November 2, 2026 09:34
…ded runner

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… allocation may overflow'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@fclairamb
fclairamb merged commit de9aac0 into main Nov 2, 2026
32 of 33 checks passed
@fclairamb
fclairamb deleted the security/scan-specs-2026-11-02 branch November 2, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge autopilot frozen Batch PR under review: takes only its own fixes, new work goes to the next batch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants