fix: prototype pollution in mergeObj and URL scheme validation in Drafty entity props - #91
Merged
Merged
Conversation
Copilot created this pull request from a session on behalf of
or-else
July 5, 2026 14:00
View session
or-else
marked this pull request as ready for review
July 5, 2026 14:13
There was a problem hiding this comment.
Pull request overview
This PR addresses two security issues in the Tinode JS client: hardening mergeObj against prototype pollution when merging server-controlled JSON, and adding URL scheme validation for Drafty entity attributes to block javascript:/data: style injections.
Changes:
- Hardened
mergeObjto avoid attacker-controlled constructors and prototype-chain traversal; added explicit key skips for prototype-pollution vectors. - Added
sanitizeUrland applied it to Drafty entity attribute generation (LN,BN,AU,IM,VD) andDrafty.getDownloadUrl. - Added regression tests for prototype pollution and URL scheme sanitization.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/utils.js | Updates mergeObj to avoid prototype pollution vectors during recursive merges. |
| src/utils.test.js | Adds tests to assert mergeObj does not allow __proto__/constructor-based pollution. |
| src/drafty.js | Introduces sanitizeUrl and applies it to Drafty props and download URL generation. |
| src/drafty.test.js | Adds tests validating URL scheme allowlist behavior through Drafty.attrValue and getDownloadUrl. |
| package-lock.json | Updates transitive dependency resolutions (e.g., argparse, js-yaml). |
Comments suppressed due to low confidence (2)
src/drafty.js:439
- IM.props uses sanitizeUrl(data.ref) for src selection, but still uses raw data.ref to decide whether to report external size. If data.ref is present but blocked (unsafe scheme), metadata like data-size will be computed as if it's an external resource even though src falls back to embedded/base64. Compute safeRef once and use it consistently.
props: data => {
if (!data) return null;
return {
// Temporary preview, or permanent preview, or external link.
src: base64toDataUrl(data._tempPreview, data.mime) ||
sanitizeUrl(data.ref) || base64toObjectUrl(data.val, data.mime, Drafty.logger),
title: data.name,
alt: data.name,
'data-width': data.width,
'data-height': data.height,
'data-name': data.name,
'data-size': data.ref ? (data.size | 0) : (data.val ? ((data.val.length * 0.75) | 0) : (data.size | 0)),
'data-mime': data.mime,
};
src/drafty.js:509
- VD.props computes safeRef but still uses raw data.ref to decide data-size. If data.ref is present but blocked by sanitizeUrl, data-size will be treated as external even though data-src falls back to embedded/base64. Use safeRef consistently here.
'data-preview': poster,
'data-duration': data.duration | 0,
'data-name': data.name,
'data-size': data.ref ? (data.size | 0) : (data.val ? ((data.val.length * 0.75) | 0) : (data.size | 0)),
'data-mime': data.mime,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+291
to
+304
| function sanitizeUrl(url) { | ||
| if (!url || typeof url != 'string') { | ||
| return url; | ||
| } | ||
| // Relative URLs have no scheme and are safe. | ||
| if (!/^\s*([a-z][a-z0-9+.-]*:|\/\/)/im.test(url)) { | ||
| return url; | ||
| } | ||
| // Among absolute URLs allow only http, https, and ftp. | ||
| if (/^(https?|ftp):\/\//i.test(url)) { | ||
| return url; | ||
| } | ||
| return null; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two medium-severity vulnerabilities: prototype pollution via server-controlled JSON in
mergeObj, and missing URL scheme validation in Drafty entitypropsallowingjavascript:/data:injection.src/utils.js—mergeObjprototype pollutiondst = src.constructor()withdst = {}—src.constructoris attacker-controlled when merging server JSONfor...in+hasOwnPropertywithObject.keys()— avoids prototype chain traversal__proto__andconstructorkeys — assigningdst['__proto__']triggers theObject.prototype.__proto__setter, polluting all objectssrc/drafty.js— URL scheme validationAdded
sanitizeUrl(url)helper; returnsnullfor any scheme that isn'thttp,https, orftp(relative URLs pass through unchanged, protocol-relative//URLs are blocked):Applied to all
propsfunctions that return server-supplied URLs:LN,BN,AU,IM,VD, andDrafty.getDownloadUrl.