Code review example: bypassSecurityTrustHtml on user content: the sanitizer was the defense in Angular
Retired from the DiffDojo practice pool in October 2026; the review below is the canonical answer. An AI wrote this pull request for the task underneath. Read the diff and decide what you would say before you scroll to the review.
The task
Render user bios with markdown
- Render the user's bio with markdown formatting (bold, links, lists).
- Bios are user-provided and shown on public profile pages.
- User content must never be able to execute script.
Adds the UserBio component: parses the bio markdown and binds the resulting HTML. Uses Angular's DomSanitizer so the [innerHTML] binding accepts the rendered markup without warnings.
The diff
src/app/components/user-bio.ts+16The review
Issue 1: bypassSecurityTrustHtml on user content: the sanitizer was the defense
bypassSecurityTrustHtmlmeans "render this without protection". Angular sanitizes[innerHTML]bindings by default, that built-in sanitizer was the exact defense the third requirement needs, and the bypass switches it off for content users type.- Markdown is not an escape mechanism.
markedpasses raw HTML through by default: a bio of<img src=x onerror="...">comes out ofmarked.parseintact, gets blessed as safe, and runs in every visitor's browser. Stored XSS on a public page. - The API name warned about it. Methods called
bypassSecurityTrust*exist for content the developer authors, never for content users submit.
The fix: drop the bypass and the sanitizer entirely, bind the parsed string and Angular sanitizes it on render. (For richer markup control, sanitize with DOMPurify before binding.)
The fix
import { Component, Input } from '@angular/core';
import { marked } from 'marked';
@Component({
selector: 'app-user-bio',
template: `<div class="bio" [innerHTML]="bioHtml"></div>`,
})
export class UserBio {
bioHtml = '';
@Input() set bio(value: string) {
// Angular sanitizes [innerHTML] bindings by default — no bypass.
this.bioHtml = marked.parse(value) as string;
}
}Why it matters
Any bypassSecurityTrust* call → ask: "who authored this content?" If the answer is "a user", it's a vulnerability regardless of what transformed the content on the way.
Real-world stakes: one crafted bio and every profile visitor runs attacker script, session theft, account takeover, worm-style spread to more bios. Models suggest the bypass constantly because it makes Angular's sanitization warning go away, and making warnings go away looks like fixing them.
The verdict
Request changes. bypassSecurityTrustHtml on user content: the sanitizer was the defense
Try a live one
Angular PRs you have not seen the answer to.
Leave inline comments and a verdict, then see what you caught. Free, no signup.
Review an Angular PR →