https://github.com/mneves75/cf-toolkit
Multi-account Cloudflare Wrangler with two independent locks against wrong-account deploys (macOS, Bash).
https://github.com/mneves75/cf-toolkit
bash cli cloudflare cloudflare-workers direnv keychain macos multi-account wrangler
Last synced: 22 days ago
JSON representation
Multi-account Cloudflare Wrangler with two independent locks against wrong-account deploys (macOS, Bash).
- Host: GitHub
- URL: https://github.com/mneves75/cf-toolkit
- Owner: mneves75
- License: apache-2.0
- Created: 2026-06-05T14:51:09.000Z (27 days ago)
- Default Branch: main
- Last Pushed: 2026-06-06T20:09:57.000Z (25 days ago)
- Last Synced: 2026-06-06T21:08:35.270Z (25 days ago)
- Topics: bash, cli, cloudflare, cloudflare-workers, direnv, keychain, macos, multi-account, wrangler
- Language: Shell
- Homepage: https://github.com/mneves75/cf-toolkit
- Size: 30.3 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# cf-toolkit
[](https://github.com/mneves75/cf-toolkit/actions/workflows/ci.yml)
[](LICENSE)


Multi-account Cloudflare Wrangler, without a wrapper around every command.
Three small scripts (with an optional `cf-toolkit` umbrella command) let each project
deploy to its **own** Cloudflare account safely: a token scoped to that account is loaded
automatically when you `cd` in, the target account is pinned in the project's
`wrangler.jsonc`, and a guard refuses to deploy if the two ever disagree.
> **Credits / prior art.** This toolkit was inspired by
> [**novincode/cfman**](https://github.com/novincode/cfman), a CLI overlay that stores
> per-account tokens and injects them via `cfman wrangler --account …`.
> cf-toolkit keeps the same goal but takes a different approach — see
> [How it differs from cfman](#how-it-differs-from-cfman).
---
## The model: two independent locks
Deploying to the wrong account is the failure this prevents, with two locks that are
independent on purpose — if one is misconfigured, the other still stops you:
1. **Target lock** — `account_id` is pinned in each project's `wrangler.jsonc`
(non-secret; it appears in dashboard URLs). Wrangler targets exactly that account.
2. **Credential lock** — a per-account API **token**, scoped in the Cloudflare dashboard
to *only that account*, is loaded from the macOS Keychain by a `.envrc`. A token for
account B physically cannot act on account A — the API rejects it.
`cf-toolkit guard` makes the agreement between the two explicit and *local*: it asks the
Cloudflare API whether the loaded token can access the pinned `account_id`, and fails
before `wrangler deploy` ever runs.
## Scripts
| Script | What it does |
|--------|--------------|
| `cf-register-account ` | Reads an API token from a hidden prompt, validates it via the token-verify API (works with any valid token, even a minimal `workers_scripts:edit` one), stores it in the macOS login Keychain as `cloudflare-token-`. Labels must match `[A-Za-z0-9][A-Za-z0-9._-]{0,63}`. |
| `cf-init-project [name]` | Run inside a project: pins a 32-hex `account_id` in `wrangler.jsonc` (or top-level `.toml`), writes/updates a secret-free cf-toolkit block in `.envrc`, runs `direnv allow`. Idempotent. |
| `cf-guard` | Fail-fast pre-deploy check: confirms the loaded token can access the pinned account. Wire as `"predeploy": "cf-toolkit guard"` or run manually. |
| `cf-toolkit ` | Umbrella command: `cf-toolkit register-account\|init-project\|guard …`, plus `version` and `help`. Maps to the three scripts above, which still work directly. |
## Install
### Homebrew (recommended)
```bash
brew install mneves75/tap/cf-toolkit # cf-toolkit + the three cf-* scripts onto PATH
brew install direnv # runtime dependency for the auto-load-on-cd flow
```
Then add the direnv hook once:
```bash
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && exec zsh
```
Upgrade later with `brew upgrade cf-toolkit`.
### Manual (clone and run, no install)
```bash
brew install direnv
git clone https://github.com/mneves75/cf-toolkit.git ~/cf-toolkit
echo 'export PATH="$HOME/cf-toolkit:$PATH"' >> ~/.zshrc
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
exec zsh
```
## Quick start
```bash
# 1) Once per Cloudflare account — create a scoped token, then register it.
# Dashboard → My Profile → API Tokens → "Edit Cloudflare Workers" template,
# and set Account Resources → Include → .
cf-toolkit register-account pessoal
# 2) Once per project — from inside the project directory.
cf-toolkit init-project pessoal
# 3) Deploy as usual. No login, no --account flag.
wrangler whoami # sanity check: resolves to the right account
cf-toolkit guard # optional explicit check
wrangler deploy
```
`cf-toolkit help` lists every subcommand. The underlying scripts (`cf-register-account`,
`cf-init-project`, `cf-guard`) are the same code and still work directly if you prefer.
See [`docs/HOWTO.md`](docs/HOWTO.md) for the full walkthrough, CI/CD parity,
troubleshooting, and uninstall.
## How it differs from cfman
| | [cfman](https://github.com/novincode/cfman) | cf-toolkit |
|---|---|---|
| Token at rest | plaintext `~/.config/cfman/tokens.json` (chmod 600) | macOS Keychain (encrypted) |
| Picking the account | `--account ` on every command | automatic on `cd` (direnv) |
| Wrong-account protection | token only | token **+** pinned `account_id` **+** `cf-toolkit guard` |
| Per-command overhead | wraps every call (`cfman wrangler …`) | none — plain `wrangler …` |
| CI/CD | separate from local flow | identical (`CLOUDFLARE_API_TOKEN` env both sides) |
| Dependency | a third-party binary in the credential path | stock `direnv`, `security`, `curl`, `node` (+ `wrangler` for deploys) |
Neither is "wrong" — cfman is a single self-contained binary, which some prefer.
cf-toolkit trades that for OS-native secret storage, zero per-command friction, and a
second independent lock.
## Security notes
- `.envrc` contains **no secret** — only a marked cf-toolkit Keychain lookup block — so it
is safe to commit. Existing `.envrc` content is preserved.
- The Keychain item is created with `-T /usr/bin/security` so the `security` CLI (and
thus direnv) can read it without a GUI prompt. This is convenient, but it means same-user
local processes that can execute `/usr/bin/security` can also request the token.
- `account_id` is **not** a secret and is committed in `wrangler.jsonc`.
- `.dev.vars`, `.dev.vars.*`, `.env`, and `.env.*` (which *can* hold secrets) are added to
`.gitignore` by `cf-init-project`; it also warns if matching files are already tracked.
- Limitation: during `cf-register-account`, the final `security add-generic-password -w`
storage step receives the token as a process argument. The Cloudflare validation request
and guard checks do not put the token in `curl` arguments. On a single-user Mac this is
acceptable; otherwise use the Keychain Access GUI to store the item and omit this helper.
- **The credential lock requires single-account tokens.** A token created with *Account
Resources → all accounts* can act on every account, so it passes `cf-guard` for any
pinned `account_id` — defeating the credential lock and leaving only the pinned
`account_id`. Always scope each token to exactly one account.
To report a vulnerability, see [SECURITY.md](SECURITY.md).
## Contributing
Issues and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). The offline test suite
(`bash tests/run-offline.sh`) needs no Cloudflare credentials, and CI runs it plus ShellCheck
on macOS for every push and pull request.
## License
[Apache-2.0](LICENSE) © 2026 Marcus Neves.