Product Decisions This Supports
- Whether to ship user-authored templating at all. The core product question for any "let customers/tenants write their own templates" feature is not can we render Blade — it's can we survive a malicious template. This package reframes that decision from "build a renderer with an allowlist" (weeks-to-months, plus ongoing security ownership) to "adopt a default-deny renderer and write policy." It makes the feature a config-and-policy exercise rather than a compiler-and-sandbox research project.
- Feature: user-generated content surfaces. Directly enables, or materially de-risks, roadmap items such as: customer-editable document/invoice/notification templates, white-label theme and layout authoring, email and Markdown template editing in-product, tenant-provided "custom views" for embedded/marketplace apps, AI/LLM-generated template preview and rendering, and low-code page builders backed by Blade.
- Build vs. buy — lean buy, with a policy layer we own. The differentiating work is the allowlist policy (which functions, methods, properties, views, components, directives, routes, DTOs and
wire:* attributes each tenant/role may reach), not the parser. Buying the sandbox and owning the policy means our engineering investment maps to product differentiation and our security exposure maps to a maintained, fuzzed core.
- Multi-tenant and self-serve tiering decisions. Policy inheritance (
extends), per-tenant/user policy resolution and package-level configure() / definePolicy() / extendPolicy() overrides give us a pricing and packaging lever: a conservative "safe" policy for free tiers, richer allowlists (custom directives, components, routes, DTOs) for paid tiers, without forking the renderer.
- Which authoring UX we can promise.
validate() and blade-sandbox:check support save-time validation; non-throwing preview() supports a live preview pane; learn() and blade-sandbox:policy support a "here's the policy your template would need" onboarding flow. That is the difference between a template editor that fails opaquely at publish time and one that guides the author.
- Reliability posture for render-time failures. Fallback rendering on error, plain-text conversion for e-mail, output cache, cooperative limits (iterations, timeout, memory, output size, depth), isolated separate-process rendering and queued rendering let us commit to SLOs for a feature that runs arbitrary customer input on our request path.
- Livewire-specific decision. If we expose Livewire components to tenants, the compile-time
wire:* checks plus a server-side request guard are the actual boundary — this is the piece most in-house attempts miss (a browser can call arbitrary public methods regardless of what the template allowed). It should be treated as a hard requirement, not a nice-to-have, before any tenant-facing Livewire surface ships.
- Data exposure decisions. DTO-based public API exposure, class-namespace allowlisting, vetted safe helpers and value-object defaults (Carbon, enums, Collections) force an explicit decision about which domain objects tenants may touch — a useful forcing function for data-minimisation and least-privilege reviews, not just a technical config.
- Roadmap sequencing. Reasonable phasing: (1) read-only render of tenant templates with a locked-down policy, (2) authoring with validation/preview, (3) per-tenant policy tiers, (4) Livewire/guarded components, (5) template loader integrations (database/API-backed) and integrity manifests if templates are stored or served outside our DB.
When to Consider This Package
Adopt when most of these hold:
- We are rendering Blade templates we did not write — customer, tenant, partner, marketplace, or model-generated — and a successful escape is a credible breach, not a cosmetic bug.
- We already run Laravel 12 or 13 (or can commit to it). Laravel 10/11 support exists but those lines are end-of-life; per the release notes, production should be 12 or 13. PHP 8.2+ with
ext-ctype, ext-dom, ext-libxml, ext-tokenizer.
- We want Blade specifically — team familiarity, existing partials/components/directives, Livewire in the stack — rather than a neutral template DSL.
- We can staff policy ownership: someone must curate and review allowlists per tenant/tier, and re-review on every policy change. The package enforces default-deny; it does not decide what is safe for our domain.
- Security review can absorb a new, single-vendor, security-critical dependency. 900+ tests, 97%+ line coverage and 3000–5000 fuzzed malicious templates per seed per CI push are strong signals — but they are the vendor's claims and CI, not our audit.
- We need the operational features (limits, isolation, queueing, caching, structured logs, events, test fakes) — rolling these ourselves is where in-house sandboxes usually fail first.
Look elsewhere (or defer) when:
- Templates are first-party only. If no untrusted author exists, this is cost and complexity with no threat to retire.
- We need a non-Blade authoring language. If the product wants Liquid/Handlebars/Mustache semantics, or a fully non-Turing-ish DSL, forcing Blade is a product-experience tax; a purpose-built template language may be the better call.
- Untrusted templates are not the threat model — the risk is plugins/extensions running arbitrary PHP. That's a process-isolation or WASM problem, not a template-sandbox one.
- Requirements include hard resource guarantees under adversarial input. Limits are described as cooperative; if we need strict CPU/memory ceilings against a determined attacker, pair with OS/container-level isolation or a separate worker fleet, and budget for that.
- We need long-term enterprise support, indemnities, or a multi-vendor market. 1.0.0 is the first stable release; the ecosystem is thin. Vendor continuity risk is real and should be documented.
- The team cannot own policy review, or the org will treat "sandbox installed" as "sandbox solved." Default-deny fails closed on usability, not on safety — but a rushed, over-broad allowlist reproduces the original risk.
Pre-adoption diligence (non-negotiable for a security boundary):
- Read SECURITY.md end-to-end and map its explicit out-of-scope items against our threat model. What it disclaims is our residual risk to own.
- Write a bypass suite before launch: our own adversarial templates, plus verifying the compiler-version/Livewire-major/policy-fingerprint cache keying actually invalidates on policy change — stale compiled templates after a policy tightening is the failure mode to test for.
- Confirm behaviour on the paths that matter:
fallback rendering on error, isolated-process rendering, and queued rendering under load.
- Decide our exit plan: how much of our policy layer is portable, and what a swap costs.
How to Pitch It (Stakeholders)
For executives — one paragraph. We want to let customers and partners author their own templates (documents, e-mails, layouts, embedded views). Doing that safely means rendering untrusted input on our servers, which is a security boundary most teams get wrong: escaping output is not enough, because template expressions reach real PHP. sytxlabs/blade-sandbox 1.0.0 is the first stable release of a default-deny sandbox built for exactly this: nothing — no function, method, property, view, component, directive, route, DTO or wire:* attribute — is reachable unless we explicitly allow it, and deny rules always win. It does not execute PHP from templates; it compiles expressions itself and mediates every operation at runtime. It arrives with policies per tenant/tier, save-time validation and preview for authors, sanitised output, render limits and isolation, and — critically — a server-side guard for Livewire components. That converts a research-and-security project into a policy configuration project, which is where our product differentiation actually lives. Cost of adoption: one new security-critical dependency, a policy owner, and a security review. Cost of not adopting: we either skip a category of features competitors will ship, or we build and indefinitely maintain a sandbox ourselves — the more expensive and riskier path.
For engineering — the short version. New dependency, MIT, PHP 8.2+, illuminate/* 10–13, nikic/php-parser ^5; laravel/framework not required. Target Laravel 12/13 in production. Two things matter: (1) our policy layer is the product surface — treat config/blade-sandbox.php and policy definitions as reviewed, versioned, tested code, and use validate() / preview() / learn() plus blade-sandbox:check in CI and at authoring time; (2) the Livewire request guard is not optional if tenants touch Livewire — the browser can call public methods directly, so compile-time checks alone are insufficient. Budget for: adversarial templates of our own, cache-invalidation tests on policy changes, isolated/queued rendering on the hot path, and reading SECURITY.md's out-of-scope list as our residual-risk register. Vendor is single-source at 1.0.0 — keep our policy definitions portable so a swap stays viable.
Ask. Approve a time-boxed spike: one representative tenant-facing template surface, a minimal policy, our own bypass tests, and a written read of SECURITY.md against our threat model. Success criteria are "no bypass found, authoring UX viable, performance acceptable on the hot path" — not "feature shipped."