Why: Nested unchanged credential masks left empty parents that looked like managed subtree overwrites. Shared members also hit an owner-only upload guard, while upload ingestion cards could disclose private filenames and parser metadata. Evidence: SELF independent nested-mask probe, HTTP nested/mixed writeback, member upload, project card privacy and authorization-change regressions failed before each fix and passed after it. Affected settings/server suite passed; exact final-object build and independent replay remain delivery gates owned by the task coordinator. Impact: Prune only parents emptied by ignored masks; explicit empty/real managed writes stay rejected. Registered uploads check live work and trusted project existence, keep originals owned by the actor, and recheck before persistence. Suppress only upload project cards when no private stream exists; explicit structure cards and unregistered upload behavior remain available. Refs: Dev project-member-access plan r1 Task 1/R5; independent nested mask P2; 2026-10-13 composed member-upload and private project-stream acceptance findings
87 lines
3.8 KiB
Go
87 lines
3.8 KiB
Go
package settings
|
|
|
|
import (
|
|
core "agent-system-core"
|
|
"bytes"
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func TestMaskedProjectMetadataCopiesSecretViews(t *testing.T) {
|
|
s := NewService(nil)
|
|
s.SetExtraSchema([]core.SettingDesc{{Key: "plugins.private.credential", Secret: true}})
|
|
raw := map[string]any{"models.chat.apiKey": "fixture-known", "legacy.token": "fixture-flat", "plugins.private": map[string]any{"credential": "fixture-schema"}, "nested": []any{map[string]any{"api_key": "fixture-array"}}, "opaque.secret": map[string]any{"value": "fixture-opaque"}, "custom.token": []any{"fixture-list"}, "custom.caption": "Allowed", "rdm.projectId": "canonical", "rdm.state": "ready"}
|
|
before, _ := json.Marshal(raw)
|
|
view := s.MaskedProjectMetadata(raw)
|
|
after, _ := json.Marshal(raw)
|
|
if string(before) != string(after) {
|
|
t.Fatal("read-only metadata view mutated its source")
|
|
}
|
|
encoded, _ := json.Marshal(view)
|
|
for _, secret := range []string{"fixture-known", "fixture-flat", "fixture-schema", "fixture-array", "fixture-opaque", "fixture-list"} {
|
|
if bytes.Contains(encoded, []byte(secret)) {
|
|
t.Fatal("metadata view exposed a fixture secret")
|
|
}
|
|
}
|
|
if view["custom.caption"] != "Allowed" || view["rdm.projectId"] != "canonical" || view["rdm.state"] != "ready" {
|
|
t.Fatal("nonsecret metadata shape/value changed")
|
|
}
|
|
if view["legacy.token"] != MaskPlaceholder || view["opaque.secret"] != MaskPlaceholder || view["custom.token"] != MaskPlaceholder {
|
|
t.Fatal("secret fallback did not mask complete dotted values")
|
|
}
|
|
}
|
|
|
|
func TestPrepareProjectMetadataWriteUsesManagedGuard(t *testing.T) {
|
|
s := NewService(nil)
|
|
s.SetExtraSchema([]core.SettingDesc{{Key: "plugins.private.managed", Scope: core.SettingScopeSystem}})
|
|
for _, raw := range []map[string]any{{"models.chat.apiKey": "fixture"}, {"models": map[string]any{"chat": map[string]any{"apiKey": "fixture"}}}, {"models": "smash"}, {"plugins.private.managed": "override"}} {
|
|
if _, err := s.PrepareProjectMetadataWrite(raw); err == nil {
|
|
t.Fatal("managed metadata write accepted")
|
|
}
|
|
}
|
|
raw := map[string]any{"custom.caption": "Allowed", "custom.token": MaskPlaceholder, "models.chat.apiKey": MaskPlaceholder}
|
|
view, err := s.PrepareProjectMetadataWrite(raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(view) != 1 || view["custom.caption"] != "Allowed" {
|
|
t.Fatal("metadata writes must preserve allowed dotted keys and ignore mask placeholders")
|
|
}
|
|
if raw["custom.token"] != MaskPlaceholder {
|
|
t.Fatal("write preparation mutated its input")
|
|
}
|
|
}
|
|
|
|
func TestPrepareProjectMetadataWritePrunesOnlyMaskedParents(t *testing.T) {
|
|
s := NewService(nil)
|
|
for _, raw := range []map[string]any{
|
|
{"models": map[string]any{"chat": map[string]any{"apiKey": MaskPlaceholder}}, "display": map[string]any{"caption": "Updated"}},
|
|
{"models.chat": map[string]any{"apiKey": MaskPlaceholder}, "display.caption": "Updated"},
|
|
{"models": map[string]any{"chat.apiKey": MaskPlaceholder}, "display.caption": "Updated"},
|
|
} {
|
|
before, _ := json.Marshal(raw)
|
|
got, err := s.PrepareProjectMetadataWrite(raw)
|
|
if err != nil {
|
|
t.Fatalf("unchanged masked credential rejected legal metadata edit: %v", err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatal("mask-only parent remained in prepared metadata")
|
|
}
|
|
after, _ := json.Marshal(raw)
|
|
if !bytes.Equal(before, after) {
|
|
t.Fatal("mask preparation changed its input")
|
|
}
|
|
}
|
|
for _, raw := range []map[string]any{
|
|
{"models": map[string]any{}},
|
|
{"models": map[string]any{"chat": map[string]any{}}},
|
|
{"models.chat": map[string]any{}},
|
|
{"models": map[string]any{"chat": map[string]any{"apiKey": MaskPlaceholder, "model": "Real overwrite"}}},
|
|
{"models.chat": map[string]any{"apiKey": MaskPlaceholder}, "models": map[string]any{"chat": map[string]any{}}},
|
|
} {
|
|
if _, err := s.PrepareProjectMetadataWrite(raw); err == nil {
|
|
t.Fatal("explicit empty or real managed overwrite accepted")
|
|
}
|
|
}
|
|
}
|