Skip to main content
← Back to list
01Issue
BugOpenSwamp CLI
AssigneesNone

Relationships

#650 datastore setup migration relocates and deletes repo-root .swamp/secrets, breaking all local_encryption vault.get

Opened by keeb · 6/13/2026

Description

Running swamp datastore setup extension <type> --config ... as a full migration (i.e. without --skip-migration) relocates the repo-root <repo>/.swamp/secrets directory into the datastore cache and then deletes the repo-root copy. But the local_encryption vault always reads from <base_dir>/.swamp/secrets/local_encryption/<vault> (base_dir = repo root), independent of the datastore. After setup, every ${{ vault.get(...) }} fails and swamp vault list-keys / read-secret report empty / "not found". The repo is functionally broken for any vault-dependent operation until the secrets are manually restored.

This is datastore-backend-agnostic: it reproduces for any extension datastore (and the filesystem datastore), because the migration target list is core's DEFAULT_DATASTORE_SUBDIRS, which includes secrets.

There is also a security dimension when the target datastore syncs the tier to shared storage: the local_encryption .key (symmetric key) sits right next to its .enc ciphertext, so migrating/syncing secrets/ co-locates key + ciphertext in the shared backend (encryption-at-rest defeated). This report is about the functional break; the sync-exposure side is being fixed independently in the affected extension.

Root cause

  • datastoreSetupExtension hardcodes the migration config without directories: src/libswamp/datastores/setup.ts:376const config = { type: "filesystem", path: cachePath };
  • migrateDatastore resolves the dirs to copy via getDatastoreDirectories(config), which falls back to DEFAULT_DATASTORE_SUBDIRS when directories is absent: src/domain/datastore/datastore_migration_service.ts:57 and src/domain/datastore/datastore_config.ts:171.
  • DEFAULT_DATASTORE_SUBDIRS includes "secrets": src/domain/datastore/datastore_config.ts:49.
  • On success, setup deletes every migrated source dir: src/libswamp/datastores/setup.ts:463 (cleanupSourceDirs(<repo>/.swamp, directoriesMigrated)), which Deno.remove(..., {recursive:true})s <repo>/.swamp/secrets.
  • The vault reads from a fixed repo-root path, unaffected by the datastore cache: src/domain/vaults/local_encryption_vault_provider.ts:80-88 ({base_dir}/.swamp/secrets/local_encryption/{vault}).

Net effect: the migration moves the vault's storage out from under it and deletes the original.

Steps to reproduce

  1. Repo with a local_encryption vault holding a secret under <repo>/.swamp/secrets/local_encryption/<vault>/.
  2. swamp datastore setup extension <type> --config ... (no --skip-migration).
  3. swamp vault list-keys <vault>count: 0; a model's ${{ vault.get(...) }} no longer resolves.
  4. <repo>/.swamp/secrets is gone; the files now live only under the datastore cache.

Proof (drives the real core functions)

Reproduced end-to-end against the core source by invoking the exact functions the setup flow uses — migrateDatastore (the setup.ts:376 call), the mirrored cleanupSourceDirs deletion, and the real LocalEncryptionVaultProvider:

[1] Secret written. Repo-root vault dir contents:
    - ROUTER_ADMIN_PASSWORD.enc
    - .key
[1] vault.get before migration => "hunter2-super-secret"

[2] DEFAULT_DATASTORE_SUBDIRS includes 'secrets': true
[2] getDatastoreDirectories(...) includes 'secrets': true

[3] migrateDatastore result.directoriesMigrated: [ "data", "secrets" ]
[3] 'secrets' was migrated into the cache: true
[3] cache now holds the secret .enc: .key, ROUTER_ADMIN_PASSWORD.enc

[4] repo-root .swamp/secrets deleted by cleanup: true

[5] vault.get after migration FAILED: true
    error: Secret 'ROUTER_ADMIN_PASSWORD' not found in local vault 'infra'

==== ISSUE #6 REPRODUCED ✅ ====

Workaround

Restore the repo-root secrets from a pre-migration copy (it lives outside the cache, so it won't re-sync):

cp -a <backup>/.swamp/secrets <repo>/.swamp/secrets

Suggested fix

The secrets/ tier should never be part of the datastore migration. Options, narrowest first:

  • Exclude secrets from the migration in datastoreSetupExtension / the filesystem setup path (don't copy or delete the vault tier).
  • Remove "secrets" from DEFAULT_DATASTORE_SUBDIRS so no backend ever migrates/syncs it.
  • Alternatively, have local_encryption read from the datastore cache path when an extension datastore is active (more invasive; couples the vault to the datastore).

Whichever path is taken, the migration must not delete <repo>/.swamp/secrets while the vault still reads it.

Environment

  • swamp CLI: 20260206.200442.0 (root cause verified against current main, commit df3947ba)
  • Originally observed with @keeb/mongodb-datastore 2026.05.05.1 on swamp 20260513.132340.0, MongoDB 7.x replica set
  • Backend-agnostic — the migration list is core's DEFAULT_DATASTORE_SUBDIRS
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

6/13/2026, 7:57:53 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.