Skip to content

feat(secrets): secrets ingest — .env → SOPS/provider secrets + vars (spec 24) - #82

Merged
gustavobertoi merged 1 commit into
mainfrom
feat/env-ingestion
Jun 30, 2026
Merged

feat(secrets): secrets ingest — .env → SOPS/provider secrets + vars (spec 24)#82
gustavobertoi merged 1 commit into
mainfrom
feat/env-ingestion

Conversation

@gustavobertoi

Copy link
Copy Markdown
Contributor

Implements feature #16 / spec 24.env ingestion. Built in a parallel worktree, integrated + verified locally (make ci + determinism green, dry-run smoke-tested).

What's here

  • secrets.Pusher (new write capability; Provider was Resolve-only): Push on aws-sm (put-secret-value), aws-ssm (put-parameter --type SecureString), infisical (secrets set) — values via stdin/env, never argv. A stdin-capable runner + SopsEncryptYAML/SopsDecryptBytes (shell sops, no Go SDK, no plaintext temp file).
  • internal/envingest (new): parse (compose-go/v2/dotenv) → classify default-deny (glob → name-token → value-heuristic ladder) → route (SOPS+age default / --to aws-sm|infisical) → encrypt/push → goccy-AST in-place devstack.yaml rewrite (comment/order-preserving; secrets→secret:// refs, non-secrets→inline literals, --from-host${env}) → scaffold a sops provider into workspace.yaml → round-trip-resolve before delete → .gitignore fence → delete .env. Decrypt-and-compare idempotency; git-tracked .env refused; no flock.
  • secrets ingest (internal/cli/secrets_ingest.go): full flag surface (--to/--dest/--service/--recipient/--secret/--public/--from-host/--prefixed/--keep-env/--dry-run/--yes/--force/--json) + a degradable huh v2 classification wizard.

Verified

make ci + make determinism green; CGO=0 cross-build clean. No-plaintext leak-test (TestRunNoSecretLeak) walks every written file + backups asserting no original secret value. Smoke: secrets ingest .env --dry-run classifies (DB_PASSWORD/STRIPE_SECRET_KEY→secret, APP_ENV/PORT→config), shows the SOPS destination, writes nothing.

Note: end-to-end SOPS encrypt/AWS/Infisical pushes are exercised via mock runners (no sops/aws/infisical binary or creds in CI); a hands-on run with a real sops install is worth a check.

🤖 Generated with Claude Code

… (spec 24)

Add the write half of the secrets boundary and a new envingest pipeline that
gets committed .env files out of the repo:

- internal/secrets: Pusher capability (SecretEntry/Pusher) on aws-sm/aws-ssm/
  infisical (values via stdin/env, never logged argv), a stdin-capable runner
  (OutputStdin), and SOPS encrypt/decrypt helpers that shell `sops` over stdin
  (no Go SDK, no plaintext temp file).
- internal/envingest: parse (compose-go dotenv) → classify default-deny
  (glob → name → value heuristics → benign → default secret) → route (sops
  file / remote Pusher) → encrypt/push → compute secret:// refs → rewrite the
  target devstack.yaml env block in place via the goccy AST (comment/order
  preserving) → scaffold a sops provider into workspace.yaml when absent →
  round-trip verify before deleting → fence .env in .gitignore → delete (or
  --keep-env). Idempotency via decrypt-and-compare; no flock.
- internal/cli: `secrets ingest [<.env>]` with --to/--dest/--service/
  --recipient/--secret/--public/--from-host/--prefixed/--keep-env/--dry-run/
  --yes/--force/--json + a degradable huh v2 classification wizard gated on
  prompt.IsInteractive.
- Tests: Pusher argv/stdin per provider, classify ladder, AST rewrite/scaffold,
  full-run leak test, git-tracked refusal, dry-run writes nothing, decrypt-
  compare idempotency, non-TTY gate routes to the flag path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gustavobertoi
gustavobertoi merged commit 03f3791 into main Jun 30, 2026
4 checks passed
@gustavobertoi
gustavobertoi deleted the feat/env-ingestion branch June 30, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant