Twig __toString() sandbox and Bugsink project-boundary checks¶
Source: GitHub Security Advisories REST API, published/updated 2026-06-05.
This batch is durable because it captures reusable offensive validation patterns: template-sandbox string-coercion policy gaps, debug/profiler output-encoding checks, and project-scoped object lookup failures in observability tooling. Use these workflows only in authorized labs or explicitly scoped assessments.
What changed¶
- Twig sandbox
__toString()policy bypasses — GHSA-pr2w-4gpj-cpq4 / CVE-2026-47732: Twig<= 3.25.0missed several string-coercion paths in sandboxed templates, allowing a sandboxed template author to invoke__toString()on reachable objects even when that method was not allowlisted. Confirmed coercion points include conditionals passed to string-coercing filters/functions,matches, loose comparisons, tests such asis empty, concatenation with null coalesce, template-name expressions, dynamic attribute names, spread arguments,do, and the..range operator. - Twig profiler HtmlDumper XSS — GHSA-2g2g-8p8h-fgwm / CVE-2026-47730: Twig
>= 3.0.0, < 3.26.0insertedProfile::getTemplate()andProfile::getName()into profiler HTML without escaping. This is a debug/profiler rendering boundary, not a sandbox escape. - Bugsink sourcemap/debug-file project scoping — GHSA-5389-f7vh-wxj8 / CVE-2026-47728: Bugsink
< 2.2.0resolved sourcemaps and debug files by debug ID without binding the lookup to the project that owned the uploaded metadata, allowing an authenticated user with access to one project to cause event processing to use another project's metadata on the same instance. - Bugsink direct UUID project-boundary checks — GHSA-vx2f-6m6h-9frf / CVE-2026-47715 and GHSA-g5vc-q7qc-v939 / CVE-2026-47716: Bugsink
< 2.2.0allowed issue-event views and bulk issue actions to act on direct event/issue UUIDs without requiring those objects to belong to the project authorized by the URL. Exploitation requires authentication and prior knowledge of valid target UUIDs.
Operator triage¶
- Search for applications that expose user-controlled Twig templates, themes, snippets, email bodies, CMS blocks, report templates, or preview features while passing rich objects into sandboxed render context.
- Prioritize Twig targets where
Stringabledomain objects expose secrets, signed URLs, object identifiers, internal paths, or capability-bearing strings through__toString(). - For Twig profiler XSS, look for debug/profiler dumps reachable by administrators, support staff, CI artifact viewers, or bug-bounty triage tooling where template names or profile names can be attacker-controlled.
- Search for self-hosted Bugsink instances in multi-project deployments. Prioritize instances where separate teams, customers, apps, or environments share one Bugsink server.
- For Bugsink, distinguish same-instance cross-project impact from hosted cross-tenant impact. The advisory states Hosted Bugsink uses separate instances per tenant, so reports need proof of a boundary actually crossed in the tested deployment.
Replayable validation boundaries¶
July 1 Twig cached-template sandbox allow-list update¶
GitHub's July 1 published feed added GHSA-529h-vh3j-85hq / CVE-2026-49981 for Twig <= 3.26.0. This is adjacent to the existing sandbox workflow, but the boundary is different: filter, tag, and function allow-list checks were compiled into a Template subclass and run at construction time, while the Environment cached that Template instance. If the same long-lived Environment renders a shared template once outside the sandbox, a later sandboxed render can reuse the cached instance without rechecking the current sandbox allow-list for that template.
Add this probe when a target uses long-lived PHP workers, shared Twig Environment instances, user-controlled sandboxed templates, CMS snippets, marketplace themes, email templates, or report builders that can include/import/extend templates also reachable outside the sandbox.
| Boundary | Safe probe |
|---|---|
| Non-sandbox render pre-warms a shared layout, macro, parent, or included template; later sandboxed render reaches that cached template with a stricter filter/tag/function policy | in a lab harness, render a shared template outside sandbox, then render an attacker-controlled sandboxed child that includes/imports/extends it and uses only inert custom filters/functions/tags as canaries |
enableSandbox() / disableSandbox(), setSecurityPolicy(), or SourcePolicyInterface decisions change between renders on the same Environment | compare a fresh process where the sandbox denies the canary construct with a warmed process where the same construct executes |
Evidence should include Twig version, worker/runtime model, whether the same Environment instance is reused, the warm-up render path, the sandboxed render path, the exact filter/tag/function allow-list, expected denial in a fresh or patched control, and observed cached-template behavior. Do not use filters or functions that execute commands, read files, access network resources, or expose real domain objects; canary filters should return fixed strings such as skillz-twig-cache-canary.
June 30 Twig sandbox residual-bypass update¶
GitHub's late June 30 published feed added four adjacent Twig sandbox advisories: GHSA-p42q-9prx-q5wq / CVE-2026-48805, GHSA-5v5v-ww74-355v / CVE-2026-48806, GHSA-8x9c-rmqh-456c / CVE-2026-48807, and GHSA-h8vq-8gpg-mhcg / CVE-2026-48808.
These are residual bypasses of earlier sandbox hardening, so keep them with the existing Twig workflow. Add these probes when an assessment includes user-controlled Twig templates, CMS snippets, marketplace themes, email templates, or report builders running under SourcePolicyInterface or sandbox policies:
| Advisory | Boundary | Safe probe |
|---|---|---|
| GHSA-5v5v-ww74-355v | dynamic array mapping keys could string-coerce a Stringable object without the sandbox __toString() check | use a disposable object whose __toString() returns skillz-twig-key-canary; prove direct output is denied but dynamic key coercion reaches the marker |
| GHSA-8x9c-rmqh-456c | Traversable values passed to join / replace materialized Stringable contents after array-only policy recursion | use a lab Traversable wrapper around a canary object; do not expose real domain objects |
| GHSA-h8vq-8gpg-mhcg | column under SourcePolicyInterface lost the current Source, so property allowlists could be bypassed | compare a denied property read with column() over canary objects in a source-policy sandbox |
| GHSA-p42q-9prx-q5wq | deprecated src/Resources/core.php wrappers failed to forward the source-aware sandbox state | only test if the application or third-party bundle calls the legacy wrappers directly; use inert array/arrow canaries |
Report the exact sandbox mode. These advisories do not imply a bypass of every Twig sandbox deployment: some require SourcePolicyInterface, some require reachable Stringable objects, and one requires legacy wrapper use.
Twig sandbox string-coercion canaries¶
Use a disposable Twig harness or in-scope staging template. Do not call methods that execute commands, mutate data, read files, or reach external networks.
- Confirm the target uses Twig
<= 3.25.0, enables sandboxing for the attacker-controlled template, and exposes at least one reachable object implementing__toString(). - Establish the baseline policy: render a direct method access or direct object output that should be denied when
__toString()is not allowlisted. - Test inert coercion points one at a time with an object whose
__toString()returns a harmless marker: - loose comparison or spaceship comparison against a guessed marker prefix;
matchesagainst a benign regex;is emptyor another Twig test;- conditional/null-coalesce expression fed into a string-coercing filter/function;
- concatenation, range, dynamic attribute name, or template-name expression in a lab-only harness.
- Vulnerable result: a denied direct path still leaks, compares, or renders the marker through one of the implicit string-coercion paths.
- Capture Twig version, sandbox policy, object class, allowed tags/filters/functions/tests, exact template canary, expected denial path, and observed coercion behavior. Frame impact around what the reachable object's
__toString()reveals or enables.
Twig profiler HtmlDumper canary¶
Keep this in debug/staging contexts; do not expose or browse production profiler output unless explicitly in scope.
- Confirm the application can generate a Twig
HtmlDumperprofiler artifact and uses Twig>= 3.0.0, < 3.26.0. - Identify whether a template name, loader key, database template row identifier, or profile name can contain attacker-controlled text.
- Use an inert HTML marker such as a unique
<span data-skillz="twig-profiler-canary">marker</span>in the template/profile name. Avoid active JavaScript unless scope explicitly allows XSS payloads. - Vulnerable result: the marker is emitted as raw HTML in the profiler dump instead of escaped text.
- Capture the input name, profiler artifact location, rendered HTML snippet, viewer role, and whether the artifact crosses from attacker input to a privileged reviewer.
Bugsink project-scoped metadata and UUID canaries¶
Use two lab projects on the same Bugsink instance. Do not access real customer events, production sourcemaps, or live issue records.
- Create two low-risk projects,
project-aandproject-b, and use separate test users/roles if the assessment scope includes project separation. - For sourcemap/debug-file scope, upload a lab sourcemap or debug file with a unique debug ID to
project-b, then submit a synthetic event inproject-areferencing the same debug ID. - Vulnerable result:
project-aevent processing resolves source context or symbolication metadata fromproject-b. - For direct event UUID scope, copy a lab event UUID from
project-b, then request the stacktrace/details/breadcrumb route through an issue URL authorized forproject-a. - For bulk issue actions, copy a lab issue UUID from
project-b, then submit a bulk resolve/mute request fromproject-a's issue list. - Vulnerable result: Bugsink discloses
project-bevent data or changesproject-bissue state while the request is authorized throughproject-a. - Capture instance version, project IDs/names, role, endpoint path, request object UUIDs, before/after issue state, and sanitized event/debug metadata. Redact DSNs, session cookies, real stack traces, and source content.
Reporting heuristics¶
- Frame Twig sandbox findings as unguarded implicit string coercion, not generic RCE. Strong reports prove a specific coercion construct reaches a non-allowlisted
__toString()and explain the value exposed by that method. - Frame Twig profiler findings as debug artifact output encoding, with a clear audience for who views the profiler artifact.
- Frame Bugsink findings as project-scoped lookup/authorization failure. Strong reports show two authorized lab projects on the same instance and avoid claiming cross-tenant impact unless the deployment actually shares tenant boundaries.
- Bugsink event-tag DoS (GHSA-5x67-j5xg-c5gj), Apache Arrow IPC use-after-free (GHSA-rgxp-2hwp-jwgg), NATS JetStream admin API authorization drift (GHSA-fhg8-qxh5-7q3w), CoreDNS cache poisoning (GHSA-h92q-fgpp-qhrq), and cert-manager PEM parsing DoS (GHSA-r4pg-vg54-wxx4) were reviewed in the same scan but not promoted here because they were availability-only, old updated-feed items, or did not add a distinct replayable operator workflow for this wiki update.
Sources¶
- GitHub Advisory Database: GHSA-pr2w-4gpj-cpq4 / CVE-2026-47732
- Twig advisory/source: https://github.com/twigphp/Twig/security/advisories/GHSA-pr2w-4gpj-cpq4 and https://github.com/twigphp/Twig
- GitHub Advisory Database: GHSA-2g2g-8p8h-fgwm / CVE-2026-47730
- GitHub Advisory Database: GHSA-5389-f7vh-wxj8 / CVE-2026-47728
- GitHub Advisory Database: GHSA-vx2f-6m6h-9frf / CVE-2026-47715
- GitHub Advisory Database: GHSA-g5vc-q7qc-v939 / CVE-2026-47716
- Bugsink source/advisories: https://github.com/bugsink/bugsink and https://github.com/bugsink/bugsink/security/advisories