Skip to content

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):

  • @import resolves only to .scss, .sass, .less or .css files inside the import folders: uploads/custom-css-js (where partial snippets are written) and the theme snippet folders, plus folders added with the ccj_preprocessor_import_paths filter. 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() and image-height() are refused in every LESS source, imported files included.
  • Partial file names are letters, digits, dash and underscore with one .scss or .less extension.
  • 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:

  • options is unserialized without objects and reduced to scalars from their allowed sets; urls keeps 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_snippet fields.
  • 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_post per snippet.
  • bb-snippets/v1/preview needs 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-url needs 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_nocache filter 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.