Skip to content

Add a Provider by pasting its URL #162

Description

@jacobdrees

Parent

CAN-17 v1: the walking skeleton in production, then the founding case

What to build

The ingress a person uses to add a Source this project does not run: paste a Provider's URL, and the
application reads its capability declaration and speaks the published contract to it from then on.

Decision 7 is the decision, and it is unambiguous about the band.
CAN-96 Record the architecture decisions of 15 August, and make the repository agree
says: "Users may paste third-party provider URLs in v1, and the duties are accepted."
ADR-0014 Decision 2 keeps it — "a pasted URL
pointing at a stranger's service is still supported" — and records the paste-a-URL shape as one of
the parts of ADR-0007 that survives unchanged.

This ticket exists because nothing built it. Decision 7 put the capability in v1 on 15 August
2026 and no ticket carried it; CAN-100 Restructure the tracker for the architecture change
found the gap while auditing its own work. Decision 4's reasoning depends on this being real —
CAN-7 Provider contract: define and publish it moved into v1 "because decision 7 means
third-party implementors exist from the start", so a contract published for implementors who cannot
be reached would be publishing into nowhere.

Listed is the boundary, and it has to be visible

CONTEXT.md defines a Listed Provider as one "this project writes and runs, named in the
product's own list rather than pasted in by a person. Anything off that list is a stranger's service
however familiar the Source behind it looks."

A pasted Provider is not Listed, and the interface must not let the two blur. ADR-0014 rejects a
community registry outright, so there is no review step to hide behind: the person pasting is
accepting a stranger's service, and the product's job is to make that legible rather than to vouch
for it. A pasted provider-tmdb lookalike is still a stranger's service.

What it must carry

Fetching a user-supplied URL server to server is the exposure. The application will make requests
to an address a person chose, which is a server-side request forgery vector: internal addresses,
link-local metadata endpoints, redirects into private ranges, and DNS that resolves differently on
the second lookup. Treat the fetch as hostile input — allowlist scheme and port, refuse private and
link-local ranges, re-check after every redirect, cap size and time.

Everything the application honours arrives from a party it does not control.
CAN-104 Read a Provider's capability declaration, and refuse what it does not serve
already says absence is refusal rather than permission; this is where that bites hardest, because a
stranger's declaration is the only thing standing between a pasted URL and displayed content. In
particular a pasted Provider declaring no content classification must not have artwork displayed,
per ADR-0012.

Its declared retention is trusted for a compliance decision. ADR-0014 records that consequence as
forced by Decision 1 rather than argued away, and it is sharper here than for a Provider we run:
CAN-102 Give Source a retention policy, and Snapshot a fetched-at stores what the
stranger claims, and CAN-103 Refresh Snapshots before their Source's retention expires, and drop what cannot be refreshed
acts on it.

Its attribution obligation is its own, carried by
CAN-105 Carry each Source's attribution obligation through to every surface that displays it
and attributed per field by
CAN-110 Carry per-field provenance to every displayed value.

Acceptance criteria

  • A signed-in person can add a Provider by pasting its URL, and the application reads its capability declaration and registers it as a Source for that person.
  • The fetch is hardened against server-side request forgery: scheme and port allowlisted, private and link-local ranges refused, redirects re-checked, response size and time capped.
  • A URL that does not answer the contract, or answers a version the application does not speak, is refused with a reason the person can act on.
  • A pasted Provider is visibly distinguished from a Listed one wherever its values are displayed, and the product never presents it as vouched for.
  • Everything the declaration asserts runs through CAN-104 Read a Provider's capability declaration, and refuse what it does not serve, with absence treated as refusal — artwork suppressed where no content classification is declared.
  • A pasted Provider's prose adds its own case to apps/web/src/app/no-linkification.test.tsx: a URL arriving from the pasted Provider renders as text and produces no anchor element. The prohibition behind it is origin-blind and enforced by lint (react/no-danger, plus a restricted-import group that is a name-matching tripwire rather than a proof, both in apps/web/eslint.config.mjs), added by CAN-108 Re-assess the illegal-content risk before a user can paste an arbitrary Provider URL, so what this ticket owes is the assertion at its own surface rather than the rule.
  • A person can remove a pasted Provider, and removing it does the right thing to the Snapshots it sourced rather than orphaning them.
  • CONTEXT.md gains the term for a pasted, unlisted Provider, since it currently defines only the Listed case.
  • Nothing here ships before CAN-108 Re-assess the illegal-content risk before a user can paste an arbitrary Provider URL lands. That is a shipping gate under s.9(4) and s.11(4), not a documentation step.

Blocked by

Amended 17 August 2026

CAN-108 Re-assess the illegal-content risk before a user can paste an arbitrary Provider URL is the ticket that redoes both statutory risk assessments, and three things above change when it merges.

The shipping gate opens when CAN-108 Re-assess the illegal-content risk before a user can paste an arbitrary Provider URL merges, and not before. Both statutory assessments are redone under s.9(4) and s.11(4), dated 17 August 2026 — but on the CAN-108 Re-assess the illegal-content risk before a user can paste an arbitrary Provider URL branch. Until that lands on main the condition the last criterion names is unmet, and this ticket must not be started. The criterion stays on the list afterwards as the record of why.

Pasted Providers are not moderated, and that is now decided rather than assumed. They are never reviewed, listed, allowlisted or vouched for; what is moderated is their content, under ICU C1 and C2 like any other. An operator blocklist of Provider URLs was considered and rejected, and is recorded as the first thing to build if the reassessment triggers fire. The reasoning is in docs/compliance/illegal-content-risk-assessment.md under Pasted Providers are not moderated; their content is.

Two constraints the redone assessments now impose on this build. Import stays a record at a time, chosen by its owner — bulk or scheduled import from a pasted Provider is a change requiring both assessments to be redone again, because it is what strains the reviewable-corpus control that holds finding 2c at low. And a pasted Provider declaring no content classification has no artwork displayed from it at all, which the children's risk assessment now carries as well as this ticket.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions