-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathusersecretvalidation.go
More file actions
201 lines (168 loc) · 6.17 KB
/
usersecretvalidation.go
File metadata and controls
201 lines (168 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package codersdk
import (
"regexp"
"strings"
"golang.org/x/xerrors"
)
const (
// MaxSecretValueSize is the maximum size of a user secret value
// in bytes. This limit applies uniformly to both env var and
// file-destined secrets because the value field is shared and
// the destination can change after creation. 32KB is generous
// for env vars (most are under 1KB) but necessary for file
// content like SSH keys, TLS certificate chains, and JSON
// configs. We are not trying to be overly restrictive here;
// users can use the full 32KB for env var values even though
// it would be unusual.
MaxSecretValueSize = 32 * 1024 // 32KB
// maxFilePathLength is the maximum length of a file path for
// a user secret. Matches Linux PATH_MAX, which is the common
// case since workspace agents almost always run on Linux.
// This does not catch all Windows path length edge cases
// (legacy MAX_PATH is 260), but the agent will surface a
// runtime error if the write fails.
maxFilePathLength = 4096
)
var (
// posixEnvNameRegex matches valid POSIX environment variable names:
// must start with a letter or underscore, followed by letters,
// digits, or underscores.
posixEnvNameRegex = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
// reservedEnvNames are system environment variables that must not
// be overridden by user secrets. This list is intentionally
// aggressive because it is easier to remove entries later than
// to add them after users have already created conflicting
// secrets.
reservedEnvNames = map[string]struct{}{
// Core POSIX/login variables. Overriding these breaks
// basic shell and session behavior.
"PATH": {},
"HOME": {},
"SHELL": {},
"USER": {},
"LOGNAME": {},
"PWD": {},
"OLDPWD": {},
// Locale and terminal. Agents and IDEs depend on these
// being set correctly by the system.
"LANG": {},
"TERM": {},
// Shell behavior. Overriding these can silently break
// word splitting, directory resolution, and script
// execution in every shell session and agent script.
"IFS": {},
"CDPATH": {},
// Shell startup files. ENV is sourced by POSIX sh for
// interactive shells; BASH_ENV is sourced by bash for
// every non-interactive invocation (scripts, subshells).
// Allowing users to set these would inject arbitrary
// code into every shell and script in the workspace.
"ENV": {},
"BASH_ENV": {},
// Temp directories. Overriding these is a security risk
// (symlink attacks, world-readable paths).
"TMPDIR": {},
"TMP": {},
"TEMP": {},
// Host identity.
"HOSTNAME": {},
// SSH session variables. The Coder agent sets
// SSH_AUTH_SOCK in agentssh.go; the others are set by
// sshd and should never be faked.
"SSH_AUTH_SOCK": {},
"SSH_CLIENT": {},
"SSH_CONNECTION": {},
"SSH_TTY": {},
// Editor/pager. The Coder agent sets these so that git
// operations inside workspaces work non-interactively.
"EDITOR": {},
"VISUAL": {},
"PAGER": {},
// IDE integration. The agent sets these for code-server
// and VS Code Remote proxying.
"VSCODE_PROXY_URI": {},
"CS_DISABLE_GETTING_STARTED_OVERRIDE": {},
// XDG base directories. Overriding these redirects
// config, cache, and runtime data for every tool in the
// workspace.
"XDG_RUNTIME_DIR": {},
"XDG_CONFIG_HOME": {},
"XDG_DATA_HOME": {},
"XDG_CACHE_HOME": {},
"XDG_STATE_HOME": {},
}
// reservedEnvPrefixes are namespace prefixes where every
// variable in the family is reserved. Checked after the
// exact-name map. The CODER / CODER_* namespace is handled
// separately with its own error message (see below).
reservedEnvPrefixes = []string{
// The Coder agent sets GIT_SSH_COMMAND, GIT_ASKPASS,
// GIT_AUTHOR_*, GIT_COMMITTER_*, and several others.
// Blocking the entire GIT_* namespace avoids an arms
// race with new git env vars.
"GIT_",
// Locale variables. LC_ALL, LC_CTYPE, LC_MESSAGES,
// etc. control character encoding, sorting, and
// formatting. Overriding them can break text
// processing in agents and IDEs.
"LC_",
// Dynamic linker variables. Allowing users to set
// these would let a secret inject arbitrary shared
// libraries into every process in the workspace.
"LD_",
"DYLD_",
}
)
// UserSecretEnvNameValid validates an environment variable name for
// a user secret. Empty string is allowed (means no env injection).
func UserSecretEnvNameValid(s string) error {
if s == "" {
return nil
}
if !posixEnvNameRegex.MatchString(s) {
return xerrors.New("must start with a letter or underscore, followed by letters, digits, or underscores")
}
upper := strings.ToUpper(s)
if _, ok := reservedEnvNames[upper]; ok {
return xerrors.Errorf("%s is a reserved environment variable name", upper)
}
if upper == "CODER" || strings.HasPrefix(upper, "CODER_") {
return xerrors.New("environment variable names starting with CODER_ are reserved for internal use")
}
for _, prefix := range reservedEnvPrefixes {
if strings.HasPrefix(upper, prefix) {
return xerrors.Errorf("environment variables starting with %s are reserved", prefix)
}
}
return nil
}
// UserSecretFilePathValid validates a file path for a user secret.
// Empty string is allowed (means no file injection). Non-empty paths
// must start with ~/ or /, must not contain null bytes, and must not
// exceed 4096 bytes.
func UserSecretFilePathValid(s string) error {
if s == "" {
return nil
}
if !strings.HasPrefix(s, "~/") && !strings.HasPrefix(s, "/") {
return xerrors.New("file path must start with ~/ or /")
}
if strings.Contains(s, "\x00") {
return xerrors.New("file path must not contain null bytes")
}
if len(s) > maxFilePathLength {
return xerrors.Errorf("file path must not exceed %d bytes", maxFilePathLength)
}
return nil
}
// UserSecretValueValid validates a user secret value. The value must
// not contain null bytes and must not exceed MaxSecretValueSize.
func UserSecretValueValid(value string) error {
if strings.Contains(value, "\x00") {
return xerrors.New("secret value must not contain null bytes")
}
if len(value) > MaxSecretValueSize {
return xerrors.Errorf("secret value must not exceed %d bytes", MaxSecretValueSize)
}
return nil
}