Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/content/articles/safe-openclaw-cron-iac-openshell.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,9 @@ Ten minutes later, the agent is live. No SSH. No manual steps. Here's what Terra

The setup scripts handle everything that can't go in user-data: creating the six OpenShell providers for credential injection, provisioning the sandbox with the network policy, cloning the repo inside the sandbox, generating the MCP config from injected environment variables, and launching Copilot CLI with `--yolo --autopilot --experimental`.

The most important design decision in the IaC: **all credentials go into OpenShell providers, never into files**. Providers are named credential bundles — `copilot`, `github`, `exa`, `perplexity`, `youtube`, `zernio` — that get injected as environment variables at runtime. When the sandbox starts, the agent gets `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `EXA_API_KEY`, and the rest injected into its environment. The credentials never touch the sandbox filesystem.
The most important design decision in the IaC: **API and service credentials go into OpenShell providers, not files**. Providers are named credential bundles — `copilot`, `github`, `exa`, `perplexity`, `youtube`, `zernio` — that get injected as environment variables at runtime. When the sandbox starts, the agent gets `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `EXA_API_KEY`, and the rest injected into its environment. These credentials never touch the sandbox filesystem.

There's one exception: `TELEGRAM_BOT_TOKEN`. The extension reads it from a `.env` file (the SDK resolves environment variables before extensions load, but `.env` parsing happens inside the extension itself). We upload the `.env` via SSH before launching. Everything else flows through providers.
The exceptions are **messaging platform tokens** — `TELEGRAM_BOT_TOKEN`, `SLACK_BOT_TOKEN`, and `SLACK_APP_TOKEN`. These are uploaded into the sandbox via a `raw-secrets.env` file because OpenShell provider env vars appear as resolver strings in SSH sessions (e.g., `openshell:resolve:env:...`) rather than raw values. The bridge service reads them from a `.env` file generated by the setup script. Everything else flows through providers.

Cost is reasonable: ~$30/month running 24/7 on `t3.medium`, ~$1.60/month if you stop the instance and pay only for EBS.

Expand Down Expand Up @@ -178,9 +178,9 @@ openshell sandbox create \
-- true
```

Inside the sandbox, `echo $COPILOT_GITHUB_TOKEN` returns the token. But the token isn't in any file. There's no `.env`, no exported variable in a shell script, no credentials stored anywhere that `find / -name "*.env"` would discover. If the sandbox is compromised, the attacker has the token for the duration of that sandbox's lifetime — but can't exfiltrate it to a file they could retrieve later, because the network policy blocks arbitrary egress.
Inside the sandbox, `echo $COPILOT_GITHUB_TOKEN` returns the token. But the token isn't in any file — it exists only as a runtime environment variable injected by OpenShell. A `find / -name "*.env"` *would* find the `.env` containing the messaging tokens (Telegram and Slack), but not the six provider-injected credentials (`COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, `EXA_API_KEY`, `PERPLEXITY_API_KEY`, `YOUTUBE_API_KEY`, `ZERNIO_API_KEY`). If the sandbox is compromised, the attacker has all tokens for the duration of that sandbox's lifetime — but can't exfiltrate them to arbitrary endpoints, because the network policy blocks all egress not in the allowlist.

This isn't perfect security. But it's a meaningfully better threat model than "API keys in files on a VM."
This isn't perfect security. But it's a meaningfully better threat model than "all API keys in files on a VM." The provider-injected credentials leave no filesystem trace; the messaging tokens are the only ones on disk, and they're scoped to bot-level access with no infrastructure permissions.

## Putting It Together

Expand All @@ -201,7 +201,7 @@ Here's what the full deployment looks like at runtime:
│ │ MCP Servers: exa, perplexity, youtube, mslearn │ │
│ │ │ │
│ │ Network: default-deny + 10 explicit allowlists │ │
│ │ Credentials: injected at runtime, never on disk │ │
│ │ Credentials: 6 providers (runtime), messaging (.env) │ │
│ └────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
```
Expand Down