Pre-fill the alert permissions and test the plugin manifest parsing

Two of the three permissions treated as manual are in fact pre-fillable. The
earlier check scraped the rendered docs page, whose table splits those rows in
a way the parse missed; the docs source lists secret_scanning_alerts and
vulnerability_alerts as supported query parameters. Only checks is genuinely
absent, so the manual list shrinks to that one entry.

That entry now names what breaks without it. Checks: Read governs the status
rollup behind gh pr checks and the annotations behind gh run view, and both
degrade to empty results rather than permission errors, so an unticked box
reads as a broken CI integration rather than a missing scope.

The marketplace and enabled-plugin extraction move out of the install function
into host_marketplaces and host_enabled_plugins so they can be exercised
directly. The tests cover the pipe separator that keeps an absent repo from
shifting a url leftwards, rejection of marketplace sources that are neither
github nor git, disabled plugins being excluded, and the allowlist refusing to
carry credentials, transcripts or history.
This commit is contained in:
2026-07-31 08:42:28 -05:00
parent dbe6c2e34b
commit b8bf9f9eff
4 changed files with 158 additions and 28 deletions
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
set -euo pipefail
# shellcheck source-path=SCRIPTDIR
# shellcheck source=tasks/ai/sbx
source "$(dirname "${BASH_SOURCE[0]}")/../tasks/ai/sbx"
failures=0
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
failures=$((failures + 1))
}
assert_url() {
local kind="$1" repo="$2" url="$3" expected="$4" actual
if actual="$(marketplace_url "$kind" "$repo" "$url")"; then
[[ "$actual" == "$expected" ]] ||
fail "marketplace_url $kind '$repo' '$url' gave '$actual', expected '$expected'"
else
[[ "$expected" == "<fail>" ]] ||
fail "marketplace_url $kind '$repo' '$url' failed, expected '$expected'"
fi
}
assert_url github obra/superpowers-marketplace "" \
"https://github.com/obra/superpowers-marketplace.git"
assert_url git "" https://git.example.com/x.git "https://git.example.com/x.git"
assert_url github "" "" "<fail>"
assert_url git "" "" "<fail>"
assert_url local /some/path "" "<fail>"
cat >"$work/known_marketplaces.json" <<'EOF'
{
"superpowers-marketplace": {
"source": { "source": "github", "repo": "obra/superpowers-marketplace" }
},
"mroberts": {
"source": { "source": "git", "url": "https://git.mroberts.dev/mroberts/claude-plugin.git" }
},
"bundled": {
"source": { "source": "local" }
}
}
EOF
mapfile -t lines < <(host_marketplaces "$work/known_marketplaces.json")
((${#lines[@]} == 3)) ||
fail "expected 3 marketplace lines, got ${#lines[@]}"
IFS='|' read -r name kind repo url <<<"${lines[1]}"
[[ "$name" == "mroberts" ]] || fail "name mis-parsed: $name"
[[ "$kind" == "git" ]] || fail "source kind mis-parsed: $kind"
[[ -z "$repo" ]] || fail "absent repo should be empty, got '$repo'"
[[ "$url" == "https://git.mroberts.dev/mroberts/claude-plugin.git" ]] ||
fail "url shifted into the wrong field: '$url'"
IFS='|' read -r name kind repo url <<<"${lines[2]}"
[[ "$kind" == "local" ]] || fail "unsupported kind mis-parsed: $kind"
marketplace_url "$kind" "$repo" "$url" >/dev/null 2>&1 &&
fail "a local marketplace should be rejected, not turned into a URL"
cat >"$work/settings.json" <<'EOF'
{
"enabledPlugins": {
"caveman@caveman": true,
"ponytail@ponytail": true,
"disabled-thing@somewhere": false
},
"other": "ignored"
}
EOF
mapfile -t plugins < <(host_enabled_plugins "$work/settings.json")
((${#plugins[@]} == 2)) ||
fail "expected 2 enabled plugins, got ${#plugins[@]}: ${plugins[*]}"
printf '%s\n' "${plugins[@]}" | grep -qx 'disabled-thing@somewhere' &&
fail "a disabled plugin was treated as enabled"
printf '%s\n' "${plugins[@]}" | grep -qx 'caveman@caveman' ||
fail "an enabled plugin is missing"
printf '{}\n' >"$work/empty.json"
mapfile -t none < <(host_enabled_plugins "$work/empty.json")
((${#none[@]} == 0)) || fail "empty settings produced ${#none[@]} plugins"
for forbidden in .credentials.json projects transcripts history.jsonl file-history cache backups; do
printf '%s\n' "${CLAUDE_CONFIG_ALLOW[@]}" | grep -qx "$forbidden" &&
fail "CLAUDE_CONFIG_ALLOW must not carry $forbidden"
done
printf '%s\n' "${CLAUDE_CONFIG_ALLOW[@]}" | grep -qx skills &&
fail "skills must not be copied; it is seeded with 'sbx skills import'"
printf '%s\n' "${CLAUDE_CONFIG_ALLOW[@]}" | grep -qx CLAUDE.md ||
fail "CLAUDE_CONFIG_ALLOW should carry CLAUDE.md"
if ((failures)); then
printf '%d assertion(s) failed\n' "$failures" >&2
exit 1
fi
printf 'All Claude manifest assertions passed.\n'
+11 -3
View File
@@ -59,13 +59,21 @@ done < <(printf '%s\n' "${TOKEN_URL_PERMISSIONS[@]}" | cut -d= -f2)
printf '%s\n' "${TOKEN_URL_PERMISSIONS[@]}" | grep -qx 'workflows=write' ||
fail "workflows must be requested at write"
for unsupported in checks= vulnerability_alerts= secret_scanning_alerts= repository=; do
for unsupported in checks= repository=; do
[[ "$query" != *"$unsupported"* ]] ||
fail "URL sends a parameter the form ignores: $unsupported"
done
[[ ${#TOKEN_MANUAL_PERMISSIONS[@]} -eq 3 ]] ||
fail "expected 3 manually-ticked permissions, found ${#TOKEN_MANUAL_PERMISSIONS[@]}"
for expected in secret_scanning_alerts=read vulnerability_alerts=read statuses=read actions=write; do
[[ "$query" == *"&$expected"* ]] ||
fail "permission dropped out of the pre-filled URL: $expected"
done
((${#TOKEN_MANUAL_PERMISSIONS[@]})) ||
fail "the manual checklist is empty; checks is not pre-fillable and must be listed"
printf '%s\n' "${TOKEN_MANUAL_PERMISSIONS[@]}" | grep -q 'Checks' ||
fail "the manual checklist must name Checks"
if ((failures)); then
printf '%d assertion(s) failed\n' "$failures" >&2