Update (March 29, 2026): Day 9 extends this design with tool-tier isolation under
tito-toolbox, nftables egress fencing, AppArmor confinement, and a narrowtito-gitdeploy path for repo pushes. Read Day 9 for the full v3 model.
Day 4 identified the problem: all API tokens were sitting in tito's environment. Day 8 implements the first mitigation layer.
The design
A system user openclaw-secrets owns the real credentials at /etc/openclaw-secrets/env (chmod 600). tito gets "proxy-managed" placeholders instead. A mitmproxy instance on 127.0.0.1:18080 injects the real credentials per destination host before forwarding requests.
Implementation
1. Create secrets user and file
bashsudo useradd --system --no-create-home --shell /usr/sbin/nologin openclaw-secrets
sudo mkdir -p /etc/openclaw-secrets
sudo chown openclaw-secrets:openclaw-secrets /etc/openclaw-secrets
sudo chmod 700 /etc/openclaw-secrets
2. Injection addon
/opt/openclaw-proxy/inject_addon.py reads secrets once at startup and injects headers per host:
pythonfrom mitmproxy import ctx, http
SECRETS = {}
try:
with open('/etc/openclaw-secrets/env') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#') or '=' not in line:
continue
k, _, v = line.partition('=')
SECRETS[k.strip()] = v.strip().strip('"').strip("'")
except Exception:
pass
def s(key):
return SECRETS.get(key, '')
INJECT_RULES = {
"places.googleapis.com": {
"X-Goog-Api-Key": s("GOOGLE_PLACES_API_KEY"),
},
"striping-app.<account>.workers.dev": {
"Authorization": f"Bearer {s('CRM_API_KEY')}",
"CF-Access-Client-Id": s("CLOUDFLARE_ACCESS_CLIENT_ID"),
"CF-Access-Client-Secret": s("CLOUDFLARE_ACCESS_CLIENT_SECRET"),
},
"api.cloudflare.com": {
"Authorization": f"Bearer {s('CLOUDFLARE_D1_API_TOKEN')}",
},
}
class InjectAddon:
def request(self, flow: http.HTTPFlow) -> None:
host = flow.request.pretty_host
if host not in INJECT_RULES:
return
for header, value in INJECT_RULES[host].items():
if value:
flow.request.headers[header] = value
addons = [InjectAddon()]
Hosts not in INJECT_RULES pass through unmodified and receive no injected credentials.
3. Systemd service
ini[Unit]
Description=OpenClaw Secrets Proxy (mitmproxy)
After=network.target
[Service]
User=openclaw-secrets
Group=openclaw-secrets
ExecStart=/opt/openclaw-proxy/mitmdump \
--listen-host 127.0.0.1 \
--listen-port 18080 \
--mode regular \
--set confdir=/etc/openclaw-proxy/certs \
--scripts /opt/openclaw-proxy/inject_addon.py
Restart=on-failure
NoNewPrivileges=yes
ProtectHome=yes
[Install]
WantedBy=multi-user.target
4. tito's environment
iniTELEGRAM_BOT_TOKEN=<real, the one credential tito holds>
CLOUDFLARE_D1_API_TOKEN=proxy-managed
CRM_API_KEY=proxy-managed
GOOGLE_PLACES_API_KEY=proxy-managed
HTTP_PROXY=http://127.0.0.1:18080
HTTPS_PROXY=http://127.0.0.1:18080
NO_PROXY=api.telegram.org
What this still doesn't fix
SSH deploy keys still live in ~/.ssh/. Git over SSH doesn't route through HTTP_PROXY.
Telegram bot token sits in the URL path, not an HTTP header. The proxy can't inject URL path segments. This is the one real credential tito keeps.
HTTP_PROXY is an env var. Python code using raw sockets bypasses it. This gets addressed in day 9 with kernel-level enforcement.
