js-xml-dsig-signature-wrapping-c14n-confusion
Done. The full solution is in ~/xmldsig/SOLUTION.md, with working code and a passing test suite. Summary below.
XMLDSig signature wrapping exploits a split identity: the verifier proves a digest over whatever node Reference URI resolves to at verification time, while the application later calls getElementById again and reads a different node. The gap is opened by:
Id vs ID vs xml:id), letting a shadow element win lookup;ds:Signature elements;Canonicalization is where signed bytes are defined, so exc-C14N must render only visibly-utilized prefixes, sort namespace decls default-first then by prefix and attributes no-namespace-first then URI then local, and treat comments consistently. Comment truncation arises when C14N drops comments but the app parses around them.
verifier.js resolves IDs once, rejects ambiguity, and returns the verified node so the app never re-resolves:
buildIdIndex — case-insensitive Id/ID/id/xml:id, rejects duplicate-id and ambiguous-id.ds:Signature, and it must be inside the expected payload (ambiguous-signature, not-enveloped).Reference must be a same-document fragment resolving inside the payload subtree (outside-subtree), and at least one must bind expectedId (unbound).enveloped-signature, exc-C14N/C14N ± comments, InclusiveNamespaces PrefixList) before hashing (bad-digest).SignedInfo signature (bad-signature).#WithComments (comment-truncation).xml.js is a dependency-free parser + exc-C14N implementation covering namespace inheritance, xmlns="", the implicit xml prefix, attribute ordering, and escaping.
cd ~/xmldsig
npm test
Result: all pass — 7 canonicalization vectors, legit document accepted, and rejected: XSW1/XSW2/duplicate-ID → duplicate-id, XSW3 → not-enveloped, XSW4 → ambiguous-signature, XSW5 → outside-subtree, XSW6 → comment-truncation, tamper → bad-digest, wrong expected ID → unbound. The shipped on-disk corpus (test/corpus/, generated by test/emit.js) is re-verified by test/corpus.test.js.
ALL TESTS PASSED
CORPUS OK
Files: xml.js, verifier.js, SOLUTION.md, test/{gen,keys,emit,run,corpus.test,c14n.test}.js, test/corpus/*.xml.
# Evidence - Problem class: js-xml-dsig-signature-wrapping-c14n-confusion - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-29T16:44:55.368Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Build an XML Signature (XMLDSig) verifier in pure Node.js with no third-party dependencies that resists signature wrapping (XSW) attacks: it must bind the signature to the exact element the application later reads by ID, not merely to whatever node the Reference URI happens to resolve to. Implement exclusive canonicalization (exc-C14N) with correct namespace inheritance and attribute ordering, reject duplicated or ambiguous ID attributes, and refuse Reference URIs that resolve outside the signed subtree. Ship a test corpus of at least six known-bad documents (XSW1-6 variants, comment truncation, duplicate ID) that must all be rejected, plus a legitimate signed document that must verify.", "environment": "node20", "language": "js", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "js-xml-dsig-signature-wrapping-c14n-confusion", "provider": "openrouter", "solved_at": "2026-09-29T16:44:55.368Z", "version": "20"}Done. The full solution is in ~/xmldsig/SOLUTION.md, with working code and a passing test suite. Summary below.
XMLDSig signature wrapping exploits a split identity: the verifier proves a digest over whatever node Reference URI resolves to at verification time, while the application later calls getElementById again and reads a different node. The gap is opened by:
Id vs ID vs xml:id), letting a shadow element win lookup;ds:Signature elements;Canonicalization is where signed bytes are defined, so exc-C14N must render only visibly-utilized prefixes, sort namespace decls default-first then by prefix and attributes no-namespace-first then URI then local, and treat comments consistently. Comment truncation arises when C14N drops comments but the app parses around them.
verifier.js resolves IDs once, rejects ambiguity, and returns the verified node so the app never re-resolves:
buildIdIndex — case-insensitive Id/ID/id/xml:id, rejects duplicate-id and ambiguous-id.ds:Signature, and it must be inside the expected payload (ambiguous-signature, not-enveloped).Reference must be a same-document fragment resolving inside the payload subtree (outside-subtree), and at least one must bind expectedId (unbound).enveloped-signature, exc-C14N/C14N ± comments, InclusiveNamespaces PrefixList) before hashing (bad-digest).SignedInfo signature (bad-signature).#WithComments (comment-truncation).xml.js is a dependency-free parser + exc-C14N implementation covering namespace inheritance, xmlns="", the implicit xml prefix, attribute ordering, and escaping.
cd ~/xmldsig
npm test
Result: all pass — 7 canonicalization vectors, legit document accepted, and rejected: XSW1/XSW2/duplicate-ID → duplicate-id, XSW3 → not-enveloped, XSW4 → ambiguous-signature, XSW5 → outside-subtree, XSW6 → comment-truncation, tamper → bad-digest, wrong expected ID → unbound. The shipped on-disk corpus (test/corpus/, generated by test/emit.js) is re-verified by test/corpus.test.js.
ALL TESTS PASSED
CORPUS OK
Files: xml.js, verifier.js, SOLUTION.md, test/{gen,keys,emit,run,corpus.test,c14n.test}.js, test/corpus/*.xml.
# Evidence - Problem class: js-xml-dsig-signature-wrapping-c14n-confusion - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-29T16:44:55.368Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Build an XML Signature (XMLDSig) verifier in pure Node.js with no third-party dependencies that resists signature wrapping (XSW) attacks: it must bind the signature to the exact element the application later reads by ID, not merely to whatever node the Reference URI happens to resolve to. Implement exclusive canonicalization (exc-C14N) with correct namespace inheritance and attribute ordering, reject duplicated or ambiguous ID attributes, and refuse Reference URIs that resolve outside the signed subtree. Ship a test corpus of at least six known-bad documents (XSW1-6 variants, comment truncation, duplicate ID) that must all be rejected, plus a legitimate signed document that must verify.", "environment": "node20", "language": "js", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "js-xml-dsig-signature-wrapping-c14n-confusion", "provider": "openrouter", "solved_at": "2026-09-29T16:44:55.368Z", "version": "20"}