Security lockdown — explicit-call ES module. Blocks dangerous APIs on
windowanddocument, redirectsinnerHTMLtoinnerText, freezes native prototypes.
Source packages/front/fw/src/sanity/base.js | Type ES module, explicit call | Worker-safe no
ES module exporting applyBase(), an explicit-call function. It is not imported via runtime.resolve() and does not auto-apply on import. Call applyBase() once before any application code to lock down the realm.
Resolve
import { applyBase } from '@awacloud/fw/sanity/base';
applyBase(); // must be called before all application code
For zero-bundler deployment (static HTML), load the built classic artifact instead, which self-applies on load:
<script src="packages/front/fw/dist/build/sanity-base-classic.min.js"></script>
<script type="module" src="src/main.js"></script>
API
applyBase() applies 15 sequential steps that lock down the realm. Returns { applied: boolean, reason?: string, steps: string[] } — the step list records the order and is useful for debugging.
| Section | Method / Property | Effect |
|---|---|---|
| window | eval, Function, Reflect |
Throws 'not allowed' |
| window | alert, confirm, prompt, open |
Throws 'not allowed' |
| window | importScripts |
Throws 'not allowed' |
| window | import() (dynamic) |
Not intercepted — the window.import property throws on access, but the syntactic import() form is language syntax, not a property, so it cannot be poisoned by slot replacement. Closed by a server CSP (script-src) or a Worker, not this tier. |
| window | Math.random |
Throws 'not allowed: use crypto.getRandomValues instead' |
| window | crypto.randomUUID |
Throws 'not allowed' |
| window | performance.now, performance.mark, performance.measure, performance.getEntries |
Throws 'not allowed' |
| document | write, writeln, open, close, execCommand, execScript, evaluate, implementation, createContextualFragment |
Throws 'not allowed' |
| document | domain (set) |
Throws 'not allowed' |
| history | pushState, replaceState, go, back, forward |
Throws 'not allowed' |
| Element.prototype | innerHTML (set), outerHTML (set), insertAdjacentHTML (set) |
Redirected to innerText / outerText / insertAdjacentText — no exception raised |
| Date | Date.now |
Rounded to 100 ms (Math.floor(ts / 100) * 100) |
| Timing | setTimeout, setInterval, requestAnimationFrame with a string |
Throws 'not allowed' |
| JSON | JSON.parse(text, reviver) |
Throws 'not allowed' if reviver is present |
| JSON | JSON.stringify(val, replacer) when replacer is a function |
Throws 'not allowed' |
| iframe / object / embed | src, srcdoc, data, href, innerHTML, codebase, archive (set) |
Throws 'not allowed' |
Frozen prototypes
Object.freeze() applied to:
Object.prototype Array.prototype Function.prototype
String.prototype Number.prototype Boolean.prototype
Date.prototype RegExp.prototype Error.prototype
On each prototype, __proto__ (set) → throws, constructor → non-writable / non-configurable.
Examples
Correct loading
// app.js — first import
import { applyBase } from '@awacloud/fw/sanity/base';
const result = applyBase(); // → { applied: true, steps: ['freezePrototypes', 'window', ...] }
// Now safe to import and use the framework
import { runtime } from '@awacloud/fw';
Or for zero-bundler deployment:
<!doctype html>
<html>
<head>
<script src="packages/front/fw/dist/build/sanity-base-classic.min.js"></script>
<script type="module" src="app.js"></script>
</head>
</html>
Verify that a block is active
try {
eval('1+1');
} catch (e) {
console.warn('eval blocked:', e.message); // "not allowed"
}
try {
Math.random();
} catch (e) {
console.warn('Math.random blocked:', e.message); // "not allowed: use crypto.getRandomValues instead"
}
Math.random replacement
// Math.random() throws — use the framework's random module instead:
const random = runtime.resolve('random');
const buf = new Uint8Array(4);
random.bytes(buf); // CTR_DRBG-AES-256
Notes
- The ES module source
src/sanity/base.jsmust be imported andapplyBase()must be called. Bare import (without calling) has no effect. - For zero-bundler deployment, use the built classic artifact
dist/build/sanity-base-classic.min.js, which self-applies on load via<script src>. innerHTML→innerTextredirections are silent — no exception is raised on assignment.LOG_ATTEMPTS = true(current source value): each blocked attempt produces aconsole.warnwith stack trace.Date.nowis rounded to 100 ms to reduce timing precision (fingerprinting / timing attacks).community.jsadopts a more lenient rounding (framework-friendly degraded layer).- Prototype freezing is irreversible for the duration of the session — call
applyBase()before any library that attempts to modify prototypes. - Dangerous elements covered:
iframe,object,embed.community.jscovers the same set with relaxed restrictions to remain compatible with popular frameworks. - Playwright drivability consequence: Observe a hardened realm over the network, never through the evaluation channel.
applyBase()makes a page undrivable by Playwright — its blocked-property getter (atbase.js:100) throws inside the injected UtilityScript in both Chromium and Firefox engines (measured). End-to-end test legs must use network observation with a barrier request afterwards to confirm confinement, never viapage.evaluate()on an applyBase'd page; absent requests and unobserved requests are indistinguishable without the barrier. - Dynamic
import()survivesapplyBase()(measured):src/crypto/wasm/runtime.jsperforms a live in-factory dynamic import afterapplyBase()has already run, in the same realm — direct proof that the syntactic form is not intercepted by this tier's property poisoning (see theimport()row above).
See also
- sanity/community — framework-friendly variant (degraded layer for framework-hosted apps)
- Security Guide — rationale and threat model
- uuid — safe replacement for
crypto.randomUUID