Skip to content

fix: prototype pollution in mergeObj and URL scheme validation in Drafty entity props - #91

Merged
or-else merged 3 commits into
develfrom
copilot/find-security-vulnerabilities
Jul 5, 2026
Merged

fix: prototype pollution in mergeObj and URL scheme validation in Drafty entity props#91
or-else merged 3 commits into
develfrom
copilot/find-security-vulnerabilities

Conversation

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Two medium-severity vulnerabilities: prototype pollution via server-controlled JSON in mergeObj, and missing URL scheme validation in Drafty entity props allowing javascript:/data: injection.

src/utils.jsmergeObj prototype pollution

  • Replace dst = src.constructor() with dst = {}src.constructor is attacker-controlled when merging server JSON
  • Replace for...in + hasOwnProperty with Object.keys() — avoids prototype chain traversal
  • Explicitly skip __proto__ and constructor keys — assigning dst['__proto__'] triggers the Object.prototype.__proto__ setter, polluting all objects

src/drafty.js — URL scheme validation

Added sanitizeUrl(url) helper; returns null for any scheme that isn't http, https, or ftp (relative URLs pass through unchanged, protocol-relative // URLs are blocked):

// "javascript:alert(1)"  → null
// "data:text/html,..."   → null
// "//evil.com/x"         → null
// "https://example.com"  → "https://example.com"
// "/v0/file/s/abc.jpg"   → "/v0/file/s/abc.jpg"

Applied to all props functions that return server-supplied URLs: LN, BN, AU, IM, VD, and Drafty.getDownloadUrl.

Copilot AI requested a review from or-else July 5, 2026 14:00
@or-else
or-else marked this pull request as ready for review July 5, 2026 14:13
Copilot AI review requested due to automatic review settings July 5, 2026 14:13
@or-else
or-else merged commit 7575044 into devel Jul 5, 2026
1 check passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 mergeObj to avoid attacker-controlled constructors and prototype-chain traversal; added explicit key skips for prototype-pollution vectors.
  • Added sanitizeUrl and applied it to Drafty entity attribute generation (LN, BN, AU, IM, VD) and Drafty.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 thread src/drafty.js
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;
}
@or-else
or-else deleted the copilot/find-security-vulnerabilities branch September 6, 2026 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants