Round 5, and the best findings are again about the fix from round 4. The scrub only ran on success. `set -e` aborts the step when `go mod download` fails, so the cleanup line after it never executed — leaving a push-capable credential on a long-lived self-hosted runner for whatever job landed there next. It is now a `trap ... EXIT`, verified against a simulated failure. It also scrubbed the wrong file in principle: `git config --global` writes to GIT_CONFIG_GLOBAL, else $XDG_CONFIG_HOME/git/config when that exists, else ~/.gitconfig — so deleting ~/.gitconfig can scrub a path the credential was never in. The step now names GIT_CONFIG_GLOBAL itself, leaving exactly one file to remove. And the verification failed open in the case that matters most: `grep -F ""` matches every file, so a run WITHOUT the secret — a fork PR, the threat model — failed the check with a message accusing it of leaking a credential it never had. Guarded on a non-empty secret. Credentials move to an Authorization header instead of being embedded in the URL, so a password containing @ : / or # can no longer break URL parsing in a way that reads as a bad password. Two list-drift holes closed with one test that reads across languages: TestOpenAICompatProvidersAreFullyWired asserts every openAICompatProviders entry is both advertised in endpointProviderNames and has a credential arm in scripts/preflight.sh. Adding a compat provider touches three places in two languages and nothing connected them. Break-checked in both directions. Finally, a whitespace-only GADFLY_BASE_URL disagreed across the boundary: Go TrimSpaces it and takes the registry path, bash called it "set" and skipped the pre-flight, so the missing key arrived as a bare 401 with no notice. Both now agree on what unset means. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
92 lines
4.1 KiB
Bash
Executable File
92 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Table test for the credential pre-flight in preflight.sh.
|
|
#
|
|
# It SOURCES the real implementation rather than copying it, so there is no
|
|
# second definition that can pass while production fails.
|
|
#
|
|
# Run: scripts/preflight_test.sh (exit 0 = all cases pass)
|
|
set -u
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
# shellcheck source=preflight.sh
|
|
. "$SCRIPT_DIR/preflight.sh"
|
|
|
|
fail=0
|
|
check() { # description, want, got
|
|
if [ "$2" = "$3" ]; then
|
|
echo "ok $1"
|
|
else
|
|
echo "FAIL $1 — want '$2', got '$3'"
|
|
fail=1
|
|
fi
|
|
}
|
|
|
|
# probe <provider> [VAR=VAL ...] — run the real function in a clean environment
|
|
# under the same shell options production uses (set -u), so an unset-variable
|
|
# bug surfaces here instead of in a live review.
|
|
probe() {
|
|
local provider="$1"; shift
|
|
env -i PATH="$PATH" HOME="$HOME" "$@" bash -c "
|
|
set -u
|
|
. '$SCRIPT_DIR/preflight.sh'
|
|
gadfly_preflight_key '$provider'
|
|
"
|
|
}
|
|
|
|
echo "== registry path: keyed providers with no key must name their variable =="
|
|
check "qwen, no key" "QWEN_API_KEY" "$(probe qwen)"
|
|
check "kimi, no key" "KIMI_API_KEY" "$(probe kimi)"
|
|
check "ollama-cloud, no key" "OLLAMA_CLOUD_API_KEY" "$(probe ollama-cloud)"
|
|
check "openai, no key" "OPENAI_API_KEY" "$(probe openai)"
|
|
check "openai-compatible, none" "OPENAI_API_KEY" "$(probe openai-compatible)"
|
|
check "anthropic, no key" "ANTHROPIC_API_KEY" "$(probe anthropic)"
|
|
|
|
echo "== registry path: the provider's own key lets it run =="
|
|
check "qwen, keyed" "" "$(probe qwen QWEN_API_KEY=k)"
|
|
check "kimi, keyed" "" "$(probe kimi KIMI_API_KEY=k)"
|
|
check "ollama-cloud, keyed" "" "$(probe ollama-cloud OLLAMA_API_KEY=k)"
|
|
check "openai-compatible, keyed" "" "$(probe openai-compatible OPENAI_API_KEY=k)"
|
|
|
|
echo "== a wrong-provider key never satisfies a provider (no cross-fallback) =="
|
|
check "qwen w/ only OPENAI key" "QWEN_API_KEY" "$(probe qwen OPENAI_API_KEY=k)"
|
|
check "kimi w/ only QWEN key" "KIMI_API_KEY" "$(probe kimi QWEN_API_KEY=k)"
|
|
|
|
echo "== an empty-string key counts as missing, not present =="
|
|
check "qwen, empty key" "QWEN_API_KEY" "$(probe qwen QWEN_API_KEY=)"
|
|
|
|
echo "== GADFLY_API_KEY does NOT substitute on the registry path =="
|
|
# resolveModel reads GADFLY_API_KEY only after its `baseURL == ""` early
|
|
# return, so on this path the built-in reads its own variable and a set
|
|
# GADFLY_API_KEY changes nothing. Treating it as sufficient was a false pass.
|
|
check "qwen w/ GADFLY_API_KEY only" "QWEN_API_KEY" "$(probe qwen GADFLY_API_KEY=k)"
|
|
|
|
echo "== override path (GADFLY_BASE_URL set) is deliberately not pre-flighted =="
|
|
# The credential there is GADFLY_API_KEY with a client-specific fallback, and
|
|
# the built-ins' own variables are never read. Checking one path's rules
|
|
# against the other produced a false pass in BOTH directions, so this path is
|
|
# left alone rather than guessed at.
|
|
check "qwen + BASE_URL, no keys" "" "$(probe qwen GADFLY_BASE_URL=https://x)"
|
|
check "qwen + BASE_URL + own key" "" "$(probe qwen GADFLY_BASE_URL=https://x QWEN_API_KEY=k)"
|
|
check "qwen + BASE_URL + GADFLY key" "" "$(probe qwen GADFLY_BASE_URL=https://x GADFLY_API_KEY=k)"
|
|
check "openai + BASE_URL, no keys" "" "$(probe openai GADFLY_BASE_URL=https://x)"
|
|
|
|
echo "== providers needing no key are never blocked, with nothing set =="
|
|
for p in ollama llama-swap llama-swaps llamaswap llamaswaps foreman google gemini some-dsn-name; do
|
|
check "unkeyed $p" "" "$(probe "$p")"
|
|
done
|
|
|
|
# google is absent from the table on purpose: it accepts GOOGLE_API_KEY *or*
|
|
# GEMINI_API_KEY, so a one-name arm would skip a correctly-configured run.
|
|
check "google w/ only GEMINI_API_KEY" "" "$(probe google GEMINI_API_KEY=k)"
|
|
|
|
echo "== a whitespace-only GADFLY_BASE_URL counts as unset, as it does in Go =="
|
|
# resolveModel TrimSpaces it and takes the registry path; if this check
|
|
# disagreed, the missing key would arrive as a bare 401 with no skip notice.
|
|
check "qwen + blank BASE_URL" "QWEN_API_KEY" "$(probe qwen GADFLY_BASE_URL=" ")"
|
|
|
|
if [ "$fail" -ne 0 ]; then
|
|
echo "RESULT: preflight table FAILED"
|
|
exit 1
|
|
fi
|
|
echo "RESULT: all pre-flight cases pass"
|