Security
Vector Expressions is designed with a zero-trust security posture. Expressions are author-controlled inputs evaluated server-side, so several hardened guardrails are built directly into the engine.
Who Can Write Expressions?
Only users with the edit_posts capability can save expression bindings to blocks. This maps to the WordPress Contributor role and above by default. Subscribers and unauthenticated visitors cannot author or modify expressions. The REST API route used for editor previews enforces the same capability check.
Property Access Restrictions
Access to context data is restricted at multiple layers:
User object: Only an explicit allow-list of properties is accessible — id, name, login, email, roles, registered, url, and the computed is_logged_in flag. Database-level fields like user_pass, user_activation_key, and capability maps (e.g., wp_capabilities) are not in the allow-list and are inaccessible by design. See Data Roots for the complete list.
Meta keys: Both post.meta and the get_meta modifier enforce two independent layers of protection:
-
WordPress convention — any key that
is_protected_meta()considers protected (i.e. keys starting with an underscore) is automatically denied. -
Sensitive-keyword scan —
ObjectProxy::is_sensitive_key()performs a substring match against a built-in list of sensitive keywords. Any meta key containing one of the following strings is blocked regardless of its prefix:pass,token,secret,api_key,auth,nonce,salt,credential,private_keyThis catches legitimately public-looking keys — e.g.
stripe_api_keyorauth_token— that the underscore convention alone would miss.The keyword list is filterable:
add_filter( 'vector_expressions/security/sensitive_meta_keywords', function( array $keywords ): array { $keywords[] = 'my_custom_secret_term'; return $keywords; } );
HTML attribute injection is blocked by two independent mechanisms:
- Prefix check (hardcoded): All
on*event-handler attributes (e.g.,onclick,onmouseover) are unconditionally blocked and cannot be overridden. - Named deny list (filterable): The
vector_expressions/sanitization/deny_listfilter blocks specific named attributes — by default:data,javascript,method,ping,srcdoc,style, andclass.
Recursion Depth Limiting
The parser enforces a maximum nesting depth of 5. This prevents maliciously crafted or accidentally deeply nested expressions from causing stack overflows. When the limit is exceeded, the parser returns the raw template string as-is rather than an empty string or an error.
Output Escaping
The engine applies different escaping strategies depending on expression syntax:
| Syntax | Escaping |
|---|---|
{{ expr }} | htmlspecialchars() with ENT_QUOTES — prevents all HTML and attribute injection |
{{{ expr }}} | wp_kses_post() — allows safe HTML; strips dangerous tags like <script> |
| HTML attribute injection | WP_HTML_Tag_Processor handles encoding natively; recognized URL attributes (href, src) are additionally run through esc_url() to block javascript: URI injection |
The raw modifier follows the same wp_kses_post() path as triple-brace syntax.
Expression Sandboxing
The expression language does not allow:
- Arbitrary PHP function calls
- File system access
- HTTP requests (core engine only — Pro adds controlled REST integrations)
- Raw SQL queries
The parser grammar is strictly defined using a custom recursive-descent tokenizer. Any syntax outside the defined grammar is rejected and returns an empty string. All variable resolution and modifier dispatch run through controlled internal classes (Context and Library) — there is no eval() or dynamic function dispatch.
Best Practices
- Keep custom roots read-only. Context roots should expose data, not trigger side effects or mutations.
- Prefix sensitive custom meta with an underscore. The engine treats underscore-prefixed meta as protected automatically via
is_protected_meta(). For public-facing meta keys that nonetheless contain sensitive data (e.g.stripe_api_key), extend the keyword list via thevector_expressions/security/sensitive_meta_keywordsfilter rather than relying on the underscore convention alone. - Use the sanitization deny list for attributes. If you allow dynamic attribute binding, audit
vector_expressions/sanitization/deny_listto ensure dangerous attributes are blocked. - Avoid raw output unless necessary.
{{{ }}}and therawmodifier bypasshtmlspecialchars. Use them only for trusted content that you control.