Implement block container feature for security - secure by default for all renderers#78
Implement block container feature for security - secure by default for all renderers#78
Conversation
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
…ment Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
|
@copilot i was asking more for a report not an implementation. Should shadowdom be the default? i was thinking in the concept of our style capabilities - does it open a opportunity for a clickjack attack? could we also render inside of a block container where rendering will not escap, but it can still inherit style from the page? |
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
You're right - I misunderstood the ask. I've created a comprehensive analysis report in Key findings:
Recommendation: Skip shadow DOM implementation entirely, or make it opt-in if there's user demand. Consider documenting the security model (templates must be developer-controlled) more explicitly. |
|
@copilot i was thinking of the type of attack where a malicious hyperlink or text can be abolutely positioned to cover some existing button. |
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Good point - I've added analysis of that specific attack vector in commit 133f5ff. TL;DR: Yes, this attack is possible if a developer writes a vulnerable template that combines positioning styles with user-controlled links. But this is a template design issue, not a library vulnerability. The attack: // Developer creates this template (vulnerable pattern)
{
a: {
href: "{{userLink}}", // User controls destination
style: { "position": "absolute", "z-index": "9999" },
$children: ["{{userText}}"]
}
}
// Attacker's link can now overlay legitimate buttonsWhy treebark can't prevent this:
Shadow DOM wouldn't help: Elements in the same shadow root can still overlay each other. Would only help if sensitive UI and user content were in separate shadow roots (doesn't match typical usage). Recommended solution: Document the dangerous pattern prominently. Added comprehensive examples in the report showing:
The report now includes a mitigation options analysis and recommends documentation over CSS restrictions. |
|
@copilot i was thinking more along the lines of an example where there is a well-known sign in link at the top right of a page, then a template absolutely positions /evilsite above it. |
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Excellent point - that's a more serious attack vector. I've revised the analysis in commit 0a44730. Critical distinction: Scenario 1: Template overlaying its own elements (less serious) Your example is Scenario 2: // Page has sign-in link at top right
// Treebark template with user link positioned to overlay it
{
a: {
href: "{{userLink}}", // Attacker controls: https://evil.com
style: { "position": "fixed", "top": "0", "right": "0", "z-index": "9999" }
}
}
// Result: Overlays the real sign-in linkShadow DOM DOES help here: Creates stacking context isolation - z-index and positioning inside shadow DOM can't reach page elements. The positioned content is confined to the shadow boundary. Revised recommendation: Implement shadow DOM as opt-in security feature (not default for backward compatibility, but strongly recommended for user-controlled content). This provides meaningful protection against the page overlay attack while keeping positioning usable within the shadow boundary. The report now analyzes both scenarios separately and recommends shadow DOM implementation. |
|
@copilot remember the href: "{{userLink}}" can actually be just hardcoded string to evilsite. Given what I've said , can you also talk about the block container (sort of a half step to shadow dom, templates cant draw out of their box but can inherit style. also, remember that a scenario was that templates can be in a database of end-user generated content, not expecting that templates are developer made. |
|
@copilot ok great we are on the same page. I suppose we should revisit all your code changes. perhaps best yet (again) lets back out all of them and start over given what we now know. And I may decide yet to make it a breaking change. |
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Done in commit f12cc9c. Reverted all code implementation:
Current state: Branch contains only the two analysis reports (SHADOWDOM_REPORT.md and SHADOWDOM_SECURITY_ANALYSIS.md). No code changes. Clean slate for fresh implementation based on the analysis findings. |
|
@copilot ok please implement block container |
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Implemented in commits 58f1074 (code) and a767b5c (docs). Implementation:
Tests: 8 new tests, all 481 tests pass (473 existing + 8 new) Usage: renderToDOM({ template: userTemplate }, { useBlockContainer: true });Documentation added to both README files explaining security benefits and when to use. |
|
@copilot so the string version doesnt need it? could it be added? |
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Good catch! Added in commit 947442f. The string version absolutely needs it - when Implementation:
Usage: const html = renderToString({ template: userTemplate }, { useBlockContainer: true });
// Output includes containment wrapperBlock container now provides security for both server-side (renderToString) and client-side (renderToDOM) rendering. |
|
@copilot lets make useBlockContainer true the default, and they have to explicitly say false if they dont want it and understand. |
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Co-authored-by: danmarshall <11507384+danmarshall@users.noreply.github.com>
Done in commits a6b96c4 (tests updated by task agent), a767b5c (markdown-it plugin), and df1907b (documentation). Block container is now the default for:
Implementation:
Usage: // Default (secure)
renderToDOM({ template });
// Opt-out (trusted templates only)
renderToDOM({ template }, { useBlockContainer: false });markdown-it plugin also passes through the option, defaulting to secure. |
Provides comprehensive security analysis reports on whether shadow DOM should be added to treebark's rendering functions, addressing security implications and alternative approaches. Critical revision addresses user-generated templates as a common use case and recommends block containers as the optimal default solution.
This PR includes both security analysis reports AND the block container implementation for renderToDOM, renderToString, and the markdown-it plugin, with block container enabled by default for security.
Report Documents
This PR includes two analysis documents:
SHADOWDOM_SECURITY_ANALYSIS.md- Crisp, focused security analysis (~130 lines)SHADOWDOM_REPORT.md- Comprehensive detailed analysis (~1000+ lines)Implementation
Block Container Feature - Secure by Default:
useBlockContaineroption to RenderOptions interface (types.ts) - defaults totruerenderToDOMfunction (dom.ts) to wrap content in block container by defaultrenderToStringfunction (string.ts) to wrap content in block container by defaultcontain: content+isolation: isolatefor stacking context isolationdata-treebark-container="true"attributeUsage (Default Behavior):
Key Findings
Three Attack Scenarios Identified:
Critical Insight: Templates can be user-generated content stored in databases (blogs, forums, CMS, wikis). Users control entire template structure and can hardcode malicious links without needing interpolation. Both server-side (renderToString) and client-side (renderToDOM) rendering are vulnerable.
Solution: Block Container (Secure by Default)
Block Container (Implemented as Default for All Renderers):
contain: content+isolation: isolateUsage Guidelines
useBlockContainer: false(understand the risk)Block Container Implementation
Testing
true, existing code will get wrapper div (can opt-out withuseBlockContainer: false)The analysis and implementation provide critical protection against positioning-based overlay attacks while maintaining usability for content rendering use cases. Block container is now the secure default for all rendering functions (renderToString, renderToDOM, and markdown-it plugin).
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.