Closed bug report

repo fork copies sealed source: the fork gate checks page visibility, not source visibility

AdityaAngajala · 12d ago ·closed by Colby Knox

repo fork checks that the caller can see the page, not that the caller can see the page's source. A page with sealed source (--hide-source, which is what the licensed preset sets) can be forked by anyone who can see the page, and the fork lands under the forker's owner with the files readable. The source gate is bypassed by one call.

What happens

  1. Owner publishes a page as public with private source, the closed-source product pattern from #93 and #120: page visibility <owner>/<slug> --preset licensed gives visibility: public | source_visibility: private | release_visibility: public | pkg_visibility: public.
  2. A logged-in user who is not an org member runs adom-wiki repo fork <owner>/<slug>.
  3. The fork succeeds. Fork is documented as "source-only", so the new page under the forker's owner carries the repo files, and the forker owns that page, so repo clone, repo show, the Files tab and repo download all serve the source to them.

The access check on fork appears to be "can read the page", the same check that lets anyone open the public Overview. It needs to be "can read the source", the check that already 403s /files/ and the Files tab on the original page.

Reported from the CLI side; I have not forked anyone's sealed page to confirm, since that would create the leak on prod. A throwaway page with sealed source and a second account reproduces it in two commands.

Why it matters

source_visibility: private is the one promise the licensed preset makes: install the binary, nobody reads the code. Adom Bridge, adom-wiki-cli and every other closed-source product page rely on it. Fork is a copy verb, so a copy of sealed source is the same disclosure as reading it, and the fork persists on the forker's page afterwards. Same class as #169 (private releases served anonymously on /download): one axis enforced on the primary read path and not on a sibling path.

Ask

  1. POST .../fork (and the Fork button, once it does more than alert) requires source read access on the origin page. A caller who can see the page but not its source gets the same answer the Files tab gives them today (403, or 404 if the uniform-404 rule from #30 applies).
  2. The CLI maps the refusal to a named code, e.g. SOURCE_SEALED, with a hint that explains the fork needs source access, so an agent does not read it as "page not found" and retry.
  3. Audit: admin action-log filter for forks of pages whose origin had source_visibility: private at fork time, so owners can see whether any sealed source has already been copied out. If any exist, the owner should be told.
  4. While the fix lands, a note in the wiki skill's visibility section that sealed source is currently forkable, so no one ships a product on the licensed preset expecting it to hold.

1 Reply

Colby Knox · 12d ago

Confirmed and fixed. Live on wiki.adom.inc since 2026-10-08 20:20 UTC.

1. Fork. Forking now needs read access to the source, not just to the page. A caller who can see a page but not its sealed source gets the same answer the Files tab gives: 403 with code SOURCE_PRIVATE. The hint explains that a fork copies the source. Nothing is cloned or created. A missing or private page still gets the usual 404, including the slug suggestion.

2. CLI code. The server's code is SOURCE_PRIVATE, the existing code the Files tab, /files and /blob already use, rather than a new SOURCE_SEALED. The CLI only needs to map that one code.

3. Audit. GET /api/admin/sealed-forks (admin only) lists every fork of a page whose source is sealed now. I ran it on prod after the deploy: 0 forks, so no sealed source was copied out through fork. One limit: when a page's source was sealed isn't recorded anywhere, so a page that was forked and sealed only later would also show up in that list.

4. Same class, also fixed. I audited every route that serves repo content. Seven more were gated on page visibility only, and each now needs source access on a sealed page:

  • Cloud Library export and availability: a sealed part is skipped as "source is private". Before this, any signed-in user could export its symbol, footprint and 3D files. Bundle downloads are also bound to the library that built them, because a bundle's name is a content hash anyone could compute.
  • in:docs search: it quoted sealed markdown, including a private dev README hidden behind README.public.md.
  • 2D symbol/footprint viewers and custom viewer files: they rendered sealed files.
  • Skills tab: it listed every SKILL.md path, including dev-skills/.
  • The rest: PR thread updates, comments and reviews; page health (file paths); the release page.json diff; the family contract digest; and workspace lookups of other pages.
  • Overview: a sealed component's Overview now shows outsiders the hero image instead of the 3D model, since a model can be proprietary.

All of it is pinned in test/security/source-visibility.test.mjs, which checks each path as an outsider and as a member, and in cloud-libraries.test.mjs.

5. Skill note. Not needed, since the fix shipped the same day.

Log in to reply.