package backend import ( "context" "os" "strconv" "strings" ) // File resolves refs from env files on disk (KEY=VALUE lines, parsed in // Go, never sourced). House rule: secret files are 0600. A file whose // permission bits reach beyond the allowed mask (default 0600; stricter // masks such as 0400 always pass) is refused before it is read. Material // is read on demand per resolve and kept in memory only. type File struct{} // NewFile returns the file backend. func NewFile() *File { return &File{} } // Name implements Backend. func (f *File) Name() string { return "file" } // Resolve implements Backend. func (f *File) Resolve(ctx context.Context, ref Ref) (string, error) { path := expandHome(ref.Source) info, err := os.Stat(path) if err != nil || info.IsDir() { return "", Err(ref, f.Name(), ReasonUnreadableSource) } if info.Mode().Perm()&^allowedMode(ref.Mode) != 0 { return "", Err(ref, f.Name(), ReasonInsecureMode) } data, err := os.ReadFile(path) if err != nil { return "", Err(ref, f.Name(), ReasonUnreadableSource) } kv, err := parseEnvFile(data) if err != nil { return "", Err(ref, f.Name(), ReasonMalformedSource) } v, ok := kv[ref.Key] if !ok { return "", Err(ref, f.Name(), ReasonMissingKey) } if v == "" { return "", Err(ref, f.Name(), ReasonEmptyValue) } return v, nil } // allowedMode parses ref.Mode ("0600"-style octal, default 0600) into a // permission mask; unparsable masks fail closed to 0 (nothing allowed). func allowedMode(mode string) os.FileMode { if mode == "" { return 0o600 } m, err := strconv.ParseUint(strings.TrimPrefix(mode, "0o"), 8, 32) if err != nil { return 0 } return os.FileMode(m) & os.ModePerm } // expandHome resolves a leading ~ / ~/ to the user's home directory; // every other path is returned unchanged. func expandHome(path string) string { if path == "~" || strings.HasPrefix(path, "~/") { home, err := os.UserHomeDir() if err == nil { if path == "~" { return home } return home + path[1:] } } return path }