Skip to content

[security] GHSA-9g62-2rj4-v227: wire the Studio Twig sandbox's object/function protection lists - #2054

Merged
robertSt7 merged 4 commits into
2026.3from
security/ghsa-9g62-2rj4-v227-twig-sandbox-object-policy-d184473ece7a1aba
Oct 2, 2026
Merged

robertSt7 merged 4 commits into
2026.3from
security/ghsa-9g62-2rj4-v227-twig-sandbox-object-policy-d184473ece7a1aba

Conversation

@pimcore-deployments

@pimcore-deployments pimcore-deployments commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Advisory: GHSA-9g62-2rj4-v227

Vulnerability

Pimcore\Twig\Sandbox\SecurityPolicy (core) takes 7 constructor arguments. Core wires all 7 from pimcore.templating.twig.sandbox_security_policy.* (bundles/CoreBundle/config/templating_twig.yaml), including the object-side protections: blockedClasses (blocks Pimcore\Model\User, Doctrine\DBAL\Connection, PDO, etc.), allowedClasses, blockedFunctions (blocks pimcore_user, pimcore_file_exists), and hardBlockedMethods (hard-blocks User::getPassword/getPasswordRecoveryToken/getTwoFactorAuthentication, Asset::getData/getLocalFile/etc., regardless of allow/deny-list mode).

studio-backend-bundle's SandboxExtensionInitializer (src/Twig/Initializers/SandboxExtensionInitializer.php) built its own SecurityPolicy from only the first 3 arguments (allowedTags, allowedFilters, allowedFunctions), and PimcoreStudioBackendExtension::populateTwigSandboxExtension() only ever bound those same 3. The other 4 constructor parameters therefore defaulted to [], and SecurityPolicy's own logic treats empty blockedClasses/hardBlockedMethods as "block nothing" and empty blockedFunctions as "the pimcore_* auto-allow rule has no exceptions."

Impact: a user with only the ordinary objects permission can supply a Twig template for a grid column ("Twig Operator", POST /pimcore-studio/api/data-objects/grid/preview, and the regular object grid/export) and call any method on any reachable object — confirmed for Asset::getData(), Asset::getLocalFile(), User::getPassword(), User::getPasswordRecoveryToken(), and the pimcore_file_exists() function. A leaked pending password-recovery token is directly exchangeable for an administrator session at POST /pimcore-studio/api/login/token, i.e. full account takeover from a content-editor-level actor.

Root cause: SandboxExtensionInitializer/PimcoreStudioBackendExtension silently dropped 4 of the 7 constructor arguments of the core SecurityPolicy class, rather than any flaw in SecurityPolicy itself.

Fix

  • SandboxExtensionInitializer::__construct() now accepts blockedClasses, allowedClasses, blockedFunctions and hardBlockedMethods, and initialize() passes all 7 arguments through to new SecurityPolicy(...).
  • PimcoreStudioBackendExtension::populateTwigSandboxExtension() binds those 4 new arguments to core's own pimcore.templating.twig.sandbox_security_policy.{blocked_classes,allowed_classes,blocked_functions,hard_blocked_methods} container parameters, rather than re-declaring the lists in Studio's own configuration tree. This was the advisory's suggested approach: reusing core's single source of truth means Studio's object-protection policy cannot silently drift from the one Pimcore core already enforces for its own sandboxed rendering.

Out of scope: the advisory also flags (as a secondary, "not separately runtime-proven" defect) that SandboxExtensionInitializer::initialize() mutates the shared Twig\Extension\SandboxExtension service's policy rather than using a dedicated instance, and that this weakened policy is never restored. With this fix, Studio's policy is now at least as strict as core's on the object/function-blocking side, which removes the practical impact of that mutation; the underlying code smell (shared-service mutation, no restore) is left untouched as a separate, non-security-critical cleanup, per the "minimal, tightly-scoped fix" brief for this pipeline.

Backward compatibility

