fix(ci): make the credential scrub failure-safe, and stop the lists drifting
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]>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -156,3 +159,37 @@ func TestBuildSpec(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestOpenAICompatProvidersAreFullyWired closes the two remaining ways this
|
||||
// provider family can be half-added. Adding one means touching three places in
|
||||
// two languages, and nothing but this test connects them:
|
||||
//
|
||||
// - endpointProviderNames is the operator-facing list. A provider the code
|
||||
// accepts but the list omits sends someone debugging a name that works.
|
||||
// - scripts/preflight.sh needs a credential arm, or a missing key for that
|
||||
// provider skips the pre-flight and arrives as five unexplained per-lens
|
||||
// failures — the exact thing the pre-flight exists to replace.
|
||||
func TestOpenAICompatProvidersAreFullyWired(t *testing.T) {
|
||||
advertised := make(map[string]bool)
|
||||
for _, n := range strings.Split(endpointProviderNames, "/") {
|
||||
advertised[strings.TrimSpace(n)] = true
|
||||
}
|
||||
|
||||
preflight, err := os.ReadFile(filepath.Join("..", "..", "scripts", "preflight.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read preflight.sh: %v", err)
|
||||
}
|
||||
|
||||
for _, p := range openAICompatProviders {
|
||||
if !advertised[p] {
|
||||
t.Errorf("openAICompatProviders has %q but endpointProviderNames does not list it — "+
|
||||
"the error message operators read would omit a name that works", p)
|
||||
}
|
||||
// The arm may be shared ("openai|openai-compatible)"), so match the
|
||||
// bare name as a case alternative rather than a whole line.
|
||||
if !regexp.MustCompile(`(?m)^\s*(\w[\w-]*\|)*` + regexp.QuoteMeta(p) + `(\|[\w-]+)*\)`).Match(preflight) {
|
||||
t.Errorf("openAICompatProviders has %q but scripts/preflight.sh has no credential arm for it — "+
|
||||
"a missing key for %s would skip the pre-flight and surface as unexplained lens failures", p, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user