mirror of
https://github.com/anthropics/claude-code.git
synced 2026-04-16 16:07:50 +00:00
- Use allowlist for issue view (numeric issue numbers only) - Enforce zero positional args for issue list / label list - Pin GH_HOST and GH_REPO explicitly to avoid ambient state - Add descriptive error messages with usage examples
97 lines
3.0 KiB
Bash
Executable File
97 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Wrapper around gh CLI that only allows specific subcommands and flags.
|
|
# All commands are scoped to the current repository via GH_REPO or GITHUB_REPOSITORY.
|
|
#
|
|
# Usage:
|
|
# ./scripts/gh.sh issue view 123
|
|
# ./scripts/gh.sh issue view 123 --comments
|
|
# ./scripts/gh.sh issue list --state open --limit 20
|
|
# ./scripts/gh.sh search issues "search query" --limit 10
|
|
# ./scripts/gh.sh label list --limit 100
|
|
|
|
export GH_HOST=github.com
|
|
|
|
REPO="${GH_REPO:-${GITHUB_REPOSITORY:-}}"
|
|
if [[ -z "$REPO" || "$REPO" == */*/* || "$REPO" != */* ]]; then
|
|
echo "Error: GH_REPO or GITHUB_REPOSITORY must be set to owner/repo format (e.g., GITHUB_REPOSITORY=anthropics/claude-code)" >&2
|
|
exit 1
|
|
fi
|
|
export GH_REPO="$REPO"
|
|
|
|
ALLOWED_FLAGS=(--comments --state --limit --label)
|
|
FLAGS_WITH_VALUES=(--state --limit --label)
|
|
|
|
SUB1="${1:-}"
|
|
SUB2="${2:-}"
|
|
CMD="$SUB1 $SUB2"
|
|
case "$CMD" in
|
|
"issue view"|"issue list"|"search issues"|"label list")
|
|
;;
|
|
*)
|
|
echo "Error: only 'issue view', 'issue list', 'search issues', 'label list' are allowed (e.g., ./scripts/gh.sh issue view 123)" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
shift 2
|
|
|
|
# Separate flags from positional arguments
|
|
POSITIONAL=()
|
|
FLAGS=()
|
|
skip_next=false
|
|
for arg in "$@"; do
|
|
if [[ "$skip_next" == true ]]; then
|
|
FLAGS+=("$arg")
|
|
skip_next=false
|
|
elif [[ "$arg" == -* ]]; then
|
|
flag="${arg%%=*}"
|
|
matched=false
|
|
for allowed in "${ALLOWED_FLAGS[@]}"; do
|
|
if [[ "$flag" == "$allowed" ]]; then
|
|
matched=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$matched" == false ]]; then
|
|
echo "Error: only --comments, --state, --limit, --label flags are allowed (e.g., ./scripts/gh.sh issue list --state open --limit 20)" >&2
|
|
exit 1
|
|
fi
|
|
FLAGS+=("$arg")
|
|
# If flag expects a value and isn't using = syntax, skip next arg
|
|
if [[ "$arg" != *=* ]]; then
|
|
for vflag in "${FLAGS_WITH_VALUES[@]}"; do
|
|
if [[ "$flag" == "$vflag" ]]; then
|
|
skip_next=true
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
else
|
|
POSITIONAL+=("$arg")
|
|
fi
|
|
done
|
|
|
|
if [[ "$CMD" == "search issues" ]]; then
|
|
QUERY="${POSITIONAL[0]:-}"
|
|
QUERY_LOWER=$(echo "$QUERY" | tr '[:upper:]' '[:lower:]')
|
|
if [[ "$QUERY_LOWER" == *"repo:"* || "$QUERY_LOWER" == *"org:"* || "$QUERY_LOWER" == *"user:"* ]]; then
|
|
echo "Error: search query must not contain repo:, org:, or user: qualifiers (e.g., ./scripts/gh.sh search issues \"bug report\" --limit 10)" >&2
|
|
exit 1
|
|
fi
|
|
gh "$SUB1" "$SUB2" "$QUERY" --repo "$REPO" "${FLAGS[@]}"
|
|
elif [[ "$CMD" == "issue view" ]]; then
|
|
if [[ ${#POSITIONAL[@]} -ne 1 ]] || ! [[ "${POSITIONAL[0]}" =~ ^[0-9]+$ ]]; then
|
|
echo "Error: issue view requires exactly one numeric issue number (e.g., ./scripts/gh.sh issue view 123)" >&2
|
|
exit 1
|
|
fi
|
|
gh "$SUB1" "$SUB2" "${POSITIONAL[0]}" "${FLAGS[@]}"
|
|
else
|
|
if [[ ${#POSITIONAL[@]} -ne 0 ]]; then
|
|
echo "Error: issue list and label list do not accept positional arguments (e.g., ./scripts/gh.sh issue list --state open, ./scripts/gh.sh label list --limit 100)" >&2
|
|
exit 1
|
|
fi
|
|
gh "$SUB1" "$SUB2" "${FLAGS[@]}"
|
|
fi
|