-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathgraphlink_process_env.py
More file actions
73 lines (66 loc) · 2.96 KB
/
Copy pathgraphlink_process_env.py
File metadata and controls
73 lines (66 loc) · 2.96 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
"""ADR-002 P0: an explicit-allowlist environment builder for subprocesses
that execute AI-generated code (Py-Coder's persistent REPL,
graphlink_plugins/pycoder/domain.py; the Virtual Environment Runner's
venv-create/pip-install/script-run trio, graphlink_plugins/code_sandbox/
domain.py). Both previously called subprocess.Popen(...) with no `env=`
argument, which means Python's default applies: the child inherits the
FULL parent os.environ - including every provider API key this app itself
reads via os.environ.get(...) fallbacks (see api_provider.py), if the user
has configured one as an environment variable rather than through Settings.
An ALLOWLIST, not a blocklist: block-listing known secret var names is
fragile - a user's own OS environment can carry secrets under names this
codebase has never heard of (a personal AWS_SECRET_ACCESS_KEY, a work VPN
token, anything). Only the variables a plain `python`/`pip`/`venv`
subprocess actually needs to function are copied through; every provider
key this app itself reads from the environment is excluded by
construction, not by name-matching.
Deliberately conservative: proxy variables (HTTP_PROXY/HTTPS_PROXY/
NO_PROXY) are NOT included, even though excluding them could break `pip
install` behind a corporate proxy - a proxy URL can itself carry embedded
credentials (`http://user:pass@host:port`), and "secure by default" wins
over that convenience for code the user has not yet reviewed.
"""
import os
# The minimum a `python`/`pip`/`venv` subprocess needs to run at all on
# Windows: resolve the interpreter/its DLLs, create a venv, resolve DNS for
# `pip install`, and read/write temp files. A short list of POSIX
# equivalents is included too, for parity if this ever runs on macOS/Linux -
# none of these carry secrets.
_SAFE_ENV_VAR_NAMES = frozenset(
{
"PATH",
"PATHEXT",
"SYSTEMROOT",
"SYSTEMDRIVE",
"WINDIR",
"COMSPEC",
"TEMP",
"TMP",
"USERPROFILE",
"HOMEDRIVE",
"HOMEPATH",
"APPDATA",
"LOCALAPPDATA",
"NUMBER_OF_PROCESSORS",
"PROCESSOR_ARCHITECTURE",
"PROCESSOR_IDENTIFIER",
"OS",
# POSIX equivalents.
"HOME",
"LANG",
"LC_ALL",
"SHELL",
"USER",
}
)
def safe_subprocess_env() -> dict[str, str]:
"""Build an explicit-allowlist environment dict for a subprocess that
will execute AI-generated code. Pass this as
`subprocess.Popen(..., env=safe_subprocess_env())` - never omit `env=`
at one of these call sites, which would silently fall back to
inheriting the full parent environment.
Case-insensitive on the allowlist match (Windows env var casing in
os.environ is inconsistent - e.g. "SystemRoot" vs "SYSTEMROOT" -
while POSIX systems are case-sensitive by convention; comparing
upper-cased names is correct for both)."""
return {name: value for name, value in os.environ.items() if name.upper() in _SAFE_ENV_VAR_NAMES}