Files
m caad0ae2d4
CI / test (push) Successful in 14s
CI / test (pull_request) Successful in 14s
DEV-000061 checkpoint interaction chat foundation
2026-08-17 14:15:07 +03:00

66 lines
1.6 KiB
Go

package secrets
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
secretcontracts "git.cin.su/m/Assistant/contracts/secrets"
)
var (
ErrEmptyCredentialRef = errors.New("empty credential ref")
ErrPathOutsideRoot = errors.New("secret path outside protected root")
ErrEmptySecret = errors.New("empty secret")
)
// FileResolver resolves CredentialRef metadata against a protected local file
// backend. It is a bootstrap mechanism, not canonical configuration storage.
type FileResolver struct {
root string
}
func NewFileResolver(root string) (*FileResolver, error) {
root = strings.TrimSpace(root)
if root == "" {
return nil, fmt.Errorf("secret backend root: %w", ErrPathOutsideRoot)
}
cleanRoot, err := filepath.Abs(filepath.Clean(root))
if err != nil {
return nil, fmt.Errorf("secret backend root: %w", err)
}
return &FileResolver{root: cleanRoot}, nil
}
func (r *FileResolver) ResolveFile(ref secretcontracts.SecretFileRef) (string, error) {
if strings.TrimSpace(string(ref.CredentialRef)) == "" {
return "", ErrEmptyCredentialRef
}
cleanPath, err := filepath.Abs(filepath.Clean(strings.TrimSpace(ref.Path)))
if err != nil {
return "", err
}
if !insideRoot(r.root, cleanPath) {
return "", ErrPathOutsideRoot
}
content, err := os.ReadFile(cleanPath)
if err != nil {
return "", err
}
secret := strings.TrimSpace(string(content))
if secret == "" {
return "", ErrEmptySecret
}
return secret, nil
}
func insideRoot(root, path string) bool {
rel, err := filepath.Rel(root, path)
if err != nil {
return false
}
return rel == "." || (!strings.HasPrefix(rel, "..") && !filepath.IsAbs(rel))
}