On this page
A piece of code with the words "Security Alert" on it. Image by Elchinator from Pixabay.

Chrome DevTools XPath safety – escape the JavaScript literal not the XPath

Prevent RCE when you inject user-XPath into inspectedWindow.eval by escaping the JavaScript literal, not the XPath itself.

XPath, or XML Path Language, is a query language designed to navigate through elements and attributes in an XML document. The document.evaluate() method in JavaScript allows developers to execute XPath expressions against an XML document, returning nodes or values based on the specified query. While this functionality is powerful, it also poses risks of injection attacks if user input is not properly handled.

When a simple XPath field becomes Remote Code Execution (RCE)

You ship a DevTools panel that lets developers paste an XPath to highlight an element.

One user types:

"]}); document.documentElement.innerHTML='<iframe src="javascript:alert(document.cookie)"></iframe>';({

Your code happily concatenates that string into inspectedWindow.eval and arbitrary JavaScript runs in the inspected page. The Chrome Web Store rejection e-mail lands minutes later.

By the end of this article you’ll have a few lines encoder that passes ESLint, keeps JSON.parse happy, and satisfies the strictest security audit.

Why document.evaluate itself is innocent

XPath 1.0 has no eval(), no inline scripts, no entity expansion tricks. The injection risk appears before the XPath engine ever sees the text-while the JavaScript parser is still tokenising the literal that contains your expression.

The JavaScript meta-character trap

These bytes let an attacker break out of a JavaScript string literal:

Code pointsCharacterWhy dangerous
U+0000 – U+001Fcontrol codeseslint screams; some are illegal in JSON
U+0022"terminates the literal
U+005C\starts an escape sequence

JSON.stringify is not enough as it produces JSON, not a JavaScript literal. A lone U+2028 or U+2029 is valid JSON but still breaks old JavaScript parsers, and it does nothing for control codes below U+0020.

Example of insecure injecting xpath
// ❌  NEVER DO THIS
chrome.devtools.inspectedWindow.eval(
  `document.evaluate("${xpath}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)`
);

Sanitizing XPath

A safe encoder in a few lines:

Sanitizing XPath implementation
function sanitizeXPath(str) {
  return JSON.stringify(str)
    .replace(/\u2028|\u2029/g, (ch) => {
      return `\\u${ch.charCodeAt(0).toString(16).padStart(4, '0')}`;
  })
}
  • The U+2028/U+2029 are valid in JSON but are line separators in JavaScript source (and historically break older parsers). The extra two escapes remove that footgun.
  • Four-digit \uXXXX keeps JSON.parse / ESLint happy.
  • No dependencies, tree-shake friendly.

Usage:

Example usage of sanitizeXPath
const literal = sanitizeXPath(userXPath);
const expr = `document.evaluate(${literal}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)`;
chrome.devtools.inspectedWindow.eval(expr, resultCallback);

Integration recipes

Chrome extension inspectedWindow.eval
const safe = sanitizeXPath(userXPath);
chrome.devtools.inspectedWindow.eval(safe, resultCallback);
new Function() inside a content script
const fn = new Function('document',
  `return document.evaluate("${sanitizeXPath(xpath)}",document,null,9,null).singleNodeValue`
);
Server-side template (Django / PHP / ASP.NET)
const xpath = JSON.parse("{{ xpath_escaped|json_encode }}"); // \uXXXX already

Solutions that fail security review

  • Regex whitelist [a-zA-Z0-9/_\[\]] – breaks valid predicates, still allows injection via ].
  • Back-tick template without tagging – newline injection, `$` interpolation.
  • We only support Chrome 120+ so we can use eval safely – no, you can’t.

Short security checklist

  • Escape ", \, U+0000-U+001F.
  • Escape U+2028 and U+2029.
  • Do not place user input into template literals without tagging / escaping.
  • Use JSON.stringify + replace or the sanitizeXPath routine, include unit tests.
  • Continuous integration (CI) test that feeds malicious payloads and asserts no arbitrary JavaScript execution.
  • Document threat model and note XPath-level DoS risk separately.
  • Keep integration examples consistent (always embed the quoted literal produced by your function and don’t add extra quotes).

Final words

XPath is a powerful ally for hunting nodes in the DOM, but the moment you ferry a user-supplied expression through a JavaScript string you have left the cosy world of queries and entered the mine-field of parsers.

A single unescaped quote or back-slash is all it takes to turn document.evaluate into eval and grant an attacker the same privileges your extension enjoys.

A small encoder in this article closes that door forever: it speaks JSON, keeps ESLint quiet, and survives the strictest security audit Chrome can throw at you. Copy it, add the unit tests to your CI pipeline, and ship your DevTools feature with confidence.

Remember: you are not sanitizing XPath – you are sanitizing the JavaScript literal that carries it. Keep the transport layer safe, and XPath will stay the helpful navigator it was meant to be.

This sanitization technique is already protecting users in the SiteLint – Web Audit Tools Chrome extension.

Related posts

Comments

Leave a Reply

Search in sitelint.com

Is your site slow?

Discover performance bottlenecks to provide a better customer experience and meet your company’s revenue targets.