Skip to content

Trusted types tests #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: trusted-types
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"@types/semver": "^6.0.2",
"@types/shelljs": "^0.8.6",
"@types/systemjs": "0.19.32",
"@types/trusted-types": "^1.0.6",
"@types/yaml": "^1.9.7",
"@types/yargs": "^15.0.5",
"@webcomponents/custom-elements": "^1.1.0",
Expand Down
1 change: 1 addition & 0 deletions packages/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ts_library(
deps = [
"//packages/zone.js/lib:zone_d_ts",
"@npm//@types/hammerjs",
"@npm//@types/trusted-types",
],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class CssKeyframesDriver implements AnimationDriver {
keyframeStr += `}\n`;

const kfElm = document.createElement('style');
kfElm.innerHTML = keyframeStr;
kfElm.textContent = keyframeStr;
return kfElm;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/compiler/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ ts_library(
"src/**/*.ts",
],
),
deps = [
"//packages:types",
]
)

ng_package(
Expand Down
60 changes: 60 additions & 0 deletions packages/compiler/src/output/jit_trusted_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

/**
* The Trusted Types policy used by Angular, or null if Trusted Types are not
* enabled/supported, or undefined if the policy has not been created yet.
*/
let trustedTypesPolicyForJit: TrustedTypePolicy|null|undefined;

/**
* Returns the Trusted Types policy used by Angular, or null if Trusted Types
* are not enabled/supported. The first call to this function will create the
* policy.
*/
function getTrustedTypesPolicyForJit(): TrustedTypePolicy|null {
if (trustedTypesPolicyForJit === undefined) {
trustedTypesPolicyForJit = null;
if (typeof window !== 'undefined') {
try {
trustedTypesPolicyForJit = window.trustedTypes?.createPolicy('angular#jit', {
createHTML: (s: string) => s,
createScript: (s: string) => s,
createScriptURL: (s: string) => s
}) ??
null;
} catch (e) {
// trustedTypes.createPolicy throws if called with a name that is already
// registered, even in report-only mode. Until the API changes, catch the
// error not to break the applications functionally. In such case, the
// code will fall back to using strings.
console.log(e);
}
}
}
return trustedTypesPolicyForJit;
}

export function trustedFunctionForJit(...args: string[]): Function {
// return new Function(...args.map((a) => {
// return (getTrustedTypesPolicyForJit()?.createScript(a) ?? a) as string;
// }));

// Workaround for a Trusted Type bug in Chrome 83. Use the above when the Angular test suite uses
// Chrome 84.
const fnArgs = args.slice(0, -1).join(',');
const fnBody = args.pop()!.toString();
const body = `(function anonymous(${fnArgs}
) { ${fnBody}
}).bind(globalThis)`;
const res =
eval((getTrustedTypesPolicyForJit()?.createScript(body) ?? body) as string) as Function;
res.toString = () => body; // To fix sourcemaps
return res as Function;
}
5 changes: 3 additions & 2 deletions packages/compiler/src/output/output_jit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {CompileReflector} from '../compile_reflector';

import {EmitterVisitorContext} from './abstract_emitter';
import {AbstractJsEmitterVisitor} from './abstract_js_emitter';
import {trustedFunctionForJit} from './jit_trusted_types';
import * as o from './output_ast';

/**
Expand Down Expand Up @@ -69,11 +70,11 @@ export class JitEvaluator {
// function anonymous(a,b,c
// /**/) { ... }```
// We don't want to hard code this fact, so we auto detect it via an empty function first.
const emptyFn = new Function(...fnArgNames.concat('return null;')).toString();
const emptyFn = trustedFunctionForJit(...fnArgNames.concat('return null;')).toString();
const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
fnBody += `\n${ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()}`;
}
const fn = new Function(...fnArgNames.concat(fnBody));
const fn = trustedFunctionForJit(...fnArgNames.concat(fnBody));
return this.executeFunction(fn, fnArgValues);
}

Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/render3/r3_identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,4 +320,5 @@ export class Identifiers {
static sanitizeUrl: o.ExternalReference = {name: 'ɵɵsanitizeUrl', moduleName: CORE};
static sanitizeUrlOrResourceUrl:
o.ExternalReference = {name: 'ɵɵsanitizeUrlOrResourceUrl', moduleName: CORE};
static trustHtml: o.ExternalReference = {name: 'ɵɵtrustHtml', moduleName: CORE};
}
10 changes: 9 additions & 1 deletion packages/compiler/src/render3/view/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1296,7 +1296,15 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
if (attr.name === NG_PROJECT_AS_ATTR_NAME) {
ngProjectAsAttr = attr;
}
attrExprs.push(...getAttributeNameLiterals(attr.name), asLiteral(attr.value));

let value = asLiteral(attr.value);
// TODO: Use the security contract module
if (attr.name === 'srcdoc') {
value = o.taggedTemplate(
o.importExpr(R3.trustHtml),
new o.TemplateLiteral([new o.TemplateLiteralElement(attr.value, attr.valueSpan)], []));
}
attrExprs.push(...getAttributeNameLiterals(attr.name), value);
});

// Keep ngProjectAs next to the other name, value pairs so we can verify that we match
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/core_render3_private_export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ export {
ɵɵsanitizeStyle,
ɵɵsanitizeUrl,
ɵɵsanitizeUrlOrResourceUrl,
ɵɵtrustHtml,
} from './sanitization/sanitization';
export {
noSideEffects as ɵnoSideEffects,
Expand Down
18 changes: 14 additions & 4 deletions packages/core/src/reflection/reflection_capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ export function isDelegateCtor(typeStr: string): boolean {

export class ReflectionCapabilities implements PlatformReflectionCapabilities {
private _reflect: any;
private _trustedFunction?: (...args: string[]) => Function;

constructor(reflect?: any) {
constructor(reflect?: any, trustedFunction?: (...args: string[]) => Function) {
this._reflect = reflect || global['Reflect'];
this._trustedFunction = trustedFunction;
}

isReflectionEnabled(): boolean {
Expand Down Expand Up @@ -279,17 +281,25 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
}

getter(name: string): GetterFn {
return <GetterFn>new Function('o', 'return o.' + name + ';');
const functionBody = 'return o.' + name + ';';
return <GetterFn>(
this._trustedFunction ? this._trustedFunction('o', functionBody) :
new Function('o', functionBody));
}

setter(name: string): SetterFn {
return <SetterFn>new Function('o', 'v', 'return o.' + name + ' = v;');
const functionBody = 'return o.' + name + ' = v;';
return <SetterFn>(
this._trustedFunction ? this._trustedFunction('o', 'v', functionBody) :
new Function('o', 'v', functionBody));
}

method(name: string): MethodFn {
const functionBody = `if (!o.${name}) throw new Error('"${name}" is undefined');
return o.${name}.apply(o, args);`;
return <MethodFn>new Function('o', 'args', functionBody);
return <MethodFn>(
this._trustedFunction ? this._trustedFunction('o', 'args', functionBody) :
new Function('o', 'args', functionBody));
}

// There is not a concept of import uri in Js, but this is useful in developing Dart applications.
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/render3/i18n/i18n_parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import '../../util/ng_i18n_closure_mode';

import {getTemplateContent, SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS} from '../../sanitization/html_sanitizer';
import {getInertBodyHelper} from '../../sanitization/inert_body';
import {getTrustedTypesPolicy} from '../../sanitization/trusted_types';
import {_sanitizeUrl, sanitizeSrcset} from '../../sanitization/url_sanitizer';
import {addAllToArray} from '../../util/array_utils';
import {assertEqual} from '../../util/assert';
Expand Down Expand Up @@ -524,7 +525,7 @@ export function parseICUBlock(pattern: string): IcuExpression {
function parseIcuCase(
unsafeHtml: string, parentIndex: number, nestedIcus: IcuExpression[], tIcus: TIcu[],
expandoStartIndex: number): IcuCase {
const inertBodyHelper = getInertBodyHelper(getDocument());
const inertBodyHelper = getInertBodyHelper(getDocument(), getTrustedTypesPolicy());
const inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
if (!inertBodyElement) {
throw new Error('Unable to generate inert body element');
Expand Down
13 changes: 9 additions & 4 deletions packages/core/src/render3/interfaces/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,18 @@ export interface ProceduralRenderer3 {
parentNode(node: RNode): RElement|null;
nextSibling(node: RNode): RNode|null;

setAttribute(el: RElement, name: string, value: string, namespace?: string|null): void;
setAttribute(
el: RElement, name: string, value: string|TrustedHTML|TrustedScript|TrustedScriptURL,
namespace?: string|null): void;
removeAttribute(el: RElement, name: string, namespace?: string|null): void;
addClass(el: RElement, name: string): void;
removeClass(el: RElement, name: string): void;
setStyle(
el: RElement, style: string, value: any,
flags?: RendererStyleFlags2|RendererStyleFlags3): void;
removeStyle(el: RElement, style: string, flags?: RendererStyleFlags2|RendererStyleFlags3): void;
setProperty(el: RElement, name: string, value: any): void;
setProperty(el: RElement, name: string|TrustedHTML|TrustedScript|TrustedScriptURL, value: any):
void;
setValue(node: RText|RComment, value: string): void;

// TODO(misko): Deprecate in favor of addEventListener/removeEventListener
Expand Down Expand Up @@ -157,9 +160,11 @@ export interface RElement extends RNode {
classList: RDomTokenList;
className: string;
textContent: string|null;
setAttribute(name: string, value: string): void;
setAttribute(name: string, value: string|TrustedHTML|TrustedScript|TrustedScriptURL): void;
removeAttribute(name: string): void;
setAttributeNS(namespaceURI: string, qualifiedName: string, value: string): void;
setAttributeNS(
namespaceURI: string, qualifiedName: string,
value: string|TrustedHTML|TrustedScript|TrustedScriptURL): void;
addEventListener(type: string, listener: EventListener, useCapture?: boolean): void;
removeEventListener(type: string, listener?: EventListener, options?: boolean): void;

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/render3/interfaces/sanitization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
/**
* Function used to sanitize the value before writing it into the renderer.
*/
export type SanitizerFn = (value: any, tagName?: string, propName?: string) => string;
export type SanitizerFn = (value: any, tagName?: string, propName?: string) =>
string|TrustedHTML|TrustedScript|TrustedScriptURL;
1 change: 1 addition & 0 deletions packages/core/src/render3/jit/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,5 @@ export const angularCoreEnv: {[name: string]: Function} =
'ɵɵsanitizeScript': sanitization.ɵɵsanitizeScript,
'ɵɵsanitizeUrl': sanitization.ɵɵsanitizeUrl,
'ɵɵsanitizeUrlOrResourceUrl': sanitization.ɵɵsanitizeUrlOrResourceUrl,
'ɵɵtrustHtml': sanitization.ɵɵtrustHtml,
}))();
25 changes: 13 additions & 12 deletions packages/core/src/sanitization/bypass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export interface SafeUrl extends SafeValue {}
export interface SafeResourceUrl extends SafeValue {}


abstract class SafeValueImpl implements SafeValue {
constructor(public changingThisBreaksApplicationSecurity: string) {}
abstract class SafeValueImpl<T> implements SafeValue {
constructor(public changingThisBreaksApplicationSecurity: string|T) {}

abstract getTypeName(): string;

Expand All @@ -69,35 +69,35 @@ abstract class SafeValueImpl implements SafeValue {
}
}

class SafeHtmlImpl extends SafeValueImpl implements SafeHtml {
class SafeHtmlImpl extends SafeValueImpl<TrustedHTML> implements SafeHtml {
getTypeName() {
return BypassType.Html;
}
}
class SafeStyleImpl extends SafeValueImpl implements SafeStyle {
class SafeStyleImpl extends SafeValueImpl<string> implements SafeStyle {
getTypeName() {
return BypassType.Style;
}
}
class SafeScriptImpl extends SafeValueImpl implements SafeScript {
class SafeScriptImpl extends SafeValueImpl<TrustedScript> implements SafeScript {
getTypeName() {
return BypassType.Script;
}
}
class SafeUrlImpl extends SafeValueImpl implements SafeUrl {
class SafeUrlImpl extends SafeValueImpl<string> implements SafeUrl {
getTypeName() {
return BypassType.Url;
}
}
class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl {
class SafeResourceUrlImpl extends SafeValueImpl<TrustedScriptURL> implements SafeResourceUrl {
getTypeName() {
return BypassType.ResourceUrl;
}
}

export function unwrapSafeValue(value: SafeValue): string;
export function unwrapSafeValue<T>(value: SafeValue): string|T;
export function unwrapSafeValue<T>(value: T): T;
export function unwrapSafeValue<T>(value: T|SafeValue): T {
export function unwrapSafeValue<T>(value: any): string|T {
return value instanceof SafeValueImpl ? value.changingThisBreaksApplicationSecurity as any as T :
value as any as T;
}
Expand Down Expand Up @@ -137,7 +137,7 @@ export function getSanitizationBypassType(value: any): BypassType|null {
* @param trustedHtml `html` string which needs to be implicitly trusted.
* @returns a `html` which has been branded to be implicitly trusted.
*/
export function bypassSanitizationTrustHtml(trustedHtml: string): SafeHtml {
export function bypassSanitizationTrustHtml(trustedHtml: string|TrustedHTML): SafeHtml {
return new SafeHtmlImpl(trustedHtml);
}
/**
Expand All @@ -161,7 +161,7 @@ export function bypassSanitizationTrustStyle(trustedStyle: string): SafeStyle {
* @param trustedScript `script` string which needs to be implicitly trusted.
* @returns a `script` which has been branded to be implicitly trusted.
*/
export function bypassSanitizationTrustScript(trustedScript: string): SafeScript {
export function bypassSanitizationTrustScript(trustedScript: string|TrustedScript): SafeScript {
return new SafeScriptImpl(trustedScript);
}
/**
Expand All @@ -185,6 +185,7 @@ export function bypassSanitizationTrustUrl(trustedUrl: string): SafeUrl {
* @param trustedResourceUrl `url` string which needs to be implicitly trusted.
* @returns a `url` which has been branded to be implicitly trusted.
*/
export function bypassSanitizationTrustResourceUrl(trustedResourceUrl: string): SafeResourceUrl {
export function bypassSanitizationTrustResourceUrl(trustedResourceUrl: string|
TrustedScriptURL): SafeResourceUrl {
return new SafeResourceUrlImpl(trustedResourceUrl);
}
7 changes: 4 additions & 3 deletions packages/core/src/sanitization/html_sanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import {isDevMode} from '../util/is_dev_mode';
import {getInertBodyHelper, InertBodyHelper} from './inert_body';
import {getTrustedTypesPolicy} from './trusted_types';
import {_sanitizeUrl, sanitizeSrcset} from './url_sanitizer';

function tagSet(tags: string): {[k: string]: boolean} {
Expand Down Expand Up @@ -242,10 +243,10 @@ let inertBodyHelper: InertBodyHelper;
* Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to
* the DOM in a browser environment.
*/
export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string {
export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string|TrustedHTML {
let inertBodyElement: HTMLElement|null = null;
try {
inertBodyHelper = inertBodyHelper || getInertBodyHelper(defaultDoc);
inertBodyHelper = inertBodyHelper || getInertBodyHelper(defaultDoc, getTrustedTypesPolicy());
// Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime).
let unsafeHtml = unsafeHtmlInput ? String(unsafeHtmlInput) : '';
inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
Expand Down Expand Up @@ -274,7 +275,7 @@ export function _sanitizeHtml(defaultDoc: any, unsafeHtmlInput: string): string
'WARNING: sanitizing HTML stripped some content, see http://g.co/ng/security#xss');
}

return safeHtml;
return getTrustedTypesPolicy()?.createHTML(safeHtml) ?? safeHtml;
} finally {
// In case anything goes wrong, clear out inertElement to reset the entire DOM structure.
if (inertBodyElement) {
Expand Down
Loading