How CSS Value Sanitization Prevents Injection Attacks in Instatic

Instatic prevents CSS injection attacks by routing every user-generated CSS value through the dependency-free sanitiseCssValue function in src/core/css-sanitize/sanitiseCssValue.ts, which employs input normalisation, allow-list validation, and dangerous pattern rejection to block malicious constructs before they reach the DOM.

In the CoreBunch/Instatic repository, CSS value sanitization establishes the critical security boundary between untrusted user content and the final HTML output. Every CSS value originating from external sources—whether from rich-text editors, SVG modules, or third-party plugins—must traverse a centralized sanitization pipeline before reaching the publisher utilities or editor canvas. This architectural constraint ensures that dangerous vectors like JavaScript URLs and CSS expressions are neutralized at the source, never reaching the browser’s rendering engine.

Three-Stage CSS Value Sanitization Process

The sanitiser implemented in src/core/css-sanitize/sanitiseCssValue.ts processes every value through a rigorous three-stage pipeline designed to normalize input and eliminate attack vectors.

Input Normalisation

The sanitizer first trims whitespace and coerces numeric inputs to strings, ensuring a predictable format for downstream validation. This normalization prevents attackers from exploiting type confusion or whitespace tricks to bypass security checks, establishing a clean baseline before validation begins.

Allow-List Validation

Instatic accepts only safe CSS tokens defined by a strict whitelist. Permitted values include length units (e.g., 16px), hexadecimal colour literals (e.g., #fff), functional syntax such as translateX(10px), and url() references pointing exclusively to http or https resources. Any value falling outside this allowed set returns null, compelling the calling module to drop the property or substitute a safe default.

Dangerous Pattern Rejection

The sanitiser explicitly rejects known XSS vectors including expression() (IE-specific dynamic code execution), javascript: URLs within CSS functions, behaviour: and -moz-binding: references that could load external XML documents, and data: URIs containing HTML content. It also blocks block-breaking characters such as { and } that could escape the CSS declaration context and inject arbitrary rules.

Injection Vectors Blocked by CSS Value Sanitization

By centralising validation, Instatic eliminates multiple attack classes that leverage CSS as an injection mechanism:

  • CSS Expressions – Legacy IE-specific syntax like expression(alert(1)) is detected and discarded.
  • JavaScript URLs – Malicious payloads within url("javascript:alert(1)") are rejected before reaching the stylesheet.
  • Behaviour and XBL Bindings – References such as -moz-binding:url("evil.xml#hack") that could execute remote code are blocked.
  • Data URIs with HTML – Constructs like url(data:text/html,<b>x</b>) that embed executable content are filtered out.

Centralized Architecture and Security Constraints

The sanitiseCssValue function is re-exported through src/core/publisher/utils.ts and the @core/css-sanitize module, serving as the sole entry point for all CSS value generation. According to Constraint #228 enforced by the project’s architecture tests, all modules emitting CSS—including the framework token system in src/core/framework/cssVariables.ts, class-based style generators in src/core/publisher/classCss.ts, and keyframe utilities—must import from this centralized location. This dependency-free design guarantees that no module can bypass the security gate, ensuring consistent protection across the entire codebase.

The test suite in src/__tests__/publisher/utils.test.ts validates both the allow-list acceptance criteria and the block-list behavior, providing automated verification that the sanitization rules remain intact through refactors.

Implementing CSS Value Sanitization in Practice

When integrating with the Instatic publisher, always route CSS values through the sanctioned import path:

// Example: sanitising user-provided CSS before persistence
import { sanitiseCssValue } from '@core/publisher';

// Raw input potentially originating from untrusted sources
const raw = 'url("javascript:alert(1)")';

// Returns null for unsafe values, blocking the injection attempt
const safe = sanitiseCssValue(raw);
if (safe === null) {
  console.error('Unsafe CSS value rejected');
  // Property is dropped or replaced with safe fallback
} else {
  storeCssValue(safe); // Safe to emit into HTML
}

For runtime sanitization within UI components:

import { sanitiseCssValue } from '@core/publisher';

function StyledBox({ css }: { css: string | number }) {
  const safeCss = sanitiseCssValue(css);
  // Falls back to '0' if sanitization fails, preventing DOM injection
  const style = safeCss 
    ? { '--custom-value': safeCss } 
    : { '--custom-value': '0' };

  return <div className="box" style={style} />;
}

Summary

  • Centralized sanitization via sanitiseCssValue in src/core/css-sanitize/sanitiseCssValue.ts processes all user-generated CSS.
  • Three-stage validation combines input normalisation, allow-list validation, and pattern rejection to neutralize threats.
  • Specific attack mitigation blocks CSS expressions, JavaScript URLs, behaviour bindings, and malicious data URIs.
  • Architectural enforcement through Constraint #228 ensures all CSS-emitting modules route through the single sanitization gate.
  • Dependency-free implementation reduces supply chain risk while maintaining consistent security boundaries.

Frequently Asked Questions

What happens when a CSS value fails sanitization in Instatic?

When sanitiseCssValue returns null for an unsafe input, the calling module either omits the CSS property entirely or substitutes a safe default value. This ensures that malformed or malicious CSS never reaches the final HTML output, neutralizing injection attempts without breaking the rendering pipeline.

Which specific CSS injection vectors does the Instatic sanitizer block?

The sanitizer explicitly blocks CSS expressions (expression(alert(1))), JavaScript URLs within url() functions, XBL bindings via -moz-binding and behaviour references, and data URIs containing HTML that could execute scripts. It also rejects characters like { and } that could break out of CSS declaration blocks.

How does Instatic enforce the use of the CSS sanitizer across all modules?

According to Constraint #228 validated by architecture tests, any module generating CSS—including src/core/framework/cssVariables.ts for CSS variables and src/core/publisher/classCss.ts for class-based styles—must import sanitiseCssValue from @core/css-sanitize. This creates a mandatory, unbypassable gatekeeper that centralizes all CSS value processing.

Is the Instatic CSS sanitizer dependent on external libraries?

No, the implementation in src/core/css-sanitize/sanitiseCssValue.ts is dependency-free, reducing the supply chain attack surface and ensuring that security behavior remains consistent and auditable without relying on third-party package updates.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →