Repository navigation
[security] GHSA-9g62-2rj4-v227: wire the Studio Twig sandbox's object/function protection lists - #2054
Merged
robertSt7 merged 4 commits intoOct 2, 2026
Conversation
…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>
…/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
Contributor
There was a problem hiding this comment.
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
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.
…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>
|
robertSt7
deleted the
security/ghsa-9g62-2rj4-v227-twig-sandbox-object-policy-d184473ece7a1aba
branch
October 2, 2026 07:56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Advisory: GHSA-9g62-2rj4-v227
Vulnerability
Pimcore\Twig\Sandbox\SecurityPolicy(core) takes 7 constructor arguments. Core wires all 7 frompimcore.templating.twig.sandbox_security_policy.*(bundles/CoreBundle/config/templating_twig.yaml), including the object-side protections:blockedClasses(blocksPimcore\Model\User,Doctrine\DBAL\Connection,PDO, etc.),allowedClasses,blockedFunctions(blockspimcore_user,pimcore_file_exists), andhardBlockedMethods(hard-blocksUser::getPassword/getPasswordRecoveryToken/getTwoFactorAuthentication,Asset::getData/getLocalFile/etc., regardless of allow/deny-list mode).studio-backend-bundle'sSandboxExtensionInitializer(src/Twig/Initializers/SandboxExtensionInitializer.php) built its ownSecurityPolicyfrom only the first 3 arguments (allowedTags,allowedFilters,allowedFunctions), andPimcoreStudioBackendExtension::populateTwigSandboxExtension()only ever bound those same 3. The other 4 constructor parameters therefore defaulted to[], andSecurityPolicy's own logic treats emptyblockedClasses/hardBlockedMethodsas "block nothing" and emptyblockedFunctionsas "thepimcore_*auto-allow rule has no exceptions."Impact: a user with only the ordinary
objectspermission 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 forAsset::getData(),Asset::getLocalFile(),User::getPassword(),User::getPasswordRecoveryToken(), and thepimcore_file_exists()function. A leaked pending password-recovery token is directly exchangeable for an administrator session atPOST /pimcore-studio/api/login/token, i.e. full account takeover from a content-editor-level actor.Root cause:
SandboxExtensionInitializer/PimcoreStudioBackendExtensionsilently dropped 4 of the 7 constructor arguments of the coreSecurityPolicyclass, rather than any flaw inSecurityPolicyitself.Fix
SandboxExtensionInitializer::__construct()now acceptsblockedClasses,allowedClasses,blockedFunctionsandhardBlockedMethods, andinitialize()passes all 7 arguments through tonew SecurityPolicy(...).PimcoreStudioBackendExtension::populateTwigSandboxExtension()binds those 4 new arguments to core's ownpimcore.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 sharedTwig\Extension\SandboxExtensionservice'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
SandboxExtensionInitializerand 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 ([], matchingSecurityPolicy'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 inPimcoreStudioBackendExtension— 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.phptestBlocksMethodCallOnBlockedClass— asserts that a class passed viablockedClassesis actually enforced by the rendering pipeline. Against the vulnerable code this either fatals (the old 3-arg constructor doesn't accept theblockedClassesnamed 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 forhardBlockedMethods, covering the hard-block path used forUser::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 againstPimcore\Twig\Sandbox\SecurityPolicy's actual logic (assertNotHardBlockedMethod,matchesAnyClass,checkMethodAllowed) read directly frompimcore/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.comTo allow these domains, add them to the
network.allowedlist in your workflow frontmatter:See Network Configuration for more information.