gl pr create can target another user's repo. The other eight subcommands cannot, and the failure is
silent rather than an error.
Only create has the flag (crates/gl/src/pr.rs:34-36), and its own help text states the intent:
/// Repo owner DID short key (defaults to your DID — use when opening a PR on another user's repo)
#[arg(long)]
owner: Option<String>,
and only create has the resolution chain (pr.rs:220-223):
// Priority: --owner flag > git remote origin > caller's own DID
let owner = owner_override.or_else(detect_remote_owner).unwrap_or_else(|| resolve_owner(&keypair));
list, view, diff, merge, review, comment, comments each get a bare let owner = resolve_owner(&keypair); (pr.rs:254, :298, :386, :407, :441, :481, :506).
detect_remote_owner() is referenced only at line 222, so even the git-remote tier is create-only:
inside a clone of someone else's repo, gl pr create targets them and gl pr list targets you.
pr.rs is the only gl module without owner/name splitting. repo.rs:598
(resolve_owner_repo_pair), star.rs:56, and the issue/cert/protect/visibility modules all accept
it.
The workaround fails silently
Passing owner/name interpolates straight into the path and produces a four-segment URL matching no
route. Built at 50d3cbb against a capture server:
$ gl pr list z6MkSOMEONEELSE/theirrepo --node http://127.0.0.1:8791
No pull requests for z6MkSOMEONEELSE/theirrepo
$ gl pr view z6MkSOMEONEELSE/theirrepo 3 --node http://127.0.0.1:8791
PR #3: ?
Status: ?
with the captured request lines:
/api/v1/repos/<caller-own-short-did>/z6MkSOMEONEELSE/theirrepo/pulls
/api/v1/repos/<caller-own-short-did>/z6MkSOMEONEELSE/theirrepo/pulls/3
The caller's own DID occupies the owner segment and the intended owner has been folded into the repo
segment. The client then renders the non-match as an empty list and as ? fields, which is the
denial-rendered-as-success shape again rather than an error.
This is a client gap, not an API restriction
server.rs:393 mounts GET /api/v1/repos/{owner}/{repo}/pulls on the public router and
api/pulls.rs:101-108 gates it with authorize_repo_read(..., "/") on an optional identity, so a
public repo owned by any DID is readable. The node permits exactly the operation the CLI cannot
express.
Scope against existing work
#208 explicitly excludes this site in its own scope section: "Genuinely different, leave out of the
duplicate set: ... pr.rs:184 (keypair-only, returns String)". Its subject is the node-DID fallback
in cert/issue/protect/visibility. #159 (webhook owner from caller identity) and #102 are unrelated,
and #148 touches gl pr create error masking only.
PR #186 partly softens the symptom: converting pr.rs to read_json makes the malformed path surface
as a real error instead of the fake PR #3: ? above. That fixes the silence, not the gap.
Severity
Medium, not high. No security or trust-boundary consequence, and the node's authorization is intact.
It is a missing capability plus a silently malformed URL, and it blocks the ordinary maintainer flow
of reviewing a PR on a repo you do not own.
Fix direction
Give the other eight subcommands the same owner/name split the rest of gl already uses, preferably
by adopting the shared helper #208 proposes rather than adding a ninth copy, and keep
detect_remote_owner in the chain so working inside a clone does the right thing everywhere rather
than only on create.
gl pr createcan target another user's repo. The other eight subcommands cannot, and the failure issilent rather than an error.
Only
createhas the flag (crates/gl/src/pr.rs:34-36), and its own help text states the intent:and only
createhas the resolution chain (pr.rs:220-223):list,view,diff,merge,review,comment,commentseach get a barelet owner = resolve_owner(&keypair);(pr.rs:254,:298,:386,:407,:441,:481,:506).detect_remote_owner()is referenced only at line 222, so even the git-remote tier is create-only:inside a clone of someone else's repo,
gl pr createtargets them andgl pr listtargets you.pr.rsis the onlyglmodule withoutowner/namesplitting.repo.rs:598(
resolve_owner_repo_pair),star.rs:56, and the issue/cert/protect/visibility modules all acceptit.
The workaround fails silently
Passing
owner/nameinterpolates straight into the path and produces a four-segment URL matching noroute. Built at 50d3cbb against a capture server:
with the captured request lines:
The caller's own DID occupies the owner segment and the intended owner has been folded into the repo
segment. The client then renders the non-match as an empty list and as
?fields, which is thedenial-rendered-as-success shape again rather than an error.
This is a client gap, not an API restriction
server.rs:393mountsGET /api/v1/repos/{owner}/{repo}/pullson the public router andapi/pulls.rs:101-108gates it withauthorize_repo_read(..., "/")on an optional identity, so apublic repo owned by any DID is readable. The node permits exactly the operation the CLI cannot
express.
Scope against existing work
#208 explicitly excludes this site in its own scope section: "Genuinely different, leave out of the
duplicate set: ...
pr.rs:184(keypair-only, returnsString)". Its subject is the node-DID fallbackin cert/issue/protect/visibility. #159 (webhook owner from caller identity) and #102 are unrelated,
and #148 touches
gl pr createerror masking only.PR #186 partly softens the symptom: converting
pr.rstoread_jsonmakes the malformed path surfaceas a real error instead of the fake
PR #3: ?above. That fixes the silence, not the gap.Severity
Medium, not high. No security or trust-boundary consequence, and the node's authorization is intact.
It is a missing capability plus a silently malformed URL, and it blocks the ordinary maintainer flow
of reviewing a PR on a repo you do not own.
Fix direction
Give the other eight subcommands the same
owner/namesplit the rest ofglalready uses, preferablyby adopting the shared helper #208 proposes rather than adding a ninth copy, and keep
detect_remote_ownerin the chain so working inside a clone does the right thing everywhere ratherthan only on
create.