Skip to main content

Declarative secret delivery across deployment platforms

Project description

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.sensitive is removed. Put sensitive values under deployment.secrets and plaintext under deployment.variables.

Breaking: Vercel destinations require teamId. Project env deployments need scope.kind: environment (and destination project). Team shared env uses scope.kind: shared-environment with optional scope.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, secretsync with 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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

secretsync_cli-0.1.2.tar.gz (57.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

secretsync_cli-0.1.2-py3-none-any.whl (75.9 kB view details)

Uploaded Python 3

File details

Details for the file secretsync_cli-0.1.2.tar.gz.

File metadata

  • Download URL: secretsync_cli-0.1.2.tar.gz
  • Upload date:
  • Size: 57.8 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for secretsync_cli-0.1.2.tar.gz
Algorithm Hash digest
SHA256 92cbe4071197a1f6e4f1ad0e526533a4208cef6e239aa58636c00a095cb7cef3
MD5 eae478528a0ea664641bf85320b69de3
BLAKE2b-256 4e7d9ef290d166ea3bbe2cc8d6348851874eaa26fad5eadec116e3f5cc88ee27

See more details on using hashes here.

File details

Details for the file secretsync_cli-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: secretsync_cli-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 75.9 kB
  • Tags: Python 3
  • Uploaded using 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}

File hashes

Hashes for secretsync_cli-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 dfad0e0a789bf4952f0fea184fca02c829dbc36b4777c0bb470a25b14b14ca10
MD5 7951cc5b7dec84b4b49cb19306f2ef81
BLAKE2b-256 f740a94d43948a6c2a0522cc167b273763df3ecd2cf52f00dfbc538908122414

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page