← Runtime Composition

Client-Side Runtime CompositionTechnical Reference

This page covers the technical implementation of the browser runtime composition system: how each component works, how data changes shape as it moves through the system, how the cryptographic chain fits together, and where observable signals remain for defenders. For the product thinking and design rationale, start with the narrative.

System Overview

The system is a two-layer web application. The visible layer is a normal React application that boots, registers a service worker, and renders a public interface. The second layer is an encrypted payload that can be unlocked locally, decrypted in the browser, and rendered inside a sandboxed iframe without fetching any additional files from the network.

The visible host must be a substantial, data-heavy application in its own right. A service worker sitting idle on a simple static page would look unusual under inspection. A service worker that actively caches and serves a rich web application (the kind that works offline, sometimes called a progressive web app) is expected behaviour. The host justifies the application’s size, memory footprint, and service worker registration simply by being a credible product.

Four components make this work:

ComponentRunsRole
Payload CompilerBuild time (Node.js)Scans payload folders, encodes files to base64, encrypts each namespace as a separate AES-256-GCM bundle
Runtime ControllerBrowser (React)Monitors input for the trigger, derives the decryption key, manages unlock, conceal, reveal, and purge states
Runtime Service WorkerBrowser (Service Worker)Holds the decrypted virtual file system in memory and serves /vfs/... requests as synthetic responses
Host ApplicationBrowser (React)The visible shell: boots the app, registers the worker, imports the encrypted bundles, wraps the layout in the controller

The components are organised as a monorepo with workspaces:

PathPurpose
packages/payload-compilerNode.js CLI that produces encrypted payload bundles
packages/runtime-controllerReact component, hooks, and crypto utilities
packages/runtime-service-workerService worker source and build script
apps/tech-archive-hostExample host application that assembles the capabilities

Execution Timeline

The system moves through ten steps from initial page load to a rendered isolated runtime. Each step is a handoff: one component finishes its job and passes the result to the next.

1 Page loads The host application boots as a standard React app and loads bundled project assets. The encrypted payload data is imported as ordinary application state during this step.
2 Service worker registers The browser installs the service worker so it can intercept future requests. Registration happens early so the worker is ready before any unlock attempt.
3 Input monitoring begins The controller starts watching keyboard input through a short buffer used only for trigger matching. The host application remains fully interactive.
4 Trigger is matched The controller hashes candidate suffixes of the input buffer with SHA-256 and compares each result to the stored fingerprint. When a match is found, the unlock sequence begins. See the cryptographic design section for the full mechanism.
5 Key derivation runs The typed trigger phrase is passed to PBKDF2 via the Web Crypto API. The browser derives an AES-256-GCM decryption key locally. This is the first CPU-intensive step and one of the more observable moments in the unlock flow.
6 Payload is decrypted The controller decrypts the selected encrypted bundle using the derived key. The result is a virtual file system object: a JSON map pairing file paths (like /vfs/launcher/index.html) to their base64-encoded contents.
7 Worker memory is hydrated The decrypted file map is sent to the service worker via postMessage. The worker stores it in a JavaScript Map in RAM, not in any persistent storage mechanism.
8 /vfs/... requests are intercepted When the iframe asks for a file, the worker checks its memory, finds the matching entry, converts it from the base64 text format back into the binary data the browser expects, figures out what type of file it is (HTML, CSS, JavaScript, image), and hands it back as a normal browser response with Cache-Control: no-store headers. Missing paths receive an explicit 404 from the worker rather than falling through to the network.
9 Iframe loads the isolated runtime The controller mounts a full-screen sandboxed iframe pointing at /vfs/launcher/index.html. From the iframe’s perspective, the virtual files behave like ordinary hosted web assets.
10 Additional payloads load on demand If the launcher requests a sub-package (via postMessage to the parent), the controller decrypts only that additional bundle and appends it to the worker’s memory map. The iframe navigates to the new /vfs/... route.

Representative code

These snippets illustrate three key moments in the flow. They are simplified for clarity.

// Worker registration (host app startup)
navigator.serviceWorker.register("/stealth-runtime-sw.js");
// Key derivation (controller, after trigger match)
const key = await crypto.subtle.deriveKey(
  { name: "PBKDF2", salt, iterations: 600000, hash: "SHA-256" },
  baseKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["decrypt"]
);
// Fetch interception (service worker)
self.addEventListener("fetch", (event) => {
  const url = new URL(event.request.url);
  if (url.pathname.startsWith("/vfs/") && volatileVfsMap) {
    event.respondWith(serveFromRAM(url.pathname));
  }
});

Component Contracts

Each component has a strict boundary: what it owns, what it produces, and what it deliberately avoids. These boundaries are what keep the system modular.

Payload Compiler

OwnsFile discovery, base64 encoding, VFS construction, AES-256-GCM encryption per namespace
InputA source directory with one subdirectory per payload namespace, plus a trigger key string
OutputOne encrypted bundle per namespace, each containing salt, iv, authTag, and ciphertext
AvoidsAny knowledge of the browser, the runtime, or visibility state

Runtime Controller

OwnsTrigger detection (suffix hash search), key derivation, decryption, iframe lifecycle, visibility state (conceal, reveal, purge)
InputKeyboard events plus encrypted payload bundles
OutputDecrypted VFS objects sent to the worker, plus the mounted iframe overlay
AvoidsServing files directly; the controller hands off to the worker and steps back

Key source files: StealthWrapper.tsx (main wrapper), useEnvironmentInput.ts (trigger hook), useDecryption.ts (decryption hook), crypto.ts (hex conversion utilities).

Runtime Service Worker

OwnsIn-memory VFS storage, /vfs/... fetch interception, base64-to-binary conversion, MIME type inference, response headers
InputHydrated VFS maps via postMessage (HYDRATE_VFS, HYDRATE_VFS_APPEND, PURGE_VFS)
OutputSynthetic Response objects with correct content types and Cache-Control: no-store, no-cache, must-revalidate
AvoidsAny decision about when content is trusted or allowed to unlock; it only stores and serves what it receives

The memory-only design is deliberate. The worker never writes decrypted content to IndexedDB, Cache Storage, or any other persistent mechanism. When the worker is terminated or purged, the data is gone. This means the runtime exists only for as long as it is actively being used.

Host Application

OwnsThe visible UI, service worker registration, encrypted data import, and controller integration
InputThe compiled encrypted payload file (imported as application data)
OutputA complete, functioning public-facing application that wraps the controller
AvoidsUnderstanding every cryptographic step or file-serving branch; it assembles rather than implements

Data Transformations

The payload changes form five times as it moves through the system. Each transformation moves the data closer to something the browser can render.

Payload FilesHTML, CSS, scripts grouped by namespace
Plain VFS Mapbase64 strings keyed by virtual path
Encrypted Bundleciphertext + salt + IV + auth tag
Decrypted VFSplaintext JSON in browser memory
Synthetic Responsesbinary blobs served as normal web files
TransformationWhere it happensWhat changes
File to base64Compiler (build time)Binary file contents become text-safe strings that can live inside JSON
VFS to ciphertextCompiler (build time)The JSON map is serialised, then encrypted with a key derived from the trigger phrase
Ciphertext to plaintextController (browser)The browser derives the same key and reverses the encryption, producing the original VFS map
Plaintext to memory entriesService worker (browser)The JSON map is received via postMessage and stored in a JavaScript Map
Memory to responsesService worker (browser)Base64 entries are decoded to binary, paired with MIME types, and returned as Response objects

Cryptographic Design

The cryptographic chain serves two purposes: it keeps the payload unreadable until the correct trigger is supplied, and it verifies that the payload data has not been altered. The system uses standard Web Crypto API primitives throughout.

