fix(config): a strict shape for a local model alias; the upload chat model must be local (adversarial review F1, F8)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 16:38:30 +02:00
co-authored by Claude Sonnet 5.5
parent 7fe746173d
commit a9a2a9270f
+16 -12
View File
@@ -3,6 +3,7 @@ package config
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"
@@ -116,6 +117,9 @@ func loadUploadConfig() (UploadConfig, error) {
if err := u.validateReconcile(); err != nil {
return UploadConfig{}, err
}
if !IsLocalModel(u.UploadChatModel) {
return UploadConfig{}, fmt.Errorf("config: TAPIR_UPLOAD_CHAT_MODEL=%q is not a local model (koala/ or iguana/): the whole private transcript is sent to it", u.UploadChatModel)
}
chars, err := intOr("TAPIR_MAX_UPLOADED_TRANSCRIPT_CHARS", defaultMaxUploadedChars)
if err != nil {
@@ -141,18 +145,18 @@ func positiveInt(key string, fallback int) (int, error) {
return n, nil
}
// localPrefixes are the host prefixes of the models that run on the homelab. Private
// meeting content may be sent to these and to nothing else for reconciliation
// (ADR-034): a cloud alias is refused at start-up rather than discovered later.
var localPrefixes = []string{"koala/", "iguana/"}
// localAlias is the shape of a model alias that runs on the homelab: a local host
// prefix, then one plain name. A bare prefix check let through aliases that are shaped
// like an escape (koala/../berget/x, koala//x, koala/x?host=berget): whether the
// gateway would route them elsewhere is unknown, so they are refused (ADR-035 review).
var localAlias = regexp.MustCompile(`^(koala|iguana)/[A-Za-z0-9][A-Za-z0-9._:-]*$`)
func isLocalModel(alias string) bool {
for _, p := range localPrefixes {
if strings.HasPrefix(alias, p) {
return true
}
}
return false
// IsLocalModel reports whether alias is a local model. Private meeting content may be
// sent to these and to nothing else: reconciliation (ADR-034) and chat over an uploaded
// recording (ADR-035) are both held to it, and a cloud alias is refused at start-up
// rather than discovered later.
func IsLocalModel(alias string) bool {
return localAlias.MatchString(alias) && !strings.Contains(alias, "..")
}
// validateReconcile checks the two-engine settings. They are inert while no base
@@ -168,7 +172,7 @@ func (u UploadConfig) validateReconcile() error {
{"TAPIR_RECONCILE_BASE_MODEL", u.ReconcileBaseModel},
{"TAPIR_RECONCILE_MODEL", u.ReconcileModel},
} {
if !isLocalModel(m.alias) {
if !IsLocalModel(m.alias) {
return fmt.Errorf("config: %s=%q is not a local model (koala/ or iguana/): private meeting content must not leave the homelab for reconciliation", m.name, m.alias)
}
}