Public repositories: viewable without an account; repos get public (default) / private visibility enforced across listings, git, and the UI #345
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#345
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What's requested
Public repositories must be viewable by anyone (including signed-out visitors). That requires repositories to have a public/private distinction:
What already exists (important — this is mostly wiring, not new machinery)
The visibility model and its enforcement already ship — the gap is that a global switch overrides it, and the surfaces never expose or filter it:
access.jsonalready carries visibility:AccessDoc{Version, Visibility, RoleBindings, UpdatedAt}(internal/identity/access.go:24-30), withVisibilityPublic/VisibilityPrivate,normalizeAccessvalidation (:61-86), and public-by-default at creation (:98).CheckReadgrantsRoleReadto an anonymous principal whendoc.Visibility == VisibilityPublic(internal/identity/access.go:246-248), otherwise falls back to bindings / org-owner.e.Access.CheckRead(...)for everyAuthReadnon-NonRepo route (internal/api/routes.go:226-231).POSTbody{visibility: "public"|"private"}validated (internal/api/placeholder.go:381-382),EnsureRepoAccess(...)materializes it (:201-237); the SDK already types it (web/sdk/src/types.js:85,web/sdk/src/access.js:18,create.js:82).access.js—{version, visibility, role_bindings[]}, triage+).The gaps that make public repos unviewable today
anonymous_readflag preempts per-repo visibility.Env.gatereturns 401 for anonymous onAuthReadwheneverserver.auth.anonymous_readis false (internal/api/env.go:741-745), and the same check gates the SPA shell, git smart-HTTP (smart.go:118/266/510), LFS (lfs.go:65) and health (health.go:232). On hub.packden.us (OIDC, anonymous_read=false) every public repo is invisible to signed-out users — the exact symptom motivating this ticket. Visibility must become the authority for repo reads: "public ⇒ readable without authentication", withanonymous_readdeciding only what unauthenticated non-repo surfaces do (or being deprecated for repo reads — decision below).repoRegistry.Owners/Repos(cmd/walhub/serve.go:498-547) and the/api/v1/owners*listings return every manifest-backed repo with no visibility consult (verified: novisibility/accessreference in the listing path). Today the flag hides them by accident (anonymous gets nothing at all); once step 1 lands, private repos would leak into public listings unless listing filters by the caller's role. This is the privacy-critical half of the change.web/srcnever sends it). Nothing inSettings.jsxmentions visibility.summaryBody(internal/api/summary.go:12) has no visibility field, so the UI cannot render a badge or gate affordances even if it wanted to.Proposed design
Access.CheckReadfirst;anonymous_read=falseno longer short-circuits a public repo. Keep the flag's meaning for non-repo surfaces (owners list, instance info, setup) or narrow it explicitly — state the chosen semantic in the PR and updatedocs/go/06_server_http.md/07_api.md(it is a spec-level change to a documented gate).Owners/Repos/repos/detailedand the/explorefeed must omit repos the caller cannot read (anonymous ⇒ public only; authenticated ⇒ public + their own/org repos). This is where the visibility check must sit — one helper consulted per row, using the existing access LRU (internal/identity/access.go:283+) so it is not a per-repo store round trip; note the cost in the PR (liveReposalready fans out per-repo manifest reads).summaryBody(and the detailed listing rows) so the UI can badge and gate; note the cached-summary ETag trap (visibility change with no ref move would serve stale — same class as #235/#247; cover it or serve the fieldno-cache).public/privatebadge on the repo header next to the mirror badge (mirror pattern, #240/#281), and a visibility control in repo settings (General tab per #235) — triage/admin-gated, matching the access API's auth. Private repos should also mark fork/import-created repos sensibly (inherit or default public — pick and document).user:/team:), org owners, and global admins; nobody else — including no anonymous. Enumerate the matrix in the PR: author, org owner, team-bound user, other authenticated user, anonymous × public/private × read/write surfaces.access.json(or with an empty/invalid visibility) resolve public — matching the model's existing default (access.go:98) so existing repos don't silently become private. Verify the pre-existing-repo path materializes access on first write or treats missing as public (theAccessBoot.EnsureRepoAccessseam) and state it.Acceptance criteria
anonymous_read = false; private repos return 401/404 to them (pick one and be consistent — 404 avoids confirming existence; document the choice)./explore,/api/v1/owners,/owners/{owner}/repos, andrepos/detailedomit repos the caller cannot read (anonymous ⇒ public only) — no private repo name leaks in any listing, count, or activity/size aggregate (#247/#248 rollups included).visibilityaccept-path is wired to the UI.summaryBody/listing rows carry visibility; the cached-response ETag covers it (or the field is served no-cache) — no stale badge after a visibility flip.anonymous_readinteraction) is stated explicitly as a spec amendment, not slipped in.Out of scope (explicitly)
Fine-grained org permission model, per-branch/per-path restrictions, and private-repo discovery UX (e.g. "request access"). This ticket is the coarse public/private boundary with the owner/org rule the user specified.
PR #353 (fix/issue-345) implements this: visibility is the read authority for repo surfaces (API, smart-HTTP, LFS, bundles, SSH fetch, repo SPA shells) with anonymous_read keeping only its non-repo meaning — spec amendment stated in 06 §8.3 / 07 §13 with the full permission matrix in the PR description. Listings (owners/repos/both detaileds//explore) filter by caller role behind the access LRU with aggregates folded post-filter; summary + rows carry visibility under the ~v ETag suffix; UI badges + General-tab control wired (create already sent it; fork/import default public). Denials stay 401/403 by choice (law 9 + #344), documented. Tests per matrix cell, -race/e2e/cover green; no new deps. Pre-existing env gaps noted in the PR (web build, :8080 smoke, browser).
REVIEW PR #353 (fix/issue-345, commit
1ce5869) — adversarial privacy pass, verified in scratch worktrees (since removed). No browser run per task instructions (tests + reasoning only).LEAK HUNT (all clear): every repo-listing surface filters via VisibleRepos/VisibleOwners/VisibleReposByOwner (internal/api/visibility.go): owners, ownerRepos, ownersDetailed, ownerReposDetailed, /explore text twin (bind_api.go Owners -> VisibleOwners). Grep confirms no remaining direct Repos.Owners/Repos.Repos/OwnerRepoCounts calls on request paths outside the helpers (only registry impl, fakes, unfiltered legacy branch). No search/sitemap/feed surfaces exist. Aggregates fold AFTER filtering: ownerRollups takes the allow map + filterCatalog strips private rows before the maxima (names, counts, activity all derive from the visible set). 401/403-never-404 is deliberate + documented (git credential-erase law 9, #344 login-page keying); existence protection comes from filtered listings. All 401s carry Bearer (writePlain/mapAccessErr).
GATE ORDER (clear): dispatch (routes.go) + open (refs.go) consult CheckRead BEFORE the flag for repo AuthRead; shared gateRepoRead covers smart-HTTP x3, LFS, bundles; SSHUploadPack takes the principal and read-gates first (17_ssh.md 17.6); repoPageGated serves public shells to anon with flag off, private falls through to gated() (401/#344 page/307). e.gate bypass loses nothing (AuthRead flag-check only). Nil gate -> legacy everywhere. identity/http.go flag uses are all non-repo user/org/team surfaces — correctly retained.
SEMANTICS (clear): CheckRead matrix tested per cell incl. flag-off (gate_test.go: bound writer, org owner, stranger 403, anon 401, anon/stranger public allow, synthesized-default allow). Missing/corrupt/invalid access.json -> public in BOTH gate (Resolve fallback) and projection (RepoVisibility) — agree, migration-safe. Team: bindings flow through the untouched Resolve path (edge_test coverage pre-existing).
ETAG/UI (clear): summary + RepoSizeRow carry visibility; ~v suffix only when wired (unwired byte-identical). Header + row badges render from the same payload; Settings General-tab PUT re-reads CAS version and preserves bindings (button only renders when the access doc loaded, so no binding-wipe path); New.jsx already sent visibility; fork/import default-public documented in 07 Decisions.
#344 (clear): no session/oauth/trio files touched; private anon curl 401, anon browser #344 login page, authed 200, nil-gate 401 — all asserted in TestRepoPageGatedVisibility (passes). Bearer/write paths unaffected (writes never consult the read gate — asserted).
COST/COVERAGE (clear): filters ride the access LRU (conditional GET, version-hit no body); admins/writers +0 probes; budgets untouched. Cover: api 95.3%, identity 97.2%, server 98.5%, sshd 96.5%. -race clean (identity/api/sshd/cmd/server). gofmt/vet clean. go build clean. e2e green. Dependents green (repoimport/issues/pulls/review/checks/social/notify/releases/tags/sizecatalog). node visibility.test.js 3/3; full unit suite shows only the 12 pre-existing env failures identical on the parent commit.
ENV GAPS (pre-existing, confirmed on parent
ceb7331): shell/concepts/live-server tests fail in scratch without make web (no pnpm) — the PR discloses these. After copying main's built dist into scratch (read-only copy, scratch-only), the full server package passes except TestUIAssetConcepts (needs concepts GIFs from a real make web; PR touches no concepts files).ONE OBSERVATION (not a blocker): anon listings with anonymous_read=false stay 401 (flag's kept non-repo meaning, locked in TestDispatchVisibilityMatrix) rather than serving the public-only slice the acceptance criterion's letter suggests. Stricter, not leakier; stated + tested + documented as the chosen semantic — flagging only so the criterion checkbox records the deviation consciously.
No fixes needed; nothing pushed. MERGE RECOMMENDATION: ready to merge.
Fixed by PR #353 (review clean — adversarial leak hunt clear, gate order, ETag, #344 no-regress verified), merged. Closing.