← back to Cncp Mcp
security: apply-pm2-caps now whitelists env (was leaking GODADDY_API_KEY etc into ecosystem files)
b5c84595d23e6110b0559b8158995290f5d3017b · 2026-05-11 09:44:46 -0700 · Steve Abrams
Files touched
M scripts/apply-pm2-caps.sh
Diff
commit b5c84595d23e6110b0559b8158995290f5d3017b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon May 11 09:44:46 2026 -0700
security: apply-pm2-caps now whitelists env (was leaking GODADDY_API_KEY etc into ecosystem files)
---
scripts/apply-pm2-caps.sh | 39 +++++++++++++++++++++++++++++----------
1 file changed, 29 insertions(+), 10 deletions(-)
diff --git a/scripts/apply-pm2-caps.sh b/scripts/apply-pm2-caps.sh
index 8d11546..8238ce8 100644
--- a/scripts/apply-pm2-caps.sh
+++ b/scripts/apply-pm2-caps.sh
@@ -113,22 +113,41 @@ for t in todo:
logmsg(f"already-has-ecosystem {t['name']}")
already += 1
continue
- # Build the script body — preserve ALL user env vars from current pm2 state.
- # pm2_env mixes internal keys (pm_*, PM2_*, status, etc.) with user env;
- # we whitelist by skipping pm2-internal prefixes.
+ # Build the script body — STRICT WHITELIST env approach.
+ # We do NOT capture arbitrary env vars from pm2_env because that pool
+ # contains real secrets (API keys, DB passwords) inherited from the shell
+ # that started pm2. Writing those into ecosystem.config.cjs and committing
+ # to git is a security leak. Instead: capture ONLY known-safe keys.
proc = [p for p in procs if p.get('name') == t['name']][0]
full_env = proc.get('pm2_env', {})
- SKIP_PREFIXES = ('pm_', 'pm2_', 'PM2_', 'PWD', 'OLDPWD', '_', 'SHLVL')
- SKIP_KEYS = {'instance_var','restart_time','unstable_restarts','created_at','axm_actions','axm_monitor','axm_options','axm_dynamic','axm_metrics','exec_interpreter','watch','env','env_diff','status','windowsHide','vizion_running','instances','exec_mode','autorestart','automation','treekill','username','versioning','version','node_args','source_map_support','exit_code','pmx','filter_env','disable_trace','merge_logs','vizion','autostart','windows_hide','watch_options','APP_PATH','HOME','PATH','SHELL','USER','LOGNAME','TERM','LANG','LC_ALL','TMPDIR','XPC_FLAGS','XPC_SERVICE_NAME','__CFBundleIdentifier','SSH_AUTH_SOCK','SSH_CLIENT','SSH_CONNECTION','SSH_TTY','TERM_PROGRAM','TERM_PROGRAM_VERSION','TERM_SESSION_ID','ITERM_PROFILE','ITERM_SESSION_ID','MAIL','OLDPWD','PYENV_SHELL','LSCOLORS','LS_COLORS','COLORTERM','HOMEBREW_PREFIX','HOMEBREW_CELLAR','HOMEBREW_REPOSITORY','MANPATH','INFOPATH','EDITOR'}
+
+ SAFE_KEYS = {'PORT', 'port', 'NODE_ENV', 'HOST', 'BIND', 'HOSTNAME',
+ 'FLASK_RUN_HOST', 'UVICORN_HOST', 'GUNICORN_BIND', 'VITE_HOST',
+ 'NEXT_TELEMETRY_DISABLED'}
+ # Any *_URL is allowed only if it doesn't contain '@' (which signals creds in URL)
+ SECRET_PATTERNS = ('_KEY', '_SECRET', '_TOKEN', '_PASSWORD', '_PASS',
+ '_AUTH', '_CREDENTIAL', 'API_', 'PRIVATE_', 'COOKIE',
+ 'SESSION_SECRET', 'JWT')
+
user_env = {}
for k, v in full_env.items():
if not isinstance(v, (str, int, bool)): continue
- if any(k.startswith(p) for p in SKIP_PREFIXES): continue
- if k in SKIP_KEYS: continue
- # Skip giant string values that aren't real config
sv = str(v)
if len(sv) > 500: continue
- user_env[k] = v
+ # Direct allowlist
+ if k in SAFE_KEYS:
+ user_env[k] = v
+ continue
+ # *_URL allowed only if no embedded creds
+ if k.endswith('_URL') and '@' not in sv:
+ # Also exclude obviously-secret URL keys
+ if any(p in k.upper() for p in SECRET_PATTERNS): continue
+ user_env[k] = v
+ continue
+ # Everything else: SKIP. Secrets, pm2 internals, macOS env, claude-session
+ # state, etc. all stay out of the ecosystem file.
+ continue
+
env_lines = []
for k, v in user_env.items():
if isinstance(v, bool):
@@ -136,7 +155,7 @@ for t in todo:
elif isinstance(v, int):
env_lines.append(f" {k}: {v},")
else:
- esc = str(v).replace('\\', '\\\\').replace('"', '\\"')
+ esc = str(v).replace("\\", "\\\\").replace('"', '\\"')
env_lines.append(f' {k}: "{esc}",')
env_block = "\n".join(env_lines) + "\n" if env_lines else ""
script_basename = os.path.basename(t['script'])
← e6e3182 load-aware dispatch: run-on-mac1 routes Mac2↔Mac1 by load +
·
back to Cncp Mcp
·
security: apply-pm2-caps now uses mktemp + trap-unlink for p c65b01b →