Hold the gh api brace exemption to an endpoint's {owner}/{repo}

gh substitutes those two from the checkout, so a path carrying them names this
repository by construction. Any other brace says nothing about what the call
reaches, and the exemption accepted one anywhere in argv: `-f name={x}` or
`--template '{{.login}}'` asserted the whole command was repository-scoped,
which left `gh api orgs/other/repos -X POST` and `gh api user` reachable.
This commit is contained in:
2026-10-13 21:14:39 -07:00
parent 5f595de0ab
commit f35556ee0b
2 changed files with 19 additions and 5 deletions
+9 -3
View File
@@ -526,11 +526,17 @@ _dejima_forge_path_verdict() {
# endpoint, so the flags go.
case "$arg" in -*) continue ;; esac
[ "$arg" = graphql ] && { scopable=1; continue; }
path="${arg#/}"
# gh substitutes {owner} and {repo} from the checkout, so a path
# carrying them names this repository by construction and comparing
# the literal braces would refuse it.
case "$arg" in *'{'*) scopable=1; continue ;; esac
path="${arg#/}"
# the literal braces would refuse it. Those two tokens only, and
# only in an endpoint under the segment: any other brace says
# nothing about which repository the call reaches, and a value
# carrying one - `-f name={x}`, `--template '{{.login}}'` - would
# otherwise be enough to assert the whole command is scoped.
case "$path" in
"$segment"/*'{owner}'*|"$segment"/*'{repo}'*) scopable=1; continue ;;
esac
case "$path" in "$segment"/*/*) ;; *) continue ;; esac
path="${path#"$segment"/}"
owner="${path%%/*}"
+10 -2
View File
@@ -5035,8 +5035,11 @@ _repo_guard_shim() {
DEJIMA_FORGE_REPO=own/thing
DEJIMA_FORGE_REPO_PATHS="api:repos")
local form
# The last one carries a brace, which is only an exemption for {owner} and
# {repo}: another repository's path is still that path with a placeholder
# in the query string.
for form in "repos/other/thing/issues" "/repos/other/thing/issues" \
"repos/other/thing"; do
"repos/other/thing" "repos/other/thing/issues?per_page={n}"; do
run env "${env_common[@]}" "$d/gh" api "$form"
assert_forge_refused "Blocked by dejima" "gh api $form" || return 1
done
@@ -5046,7 +5049,12 @@ _repo_guard_shim() {
# reached every endpoint the token did, with a write among them (#365).
# Under the declared subcommand the question is inverted: a target this can
# read has to be present.
for form in "user/repos" "/orgs/someone/repos" "-X POST /user/repos" "rate_limit"; do
# The brace exemption below is for {owner}/{repo} in an endpoint. A brace
# anywhere else - a field value, a --template - says nothing about which
# repository the call reaches, so it must not stand in for a target.
for form in "user/repos" "/orgs/someone/repos" "-X POST /user/repos" "rate_limit" \
"user/repos -f x={y}" "-X POST /orgs/someone/repos -f name={x}" \
"user --template {{.login}}"; do
# shellcheck disable=SC2086
run env "${env_common[@]}" "$d/gh" api $form
assert_forge_refused "names no endpoint under" "gh api $form" || return 1