Description
The noent: true option in libxmljs or libxmljs2 expands entity references into their values. If untrusted XML declares an external entity and the parser can load it, parsing may access local files or cause server-side request forgery (SSRF). Actual file and network access, and expansion limits, depend on the bundled libxml2 version, build and loader configuration.
Potential impact
- External entities may read local files, such as
/etc/passwd, and disclose their contents in responses or logs. - The parser may access internal or metadata services, such as
http://169.254.169.254, and expose sensitive information. - Excessive or recursive entity expansion may exhaust CPU or memory and disrupt the service.
Remediation
- Keep
noentdisabled or set it tofalsefor untrusted XML. - Reject DTDs when they are not needed. Also check additional options and custom loaders that read external DTDs or entities.
- The XML parser handles predefined references such as
&and<. Do not decode parsed text again with an HTML entity decoder. - Limit input size and accepted formats, handle parsing errors, and check expansion, memory and execution-time limits.
- Minimize network and file permissions.
noent: falsealone does not block every external-resource access path.
Examples
Before
const express = require('express');
const libxml = require('libxmljs');
const app = express();
app.use(express.text({ type: '*/*' }));
// Parse externally supplied XML with noent: true
app.post('/api/parse-xml', (req, res) => {
const xmlPayload = String(req.body || '');
// noent: true may expand external entities, enabling file access, SSRF or DoS
const doc = libxml.parseXmlString(xmlPayload, { noent: true });
res.json({ root: doc.root().name() });
});
app.listen(3000);
After
const express = require('express');
const libxml = require('libxmljs');
const app = express();
app.use(express.text({ type: '*/*' }));
// Disable noent and reject DTDs
app.post('/api/parse-xml', (req, res) => {
const xmlPayload = String(req.body || '');
// Reject DTD declarations, including external entity declarations
if (/<!DOCTYPE/i.test(xmlPayload)) {
return res.status(400).send('DTD is not allowed');
}
// Keep noent disabled explicitly
const doc = libxml.parseXmlString(xmlPayload, { noent: false });
// Do not decode the parser output a second time
const textContent = doc.root().text();
res.json({ root: doc.root().name(), text: textContent.slice(0, 200) });
});
app.listen(3000);
The first example enables entity expansion. The second rejects DTD declarations in the string decoded by Express and disables noent. Do not assume this simple check protects every XML input path. Verify the parser's external-loading settings and add error responses for malformed XML. The example uses the parsed text directly without a second HTML entity decoding step.