Skip to content

Feat/infisical recursive sync - #327

Open
JRoppert wants to merge 6 commits into
Infisical:mainfrom
RSE23:feat/infisical-recursive-sync
Open

Feat/infisical recursive sync#327
JRoppert wants to merge 6 commits into
Infisical:mainfrom
RSE23:feat/infisical-recursive-sync

Conversation

@JRoppert

Copy link
Copy Markdown

Summary

Adds an opt-in recursive sync mode for vaults backed by an Infisical credential store. Until now, only secrets directly at the configured secret_path were synced — subfolders were silently ignored. With this change a vault can be configured to sync the entire folder tree beneath its secret path.

  • New recursive option on the Infisical vault config (stored in the existing config_json blob — no DB migration, false by default, fully backward compatible; legacy rows keep behaving as before).
  • Web UI: "Recursive sync" toggle in the vault create/edit form, with a tooltip explaining the uniqueness constraint; the vault settings view shows the current state. A recursion-only change still goes through the existing type-the-vault-name destructive confirmation.
  • CLI: --infisical-recursive flag on vault create and vault credential-store set; vault credential-store show prints the setting.
  • API: "recursive": true inside the config object on POST /v1/vaults / PATCH /v1/vaults/{name}/credential-store (passes through the existing untyped config, no request-shape change).

Collision semantics (deliberate design decision): synced credential keys stay flat, so with recursion the same secret name in two folders (e.g. /stripe/TOKEN and /github/TOKEN) would collide. The SDK's default behavior would silently keep an arbitrary winner; instead we fetch with SkipUniqueValidation and reject any duplicate ourselves — the whole sync fails with a new external_store_duplicate_key error naming the key and its folders (deterministic, sorted output). This mirrors the existing all-or-nothing external_store_invalid_key behavior: the vault keeps serving its previous snapshot until the operator renames the secret upstream. The error detail is redacted for callers who cannot see the vault's upstream config, mirroring the invalid-key gate. Dynamic secrets remain path-scoped and are never discovered recursively.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / build

Test plan

  • Existing tests pass (make test)
  • Added/updated tests for new behavior
  • Manual testing (describe below)

Developed test-first. New coverage:

  • internal/infisical: config round-trip + legacy-JSON-defaults-to-false; listSecretsOptions sets Recursive/SkipUniqueValidation only in recursive mode (non-recursive request unchanged); duplicate-across-folders rejection incl. deterministic error text under reversed input order; syncer propagates the flag to the fetcher and surfaces ErrDuplicateKey verbatim in last_sync_error.
  • internal/server: manual sync returns 400 external_store_duplicate_key with full detail for owners and a redacted message for members.
  • cmd: flag registration on both commands; payload builder includes recursive (default false).
  • Frontend has no test framework; verified via tsc && vite build and make build.

Known gap: the prepareInfisicalSnapshot duplicate-key branch (create/connect probe) has no integration test because the server holds a concrete *infisical.Client; it is a three-line mirror of the tested ErrInvalidKey branch. Recommended before release: one manual smoke test against a live Infisical instance, mainly for the ExpandSecretReferences × Recursive interplay, which is Infisical-server-side behavior we cannot unit-test.

Security checklist

  • No secrets or credentials in code
  • No new unauthenticated endpoints
  • Input validation on new API surfaces
  • Checked for OWASP top 10 (injection, XSS, etc.)

Notes: the new field is a boolean flowing through the existing ParseConfigJSON/Validate path; create/connect remain owner-only. Duplicate-key error messages expose upstream folder topology only to callers already authorized to see the vault's upstream config (callerCanSeeVaultUpstream); everyone else gets a generic redacted message, consistent with the existing invalid-key handling.

JRoppert and others added 6 commits July 22, 2026 13:14
…y rejection

Add a Recursive option to VaultConfig (persisted in config_json, no
migration). When enabled, ListSecrets runs with Recursive and
SkipUniqueValidation so cross-folder duplicates reach us instead of
being collapsed to an arbitrary winner by the SDK; any key found at
more than one path fails the whole fetch with ErrDuplicateKey
(all-or-nothing, mirroring ErrInvalidKey). The syncer surfaces the
duplicate-key message verbatim in last_sync_error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…_duplicate_key

Manual sync returns 400 with the colliding key and folder paths for
callers who may see the vault's upstream config, and a redacted message
otherwise (mirroring the external_store_invalid_key gate). The
create/connect probe surfaces the error verbatim before the generic 502
scrub — those paths are owner-only, so no redaction gate is needed.

The prepareInfisicalSnapshot branch has no integration test because the
server holds a concrete *infisical.Client; it is a three-line mirror of
the tested ErrInvalidKey branch below it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tore set

The flag lands in the config payload as "recursive"; credential-store
show/set output prints the setting when the server reports it (guarded
so older servers' responses render unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a "Recursive sync" toggle to the shared vault form (create and
edit flows) with an info tooltip noting that secret names must be
unique across the whole folder tree. The settings view shows the
current state; a recursion-only change flows through the existing
configChanged gate and thus keeps the destructive-edit confirmation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@infisical-cla-app

Copy link
Copy Markdown

📝 Contributor License Agreement required

Before this PR can merge, every contributor must sign the Infisical CLA.
Signing is quick: sign in with GitHub, review the CLA, and accept.

👉 Sign the CLA

Still needs to sign:

Once everyone has signed, the check updates automatically — no need to close and reopen the PR.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds opt-in recursive synchronization for Infisical-backed vaults. The main changes are:

  • Recursive configuration in the server, CLI, API payloads, and web UI.
  • Duplicate-key detection across folders with deterministic errors.
  • Authorization-aware redaction of upstream folder details.
  • Documentation and tests for recursive sync behavior.

Confidence Score: 4/5

The CLI credential-store update path can silently disable recursive sync and should be fixed before merging.

  • An omitted boolean flag is serialized as an explicit false value.
  • Existing update scripts can stop syncing subfolder secrets after an unrelated configuration change.
  • The server and web paths otherwise propagate and report the new setting consistently.

cmd/vaults.go

Important Files Changed

Filename Overview
internal/infisical/client.go Adds recursive SDK options and deterministic duplicate-key rejection before secrets enter the flat vault keyspace.
internal/infisical/config.go Adds a backward-compatible recursive boolean to the stored Infisical configuration.
internal/infisical/sync.go Persists duplicate-key errors using the existing actionable sync-error path.
internal/server/handle_vaults.go Maps duplicate-key failures to a dedicated API error and redacts topology for unauthorized callers.
cmd/vaults.go Adds recursive flags and output, but credential-store updates can reset recursion when the flag is omitted.
web/src/components/VaultForm.tsx Adds recursive form state, payload serialization, and the new toggle.
web/src/pages/vault/SettingsTab.tsx Displays recursive state and includes it in edit initialization and change detection.
docs/learn/credential-stores.mdx Documents recursive behavior, collision handling, and static-secret scope.
docs/reference/cli.mdx Documents the recursive flag for vault creation and credential-store updates.

Reviews (1): Last reviewed commit: "Merge branch 'main' into feat/infisical-..." | Re-trigger Greptile

Comment thread cmd/vaults.go
projectID, _ := cmd.Flags().GetString("infisical-project-id")
environment, _ := cmd.Flags().GetString("infisical-environment")
secretPath, _ := cmd.Flags().GetString("infisical-path")
recursive, _ := cmd.Flags().GetBool("infisical-recursive")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Omitted Flag Disables Recursion

When vault credential-store set updates an existing recursive store without repeating --infisical-recursive, GetBool returns the flag default and the payload sends recursive: false. Existing scripts that only change another setting therefore silently disable recursive sync and drop subfolder secrets from the next snapshot.

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