At a high level, the trigger phrase becomes a key, and that key unlocks the payload. PBKDF2 is used so the browser derives proper key material instead of treating the raw phrase itself as the key. AES-GCM is used because it provides both confidentiality (the data is unreadable without the key) and integrity (the browser can reject altered payload data rather than rendering it). Each encrypted bundle includes the small pieces of metadata the browser needs to reverse the process: a salt (randomness added during key derivation), an initialisation vector (randomness added during encryption), and an authentication tag (a check that confirms the data has not been tampered with).

Key derivation (PBKDF2)

The trigger phrase is the starting material for key derivation. Rather than using the phrase directly as an encryption key, it is passed through PBKDF2 (Password-Based Key Derivation Function 2), which turns a human-readable phrase into cryptographic key material. This adds intentional computational cost, making brute-force guessing slower, and it means the raw phrase is never used as a key directly.

ParameterValuePurpose
AlgorithmPBKDF2Derives a strong key from a human-readable phrase
HashSHA-256The hash function used internally during derivation
SaltRandom (per encryption)Ensures that encrypting the same payload with the same phrase produces different output each time
OutputAES-256-GCM key256-bit symmetric key used for encryption and decryption

Encryption (AES-256-GCM)

AES-GCM (Galois/Counter Mode) provides both confidentiality and integrity. Confidentiality means the data is unreadable without the key. Integrity means the browser can detect if the ciphertext has been tampered with, and will reject it rather than producing a corrupted result.

ParameterPurpose
Initialisation vector (IV)Unique randomness per encryption operation, so the same plaintext produces different ciphertext each time
Authentication tagA checksum that lets the browser verify the ciphertext is intact before decrypting
CiphertextThe encrypted VFS data

The encrypted bundle stores all four values together: salt, IV, authentication tag, and ciphertext. That is everything the browser needs to derive the same key and reverse the encryption.

Trigger verification (SHA-256)

The trigger phrase is stored in the source code as a hash: a one-way fingerprint produced by running the phrase through a mathematical function (SHA-256). You can produce the fingerprint from the phrase, but you cannot reverse it to recover the phrase. That means someone inspecting the application’s code would find only the fingerprint, and working out what produced it requires already knowing the answer.

On every key press, the controller tests every possible suffix of the current input buffer by hashing each one with SHA-256 and comparing the result to the stored fingerprint. This suffix search means the trigger phrase can be recognised regardless of what was typed before it.

Lifecycle States

The system moves through six states during a complete session. The transitions are deterministic: each state leads to the next through a specific event.

1 Dormant The encrypted payload exists as ordinary application data. The controller is loaded but idle. The service worker has registered but holds nothing in memory.
2 Monitoring The controller is actively watching input. A short buffer accumulates recent characters and each keystroke triggers a hash comparison against the stored fingerprint.
3 Unlocked The trigger has matched. The browser has derived the key via PBKDF2 and decrypted the payload into a plaintext VFS object. The decrypted data exists in browser memory.
4 Hydrated The decrypted VFS has been posted to the service worker. The worker now holds the file map in memory and can respond to /vfs/... requests.
5 Serving The iframe is mounted and the worker is actively serving virtual files. The isolated runtime is visible and interactive. Additional payloads can be decrypted and appended on demand.
6 Cleared The user has pressed Escape (purge). The iframe is unmounted, the controller resets its state, and the worker clears its in-memory map. The system returns to the Monitoring state.

Two intermediate behaviours sit alongside these states:

Failure Modes

Each step in the execution chain has a specific failure condition. The system is designed to fail silently: a wrong trigger produces no error message, a corrupted payload is rejected cleanly, and a missing worker simply means the virtual files cannot be served.

