Skip to content

fix(issue-watch): visibility-aware access gate for subscriptions + fan-out - #742

Merged
JSONbored merged 3 commits into
mainfrom
codex/fix-issue-watch-vulnerability-in-gittensory
Jun 14, 2026
Merged

fix(issue-watch): visibility-aware access gate for subscriptions + fan-out#742
JSONbored merged 3 commits into
mainfrom
codex/fix-issue-watch-vulnerability-in-gittensory

Conversation

@JSONbored

@JSONbored JSONbored commented Jun 14, 2026

Copy link
Copy Markdown
Owner

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 failing expected [] 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).
  • watch/unwatch gate (requireWatchableRepo) applies only to session identities — non-session private-token callers stay trusted, consistent with canAccessRepo. Sessions are the documented threat surface ("prevent sessions from subscribing to arbitrary repos").
  • 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 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

@ghost

ghost commented Jun 14, 2026

Copy link
Copy Markdown

Note

Gittensory Gate skipped

PR closed before full evaluation. No late first comment was created.

Signal Result Evidence Action
Gate result ⚠️ Skipped #742 is no longer open. No action.

💰 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.

@ghost ghost added the gittensory:reviewed label Jun 14, 2026
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@ghost

ghost commented Jun 14, 2026

Copy link
Copy Markdown

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 · gpt-oss-120b — recommends ✅ merge
The PR adds proper access checks for issue‑watch subscriptions, ensuring private or unknown repositories are only fan‑out to authorized users, and introduces corresponding helper functions and tests. The changes are well‑scoped, type‑safe, and maintain existing behavior for public repos.

Suggestions

  • Consider adding a comment to requireWatchableRepo explaining why non‑session identities are trusted, for future maintainers.
  • Ensure the new error message "Forbidden: session cannot watch this repository." aligns with other Forbidden messages in the codebase for consistency.

Worth double-checking

  • If getRepository ever returns undefined for a tracked public repo due to a race condition, watchers may be incorrectly filtered out.
  • Future changes to repo visibility handling must keep the fail‑closed semantics for unknown repos.

Reviewer B · mistral-small-3.1-24b-instruct — recommends ✅ merge
This PR enforces repository access checks for issue-watch subscriptions and notification fan-out. The changes are well-structured and include necessary updates to the service logic, control panel roles, MCP server, and tests. The overall quality is high.

Suggestions

  • Consider adding comments to explain the logic in the canLoginAccessRepo and canWatchRepo functions for better clarity.
  • Ensure that the canWatchRepo function handles edge cases where the repository might not be found or has unknown visibility.

Worth double-checking

  • Verify that the access checks do not introduce performance bottlenecks, especially with the Promise.all usage in detectIssueWatchEvents.
  • Double-check that the new access checks do not inadvertently block legitimate watchers from receiving notifications.

@ghost ghost added the gittensory-review label Jun 14, 2026
…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.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

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

@JSONbored JSONbored changed the title fix: enforce repo access for issue-watch subscriptions and notification fan-out fix(issue-watch): visibility-aware access gate for subscriptions + fan-out Jun 14, 2026
@JSONbored
JSONbored merged commit 32e22a7 into main Jun 14, 2026
12 checks passed
@JSONbored
JSONbored deleted the codex/fix-issue-watch-vulnerability-in-gittensory branch June 14, 2026 19:04
@github-project-automation github-project-automation Bot moved this from Todo to Done in gittensory - v1 roadmap Jun 14, 2026
galuis116 added a commit to galuis116/gittensory that referenced this pull request Jun 16, 2026
…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.
JSONbored added a commit that referenced this pull request Jun 17, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant