# How uBlock Origin's Scriptlet Injector Safely Evaluates Code: Architecture and Implementation

> Discover how uBlock Origin's scriptlet injector safely evaluates code using sentinel verification and automatic Blob URL fallback even with blocked inline scripts.

- Repository: [Raymond Hill/uBlock](https://github.com/gorhill/uBlock)
- Tags: architecture
- Published: 2026-03-05

---

**uBlock Origin uses a sentinel-based verification system with automatic Blob URL fallback to ensure scriptlets execute safely even when Content Security Policies block inline scripts.**

The gorhill/uBlock repository implements a sophisticated scriptlet injection mechanism that evaluates JavaScript filter code securely within web pages. This system, centered around `vAPI.scriptletsInjector`, guarantees execution success while preventing duplicate injections and bypassing Content Security Policy (CSP) restrictions.

## The Scriptlet Injector Architecture

### Injector Initialization and Structure

The core injector is defined as an Immediately Invoked Function Expression (IIFE) in [`platform/firefox/vapi-background-ext.js`](https://github.com/gorhill/uBlock/blob/main/platform/firefox/vapi-background-ext.js) (lines 54-104). This pattern initializes a global tracking mechanism and returns a function that accepts a hostname and details object:

```javascript
// https://github.com/gorhill/uBlock/blob/master/platform/firefox/vapi-background-ext.js#L54-L104
vAPI.scriptletsInjector = (( ) => {
    self.uBO_scriptletsInjected = '';
    // Returns function for hostname/details processing
})();

```

### Sentinel Generation for Execution Verification

Before each injection, the system generates a one-time random token using `vAPI.generateSecret(3)`, implemented in [`platform/common/vapi-background.js`](https://github.com/gorhill/uBlock/blob/main/platform/common/vapi-background.js) (lines 91-96). This sentinel serves as a cryptographic proof-of-execution:

- The secret is embedded in the injected code as `self['<secret>'] = true`
- Post-injection verification checks for this property on `self.wrappedJSObject`
- Absence triggers the fallback mechanism, indicating CSP or execution failure

### Script Assembly and Hostname Validation

The injector constructs a self-executing wrapper function (lines 55-84) that performs multiple safety checks before running scriptlet code:

1. Verifies `self.uBO_scriptletsInjected` is unset to prevent double-injection
2. Validates `document.location.hostname` matches the provided target hostname
3. Sets the sentinel property to signal execution
4. Creates a script element containing the payload and appends it to `document.documentElement`

## Safe Evaluation Mechanisms

### Preventing Accidental Double-Injection

The first operation in the injected wrapper checks `self.uBO_scriptletsInjected`. If this property exists, indicating previous successful injection, the function returns immediately. Upon successful execution, the property is set to the list of applied filter strings, providing both auditability and duplicate prevention.

### CSP Bypass via Blob URL Fallback

When inline script execution fails (sentinel not detected), the injector automatically falls back to a Blob-based approach (lines 89-104):

```javascript
const blob = new Blob(
    [ details.scriptlets ],
    { type: 'text/javascript; charset=utf-8' }
);
const url = URL.createObjectURL(blob);
// Script element created with src=url, async=false

```

This technique converts inline code into an external resource, bypassing `script-src` restrictions that prohibit inline execution while maintaining synchronous loading through `async = false`. The Blob URL is revoked immediately after insertion to prevent memory leaks.

### Isolated World Execution

For scriptlets requiring protection from page-script interference, the `isolatedWorldInjector` in [`src/js/scriptlet-filtering.js`](https://github.com/gorhill/uBlock/blob/main/src/js/scriptlet-filtering.js) (lines 10-43) provides a separate execution context. This mechanism ensures that sensitive filter operations—such as modifying `fetch` or `XMLHttpRequest` prototypes—remain inaccessible to the host page's JavaScript environment, preventing detection or tampering by malicious scripts.

## Implementation Pipeline and Caching

### High-Level Orchestration

The `ScriptletFilteringEngineEx.retrieve` method in [`src/js/scriptlet-filtering.js`](https://github.com/gorhill/uBlock/blob/main/src/js/scriptlet-filtering.js) (line 308) orchestrates the complete injection workflow:

1. Matches filter rules to scriptlet templates using [`src/js/scriptlet-filtering-core.js`](https://github.com/gorhill/uBlock/blob/main/src/js/scriptlet-filtering-core.js)
2. Invokes `vAPI.scriptletsInjector(hostname, details)` to generate wrapped code
3. Registers the resulting code with the content script system

### Per-Hostname Caching Strategy

To optimize performance, the system maintains an MRU cache (`scriptletCache`, lines 68-76) storing assembled code templates per hostname. This eliminates redundant secret generation and string assembly for repeat visits. Each cached entry maintains a fresh sentinel, ensuring security verification remains accurate across multiple injection attempts.

## Practical Code Examples

### Programmatic Scriptlet Injection

```javascript
// Background script context using vAPI
const hostname = 'example.com';
const scriptletDetails = {
    hostname,
    mainWorld: `console.log('uBO scriptlet executing');`,
    isolatedWorld: '',
    filters: ['@@||example.com^$scriptlet']
};

// Generate injection code with embedded sentinel and fallback logic
const injectionCode = vAPI.scriptletsInjector(hostname, scriptletDetails);

// Register with the content script manager
contentScriptRegisterer.register(hostname, injectionCode);

```

### Verification Logic Structure

The injected wrapper follows this safety pattern:

```javascript
// Executed in the main world context
if ( self.uBO_scriptletsInjected ) { return; }
if ( document.location.hostname !== targetHostname ) { return; }

// Set sentinel before scriptlet execution
self[secretToken] = true;

// ... actual scriptlet code executes here ...

// Background script later verifies:
// if ( self.wrappedJSObject[secretToken] ) { success }

```

## Summary

- **uBlock Origin** implements scriptlet safety through `vAPI.scriptletsInjector` in [`platform/firefox/vapi-background-ext.js`](https://github.com/gorhill/uBlock/blob/main/platform/firefox/vapi-background-ext.js), using sentinel-based verification to guarantee execution
- **Double-injection prevention** relies on the `self.uBO_scriptletsInjected` global flag checked before any code runs
- **CSP circumvention** automatically falls back to Blob URL injection when inline scripts are blocked, using synchronous external script loading
- **Isolated world support** protects sensitive operations through separate injection contexts defined in [`src/js/scriptlet-filtering.js`](https://github.com/gorhill/uBlock/blob/main/src/js/scriptlet-filtering.js)
- **MRU caching** in the filtering engine optimizes performance by reusing assembled templates per hostname while maintaining unique security tokens for each injection

## Frequently Asked Questions

### How does uBlock Origin verify that a scriptlet actually executed?

The injector generates a random sentinel using `vAPI.generateSecret(3)` and embeds code that sets this token as a property on the global object (`self['<secret>'] = true`). After the script element is removed, the background script checks `self.wrappedJSObject` for this property. Presence confirms successful execution; absence triggers the Blob URL fallback mechanism.

### What happens when Content Security Policy blocks inline scripts?

When sentinel verification fails (indicating CSP blocked the inline script), the injector automatically creates a `new Blob()` containing the scriptlet code and generates an object URL. This converts the injection into an external script resource, which most CSP configurations permit via `script-src` directives allowing `blob:` or `self` origins. The script loads synchronously using `async = false` to maintain execution order.

### How does the system prevent scriptlet code from leaking to the page?

The injector stores the sentinel on `wrappedJSObject`—a cross-origin wrapper in Firefox that provides safe access to page globals—and immediately deletes it after verification. For complete isolation, the `isolatedWorldInjector` executes code in a separate JavaScript realm where page scripts cannot access or modify the injected functionality, protecting against interference from hostile web pages.

### Where are assembled scriptlets cached for performance?

Prepared injection code is cached in an MRU (Most Recently Used) cache implemented in [`src/js/scriptlet-filtering.js`](https://github.com/gorhill/uBlock/blob/main/src/js/scriptlet-filtering.js) (lines 68-76). The cache maps hostnames to assembled code strings, avoiding repeated string concatenation and secret generation for repeat visits. Each cache entry includes the complete wrapper with a specific sentinel, though the cache is cleared periodically to prevent stale entries.