fix(qwen): the own-key fallback made the override path checkable
Round 9's best finding is that my own round-8 change falsified a rationale I
wrote in round 5. The pre-flight skips the endpoint-override path because "a
built-in's own variable is never consulted there" — then I gave kimi/qwen an
own-key fallback that consults exactly that variable on exactly that path. So a
keyless override config sailed past the check and failed as a 401, which is the
failure the check exists to replace.
Now that the rule is statable for those two providers, they are checked on both
paths ("own key or GADFLY_API_KEY"), while everything else stays silent on the
override path because its rules still are not.
The missing-key hint on the GADFLY_ENDPOINT_* path named the endpoint variable
— telling a keyless operator to put a credential in a Gitea var, which is not
masked, and contradicting the README warning added one round earlier. It now
always names the provider's own masked secret.
Also: the model argument is trimmed, since Go trims GADFLY_MODEL and padding
would otherwise slip past the claude-code exemption; the test job takes
`permissions: contents: read`, being the one job that executes PR-authored
code; and the ollama-cloud rationale is stated once.
Deliberately not taken, with reasons rather than silence: the credential-scrub
bash could be extracted to a testable script like preflight.sh was — fair, and
a follow-up, since moving it now would be a fresh untested surface at merge
time. `tr -d [:space:]` strips POSIX whitespace where Go strips Unicode, which
differs only for a GADFLY_BASE_URL made entirely of non-ASCII spaces. And the
two provider tests overlap but assert different contracts that should be able
to fail independently.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
+7
-4
@@ -74,12 +74,15 @@ func openAICompatOptions(provider, baseURL, key, keyHint string) []openai.Option
|
||||
// this function exists to prevent — it is the same vendor's key — and
|
||||
// it lets an operator keep the credential in a masked secret while the
|
||||
// endpoint URL lives in a var, which is NOT masked.
|
||||
//
|
||||
// The hint always names that secret, never the caller's keyHint: on the
|
||||
// GADFLY_ENDPOINT_* path the caller's is the endpoint variable, and
|
||||
// pointing a keyless operator at it advises them to put a credential
|
||||
// somewhere Gitea does not mask.
|
||||
if key == "" {
|
||||
if own := os.Getenv(builtinCompatKeyEnv(provider)); own != "" {
|
||||
key, keyHint = own, builtinCompatKeyEnv(provider)
|
||||
}
|
||||
key = os.Getenv(builtinCompatKeyEnv(provider))
|
||||
}
|
||||
opts = append(opts, openai.WithAPIKey(key), openai.WithAPIKeyName(keyHint))
|
||||
opts = append(opts, openai.WithAPIKey(key), openai.WithAPIKeyName(builtinCompatKeyEnv(provider)))
|
||||
case key != "":
|
||||
opts = append(opts, openai.WithAPIKey(key))
|
||||
// openai/openai-compatible with no explicit key keep openai.New's
|
||||
|
||||
@@ -269,7 +269,7 @@ func TestBuiltinCompatProvidersNeverInheritOpenAIKey(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("resolveModel: %v", err)
|
||||
}
|
||||
assertFailsClosed(t, m, seen, foreign, "GADFLY_API_KEY")
|
||||
assertFailsClosed(t, m, seen, foreign, "QWEN_API_KEY", "KIMI_API_KEY")
|
||||
})
|
||||
|
||||
t.Run(provider+" via GADFLY_ENDPOINT_*", func(t *testing.T) {
|
||||
@@ -283,7 +283,7 @@ func TestBuiltinCompatProvidersNeverInheritOpenAIKey(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Model: %v", err)
|
||||
}
|
||||
assertFailsClosed(t, m, seen, foreign, "GADFLY_ENDPOINT_EP")
|
||||
assertFailsClosed(t, m, seen, foreign, "QWEN_API_KEY", "KIMI_API_KEY")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -303,7 +303,7 @@ func leakServer(t *testing.T) (*httptest.Server, *[]string) {
|
||||
return srv, &seen
|
||||
}
|
||||
|
||||
func assertFailsClosed(t *testing.T, m llm.Model, seen *[]string, foreign, wantHint string) {
|
||||
func assertFailsClosed(t *testing.T, m llm.Model, seen *[]string, foreign string, wantAnyHint ...string) {
|
||||
t.Helper()
|
||||
_, err := m.Generate(context.Background(), llm.Request{Messages: []llm.Message{llm.UserText("hi")}})
|
||||
|
||||
@@ -313,15 +313,23 @@ func assertFailsClosed(t *testing.T, m llm.Model, seen *[]string, foreign, wantH
|
||||
}
|
||||
}
|
||||
if len(*seen) > 0 {
|
||||
t.Errorf("a keyless %s provider reached the network (%d request(s)) instead of failing closed", wantHint, len(*seen))
|
||||
t.Errorf("a keyless provider reached the network (%d request(s)) instead of failing closed", len(*seen))
|
||||
}
|
||||
// The positive half: prove it refused for the right reason, so the test
|
||||
// cannot pass on a provider that quietly did nothing at all.
|
||||
if err == nil {
|
||||
t.Fatal("keyless provider returned no error; expected a missing-key failure")
|
||||
}
|
||||
if !strings.Contains(err.Error(), wantHint) {
|
||||
t.Errorf("error = %v, want it to name %s so the operator knows what to set", err, wantHint)
|
||||
// The hint must name a MASKED secret the operator can set, never the
|
||||
// unmasked GADFLY_ENDPOINT_* variable.
|
||||
named := false
|
||||
for _, h := range wantAnyHint {
|
||||
if strings.Contains(err.Error(), h) {
|
||||
named = true
|
||||
}
|
||||
}
|
||||
if !named {
|
||||
t.Errorf("error = %v, want it to name one of %v so the operator knows what to set", err, wantAnyHint)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user