SandboxExtensionInitializer and its interface are not marked @internal, so per the BC promise this is treated as public API. The 4 new constructor parameters are added at the end with BC-safe defaults ([], matching SecurityPolicy's own defaults), so any existing direct instantiation with the old 3-argument signature continues to compile and behave exactly as before. The actual security fix is achieved at the one real call site — the bundle's own DI wiring in PimcoreStudioBackendExtension — which now explicitly supplies all 7 arguments. No public return types, defaults for existing parameters, or visibility changed.

Testing

Tests added: tests/Unit/Twig/TemplateGeneratorTest.php

  • testBlocksMethodCallOnBlockedClass — asserts that a class passed via blockedClasses is actually enforced by the rendering pipeline. Against the vulnerable code this either fatals (the old 3-arg constructor doesn't accept the blockedClasses named argument) or, if adapted to compile, fails because the call was previously silently ignored; against the fix it passes because the method call is rejected.
  • testBlocksHardBlockedMethod — same shape for hardBlockedMethods, covering the hard-block path used for User::getPassword()-style getters, which bypasses allow-list mode entirely.
  • testAllowsMethodCallWhenObjectIsNotRestricted — confirms legitimate templates that call methods on objects with no configured restriction keep working unchanged.

These exercise the exact defect (constructor arguments being dropped before reaching SecurityPolicy) independent of any real container wiring, and complement the existing tag/filter/function tests in the same file. I could not execute the suite in this sandbox (no Composer/vendor, no network access); the tests were written and reasoned through against Pimcore\Twig\Sandbox\SecurityPolicy's actual logic (assertNotHardBlockedMethod, matchesAnyClass, checkMethodAllowed) read directly from pimcore/pimcore.

Security-Advisory: pimcore/platform-version/GHSA-9g62-2rj4-v227

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • api.anthropic.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "api.anthropic.com"

See Network Configuration for more information.

Generated by Draft a security-advisory fix · claude · sonnet50 · 197.1 AIC · ⌖ 25.3 AIC · ⊞ 7.6K · ◷

…rj4-v227)

SandboxExtensionInitializer built its SecurityPolicy from only 3 of the 7
constructor arguments the class takes, leaving blockedClasses, allowedClasses,
blockedFunctions and hardBlockedMethods empty. With those empty, the policy
refuses nothing on the object side, so any Twig Operator template (reachable
with the ordinary "objects" permission) could call any method on any object,
including Pimcore\Model\User::getPassword()/getPasswordRecoveryToken() and
Pimcore\Model\Asset::getData()/getLocalFile().

Pass all 7 arguments through, sourcing the four object/function lists from
core's own pimcore.templating.twig.sandbox_security_policy.* container
parameters rather than redeclaring them, so Studio's sandbox cannot drift
from the policy Pimcore core already enforces.

Co-Authored-By: Claude <noreply@anthropic.com>
@pimcore-deployments pimcore-deployments added ai-generated This content was generated using AI. Security labels Sep 23, 2026
@mcop1
mcop1 changed the base branch from 2026.2 to 2026.3 September 24, 2026 08:01
robertSt7 and others added 2 commits September 25, 2026 13:38
…/function policy

The fix wires blockedClasses/allowedClasses/blockedFunctions/hardBlockedMethods
into core's SecurityPolicy, added in pimcore/pimcore 2026.2.5 (core PR #19303).
The previous ^2026.2.0 floor let Composer's lowest-dependency resolution pick
an older core whose SecurityPolicy only accepts 3 constructor arguments; PHP
silently drops the 4 extra positional arguments there, so the new object/
function protection lists never take effect and CI's --prefer-lowest jobs
failed (PHPStan flagged the arg-count mismatch, and both new regression tests
found no exception thrown).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…2rj4-v227-twig-sandbox-object-policy-d184473ece7a1aba

# Conflicts:
#	composer.json
@robertSt7
robertSt7 marked this pull request as ready for review September 25, 2026 13:54
Copilot AI balanced review requested due to automatic review settings September 25, 2026 13:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Security-critical DI wiring and two newly forwarded policy paths lack regression coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

This PR secures Studio Twig rendering by forwarding Pimcore core’s complete sandbox policy.

Changes:

  • Adds four object/function protection lists to the initializer.
  • Wires lists from core container parameters.
  • Adds partial regression coverage.

Review assessment:

  • Root cause and boundary are correctly addressed (SandboxExtensionInitializer.php:23-45, PimcoreStudioBackendExtension.php:532-566).
  • Existing three-argument construction remains compatible through trailing defaults.
  • Coverage omits production DI wiring, blocked functions, and allow-list behavior (TemplateGeneratorTest.php:187-275).
  • Shared sandbox mutation remains an acknowledged risk.
File Description
src/​Twig/​Initializers/​SandboxExtensionInitializer.php Forwards all seven policy arguments.
src/​DependencyInjection/​PimcoreStudioBackendExtension.php Injects core protection-list parameters.
tests/​Unit/​Twig/​TemplateGeneratorTest.php Tests blocked classes and hard-blocked methods.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/DependencyInjection/PimcoreStudioBackendExtension.php
Comment thread tests/Unit/Twig/TemplateGeneratorTest.php
@robertSt7 robertSt7 self-assigned this Sep 25, 2026
…tion policy

Two regression gaps flagged by Copilot review on this PR:

- TemplateGeneratorTest built SandboxExtensionInitializer directly with manual
  arrays, so nothing verified that PimcoreStudioBackendExtension::
  populateTwigSandboxExtension() actually binds blockedClasses/allowedClasses/
  blockedFunctions/hardBlockedMethods to core's own
  pimcore.templating.twig.sandbox_security_policy.* parameters. Removing one of
  those setArgument() calls left every existing test green. Add
  SandboxSecurityPolicyWiringTest, which loads the real service definition from
  config/twig.yaml and invokes the real (private) wiring method via reflection,
  then asserts each argument resolves to its core parameter's value.

- The existing regression tests covered blockedClasses and hardBlockedMethods
  but never exercised blockedFunctions (the blocked pimcore_* function path) or
  allowedClasses (allow-list mode). Add testBlocksBlockedPimcoreFunction and
  testAllowlistModeBlocksClassNotOnTheAllowList to TemplateGeneratorTest.

Verified the new DI-wiring test fails red when a setArgument() call is removed
(confirming it catches exactly the regression it targets), then restored the
source and reran everything green, plus a clean PHPStan pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The fix addresses a critical sandbox escape and should receive final maintainer security review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

@robertSt7 robertSt7 added this to the 2026.3.1 milestone Oct 2, 2026
@robertSt7
robertSt7 merged commit c26e483 into 2026.3 Oct 2, 2026
25 checks passed
@robertSt7
robertSt7 deleted the security/ghsa-9g62-2rj4-v227-twig-sandbox-object-policy-d184473ece7a1aba branch October 2, 2026 07:56
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

ai-generated This content was generated using AI. Security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants