Skip to content

Python policy-sandbox traversal bypasses, CMS reference-expansion authz, and client-side trust violations (Sept 17 16:29–17:19Z waves)

Source: hourly offensive-security scan of GitHub Security Advisories, 2026-09-17 (waves published 2026-09-17T16:29Z–17:19Z).

One invariant ties the durable records: a policy guard wraps the doors it knows about, and the standard library — or the framework's own expansion logic — has other doors. string.Formatter and str.format_map perform their own attribute/item traversal with Python's real getattr/getitem, stepping around RestrictedPython and Zope AccessControl guards. Umbraco enforces Public Access at the controller layer for the node you request, then expands referenced nodes with no check at all. MariaDB Connector/J's allowLocalInfile=false is a client-side promise the server can simply not honor.

Advisory table

Product GHSA Sev. Boundary
RestrictedPython GHSA-hp3v-5vw7-fx9w high When a policy exposes the stdlib string module (import policy or globals), string.Formatter.get_field performs attribute and item traversal internally and returns live object references — bypassing RestrictedPython's safer_getattr guards entirely. Reachable objects include function globals, builtins, file access, and code-execution primitives. Fix blocks Formatter and unsafe traversal methods in safer_getattr.
Zope AccessControl GHSA-pq59-9fq7-m886 high str.format/format_map let a caller-controlled format string "read" objects recursively via {a.b.c[d]} traversal — using Python's full getattr/getitem, not AccessControl's restricted _getattr_/_getitem_. Prior mitigations guarded format on str instances but missed str subclasses; a subclass instance's format slips through. Fixed in AccessControl 7.4. Information disclosure of anything reachable from an accessible object.
Umbraco Delivery API GHSA-wr57-hqmp-fgvh high Member / Public Access protection is enforced only at the controller layer, on the directly requested node. When a public node references a protected node through a Content Picker or Multi-Node Tree Picker (including nested inside Block List / Block Grid / Rich Text blocks), the Delivery API expands and serializes the protected node with no access check: name/route/id always, full property values with ?expand. Alias-based content-type allow/deny restrictions are bypassed the same way. A direct request for the protected node correctly returns 401 — the protection looks intact. Fixed 13.15.1 / 17.5.3 / 18.0.2.
MariaDB Connector/J GHSA-wxmm-q36w-r9xj moderate allowLocalInfile=false is not enforced on server-initiated LOCAL INFILE requests (protocol packet 0xfb). A rogue or MitM MySQL server echoes the exact filename the client already put in a LOAD DATA LOCAL INFILE query, and the connector transmits the file content without checking the flag. Constrained — the server cannot redirect to arbitrary paths — but the defense-in-depth guarantee is void whenever an app loads sensitive files over an untrusted network.

Adjacent records from the same 17:04Z amqp091-go publication batch and the 16:30Z wave that were folded elsewhere or tracked are listed at the bottom.

Why this is worth an operator page

  • Format strings are traversal engines. {obj.attr[key]} in str.format/format_map, and Formatter.get_field directly, are read-only object-graph walkers that run inside the stdlib — no _getattr_, no sandbox rewrite, no guard hook. Any application that (a) evaluates user-controlled format strings against rich objects (template/i18n/error-message features) or (b) embeds RestrictedPython/AccessControl and exposes string/Formatter/str subclasses to restricted code has a disclosure primitive. Audit rule: grep for .format(, .format_map(, and Formatter( where the format string (not just the arguments) can be influenced by lower-privilege input, and enumerate what's reachable from the bound objects.
  • Guards written against a class miss its subclasses. Zope blocked format on str instances; class My(str) passes the type check and carries the method. Same canonicalization/inheritance family as the Sept 5 Grav dot-keyed flag-strip miss: the guard's type/key model is a simplified copy of the real object model. Test every subclass/wrapper/proxy variant of any blocked-capability type.
  • Entry-point authorization ≠ expansion authorization (the CMS headless-API pattern). Umbraco's leak is the reference-expansion dual of the Sept 17 Vendure guard-composition item: the check exists but runs at the wrong layer, so an indirect route returns the same data. On any headless/Delivery/Content API in scope: pick a known protected item, find (or in a lab, create) a public item that references it via a picker/tree/nested-block field, and request the public item with ?expand. Direct 401 + expanded 200 is the decision table. Also sweep alias-based type restrictions — they're enforced on the entry node only.
  • Client-side safety flags are claims, not controls. allowLocalInfile=false, TLS options, redirect-blocklist-on-first-URL — when enforcement depends on the client honoring the setting and the peer drives the protocol message (server-initiated 0xfb), the flag only constrains honest servers. For MySQL-protocol tooling you run or that runs in your engagement infra (rogue-server harnesses, mysql:// URI sinks in other apps), a malicious server answering a LOAD DATA LOCAL INFILE is the test; the file returned is limited to the one the query names, so frame impact to what the target app actually loads.

Validation workflow (authorized scope only)

Local harnesses and disposable content only

Run the Python sandbox checks in local environments with synthetic policy hooks and objects. Use lab Umbraco instances with marker-only protected content. MariaDB proofs use a lab database and fake files. Never evaluate untrusted format strings against production application objects, never read real member-gated production content, never MitM a real database connection.

  1. Formatter traversal harness (RestrictedPython-class). Local embedding with a disposable policy that denies attribute access; expose a synthetic object graph (policy_root.secret_marker); run restricted code using from string import Formatter (or "{0.secret_marker}".format where the format string is sandbox-controlled) against a bound object. Positive: the marker value returns despite the deny-hook. Negative control: patched release rejects the import/attribute. Chain stops at disclosure — do not escalate to code execution in the harness.
  2. str-subclass format probe (AccessControl-class). Local Zope-based lab: confirm "%s" % / direct format on str instances is blocked but (class X(str))("x").format(secret_attribute_holder) discloses; evidence is the marker string, redacted in report.
  3. Reference-expansion authz sweep: for any content API in scope — identify a protected route (direct request must 401), enumerate public documents' picker/nested/reference fields (schema introspection or observed UI requests), replay with ?expand on each reference-bearing field, and record a per-renderer decision table (direct vs expanded × each picker/block type). Presence-only evidence (title/id of the protected node), never full gated payloads in public reports.
  4. Content-type alias restriction re-check: if the API exposes allow/deny lists by content type alias, request a denied type as a reference from an allowed type; compare against direct-request behavior.
  5. LOCAL INFILE constraint check (lab): with an owned lab MySQL-compatible server and an app configured allowLocalInfile=false, issue LOAD DATA LOCAL INFILE against a canary file in a lab harness and confirm whether the connector transmits content when the server answers 0xfb. Evidence: canary content received by the owned server. Stop there; never against shared or production databases.

Folded to existing pages from the same window

  • Grav 2026-09-17 17:15Z follow-up: FlexDirectory::dynamicDataField() executes arbitrary callables from blueprint data (incomplete fix of the earlier Blueprint callable RCE — the registered data handler routes around the patched dynamicData() validation), plus the RabbitMQ amqp091-go framing/URI items on the Sept 15 parser page.
  • Skipper 2026-09-17 follow-up: truncated_body mitigation itself fails open on oversized chunked/HTTP/2 bodies (third variant of the fix chain).
  • Archive extraction page: Junrar LocalFolderExtractor intermediate-mkdir containment escape.

September 17 late wave (17:15–17:19Z): management-port exposure, template-engine SSRF, and HTTP-client credential-scope violations

Five more durable records landed 15 minutes after the main wave:

  • Kestra unauthenticated everything (GHSA-hpj9-grjp-7vc7, GHSA-r56g-q4p6-m3p6) — two independent no-auth surfaces on a default install. (1) Micronaut management endpoints listen on separate port 8081 with no authentication even when API basic-auth is on: GET :8081/env returns the full resolved configuration, POST :8081/loggers/{name} mutates runtime state — the auth filter is scoped to /api/v1/** on 8080 only, and the vendor's own reference docker-compose.yml publishes 8081. (2) The Pebble template engine's http() function passes user URLs straight to URI.create() with no IP/scheme filtering, and flow import/execution needs no authentication (tenant check only) → unauthenticated SSRF to metadata/LAN via a malicious Flow YAML. Recon pattern: on any Java/Micronaut/Spring management-port product, port+1 and /env, /loggers, /heapdump are first-class sweep targets next to the authenticated API port — a "basic-auth enabled" banner does not cover them. And on any workflow/orchestration platform where YAML/import is the normal user entry point, template-engine built-in functions (http(), env lookups) are pre-auth SSRF candidates, same validate-then-use family as the Graylog item.
  • AsyncHttpClient credential-scope violations (GHSA-xr57-gcx8-52hf, GHSA-f8m2-889x-vw4x, GHSA-fj9w-c36g-h5x8) — three ways an "authenticated" Java client hands your credentials to the wrong peer. Origin Basic/NTLM/Kerberos credentials are attached to the plaintext CONNECT sent to an HTTP proxy before TLS exists (proxy + anyone on that hop reads them); client-wide (config-level) realms are re-derived from config after the cross-origin-redirect strip and re-sent to the redirect target on a 401; SCRAM/Digest mutual-auth verification is computed but only logged — an impostor server that never proved the shared secret is accepted off-TLS. Operator angle: these are the passive-harvest opportunities when you control or sit on a corporate proxy path used by Java services (CONNECT carries origin creds in the clear), and the audit rule for any HTTP client you assess: credentials must be scoped to origin+scheme+port, survive no strip via config re-derivation, and mutual-auth results must be acted on, not logged. Rogue-server tests (a fake server that returns garbage SCRAM ServerSignature and observes whether the client accepts results) prove the third leg in a lab.
  • SSH.NET SCP path-injection-by-design (GHSA-mggc-4xg6-vcxf, high) — default RemotePathTransformation.DoubleQuote does not neutralize $(...)/backticks inside double quotes on POSIX shells, so an attacker-influenced remote path reaching ScpClient against a shell-based server executes on the server as the SSH user (cf. CVE-2020-15778). Reusable rule: any tool that builds a remote shell command from path arguments is a command-injection surface regardless of client-side quoting — when an app exposes "download/upload this path via SFTP/SCP" with user-influenced filenames, test metacharacter canaries ($(touch marker)) against a lab SSH server.
  • oras-go registry-response trust pair (GHSA-h7vf-4x9w-h99v blind SSRF, GHSA-m37j-52j7-pjw7 symlink-chain tar slip, ≤ v2.6.1) — pagination follows the registry's Link header absolute URL verbatim (malicious registry makes any oras client GET metadata/internal hosts during Tags/Referrers/Repositories listing), and unpack-mode layer extraction validates symlink targets lexically (filepath.Join) while the kernel resolves them, so a chain of in-root links escapes the store root; root-level regular-file entries skip the parent-symlink Lstat walk. Both are the "consume hostile protocol response/server-side data" axis: Link-header following must re-check host+scheme, and symlink containment must be resolved-path not string-path — the same lexical-vs-canonical split as the Junrar and aqua items on the archive extraction page. Registry-adjacent recon angle: an authorized client that can be pointed at an attacker registry will egress to whatever the Link header names.

Tracked from the same batch without promotion: ExifReader HEIC/AVIF iloc box memory-exhaustion DoS (GHSA-pj96-35fp-cfcc), RabbitMQ amqp091-go PLAIN credentials retained in exported struct fields (log/APM leak hygiene, no remote boundary), QoS signed-cast flooding, missing TLS minimum, sub-spec frame-size DoS, unbounded body allocation DoS, malformed-field-length DoS, AsyncHttpClient unbounded HTTP/1.1 response decompression DoS (GHSA-7grg-jcf7-rpmx), vLLM request-selected PyNvVideoCodec GPU decode VRAM exhaustion (GHSA-8pw2-6jv3-mj5j). Grav watermark traversal and the Flex callable incomplete-fix pair were folded onto the Aug 25 Grav page.

Reporting heuristics

  • For sandbox traversal bypasses, name the stdlib entry point (Formatter.get_field, format_map), the exact guard it circumvents (safer_getattr, _getattr_), and the highest-value reachable object class; prove disclosure with an in-memory marker only.
  • For reference-expansion authz, the report core is the direct-vs-expanded decision table by renderer type — one row per picker/block family — plus the observation that the direct route returns 401 (that contrast is what makes it survivable triage).
  • For client-flag non-enforcement, state the protocol message that drives the client (0xfb), the filename-constraint ceiling, and the app-behavior precondition; do not claim arbitrary file read.