Skip to content

fix(solid): refuse terms Turtle cannot write, so an import adds no statements - #326

Merged
ixuz merged 1 commit into
mainfrom
fix/iri-escaping
Oct 26, 2026
Merged

ixuz merged 1 commit into
mainfrom
fix/iri-escaping

Conversation

@ixuz

@ixuz ixuz commented Oct 26, 2026

Copy link
Copy Markdown
Member

What and why

A JSON-LD file can hold IRIs that Turtle can't write: an IRI with > or a space in its fragment, a predicate or datatype IRI of either kind, or a blank node label with any characters. The Turtle writers every save goes through write these as they are: the Solid client library's, patchBody and turtleOf. A > then ends the IRI, and the pod reads the rest as statements of their own. A deck file import could so add a statement to the new cards document that no shape or ownership check saw (GHSA-x937-hx25-qfp4).

Turtle has no legal escape for such a character in an IRI, so these terms are refused instead of escaped:

  • packages/solid/src/writableTerms.ts names what Turtle can write as it is: IRIs (isWritableIri in domain/webId.ts), blank node labels and language tags by Turtle's grammar, no rdf:langString without a language, no base direction, no quoted triple.
  • Every write refuses the rest with the new error unwritableTerm before anything is sent: a save's PATCH or PUT (saveDataset), an insert-only PATCH (appendAllToDocument), a release's Turtle (turtleOf) and an export.
  • A deck file import leaves such triples out. Drafts → from file refuses the whole file, since a draft keeps a release's triples as they are.
  • A WebID must be writable. A picture's address typed in the Studio has those characters percent-encoded (Mona Lisa.jpg is saved as Mona%20Lisa.jpg).
  • turtleWriter.test.ts had asserted that <https://other.example/a b> was written as it is; it now uses %20.
  • docs/data-model.md says it under Write discipline and in the import list.

Refs: GHSA-x937-hx25-qfp4

Checklist

  • One issue. This pull request addresses one issue, linked above.
  • One step. This pull request does not finish its issue, so it says Refs:, not Closes:. Leave this unchecked otherwise.
  • Done, as AGENTS.md defines it: npm run check passes (lint, Markdown and spelling included), npm run lint:reuse too if a file was added, and npm run crosscheck too if ns/ or decks/ changed.
  • Tests. New and changed code has its tests, and every package stays at 100% coverage (docs/testing.md).
  • Docs. The docs say what changed, and a new page in docs/ is listed in docs/docs.ttl.
  • Languages. A new or changed message is in every locale: en, sv, ko, de, es and fr (packages/ui/src/i18n/).
  • Vocabulary and shapes. Any change to ns/ was agreed in an issue first; no published ns/shapes/*/vN.ttl or decks/*/vN.ttl was edited; generated files were regenerated, not edited. (n/a: no change to ns/ or decks/)
  • Breaking? Nothing in the public API breaks, or a commit says so (! and a BREAKING CHANGE: footer). npm run check:breaking sees a line of api/ that goes, but not a feature removed, nor a reader that reads less than before.
  • Pod data. Any change to what the app writes to a pod comes with its migration (docs/migrations.md). (n/a: what the app writes is unchanged; only writes holding terms Turtle can't write are refused)
  • Policy. Any change to an external rule the code follows (SM-2, the plural rules, a specification) is cited. (the Turtle grammar's IRIREF, BLANK_NODE_LABEL and LANGTAG, named in writableTerms.ts)
  • Licenses. New content is MIT, or a deck states its own license; a new file has its SPDX header (npm run lint:reuse), or a new deck its line in REUSE.toml; third-party content is credited.
  • Architecture. Imports follow docs/boundaries.md and the layers of docs/architecture.md.
  • Run. I ran the app (npm run dev) and tried the change, and considered the user journeys.
  • Authorship. I read and understand every line of this pull request, and answer for it, whatever tools helped write it (CONTRIBUTING.md).
  • Commits. Each commit is one logical change, in Conventional Commits form (type(scope): subject, ! and a BREAKING CHANGE: footer if it breaks something), with an imperative subject and a body that explains why (CONTRIBUTING.md).

…atements

A JSON-LD file keeps an IRI with `>` or a space in its fragment, a
blank node label with any characters, and a datatype or predicate IRI
of either kind. The Turtle writers every save goes through (the Solid
client library's, patchBody, turtleOf) write these as they are, so a
`>` ends the IRI and the pod reads the rest as statements of their own.
An imported card picture could so add a statement to the new cards
document that no shape or ownership check saw, and the ownership rules
rely on what those documents say.

Turtle can't escape such a character in an IRI, so these terms are
refused instead. writableTerms.ts names what Turtle can write as it is:
IRIs (isWritableIri), blank node labels and language tags by Turtle's
grammar, no rdf:langString without a language, no base direction, no
quoted triple. Every write refuses the rest with unwritableTerm before
anything is sent: PATCHes and PUTs in saveDataset, insert-only PATCHes,
a release's Turtle and an export. A deck import leaves such triples
out, and a release read from a file is refused, since a draft keeps its
triples as they are. A WebID must be writable, and a picture's address
typed in the Studio has those characters percent-encoded.

Refs: GHSA-x937-hx25-qfp4
@ixuz ixuz added the bug Something isn't working label Oct 26, 2026
@ixuz
ixuz enabled auto-merge October 26, 2026 21:51
@ixuz
ixuz added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 63edf09 Oct 26, 2026
35 checks passed
@ixuz
ixuz deleted the fix/iri-escaping branch October 26, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant