chore: Prevent users overriding configsource

This commit is contained in:
jamesread 2026-07-25 22:20:14 +01:00
parent 8b8b422c19
commit b000e82238
4 changed files with 81 additions and 6 deletions

View File

@ -40,7 +40,7 @@ type Action struct {
Groups []string `koanf:"groups"`
Justification string `koanf:"justification"`
// SourceFile is set by OliveTin when loading config (not user YAML).
SourceFile string `koanf:"x-olivetin-source-file"`
SourceFile string `koanf:"-"`
}
func (action *Action) RequiresJustification() bool {
@ -113,7 +113,7 @@ type EntityFile struct {
Icon string `koanf:"icon"`
Properties []EntityProperty `koanf:"properties"`
// SourceFile is set by OliveTin when loading config (not user YAML).
SourceFile string `koanf:"x-olivetin-source-file"`
SourceFile string `koanf:"-"`
}
// EntityProperty defines a column shown when listing entity instances in the UI.

View File

@ -50,6 +50,7 @@ func AppendSource(cfg *Config, k *koanf.Koanf, configPath string) {
return
}
applyStampedSourceFiles(k, cfg)
afterLoadFinalize(cfg, configPath)
}

View File

@ -8,7 +8,8 @@ import (
const sourceFileKey = "x-olivetin-source-file"
// stampSourceOnMaps sets the OliveTin source-file marker on each map in a
// koanf slice value (actions or entities).
// koanf slice value (actions or entities). Always overwrites so user-provided
// x-olivetin-source-file values cannot spoof the real config path.
func stampSourceOnMaps(raw any, sourceFile string) any {
if sourceFile == "" {
return raw
@ -30,9 +31,6 @@ func stampSourceOnMap(item any, sourceFile string) {
if !ok {
return
}
if existing, ok := m[sourceFileKey].(string); ok && existing != "" {
return
}
m[sourceFileKey] = sourceFile
}
@ -49,3 +47,50 @@ func stampConfigKey(k *koanf.Koanf, key, configPath string) {
}).Errorf("Failed to persist source stamps: %v", err)
}
}
// applyStampedSourceFiles copies stamped source paths from koanf maps onto
// unmarshaled actions/entities. SourceFile uses koanf:"-" so YAML cannot set it.
func applyStampedSourceFiles(k *koanf.Koanf, cfg *Config) {
actionPaths := stampedSourcePaths(k.Get("actions"))
for i, action := range cfg.Actions {
if action != nil {
action.SourceFile = sourcePathAt(actionPaths, i)
}
}
entityPaths := stampedSourcePaths(k.Get("entities"))
for i, entity := range cfg.Entities {
if entity != nil {
entity.SourceFile = sourcePathAt(entityPaths, i)
}
}
}
func sourcePathAt(paths []string, index int) string {
if index >= len(paths) {
return ""
}
return paths[index]
}
func stampedSourcePaths(raw any) []string {
items, ok := raw.([]interface{})
if !ok {
return nil
}
paths := make([]string, len(items))
for i, item := range items {
paths[i] = stampedSourceFromMap(item)
}
return paths
}
func stampedSourceFromMap(item any) string {
m, ok := item.(map[string]interface{})
if !ok {
return ""
}
path, _ := m[sourceFileKey].(string)
return path
}

View File

@ -49,3 +49,32 @@ entities:
require.Len(t, cfg.Entities, 1)
assert.Equal(t, includePath, cfg.Entities[0].SourceFile)
}
func TestAppendSourceIgnoresUserProvidedSourceFile(t *testing.T) {
dir := t.TempDir()
basePath := filepath.Join(dir, "config.yaml")
require.NoError(t, os.WriteFile(basePath, []byte(`
actions:
- title: Spoofed
shell: echo hi
x-olivetin-source-file: /tmp/fake-user-path.yaml
entities:
- name: host
file: hosts.yaml
x-olivetin-source-file: /tmp/fake-entity-path.yaml
`), 0o644))
k := koanf.New(".")
require.NoError(t, k.Load(file.Provider(basePath), yaml.Parser()))
cfg := config.DefaultConfig()
config.AppendSource(cfg, k, basePath)
require.Len(t, cfg.Actions, 1)
assert.Equal(t, basePath, cfg.Actions[0].SourceFile)
assert.NotEqual(t, "/tmp/fake-user-path.yaml", cfg.Actions[0].SourceFile)
require.Len(t, cfg.Entities, 1)
assert.Equal(t, basePath, cfg.Entities[0].SourceFile)
assert.NotEqual(t, "/tmp/fake-entity-path.yaml", cfg.Entities[0].SourceFile)
}