SecretSync
Local-first CLI that pushes secrets from your vault-backed environment into deployments using a checked-in YAML file — without pasting values into Slack or dashboards.
⚠️ Disclaimer: This software is pre-alpha and under development. Use with caution.
Motivation
Vaults solve storage, rotation, access control, and audit. The gap is delivery: getting the same secret into GitHub Actions, Vercel, and SST without paste-into-Slack, clipboard history, or hand-copying the same key into three dashboards — and without half-rotated deploys when one destination is forgotten.
SecretSync is a thin CLI for that gap. You declare routing in a checked-in YAML file (reviewable in PRs; plaintext stays out of git), inject values from your vault into the process environment (op run, Doppler, etc.), and push. Destination quirks — GitHub repo/environment/org scopes, Vercel deployment targets, SST stages — stay behind connectors so the config stays simple.
Full secrets platforms (Infisical and similar) can do this and more, but they are overkill when you already trust a vault and only need to say which names land where. Plaintext should only move through process memory, authenticated provider APIs, or one-shot env injection — never config, plans, logs, or temp files.
Quickstart
## Install with uv
uv tool install secretsync-cli
# Scaffold config + 1Password-style env template
secretsync init
Setup
# .env.tpl — vault refs injected into the process env (e.g. via `op run`)
GITHUB_TOKEN="op://vault/github/token" # connector auth
API_KEY="op://vault/app/api-key" # secret value SecretSync reads
# secretsync.yaml
version: 1
changeDetection: always-write
# Logical secret ids → which process env var holds the plaintext value
secrets:
apiKey:
env: API_KEY # reads os.environ["API_KEY"]
# Non-secret configuration (same shape; never written to a secure store as a secret)
variables:
logLevel:
env: LOG_LEVEL
# Named bundles of secrets/variables to ship together
sets:
production:
include: [apiKey, logLevel]
# Where values can be written (connector + auth)
destinations:
github:
connector: github-actions
# Either repository: owner/name …
repository: owner/repo
# … or organization: + repository: name (not both with owner/name)
# organization: owner
# repository: repo
auth:
tokenEnv: GITHUB_TOKEN # reads os.environ["GITHUB_TOKEN"] for API auth
# Concrete pushes: which set → which destination, under what remote names
deployments:
- name: github-production
set: production
destination: github
scope:
kind: environment
environment: production
secrets:
# logical id → remote secret name in GitHub Actions
apiKey: API_KEY
variables:
# logical id → remote variable name (Actions Variables API)
logLevel: LOG_LEVEL
# Org secrets/variables: org is destination.organization, or the owner of
# repository (acme from acme/web). Org-only destinations work for this scope.
# Token needs admin:org (classic PAT). visibility is required.
- name: github-org
set: production
destination: github
scope:
kind: organization
visibility: private # required: all | private | selected (+ selectedRepositoryIds)
secrets:
apiKey: API_KEY
variables:
logLevel: LOG_LEVEL
Kind is declared once under secrets or variables. Connectors map kind to the provider primitive (GitHub secrets vs variables APIs; Vercel type: sensitive vs encrypted). SST supports secrets only — non-secret SST config belongs in code as Linkables.
Breaking: Vercel
scope.sensitiveis removed. Put sensitive values underdeployment.secretsand plaintext underdeployment.variables.Breaking: Vercel destinations require
teamId. Project env deployments needscope.kind: environment(and destinationproject). Team shared env usesscope.kind: shared-environmentwith optionalscope.projects.
Vercel destination modes (selected by scope.kind):
destinations:
vercel:
connector: vercel
teamId: team_xyz # required
project: prj_abc # optional; required for kind: environment
auth:
tokenEnv: VERCEL_TOKEN
deployments:
- name: vercel-production
set: production
destination: vercel
scope:
kind: environment
targets: [production]
secrets:
apiKey: API_KEY
- name: vercel-shared
set: production
destination: vercel
scope:
kind: shared-environment
targets: [production]
projects: [prj_abc, prj_def] # optional link set
secrets:
sharedSecret: SHARED_SECRET
Deploy secrets
# After editing secretsync.yaml (repo/project/stage names) and .env.tpl (secret references),
# then inject env and run command:
# Example with 1Password CLI:
# Run a health check to ensure secretsync is authenticated
op run --env-file=.env.tpl -- secretsync health
# Validate and plan a config
op run --env-file=.env.tpl -- secretsync validate ## makes sure the config is valid
op run --env-file=.env.tpl -- secretsync plan ## shows you what changes will be made
## Apply changes into your destination
op run --env-file=.env.tpl -- secretsync apply ## applies the changes
You can also target a single deployment or destination
op run --env-file=.env.tpl -- secretsync --deployment github-production plan
op run --env-file=.env.tpl -- secretsync --deployment github-production apply --yes
op run --env-file=.env.tpl -- secretsync --destination github plan
--deployment and --destination are repeatable; when both are set, only the intersection runs.
👉 While this example uses the 1Password CLI and vault,
secretsyncwith any system that can securely set secrets in your environment (Doppler, mise, Infisical etc.)
Commands
| Command | Purpose |
|---|---|
secretsync init |
Initialize secretsync.yaml + .env.tpl template |
secretsync validate |
Check config + required env presence |
secretsync plan |
Plan the necessary changes (--prune lists remotes and plans deletes) |
secretsync apply |
Resolve the plan and write secrets (--prune also deletes orphaned secrets) |
secretsync health |
Auth/reachability checks (skips unset tokens) |
secretsync ui |
Interactive Textual review/apply |
secretsync connectors |
List built-in connectors |
Useful flags: --config, --format json, --verbose, --quiet, --deployment, --destination, --prune.
With --prune, SecretSync lists remote names at plan time (secrets and variables separately) and treats YAML as the full desired inventory for each destination scope + kind — remote entries not listed in the config are planned for deletion. Without --prune, apply is put-only.
For Vercel, a remote env var belongs to a deployment only when its target set exactly matches scope.targets (and, for shared env, scope.projects). A multi-target remote such as [production, preview] is owned by a deployment that declares that same multi-target scope — not by a production-only or preview-only sibling.
Supported Destinations
We currently support these destinations.
Audit
secretsync writes an audit trail under .secretsync/audit.log so you can keep track of what changed when. The .secretsync folder can safely be checked into source control.
License
MIT — see LICENSE.
Metadata
Release files for secretsync-cli 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| secretsync_cli-0.1.2.tar.gz | 57.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| secretsync_cli-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 133.7 kB
Release files / secretsync_cli-0.1.2.tar.gz
| Download URL | secretsync_cli-0.1.2.tar.gz |
|---|---|
| Size | 57.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
92cbe4071197a1f6e4f1ad0e526533a4208cef6e239aa58636c00a095cb7cef3
|
|
BLAKE2b-256 checksum How to use checksums |
4e7d9ef290d166ea3bbe2cc8d6348851874eaa26fad5eadec116e3f5cc88ee27
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / secretsync_cli-0.1.2-py3-none-any.whl
| Download URL | secretsync_cli-0.1.2-py3-none-any.whl |
|---|---|
| Size | 75.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dfad0e0a789bf4952f0fea184fca02c829dbc36b4777c0bb470a25b14b14ca10
|
|
BLAKE2b-256 checksum How to use checksums |
f740a94d43948a6c2a0522cc167b273763df3ecd2cf52f00dfbc538908122414
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|