Low-level security primitives shared by the entire rendering pipeline — URL safety, DOM-clobbering,
on*attributes, blocked tags, dangerous CSS. Single source of truth: the policy lives here, other modules consume it.
Module secPolicy | Source packages/front/fw/src/dom/rendering/secPolicy.js | Deps none | Worker-safe yes
Before secPolicy, the safe-URL check (_isSafeUrl), the URL-bearing attribute list, the DOM-clobber name list, and the on* regex were duplicated in template.js (DOM path), render.js (SSR path), and partially re-implemented in sanitize.js. secPolicy centralises them. All framework consumers inject the module and read the constants / call the functions instead of inlining.
Resolve
const sp = runtime.resolve('secPolicy');
The returned object exposes constants (Sets, RegExp) and pure functions (testers / classifiers). No state, no mutating methods.
API
| Method | Signature | Returns |
|---|---|---|
isSafeUrl |
(url, schemes?, allowDataImage?) => boolean |
URL acceptable on a URL-bearing attribute |
isClobberValue |
(attrName, value) => boolean |
Pair (id|name, value) is a DOM clobbering vector |
isEventAttr |
(name) => boolean |
Name is an on* event handler |
isSafeAttrName |
(name) => boolean |
Name is shape-conformant AND not on* |
isBlockedTag |
(name) => boolean |
Tag rejected at parse-time |
isSafeCss |
(prop, val) => boolean |
style.setProperty call is acceptable |
URL_ATTRS |
Set<string> |
URL-bearing attributes |
SCHEME_DANGEROUS_RE |
RegExp |
Refused schemes |
SAFE_SCHEMES |
Set<string> |
Default allowed schemes |
DATA_IMAGE_RE |
RegExp |
Accepted data:image/* MIMEs |
CLOBBER_ATTRS |
Set<string> |
Clobberable attributes (id, name) |
CLOBBER_NAMES |
Set<string> |
Values that shadow document.* props |
EVENT_ATTR_RE |
RegExp |
Matches on* (case-insensitive) |
SAFE_ATTR_NAME_RE |
RegExp |
Accepts a name that starts with a letter |
BLOCKED_TAGS_RE |
RegExp |
Blocked tags (script, object, embed, iframe) |
CSS_DANGEROUS_RE |
RegExp |
Detects expression(...) and url(javascript:) etc. |
CSS_BLOCKED_PROPS |
Set<string> |
Properties blocked by name |
URL safety
| Symbol | Type | Description |
|---|---|---|
URL_ATTRS |
Set<string> |
URL-bearing attributes: href, src, action, formaction, srcset, xlink:href, data, codebase |
SCHEME_DANGEROUS_RE |
RegExp |
Categorically refused schemes: javascript:, vbscript:, file:, jar:, data:text, data:application |
SAFE_SCHEMES |
Set<string> |
Default allowed schemes: http, https, mailto, tel, ftp |
DATA_IMAGE_RE |
RegExp |
Accepted data:image/* MIMEs when allowDataImage=true |
isSafeUrl(url, schemes?, allowDataImage?) |
function | Decides whether url can be set on a URL-bearing attribute |
isSafeUrl applies in order:
- Non-string → unsafe
- Empty (after stripping control chars) → safe
- Starts with
#,/,?,.→ safe (anchor / relative) - Matches
SCHEME_DANGEROUS_RE→ unsafe data:→ safe only ifallowDataImageAND matchesDATA_IMAGE_RE- No
:→ safe (relative) :after a/→ safe (path containing:)- Scheme in
schemes(orSAFE_SCHEMESby default)
Spaces and ASCII control characters are stripped before any comparison — neutralises obfuscation such as JAVA\tSCRIPT:.
DOM clobbering
| Symbol | Type | Description |
|---|---|---|
CLOBBER_ATTRS |
Set<string> |
Attributes whose value can clobber: id, name |
CLOBBER_NAMES |
Set<string> |
~30 values that shadow native document.* props (cookie, domain, body, forms, …) |
isClobberValue(attrName, value) |
function | true if the pair is a known DOM clobbering vector |
Attribute shape
| Symbol | Type | Description |
|---|---|---|
EVENT_ATTR_RE |
RegExp |
Matches on* (case-insensitive) |
SAFE_ATTR_NAME_RE |
RegExp |
Accepts a name that starts with a letter |
isEventAttr(name) |
function | true if name is an on* event handler |
isSafeAttrName(name) |
function | true if name is shape-conformant AND not an on* |
Blocked tags
| Symbol | Type | Description |
|---|---|---|
BLOCKED_TAGS_RE |
RegExp |
^(script|object|embed|iframe)$ |
isBlockedTag(name) |
function | true if tag is rejected at parse-time |
CSS safety
| Symbol | Type | Description |
|---|---|---|
CSS_DANGEROUS_RE |
RegExp |
Detects expression(...) and url(javascript:) / url(vbscript:) / url(data:text) / url(data:application) |
CSS_BLOCKED_PROPS |
Set<string> |
Properties whose name is dangerous: behavior, -ms-behavior |
isSafeCss(prop, val) |
function | true if the style.setProperty(prop, val) call is acceptable |
Examples
const sp = runtime.resolve('secPolicy');
// URL
sp.isSafeUrl('https://example.com'); // → true
sp.isSafeUrl('javascript:alert(1)'); // → false
sp.isSafeUrl('JAVA\tSCRIPT:alert(1)'); // → false (obfuscation neutralised)
sp.isSafeUrl('/foo/bar'); // → true (relative)
sp.isSafeUrl('data:image/png;base64,…'); // → true (image, allowed by default)
sp.isSafeUrl('data:text/html,…'); // → false
sp.isSafeUrl('intent:#Intent;'); // → false (unknown scheme)
// Custom restrictions
sp.isSafeUrl('mailto:a@b.com', ['http', 'https']); // → false
sp.isSafeUrl('data:image/png;…', undefined, false); // → false
// Clobbering
sp.isClobberValue('id', 'cookie'); // → true
sp.isClobberValue('class', 'cookie'); // → false (class is not clobberable)
// Attributes
sp.isEventAttr('onclick'); // → true
sp.isEventAttr('Onclick'); // → true
sp.isSafeAttrName('data-foo'); // → true
sp.isSafeAttrName('1-bad'); // → false (does not start with a letter)
// Tags
sp.isBlockedTag('script'); // → true
sp.isBlockedTag('div'); // → false
// CSS
sp.isSafeCss('color', 'red'); // → true
sp.isSafeCss('background', 'url(javascript:alert(1))'); // → false
sp.isSafeCss('behavior', 'whatever'); // → false
Worker Usage
Pure module with no DOM or timers — usable as-is in a Worker via dependencies: ['secPolicy'].
Notes
- Internal consumers:
template(DOM render),render(SSR),parser(parse-time),sanitize,dom(isSafeCssinstyleApply). - Policy evolution: update the constant or test in
secPolicy.js, add a test — all internal consumers inherit the change automatically. - SSR back-end mirror:
packages/back/shared/src/ssr/{parser,render}.tscarry inline copies (self-contained, no fw runtime dependency) — update them explicitly. - The module is stateless and has no mutating methods: all functions are pure.
See also
- Framework security — threat model overview and defences
- Rendering pipeline — where the security policy is applied
- SSR back-end port:
packages/back/shared/src/ssr/{parser,render}.ts(self-contained, secPolicy mirror)