fix(secrets): drop retired org-level guard from SetGlobal (global vendor keys are tenant-owned) #2002

Merged
hongming merged 1 commits from fix/setglobal-drop-retired-org-billing-guard into main 2026-05-29 04:38:15 +00:00
2 changed files with 49 additions and 55 deletions
+9 -27
View File
@@ -67,14 +67,6 @@ func platformManagedLLMModeForWorkspace(c *gin.Context, workspaceID string) bool
return strings.EqualFold(res.ResolvedMode, LLMBillingModePlatformManaged)
}
// platformManagedLLMMode is the legacy org-level gate retained for any test
// harness still asserting the env-var-only behavior. Production code paths
// must call platformManagedLLMModeForWorkspace instead so a workspace-level
// byok override actually takes effect on the secrets-write path.
func platformManagedLLMMode() bool {
return strings.EqualFold(strings.TrimSpace(os.Getenv("MOLECULE_LLM_BILLING_MODE")), "platform_managed")
}
// rejectPlatformManagedDirectLLMBypassForWorkspace is the per-workspace
// successor to rejectPlatformManagedDirectLLMBypass (internal#691). The
// strip-list ONLY applies when this specific workspace resolves to
@@ -91,22 +83,6 @@ func rejectPlatformManagedDirectLLMBypassForWorkspace(c *gin.Context, workspaceI
return true
}
// rejectPlatformManagedDirectLLMBypass is the legacy org-level shim. Retained
// only for backwards compatibility with any external/test caller still on the
// old shape; new code MUST use the per-workspace variant above. Production
// code paths (the secrets.go handlers + workspace.go create-secret path) all
// switched in internal#691.
func rejectPlatformManagedDirectLLMBypass(c *gin.Context, key string) bool {
if !platformManagedLLMMode() || !isPlatformManagedDirectLLMBypassKey(key) {
return false
}
c.JSON(http.StatusBadRequest, gin.H{
"error": "direct Hermes custom provider secrets are blocked for platform-managed LLM workspaces; use MODEL/LLM_PROVIDER or the platform LLM proxy env instead",
"key": key,
})
return true
}
type SecretsHandler struct {
restartFunc func(workspaceID string) // Optional: auto-restart after secret change
}
@@ -486,9 +462,15 @@ func (h *SecretsHandler) SetGlobal(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
return
}
if rejectPlatformManagedDirectLLMBypass(c, body.Key) {
return
}
// internal#718: the org-level LLM billing rung was retired — billing is
// resolved per-workspace, not per-org. A global secret is the tenant's OWN
// shared credential; the provision-time provider-matched strip
// (workspace_provision) removes any global cred a given workspace's resolved
// provider does not accept, so a platform-managed workspace can never USE a
// non-matching global vendor/oauth key. The legacy org-env SetGlobal gate
// (keyed off the retired MOLECULE_LLM_BILLING_MODE) is therefore removed;
// per-workspace writes still enforce the strip-list via
// rejectPlatformManagedDirectLLMBypassForWorkspace.
encrypted, err := crypto.Encrypt([]byte(body.Value))
if err != nil {
@@ -979,47 +979,59 @@ func TestSetGlobal_AutoRestartsAffectedWorkspaces(t *testing.T) {
}
}
// TestSetGlobal_RejectsPlatformBypassKeyOnPlatformManagedTenant is the
// molecule-core#1994 co-mingling GUARD regression. Removing the byok strip is
// only safe if the platform's own credential is never written into a tenant's
// global_secrets. SetGlobal is the in-code write boundary: on a tenant whose
// resolved LLM mode is platform_managed (the metered default), a direct
// vendor / oauth bypass-list key MUST be rejected (400) and NOT persisted —
// the tenant is supposed to route through the CP proxy, not carry a direct
// platform-shaped credential at global scope. This is what keeps a
// platform-origin token out of global_secrets going forward.
// TestSetGlobal_AllowsTenantOwnedVendorKeyDespiteLegacyOrgEnv pins the
// internal#718 correction: the org-level LLM billing rung is RETIRED (billing
// is resolved per-workspace, not per-org). A global secret is the tenant's OWN
// shared credential and is always writable at global scope; the provision-time
// provider-matched strip (workspace_provision) keeps any platform-managed
// workspace from USING a non-matching global cred, and per-workspace secret
// writes still enforce the strip-list via the per-workspace guard. So even with
// the legacy MOLECULE_LLM_BILLING_MODE env still set to platform_managed, a
// global vendor/oauth key write MUST SUCCEED (200) and persist — the retired
// org rung no longer gates it.
//
// (On a byok/disabled tenant the same write is ALLOWED — that key is the
// tenant's OWN credential, which the corrected model expects at global scope.
// TestSetGlobal_AutoRestartsAffectedWorkspaces covers that allowed path.)
//
// Mutation: drop the rejectPlatformManagedDirectLLMBypass guard from SetGlobal
// → the write reaches the INSERT (no 400) → this test RED.
func TestSetGlobal_RejectsPlatformBypassKeyOnPlatformManagedTenant(t *testing.T) {
setupTestDB(t)
handler := NewSecretsHandler(nil)
// Mutation: re-add the org-level rejectPlatformManagedDirectLLMBypass guard to
// SetGlobal → the write 400s before the INSERT → this test RED.
func TestSetGlobal_AllowsTenantOwnedVendorKeyDespiteLegacyOrgEnv(t *testing.T) {
mock := setupTestDB(t)
setupTestRedis(t)
// Org/tenant default is platform_managed — the metered path. A direct
// vendor key write into global_secrets must be refused here.
restarted := make(chan string, 2)
handler := NewSecretsHandler(func(id string) { restarted <- id })
// Legacy org env still platform_managed — it must no longer gate the write.
t.Setenv("MOLECULE_LLM_BILLING_MODE", LLMBillingModePlatformManaged)
mock.ExpectExec("INSERT INTO global_secrets").
WithArgs("CLAUDE_CODE_OAUTH_TOKEN", sqlmock.AnyArg(), sqlmock.AnyArg()).
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectQuery("SELECT id FROM workspaces").
WithArgs("CLAUDE_CODE_OAUTH_TOKEN").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow("ws-a"))
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
body := `{"key":"CLAUDE_CODE_OAUTH_TOKEN","value":"sk-ant-oat01-platform-shaped"}`
body := `{"key":"CLAUDE_CODE_OAUTH_TOKEN","value":"sk-ant-oat01-tenant-own"}`
c.Request = httptest.NewRequest("POST", "/admin/secrets", bytes.NewBufferString(body))
c.Request.Header.Set("Content-Type", "application/json")
handler.SetGlobal(c)
if w.Code != http.StatusBadRequest {
t.Fatalf("expected 400 (bypass-list key rejected for platform_managed tenant), got %d: %s", w.Code, w.Body.String())
if w.Code != http.StatusOK {
t.Fatalf("expected 200 (global write allowed; org rung retired), got %d: %s", w.Code, w.Body.String())
}
if !strings.Contains(w.Body.String(), "blocked") {
t.Errorf("response should explain the block; got %s", w.Body.String())
// Wait on the async restart fan-out so its SELECT drains before db swap.
select {
case id := <-restarted:
if id != "ws-a" {
t.Errorf("expected ws-a restarted, got %s", id)
}
case <-time.After(2 * time.Second):
t.Fatal("auto-restart not fired for affected workspace")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("unmet sqlmock expectations: %v", err)
}
// No INSERT was expected on the mock — sqlmock would error on an
// unexpected ExecContext, so reaching here with a 400 proves the write
// was refused before the DB.
}
// TestDeleteGlobal_AutoRestartsAffectedWorkspaces covers the delete branch of #15.