Jupyter, VM2, and token-type boundary batch¶
Source: GitHub Security Advisories, published/updated 2026-05-05.
This batch is durable because all three clusters are recurring trust-boundary mistakes: URL/path prefix checks that are not canonical boundary checks, JavaScript sandbox escapes that rely on new language/runtime surfaces, and JWT validation that treats any trusted signature as sufficient proof of token purpose.
Advisories covered¶
- Jupyter Server <= 2.17.0 — fixed in 2.18.0:
- GHSA-5789-5fc7-67v3: authenticated path traversal via
startswith()root-directory checks that can expose sibling directories sharing the same prefix. - GHSA-24qx-w28j-9m6p: CORS origin bypass when
allow_origin_patis evaluated withre.match()instead of a full-string match. - GHSA-5mrq-x3x5-8v8f: password reset and server restart do not revoke existing authentication cookies because the cookie secret remains persistent.
- GHSA-qh7q-6qm3-653w: open redirect through insufficiently validated
nextquery parameter. - vm2 sandbox escapes:
- GHSA-v37h-5mfm-c47c: inspect-function proxy unwrap breakout, fixed in 3.11.0 for
<= 3.10.3. - GHSA-qvjj-29qf-hp7p: Promise species bypass of a prior sandbox fix, fixed in 3.10.5 for
<= 3.10.3. - GHSA-55hx-c926-fr95:
SuppressedErrorescape on modern Node, fixed in 3.11.0 for<= 3.10.4. - GHSA-ffh4-j6h5-pg66: WASM exception-handling /
JSTagescape on Node 25, fixed in 3.10.5 for3.10.4. - nuts-node <= 1.1.0 — GHSA-9hmg-827w-9rhj: v1 access-token introspection accepts Verifiable Presentation JWTs as active access tokens because it validates a known signature without enforcing JWT type, issuer-to-key binding, and required access-token claims. No patched version was listed in the advisory at scan time.
Operator triage¶
Jupyter Server¶
- Inventory exposed notebook/server instances and confirm
jupyter-serverversion. Treat public, shared, or multi-tenant servers as priority targets. - Upgrade to 2.18.0+.
- After upgrade, rotate secrets by deleting/regenerating the Jupyter cookie secret on affected hosts and force active sessions to reauthenticate. A password reset alone was not enough for the cookie-secret issue.
- Review configuration for
allow_origin_pat; anchor patterns or convert them to exact-origin allowlists. Do not rely on prefix-like regex behavior. - Hunt for path traversal attempts against
/api/contents/using encoded.., sibling directory names that share the configured root prefix, or checkpoint/create/delete calls outside expected notebooks. - Hunt for login URLs with external
nexttargets such as protocol-relative URLs (///host) or lookalike domains.
vm2¶
- Treat any service that runs attacker-controlled JavaScript in vm2 as high risk, especially if it is reachable from tenants, users, CI jobs, templates, extensions, agents, or plugin systems.
- Upgrade to a version containing the relevant fix; for mixed advisory coverage, prefer 3.11.0+ rather than stopping at a narrower 3.10.5 fix.
- If untrusted code has run, assume possible host-process command execution. Preserve logs, rotate secrets reachable by the Node process, and rebuild worker hosts from clean images when exposure was internet-facing or multi-tenant.
- Reduce blast radius even after patching: run sandbox workers in containers or microVMs, with no long-lived credentials, read-only filesystems where possible, egress allowlists, low privileges, and per-job teardown.
- Add regression tests for new JavaScript/runtime surfaces:
SuppressedError,DisposableStack, Promise species manipulation,inspect/custom inspection, WASM exception handling, and current Node release lines.
nuts-node / JWT type confusion¶
- Do not treat a trusted JWT signature as sufficient authorization. Verify token family/type, issuer-to-key binding, audience, subject semantics, expiry, and all purpose-specific claims before returning
active: true. - For affected deployments, disable or restrict the v1 introspection endpoint until fixed, especially across trust domains where Verifiable Presentation JWTs may be attacker-controlled.
- Hunt for introspection calls where the presented JWT resembles a Verifiable Presentation rather than an access token, including missing service/purpose claims or ambiguous
typ: JWTheaders. - Prefer explicit JOSE media types such as
vp+jwt/at+jwtand reject ambiguous tokens at the boundary.
Durable controls¶
- Replace string-prefix path containment with canonical path resolution plus boundary checks (
commonpath/realpath-style logic), then test sibling-prefix cases (rootvsroot2). - Prefer exact origin allowlists or full-string regex matching for browser security decisions. Prefix matches are not origin checks.
- Couple credential rotation with session-secret rotation. If the signing key remains valid, old cookies remain credentials.
- Do not depend on language-level JavaScript membranes as the only isolation boundary for hostile code. Runtime features evolve faster than proxy-based sandboxes.
- Model token validation as a typed protocol decision, not a crypto-only decision: every token class needs an allowlisted issuer, audience, type, and claim contract.
Jupyter CORS origin-regex validation update¶
Updated: hourly offensive-security scan, 2026-06-15. GitHub updated GHSA-24qx-w28j-9m6p / CVE-2026-40110 with a clear exploit precondition: Jupyter Server validates allow_origin_pat with Python re.match(), so an unanchored trusted-origin regex can match an attacker-controlled suffix origin. Upstream references include the Jupyter Server advisory, pull request 603, and patch commits 057869a and 49b3439.
Why this is operator-useful¶
This is a reusable browser-origin trust-boundary pattern: a server accepts a regex for trusted CORS origins, but evaluates it as a prefix match instead of a whole-origin match. A suffix domain such as https://trusted.example.com.attacker-owned.test can satisfy a pattern intended only for https://trusted.example.com when that pattern lacks explicit anchors.
Safe validation workflow¶
- Confirm the path exists. Test only deployments using
allow_origin_pator an equivalent regex-based CORS allowlist. Do not assume every Jupyter deployment is affected. - Use an owned suffix origin. Host the proof page on a canary domain controlled by the test team, for example
https://trusted.example.com.attacker-owned.test. - Baseline regex behavior locally. Show the semantic mismatch without touching the target:
import re
pattern = r"https://trusted\.example\.com"
origins = [
"https://trusted.example.com",
"https://trusted.example.com.attacker-owned.test",
"https://other.example.com",
]
for origin in origins:
print(origin, bool(re.match(pattern, origin)), bool(re.fullmatch(pattern, origin)))
- Exercise only a harmless endpoint. From the canary origin, request an owner-approved status/version route with browser
fetch(..., { credentials: "include", mode: "cors" }). The proof is the browser exposing a response or CORS header to the suffix origin, not access to notebooks or files. - Add controls. Compare the suffix origin with a clearly unrelated origin and, where possible, a lab configuration using anchored
^...$matching.
Evidence boundaries¶
- Capture version/package evidence, the redacted
allow_origin_patshape, the requestOrigin, and the CORS response header. - Keep screenshots and HAR files limited to harmless status/version responses.
- Never collect notebooks, terminals, tokens, model files, datasets, environment variables, cloud credentials, or user content.
- Report the root cause as prefix regex matching used for origin authorization, not merely generic CORS misconfiguration.
Jupyter Server sibling-prefix path traversal follow-up¶
Updated: hourly offensive-security scan, 2026-07-09. GitHub updated GHSA-gf7q-q4j7-hp7c / CVE-2026-5422 with a clearer root cause and patched version boundary: jupyter-server before 2.18.2 can allow authenticated file access outside root_dir when _get_os_path() checks path.startswith(root) without a trailing path separator and to_os_path() preserves traversal parts. The operator-useful pattern is the same durable class as the original Jupyter entry: string-prefix containment is not filesystem containment.
Safe validation workflow¶
- Build a disposable lab tree. Use a throwaway user and temporary directory names that intentionally share a prefix, for example
/tmp/jupyter-rootand/tmp/jupyter-root-sibling. Put only a synthetic marker file in the sibling directory. - Confirm the target shape. Validate only Jupyter Server deployments where the assessment scope includes authenticated Contents API testing and the package version is below
2.18.2. - Probe with canary paths only. Exercise
/api/contents/with encoded traversal and sibling-prefix path variants that should remain outsideroot_dir. The proof is access to the synthetic sibling marker or an unexpected write to a disposable sibling path. - Capture negative controls. Show that a non-prefix sibling such as
/tmp/other-siblingis denied, and that the same canary path is denied after upgrading or applying a canonical boundary check. - Stop at boundary proof. Do not enumerate directories or read real notebooks, datasets, credentials, terminals, model artifacts, or cloud configuration files.
Evidence to keep¶
jupyter-serverversion, configuredroot_dirshape, and the redacted Contents API route used.- The synthetic sibling directory names and marker filename, with contents limited to a harmless canary string.
- A before/after result table covering vulnerable, patched, prefix-sibling, and non-prefix-sibling cases.
- Source links: GitHub advisory, huntr report, and upstream fix commit.