sytxlabs/blade-sandbox
Blade Sandbox provides a safe, isolated environment for rendering untrusted Blade templates in Laravel. It helps preview user-supplied views while limiting access to app data and risky directives, ideal for CMS previews, template builders, and multi-tenant apps.
Architecture fit. sytxlabs/blade-sandbox is an additive render path, not a replacement for Blade. It ships its own compiler, AST validator, runtime mediator, and policy engine, so existing resources/views and the framework's view finder are untouched. That is an unusually low blast radius for a security-critical dependency and makes it viable for a single bounded surface first (e.g. customer-editable notification templates) rather than a platform-wide mandate.
The package addresses a problem most Laravel teams solve badly today: any user-supplied Blade string routed through Blade::render() or eval is remote code execution. A default-deny, explicit-allow model with deny-wins semantics is the correct shape for a multi-tenant or user-authored template feature. Two design choices carry the security claim and should be verified in a spike, not taken on faith:
ArrayAccess). This is the right architecture — allowlisting at the AST is far stronger than regex filtering or strip_tags.wire:* checks are cosmetic against a browser that can POST arbitrary public methods. The release correctly identifies the request guard as "the real boundary." If we adopt Livewire exposure, this component deserves independent review.Integration feasibility. High. Composer-only install, no framework hard dependency (illuminate/support|view|contracts|filesystem only), and a config publish step. The learn() / blade-sandbox:policy authoring tools are a genuine migration accelerator: we can derive an initial policy from templates that already work, then tighten, rather than hand-authoring a policy from scratch. validate() and non-throwing preview() give us a build-mode UX and a CI gate — the single most important operational control for a sandbox, since it moves policy violations from runtime incidents to pull-request failures.
Technical risk.
| Risk | Severity | Notes |
|---|---|---|
| Vendor maturity | High | 1.0.0 is the first stable release. Strong test posture (900+ tests, 97% lines, 3k–5k fuzzed templates per seed per push) is a good signal but not a substitute for time-in-production. No LTS history, no published CVE response track record. |
| Cooperative limits | Medium | Iteration, timeout, memory, output-size, and depth limits are explicitly cooperative. The hard boundary is the separate render process. Teams that enable "isolated rendering = off" for latency silently lose the real containment. |
| Blast radius of DTO exposure | Medium | Exposing DTOs via "own public API exposure" plus value-object defaults (Carbon, enums, Collections) is where data leakage usually appears — now(), tenant-scoped query objects, enum cases. Needs a deliberate data contract per surface. |
| Integrity manifest key management | Medium | SHA-256 + HMAC-signed manifests are only as good as key custody and rotation. This becomes our operational secret, not the vendor's. |
| Dependency conflict | Low–Medium | nikic/php-parser ^5 collides with tooling pinned to ^4; illuminate/* 10–13 may fight our lockfile. Resolve in the spike. |
| Security scope drift | Medium | SECURITY.md defines out-of-scope items we have not read. Threat model must be reconciled with ours before adoption, not after. |
Key questions.
Stack fit. Compatible with Laravel 10–13, PHP 8.2–8.5, with or without Livewire. We should target Laravel 12 or 13 — the release itself flags 10 and 11 as end-of-life, and shipping a security boundary on an EOL framework undermines the whole exercise. Required extensions (ctype, dom, libxml, tokenizer) are present in any standard PHP build and should be asserted in CI and in the container image healthcheck, not assumed.
Compatibility sequencing. Two hard gates:
Migration path. Depends on what we have today:
learn() against a representative sample, diff the generated policy against intent, then hand-tighten. Budget for the tightening, not the learning.Blade::render() on user input: this is an active vulnerability. Treat as a hotfix candidate — swap to the sandbox on the affected surface before the broader rollout.Sequencing (suggested).
config/blade-sandbox.php, wire validate() / blade-sandbox:check into CI as a required check.preview() in the authoring UI, blade-sandbox:policy in the template editor's save path.Maintenance. MIT license means no procurement friction and a fork option, but also no support contract, no SLA, and no guaranteed security disclosure timeline. Operationally we must assign an internal owner (name, not a team alias) for: policy files, HMAC key rotation, Livewire guard coverage, and vendor release monitoring. Policy is code — it belongs in review, version control, and the same change-management gate as migrations. The compiled-template cache is keyed by compiler version, Livewire major, and policy fingerprint; every policy change is a cache-invalidation event. Plan deploys accordingly.
Support. Assume community-only. That means: subscribe to the repo's releases and security advisories, pin exact versions (1.0.0, not ^1.0), and rehearse the fork. A single-maintainer security dependency in the render path is a real concentration risk; the mitigation is our ability to patch it ourselves, which requires at least one engineer who has read the compiler.
Scaling. The cost model shifts from HTTP-bound to queue-bound. Queued + isolated rendering means throughput is a function of worker concurrency and process-spawn overhead, not web workers. Autoscaling must key on queue depth, not request rate. Three levers to plan: the output cache (repeat renders are near-free), the compiled-template cache (warm it on deploy, don't let the first tenant request pay for compilation), and per-render memory ceilings (process isolation multiplies peak RSS by concurrency). Budget a load test with realistic template complexity before committing to a capacity number.
Failure modes and required instrumentation.
| Failure | Observable | Mitigation |
|---|---|---|
| Fallback rendering on error | Fallback rate | Alert on it. Fallback is silent degradation by design and will hide policy regressions indefinitely. |
| Runtime denial of an allowed operation | Denial events | Structured logs → dashboard; spike = policy gap, steady trickle = fine. |
| Cooperative limit breach / runaway render | Timeout, memory, output-size events | Hard containment via isolated process; per-tenant rate limit. |
| Policy fingerprint change → mass recompile | Latency spike at deploy | Warm caches pre-traffic; deploy policies outside peak. |
| HMAC manifest mismatch | Template rejection | Key rotation runbook; mismatch is either tampering or an ops error — distinguish them. |
| Livewire guard regression after Livewire upgrade | Guarded-component request succeeding | Pin Livewire, add a per-component regression test, run the Livewire upgrade in isolation. |
Ramp-up. Two audiences, two curves. Engineers need roughly 1–2 sprints to internal comfort: AST-level mental model, policy inheritance and override semantics (configure() / definePolicy() / extendPolicy()), and the Livewire guard. Add an independent security review before the first hostile-tenant exposure — the vendor's fuzzing is evidence, not a substitute for our own review of the boundary. Template authors need a shorter curve: preview() plus validate() plus clear denial messages in the editor UI, and author lockout must be tuned carefully — it is a usability control that, mis-set, reads as "the editor is broken."
Net recommendation. Adopt, scoped, on Laravel 12/13, with a named internal owner, pinned versions, CI-gated validation, isolated rendering on, fallback alerting, and a written fork contingency. Do not adopt as a blanket replacement for the view layer; adopt it where untrusted input currently reaches a template engine.
How can I help you explore Laravel packages today?