Security#
Who can do what#
| Actor | Can |
|---|---|
| Visitors, subscribers | See what snippets print. Nothing else: the REST routes, previews and admin actions need a snippet capability. |
| Contributors, authors, editors | Place [ccj] shortcodes in their posts (attribute values are escaped, see below). No snippet screens, no preview, no URL tester. |
Web Designer role (css_js_designer): the *_custom_css* capabilities without unfiltered_html |
CSS snippets for the site. Not JavaScript, HTML, or anything loading on admin pages, the login page or the block editor (since 0.3.2): those would let them run script in an administrator's browser. Settings → Code can allow it again, which makes them administrators in effect. |
Editors and administrators (they have unfiltered_html on a single site) |
Every kind of snippet, as WordPress lets them put code in content. |
Administrators (manage_options) |
Also settings, import, and deleting unused files. |
Snippet code never runs as PHP: conditional rules are parsed against a list of WordPress conditional tags, shortcode
snippets are plain HTML, and nothing is written with a PHP extension. A CSS snippet cannot end its <style> element
(</style is printed as <\/style), so CSS-only editors cannot add script that way. The checks sit on the capability
itself (editing, deleting, publishing, toggling, restoring a revision, previewing), not only on the screens.
define( 'BB_SNIPPETS_EVAL_CONDITIONALS', true ) brings back eval() of conditional rules for sites that need arbitrary
PHP there. It gives every snippet editor, the Web Designer role included, PHP execution on the server. Leave it off.
SASS and LESS#
Both compilers can read files, so they run sandboxed (since 0.3.1):
@importresolves only to.scss,.sass,.lessor.cssfiles inside the import folders:uploads/custom-css-js(where partial snippets are written) and the theme snippet folders, plus folders added with theccj_preprocessor_import_pathsfilter. Links and..are resolved before the check. Plain CSS imports (@import url(https://…)) stay in the output and read nothing.- LESS's file functions
data-uri(),image-size(),image-width()andimage-height()are refused in every LESS source, imported files included. - Partial file names are letters, digits, dash and underscore with one
.scssor.lessextension. - Compiled results are cached by content; a compile that does not finish (an endless loop) is reported as failed instead of being retried on every request.
Simple Custom CSS and JS PRO 4.43 bundles the same compiler versions without these limits: anyone who can edit
snippets there can read server files (wp-config.php included) into a LESS snippet's output.
Data from other sites and files#
Snippet meta can arrive from a BB Migrate push, a database import, SQL or a JSON export. It is treated as input:
optionsis unserialized without objects and reduced to scalars from their allowed sets;urlskeeps only rules of the nine known types with text values; a snippet whose stored settings still cannot be read is skipped with a warning, and the other snippets build as usual.- JSON imports (administrators only) go through the same checks, and keep only the known
_bb_snippetfields. - Theme file snippets are code in the theme: whoever can write theme files can already run PHP.
Files written#
| Where | What |
|---|---|
wp-content/cache/bb-snippets/manifest-*.php |
The manifest, as var_export() data behind an ABSPATH guard; new name per build |
wp-content/cache/bb-snippets/compiled/ |
Compiled SASS/LESS results and in-progress markers |
wp-content/uploads/custom-css-js/ |
{ID}.css / {ID}.js for snippets that print on this site (inactive, expired, draft and other-environment snippets have no public file, since 0.3.2), partials under their names, block_css.css / block_js.js, an index.php and an .htaccess that denies PHP in the folder on Apache |
Admin and REST#
- Every state change checks a nonce and a capability; toggles and bulk actions check
edit_postper snippet. bb-snippets/v1/previewneeds the snippet capability and, with an ID, that the ID is a snippet the user may edit. A preview link works only for the user who made it, and the page is never cached.bb-snippets/v1/test-urlneeds the snippet capability, and names a page only when the user may read it.- Downloads are sent as attachments with
X-Content-Type-Options: nosniff.
Shortcode placeholders#
[ccj id="…" name="value"] fills {$name} in an HTML shortcode snippet. Values come from post authors, so each is
escaped for where it goes: {$name} / {$name|attr} for text and quoted attribute values (HTML-escaped,
javascript: / vbscript: / data: values dropped), {$name|url} for links, {$name|js} for a JavaScript string,
{$name|int} for a number. Never put a placeholder inside an on…="" handler or an unquoted attribute.
With a page cache#
- Device-targeted (desktop/mobile) HTML snippets follow BB Cache's own device test, and pages carrying one are not
cached when BB Cache keeps a single copy for all devices (
bb_snippets_device_nocachefilter for other caches). - URL rules match the query string, as in the old plugin. BB Cache (from 2026-11-08) never stores a page requested with
marketing parameters (
utm_*,gclid,fbclid…), which it ignores in its cache key, so a crafted link cannot change what other visitors are served. Other page caches that ignore such parameters need the same rule. - A manifest rebuild found necessary by many requests at once runs once (a lock), and its cache purge with it.
Reporting#
See SECURITY.md.