Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (sync-cf-secrets) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
sync-cf-secrets
Sync secrets from your password manager to Cloudflare Workers. Supports 1Password and Bitwarden with a pluggable provider architecture.
Why
Managing Cloudflare Workers secrets is tedious — tokens from third parties are often single-use, so they end up in your password manager, then need to be manually pushed via wrangler secret put for each environment. This tool automates that.
Install
npm install -g sync-cf-secrets
# or as a devDependency
npm install -D sync-cf-secretsRequires Node.js 20+ and wrangler installed.
A bundled Claude Code skill is available — run sync-cf-secrets install-skill to let AI assistants help manage your secrets.
Quick Start
# 1. Bootstrap: create password manager items for all environments
sync-cf-secrets init
# 2. Copy local secrets to staging (most values are the same)
sync-cf-secrets copy local staging
# 3. Edit staging item in your password manager — change any values that differ
# 4. Push secrets to Cloudflare
sync-cf-secrets push staging
# 5. Generate .dev.vars for local dev from password manager
sync-cf-secrets pull localCommands
init
Create password manager items for all environments discovered from your wrangler config. Reads field names and values from .dev.vars:
- local item gets the actual values from
.dev.vars - staging, production, etc. get the same fields with
CHANGE_MEplaceholders
Any variables already defined as vars in your wrangler config are automatically excluded — they're non-secret config and don't belong in the password manager.
If items already exist, you'll be prompted before they're replaced.
sync-cf-secrets init
sync-cf-secrets init --dry-runcopy <from> <to>
Copy secrets from one environment's password manager item to another. Useful when environments share most values (e.g. local and staging both use test/sandbox API keys).
Variables defined as vars in the target environment's wrangler config are excluded.
Use --fields to copy specific fields only — this merges into the existing target item instead of replacing it:
sync-cf-secrets copy local staging
sync-cf-secrets copy staging production
sync-cf-secrets copy staging production --fields GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRETpush <env>
Push secrets from your password manager to a Cloudflare Workers environment.
Variables already defined as vars in your wrangler config are automatically skipped.
sync-cf-secrets push staging
sync-cf-secrets push production --dry-run # preview without pushingpull <env>
Generate a .dev.vars file from your password manager.
sync-cf-secrets pull locallist <env>
List secret names deployed to a Cloudflare Workers environment.
sync-cf-secrets list stagingdiff <env>
Compare secrets in your password manager vs what's deployed to Cloudflare.
sync-cf-secrets diff production --verboseOptions
| Flag | Description |
|---|---|
--provider <name> |
Password manager: 1password, bitwarden (auto-detected by default) |
--vault <name> |
Override vault name |
--fields <a,b,...> |
Only copy specific fields (for copy command) |
--dry-run |
Show what would happen without doing it |
--verbose |
Show more detail |
--help |
Show help |
Wrangler Vars vs Secrets
The tool automatically reads your wrangler config (wrangler.toml / wrangler.jsonc) and excludes any names defined as vars. These are non-secret environment config (like DEPLOY_ENV or AUTH_URL) that Cloudflare manages as plaintext bindings — pushing them as secrets would cause a "Binding name already in use" error.
This filtering is per-environment, so a variable that's a var in staging but not in production is handled correctly.
Configuration
Create a .sync-cf-secrets.json in your project root (all fields optional):
{
"provider": "1password",
"vault": "Engineering",
"prefix": "myproject",
"wranglerConfig": "apps/web/wrangler.jsonc",
"devVarsPath": "apps/web/.dev.vars",
"environments": {
"local": { "item": "myproject local" },
"staging": { "item": "myproject staging" },
"production": { "item": "myproject production" }
}
}Defaults
- provider — auto-detected (prefers 1Password SDK when
OP_SERVICE_ACCOUNT_TOKENis set, thenopCLI, thenbwCLI) - vault — project name from
package.json - prefix — same as vault
- wranglerConfig — auto-searches for
wrangler.toml,wrangler.jsonc, orwrangler.json(includingapps/web/) - devVarsPath —
.dev.varsnext to your wrangler config - environments — auto-discovered from your wrangler config's
envblock, pluslocal
Password Manager Setup
1Password
The easiest way to get started is sync-cf-secrets init, which creates Secure Note items with the right fields. You can also create them manually — one Secure Note per environment with custom fields where the label is the env var name and the value is the secret.
Two backends are available:
1. JavaScript SDK (recommended for CI/AI agents): When OP_SERVICE_ACCOUNT_TOKEN is set, the tool uses the @1password/sdk package directly — no CLI binary needed. This works in sandboxed environments (Claude Code, CI containers, etc.) where the op CLI can't run.
export OP_SERVICE_ACCOUNT_TOKEN="ops_..."Create a service account with read/write access to the vault. Service accounts can only access custom vaults (not Personal/Private).
2. CLI (op): For interactive use with biometric/Touch ID auth via the 1Password desktop app. Requires op CLI version 2.18.0+. Used automatically when no service account token is set.
Bitwarden
Same concept — one Secure Note per environment with custom fields for each secret.
Requires the Bitwarden CLI (bw).
Adding a Provider
Providers implement the SecretProvider interface:
interface SecretProvider {
name: string;
cli: string;
validate(): Promise<void>;
exists(opts: { vault: string; item: string }): Promise<boolean>;
fetch(opts: { vault: string; item: string }): Promise<Map<string, string>>;
save(opts: { vault: string; item: string; secrets: Map<string, string> }): Promise<void>;
listFields(opts: { vault: string; item: string }): Promise<string[]>;
}AI Skill
A Claude Code skill is bundled with this package. It teaches AI assistants the full command reference and workflow patterns so they can help manage your secrets — for example, asking Claude to "copy staging secrets to production" will run the right command.
To install it:
sync-cf-secrets install-skillThis copies the skill to ~/.claude/skills/sync-cf-secrets/. Claude Code picks it up automatically from there.
To see where the bundled skill file lives (e.g. to copy it to a different tool's skill directory):
sync-cf-secrets reveal-skillContributing
This project uses changesets for version management and OIDC trusted publishing for npm publish. The actual publish only ever runs from GitHub Actions, triggered by a vX.Y.Z tag push, and npm stamps the released package with a provenance attestation.
# After making changes, create a changeset and commit it alongside the change
pnpm changeset
# → interactive prompt: pick patch/minor/major, write a summary
# When ready to release
pnpm release
# → guards (clean tree, on main, up-to-date, pending changeset),
# runs typecheck/test/build, bumps package.json + CHANGELOG.md,
# commits as "Version X.Y.Z", shows you the diff, and prompts before
# pushing main + the vX.Y.Z tag. Tag push triggers
# .github/workflows/release.yml, which publishes to npm via OIDC.If the release workflow fails after the tag is pushed
The Version X.Y.Z commit is on main and the vX.Y.Z tag is pushed, but
nothing has been published to npm. Because pnpm release has already consumed
the pending changeset, you cannot rerun pnpm release to retry — it will
fail the "no pending changesets" guard.
Recovery:
# 1. Delete the bad tag locally and on the remote
git tag -d vX.Y.Z
git push origin :refs/tags/vX.Y.Z
# 2. Fix whatever broke, commit the fix on main, push
git commit -am "fix: ..."
git push origin main
# 3. Re-tag HEAD (annotated, matching how changesets creates them) and
# push the tag to re-trigger the release workflow
git tag -a vX.Y.Z -m "Version X.Y.Z"
git push origin vX.Y.ZSee CLAUDE.md for the full architecture and release notes.
Security
- Secret values are piped to wrangler via stdin — never exposed in CLI arguments or process listings
- No intermediate temp files
- Requires password manager authentication (biometrics/master password)
- Generated
.dev.varsfiles include a "do not commit" warning - Duplicate items are detected and cleaned up by unique ID
License
MIT