fix(issue-watch): visibility-aware access gate for subscriptions + fan-out - #742
Conversation
|
Note Gittensory Gate skippedPR closed before full evaluation. No late first comment was created.
💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers. |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
gittensory · advisory review Reviewed 4 changed file(s) — two independent AI reviewers. Suggested action: ✅ Safe to merge — both reviewers found no blocking issues. Reviewer A · Suggestions
Worth double-checking
Reviewer B · Suggestions
Worth double-checking
|
…er-only) The repo-access enforcement added to issue-watch (this PR) used the maintainer/owner/operator scope (canLoginAccessRepo) unconditionally — which BREAKS the feature: issue-watch is a miner tool, and miners watch PUBLIC gittensor-tracked repos they do NOT own or maintain. Gating on maintainer access blocked every legitimate watcher (the failing `expected [] to deeply equal ['watcher']` test was exactly this). Make the gate visibility-aware instead, matching the real threat (a session subscribing to private repos it cannot see): - New canWatchRepo(env, login, fullName): a tracked PUBLIC repo is watchable by any contributor; a PRIVATE repo requires maintainer/owner/ operator access; an untracked repo (unknown visibility) is not watchable (fail-closed). - watch/unwatch gate (requireWatchableRepo) applies only to SESSION identities (non-session private-token callers stay trusted, matching canAccessRepo) — this is the documented threat surface. - Fan-out resolves the repo once: a public repo notifies every matching watcher; a private/untracked repo only watchers who can access it, so private-repo issues never reach a non-collaborator. Tests reworked accordingly: public-repo watchers no longer need to be admins; added a session-watches-public-repo case, a private-repo authorized-watcher fan-out case, and a fail-closed untracked-repo case.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | cbd6c70 | Commit Preview URL Branch Preview URL |
Jun 14 2026, 07:02 PM |
…e admits fan-out The main merge brought in JSONbored#742's visibility-aware fan-out gate, which only fans out issue-watch events for a tracked, accessible repo. The case-insensitivity regression test subscribed to a repo it never upserted, so the gate (correctly) returned no events. Upsert it as a tracked PUBLIC repo, matching the other detectIssueWatchEvents tests, so the case-insensitive match is exercised end-to-end.
…747) * fix(notifications): match issue-watch repo names case-insensitively issue-watch subscriptions stored and matched repoFullName case-sensitively while the rest of the codebase treats repo names case-insensitively. A contributor who subscribed via gittensory_watch_issues with non-canonical casing (GitHub repo names are case-insensitive) was silently never matched by the webhook's canonical repository.full_name lookup, so the feature quietly delivered no notifications. Lowercase repoFullName on store and on both lookups (mirroring the existing login/label normalization), so matching is symmetric and the (login, repo) unique index dedupes across casings. Closes #746 * test(issue-watch): track the repo so the #742 visibility gate admits fan-out The main merge brought in #742's visibility-aware fan-out gate, which only fans out issue-watch events for a tracked, accessible repo. The case-insensitivity regression test subscribed to a repo it never upserted, so the gate (correctly) returned no events. Upsert it as a tracked PUBLIC repo, matching the other detectIssueWatchEvents tests, so the case-insensitive match is exercised end-to-end. * test(issue-watch): exclude the unreachable upsert fallback from coverage codecov/patch flagged the upsert's ternary fallback as a partial branch: the `: { login, repoFullName, labels }` arm is unreachable because the row always exists immediately after the insert/onConflictDoUpdate. Mark it /* v8 ignore */ (the established convention for defensive type-safety fallbacks) so the diff is fully covered. --------- Co-authored-by: galuis116 <galuis116@users.noreply.github.com> Co-authored-by: ghost <49853598+JSONbored@users.noreply.github.com>
What
Closes a real access-control gap in issue-watch (#699 path B / #735): a session could subscribe to arbitrary repo names — including private repos it cannot see — and receive their issue notifications via fan-out.
Correction vs. the original approach
The first cut enforced the maintainer/owner/operator scope (
canLoginAccessRepo) unconditionally. That breaks the feature: issue-watch is a miner tool, and miners watch public gittensor-tracked repos they do not own or maintain. Gating on maintainer access blocked every legitimate watcher — the failingexpected [] to deeply equal ['watcher']test was exactly that regression (it had been masked by making test watchers admins).This revision makes the gate visibility-aware, matching the actual threat:
canWatchRepo(env, login, fullName)— a tracked PUBLIC repo is watchable by any contributor (the miner use case); a PRIVATE repo requires maintainer/owner/operator access; an untracked repo (unknown visibility) is not watchable (fail-closed).requireWatchableRepo) applies only to session identities — non-session private-token callers stay trusted, consistent withcanAccessRepo. Sessions are the documented threat surface ("prevent sessions from subscribing to arbitrary repos").Tests
Reworked so public-repo watchers no longer need to be admins, plus new cases: a session watching a public repo it doesn't maintain succeeds; a private-repo issue does fan out to an authorized watcher; an untracked repo fans out to no one; a session is blocked from watching a private repo it can't access.
Verification
typecheck✅ ·test:coverage✅ (1844 passed; branches 97.04%, all metrics ≥97) ·git diff --check✅ ·ui:openapi:check✅ ·test:workers✅