Skip to content

fix: escape IRIREF-forbidden characters and full control range in Writer (#659) - #661

Draft
jeswr wants to merge 1 commit into
rdfjs:mainfrom
jeswr:fix/659-escape-iris-and-control-chars
Draft

fix: escape IRIREF-forbidden characters and full control range in Writer (#659)#661
jeswr wants to merge 1 commit into
rdfjs:mainfrom
jeswr:fix/659-escape-iris-and-control-chars

Conversation

@jeswr

@jeswr jeswr commented Jul 5, 2026

Copy link
Copy Markdown
Member

🤖 Filed by @jeswr's PSS agent (Claude); @jeswr runs multiple agents on this account — the SPARQ agent is a separate party in these discussions.

Draft — addresses #659. N3Writer emitted NamedNode values verbatim between angle brackets, so a value containing an IRIREF-forbidden character (>, <, ", {, }, |, ^, backtick, \, space, or a C0 control) closed its own <…> and injected forged triples (repro 1 in the issue). Separately, literal escaping only covered U+0000–U+0019, so U+001A–U+001F, DEL (U+007F) and the C1 block (U+0080–U+009F) passed through raw (repro 2).

The fix (src/N3Writer.js):

  1. IRIs_encodeIriOrBlank (and @prefix lines) escape the IRIREF-forbidden set (U+0000–U+0020 and < > " { } | ^ ` \) as UCHAR (\uXXXX/\UXXXXXXXX), via a dedicated replacer because IRIREF permits only UCHAR escapes, not ECHAR.
  2. Literals_encodeLiteral widens the escaped ranges to the full C0 plus DEL and C1 (U+0000–U+001F, U+007F–U+009F).

Why escape (UCHAR) rather than percent-encode or throw: percent-encoding changes IRI identity (>%3E doesn't reverse), whereas UCHAR escaping round-trips byte-identically; a valid IRI contains no forbidden characters, so well-formed data is byte-for-byte unchanged; and re-reading escaped malformed input with N3's own strict parser rejects the invalid IRI instead of materialising an injected quad. If validate-and-throw is preferred instead, that's a small swap — the main reason this is a draft. (DEL/C1 are grammar-permitted raw inside an IRIREF, so they're only escaped in literals; escaping them in IRIs too is a one-line range addition if wanted.)

Tests: a Writer term-serialisation safety (#659) block covers the issue's repros (re-parsing never yields the forged quad, an injected newline can't start a statement), every forbidden character emitted as UCHAR, valid IRIs (%20, query, fragment, punycode host, urn:, did:) byte-for-byte unchanged, astral \U… behaviour preserved, the full C0+DEL+C1 set in literals, and a hostile @prefix. npm test green (6512 tests, 100% coverage on N3Writer.js), lint clean.

Closes #659

…ter (rdfjs#659)

The Writer emitted NamedNode values verbatim between angle brackets, so an
IRI containing a `>` (or space, `<`, `"`, `{`, `}`, `|`, `^`, backtick,
backslash, or a C0 control) closed its own `<...>` and injected forged
triples (RDF-injection). Literals also passed U+001A-U+001F, DEL and the C1
block (U+0080-U+009F) through raw.

- `_encodeIriOrBlank` now escapes the Turtle/N-Triples IRIREF-forbidden set
  (U+0000-U+0020 and `<>"{}|^` + backtick + `\`) as UCHAR (`\uXXXX`/`\UXXXXXXXX`),
  the only escape an IRIREF permits. Valid IRIs are untouched; astral chars
  still serialise as `\U...`. `@prefix` IRIs are escaped the same way.
- Literal escaping is widened from U+0000-U+0019 to the full C0 range plus
  DEL and C1 (U+0000-U+001F, U+007F-U+009F).

UCHAR escaping is identity-preserving (unlike percent-encoding), so round-trip
of valid terms is unchanged. Adds regression tests covering the injection
vectors and control-char escaping.

@jeswr jeswr left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We need to discuss the following RDF/JS assumptions before this PR is merged:

  • Do we assume named node IRI's should be pre-escaped

We need to discuss the following N3.js assumption:

  • Do we assume prefixes to be sanitised

The following N3.js assumption is known: We expect named nodes to be valid RDF/JS terms.

@jeswr jeswr added ai-generated Authored or prepared by an AI coding agent needs-author-review Awaiting author review; agent has done its part (author removes if follow-ups remain) labels Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-generated Authored or prepared by an AI coding agent needs-author-review Awaiting author review; agent has done its part (author removes if follow-ups remain)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Writer does not escape IRIs (RDF-injection) and emits raw C1/DEL control chars in literals

1 participant