ConditionWhat happensWhy
Wrong trigger phraseThe hash comparison never matches, so the unlock path never startsThe controller compares hashes on every keystroke; a mismatch is the normal state
Key derivation failureDecryption cannot begin; the system stays dormantPBKDF2 depends on the Web Crypto API being available and the parameters being correct
Payload corruption or tamperingAES-GCM rejects the ciphertext during decryptionThe authentication tag verification fails, so the browser refuses to produce output rather than returning corrupted data
Service worker missingThe controller may decrypt successfully, but the virtual files cannot be served through /vfs/...Without the worker’s fetch interception, the iframe’s requests have nowhere to resolve
Worker state cleared by browserThe in-memory VFS disappears; the runtime stops workingMemory-only storage means the data is gone whenever the browser decides to terminate the worker
Tab switch or window blurThe overlay hides; the runtime stays loaded in the backgroundThis is intentional concealment, not a failure, but the user must re-enter the trigger to see the runtime again
Escape key (panic purge)The iframe unmounts, the controller resets, and the worker empties its memoryThis is the deliberate full-purge path; the system returns to monitoring

Observable Signals

This architecture shifts where visibility sits. Visibility persists across every layer. Every capability used is a standard browser feature, and standard browser features leave traces that defenders, monitoring tools, and browser developer tools can inspect.

LayerWhat remains observableWhy it matters
NetworkUnusually large initial download relative to the visible site’s purpose; browsing sessions that go quiet after the first transferThe encrypted payload ships with the initial page load, inflating the bundle size beyond what the visible application would justify on its own
JavaScript runtimeCalls to crypto.subtle.deriveKey and crypto.subtle.decrypt; CPU spikes during key derivationPBKDF2 is intentionally expensive and AES-GCM decryption produces measurable computation; endpoint monitoring can profile this
MemoryBase64 decoding, object creation, and Blob construction leave transient artefacts visible in heap snapshotsDecrypted content exists in memory for as long as the runtime is active; memory profiling tools can capture it
Service workerWorker registration, fetch interception patterns, and synthetic responses for /vfs/... pathsService workers are inspectable through chrome://serviceworker-internals, the Application panel in DevTools, and organisational policy enforcement
UI behaviourIframe overlays appearing and disappearing, reveal and conceal cycles, interaction patterns inconsistent with the visible shellBoth automated screen-capture tools and human review can detect usage patterns that diverge from the public application’s expected behaviour

Inspecting in DevTools

The system can be examined directly using standard browser developer tools:

Defensive Controls

Organisations and security teams retain practical leverage over this architecture at multiple layers.

ControlWhat it addresses
Service worker origin allowlistingRestrict which domains are permitted to register service workers, preventing untrusted sites from using fetch interception
Aggressive worker state clearingClear service worker caches and state on session exit, ensuring memory-backed content is cleared between visits
Iframe policy restrictionsLimit iframe capabilities (sandboxing attributes, Content Security Policy) on untrusted or unreviewed sites
Endpoint crypto profilingMonitor crypto.subtle usage patterns and correlate with user behaviour to flag unexpected key derivation or decryption activity
Network payload analysisFlag mismatches between a site’s visible purpose and its initial download size or traffic shape
Session behaviour correlationCross-reference time-on-page, focus changes, and interaction patterns with what the visible application would justify

Defensive framing: This architecture moves where visibility occurs, shifting signals between network, runtime, memory, and UI layers. A layered defensive approach that watches across all these surfaces retains clear leverage.

Performance and Tradeoffs

Costs of this design

Alternatives considered

AlternativeTradeoff
Fetch the payload later (on demand)Reduces initial bundle size but creates an obvious network event at unlock time, making the second-stage load clearly visible
Use IndexedDB for storageWould preserve the runtime across page reloads but weakens the memory-only design; persistent storage is more inspectable and leaves durable forensic artefacts
Use WebAssembly for the runtimeCould change execution characteristics but would not remove observability; the loading, compilation, and execution of Wasm modules is itself an observable event
Render into the main DOM instead of an iframeWould remove the iframe boundary but collapse the isolation between the host and the runtime, making both harder to manage and easier for DOM-scraping tools to inspect