env.go RawHistoryFolder
1package domain
2
3import (
4 "bufio"
5 "bytes"
6 "regexp"
7 "strings"
8)
9
10var (
11 envKeyRegex = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
12 containerNameRegex = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_-]*$`)
13 secretRefRegex = regexp.MustCompile(`\$\{(pass|sops):[^}]+\}`)
14)
15
16// SanitizeDomainForEnvFile validates and sanitizes a domain name for env file storage.
17// Returns the collision-resistant storage key used for filenames.
18func SanitizeDomainForEnvFile(domainName string) (string, error) {
19 safeDomain, err := NewEnvStorageKey(domainName)
20 if err != nil {
21 return "", err
22 }
23
24 return string(safeDomain), nil
25}
26
27// ParseEnvData parses env data into a key-value map.
28func ParseEnvData(data []byte) (map[string]string, error) {
29 secrets := make(map[string]string)
30 scanner := bufio.NewScanner(bytes.NewReader(data))
31 // Allow large env values (certs/keys) without hitting the default ~64KB limit.
32 scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
33 for scanner.Scan() {
34 line := strings.TrimSpace(scanner.Text())
35 if line == "" || strings.HasPrefix(line, "#") {
36 continue
37 }
38
39 parts := strings.SplitN(line, "=", 2)
40 if len(parts) != 2 {
41 continue
42 }
43
44 key := strings.TrimSpace(parts[0])
45 rawValue := parts[1]
46
47 trimmedValue := strings.TrimSpace(rawValue)
48 value := trimmedValue
49
50 if len(trimmedValue) >= 2 {
51 if (strings.HasPrefix(trimmedValue, "\"") && strings.HasSuffix(trimmedValue, "\"")) ||
52 (strings.HasPrefix(trimmedValue, "'") && strings.HasSuffix(trimmedValue, "'")) {
53 value = trimmedValue[1 : len(trimmedValue)-1]
54 }
55 }
56
57 if key != "" {
58 secrets[key] = value
59 }
60 }
61
62 if err := scanner.Err(); err != nil {
63 return nil, err
64 }
65
66 return secrets, nil
67}
68
69// ValidateEnvKey validates an env key for storage.
70func ValidateEnvKey(key string) error {
71 if key == "" {
72 return ErrInvalidEnvKey
73 }
74 if strings.Contains(key, "..") || strings.ContainsAny(key, "/\\") {
75 return ErrPathTraversal
76 }
77 if !envKeyRegex.MatchString(key) {
78 return ErrInvalidEnvKey
79 }
80 return nil
81}
82
83// ContainsSecretReference detects if a string contains a secret provider reference
84// like ${pass:path} or ${sops:path}. This is used to prevent attacker-controlled
85// env files from persisting secret references that would later resolve against
86// host secret providers.
87func ContainsSecretReference(value string) bool {
88 return secretRefRegex.MatchString(value)
89}
90
91// ValidateContainerName validates a container name for attachment storage.
92// Container names can contain alphanumeric characters, hyphens, and underscores,
93// but must start with a letter.
94func ValidateContainerName(name string) error {
95 if name == "" {
96 return ErrInvalidContainerName
97 }
98 if strings.Contains(name, "..") || strings.ContainsAny(name, "/\\") {
99 return ErrPathTraversal
100 }
101 if !containerNameRegex.MatchString(name) {
102 return ErrInvalidContainerName
103 }
104 return nil
105}