From 896ce6a9522b6ec2b648ac80db3764e06a689eeb Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 2 Oct 2026 16:37:49 +0200 Subject: [PATCH] test(config): a strict shape for a local model alias, and the upload chat model must be local (red; adversarial review F1, F8) Co-Authored-By: Claude Sonnet 5.5 --- internal/config/uploads_local_test.go | 74 +++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 internal/config/uploads_local_test.go diff --git a/internal/config/uploads_local_test.go b/internal/config/uploads_local_test.go new file mode 100644 index 0000000..f7adccd --- /dev/null +++ b/internal/config/uploads_local_test.go @@ -0,0 +1,74 @@ +package config + +import "testing" + +// What counts as a LOCAL model alias, and where that is demanded (ADR-034, ADR-035 review). +// A private transcript may be sent to a local model and to nothing else; the check is a +// guard against a misconfiguration or a crafted alias, so it must be strict about shape, +// not just about a prefix. + +func TestIsLocalModel_AcceptsRealLocalAliasesAndNothingShapedLikeAnEscape(t *testing.T) { + tests := []struct { + alias string + want bool + }{ + {"koala/phi4-mini", true}, + {"iguana/gemma4-26b", true}, + {"iguana/gemma4-31b-think", true}, + {"koala/qwen36-35b-a3b", true}, + {"iguana/kb-whisper", true}, + {"iguana/hf.co-model_v1:q4", true}, + {"", false}, + {"koala/", false}, + {"iguana/", false}, + {"gemma4-26b", false}, + {"berget/mistral-small", false}, + {"berget/koala/x", false}, + {"xkoala/x", false}, + {"Koala/x", false}, + {" koala/x", false}, + {"koala/x ", false}, + {"koala/x y", false}, + {"koala/x\n", false}, + {"koala/../berget/kb", false}, + {"koala//berget/mistral-small", false}, + {"koala/x/y", false}, + {"koala/x?host=berget", false}, + {"koala/x#frag", false}, + {"koala/x@evil", false}, + {"koalа/x", false}, // Cyrillic а + } + for _, tt := range tests { + t.Run(tt.alias, func(t *testing.T) { + if got := IsLocalModel(tt.alias); got != tt.want { + t.Errorf("IsLocalModel(%q) = %v, want %v", tt.alias, got, tt.want) + } + }) + } +} + +func TestLoad_UploadChatModelMustBeLocalBecauseTheWholeTranscriptIsSentToIt(t *testing.T) { + for _, alias := range []string{"berget/mistral-small", "gemini/gemini-2.5-pro", "gemma4-26b", "koala/../berget/kb"} { + t.Run(alias, func(t *testing.T) { + clearUploadEnv(t) + setEnv(t, map[string]string{"TAPIR_UPLOAD_CHAT_MODEL": alias}) + + if _, err := Load(); err == nil { + t.Errorf("Load accepted TAPIR_UPLOAD_CHAT_MODEL=%q: a private transcript of up to 90,000 characters would be sent to it", alias) + } + }) + } +} + +func TestLoad_ALocalUploadChatModelIsAccepted(t *testing.T) { + clearUploadEnv(t) + setEnv(t, map[string]string{"TAPIR_UPLOAD_CHAT_MODEL": "iguana/gemma4-31b"}) + + cfg, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + if cfg.UploadChatModel != "iguana/gemma4-31b" { + t.Errorf("UploadChatModel = %q", cfg.UploadChatModel) + } +}