Skip to main content

Mind Castle - Build a wall around your secrets

A universal store for your secret data. Don't delay securing you or your customer's data by deliberating over cloud secret stores. Mind Castle makes it easy to get started, and easy to switch between cloud secret stores.

Mind Castle currently supports:

  • AWS KMS
  • Local Encryption
  • None (passthrough - Mind Castle doesn't modify your data)

Architecture

Mind Castle comes in three parts:

  • A unified interface for several secret stores.
  • An SQLAlchemy column type that transparently stores and retrieves secrets for you.
  • A migration tool to convert your existing DB column data into secrets.

Some other notes:

  • Mind Castle is configured and secret stores are initialised at import time. That means env-vars used for configuration need to be defined when Mind Castle is imported.
  • Mind Castle makes no attempt to manage secrets in memory. Memory management in Python is futile, and if you need that level of control it's best to use another language.

Install

pip install mind-castle

Configure

You can configure Mind Castle by setting environment variables for your chosen secret store. To see what configuration options are required for each store:

$ python -m mind_castle
╭───────────────────────────────────────── MIND CASTLE ─────────────────────────────────────────╮
│                                                                                               │
│                                         Secret Stores                                         │
│ ┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │
│ ┃ Store Type        ┃ Required env var                  ┃ Optional env var                  ┃ │
│ ┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │
│ │ memory            │                                   │                                   │ │
│ │                   │                                   │                                   │ │
│ │ plaintext         │                                   │                                   │ │
│ │                   │                                   │                                   │ │
│ │ awssecretsmanager │ MIND_CASTLE_AWS_REGION            │ MIND_CASTLE_AWS_SECRET_KEY_PREFIX │ │
│ │                   │ MIND_CASTLE_AWS_ACCESS_KEY_ID     │                                   │ │
│ │                   │ MIND_CASTLE_AWS_SECRET_ACCESS_KEY │                                   │ │
│ │                   │ --- OR ---                        │                                   │ │
│ │                   │ MIND_CASTLE_AWS_REGION            │ MIND_CASTLE_AWS_SECRET_KEY_PREFIX │ │
│ │                   │ MIND_CASTLE_AWS_USE_ENV_AUTH      │                                   │ │
│ │                   │                                   │                                   │ │
│ │ hashicorpvault    │ MIND_CASTLE_VAULT_HOST            │                                   │ │
│ │                   │ MIND_CASTLE_VAULT_TOKEN           │                                   │ │
│ │                   │                                   │                                   │ │
│ │ json              │                                   │                                   │ │
│ │                   │                                   │                                   │ │
│ └───────────────────┴───────────────────────────────────┴───────────────────────────────────┘ │
╰───────────────────────────────────────────────────────────────────────────────────────────────╯

AWS KMS data-key cache (optional)

The awskms store uses envelope encryption: each secret is sealed with its own data key, which KMS must decrypt on every read. When the same secret is read repeatedly (e.g. frequent full re-fetches), this drives repeated KMS Decrypt calls. You can opt in to caching the decrypted data keys in-process:

Env var Description Default
MIND_CASTLE_AWS_KMS_DATAKEY_CACHE Enable the cache (true/1/yes) off
MIND_CASTLE_AWS_KMS_DATAKEY_CACHE_TTL Cache entry TTL, seconds 60
MIND_CASTLE_AWS_KMS_DATAKEY_CACHE_CAPACITY Max cached data keys 1000

Notes:

  • Caching applies to decryption only; create_secret still generates a fresh data key per secret. Legacy non-envelope secrets are never cached.
  • A revoked/rotated KMS key keeps working from cache until the TTL expires — keep the TTL short. Invalid or non-positive TTL/capacity disables the cache.
  • The cache is per-process (each worker has its own) and holds decrypted data keys in memory (up to CAPACITY, until TTL). Config is read at startup; changing it requires a restart.

Use

Migrating Existing Data

A migration tool is provided to move your existing data into a secret store. The tool assumes you have a database that can be connected to using SQLAlchemy and create_engine(<db_uri>).

You will need to configure your selected secret store using environment variables as described above. The migration tool depends on iterfzf, which isn't installed by the base package - install it with pip install "mind-castle[migrate]".

You can see what other options the migration tool accepts with:

$ python migration_tool.py --help

 Usage: migration_tool.py [OPTIONS] DB_URI

╭─ Arguments ─────────────────────────────────────────────────────────────────────────╮
│ *    db_uri      TEXT  [default: None] [required]                                   │
╰─────────────────────────────────────────────────────────────────────────────────────╯
╭─ Options ───────────────────────────────────────────────────────────────────────────╮
│ --target-table                        TEXT  [default: None]                         │
│ --target-column                       TEXT  [default: None]                         │
│ --dry-run           --no-dry-run            [default: dry-run]                      │
│ --demigrate         --no-demigrate          [default: no-demigrate]                 │
│ --to-secret-type                      TEXT  [default: json]                         │
│ --help                                      Show this message and exit.             │
╰─────────────────────────────────────────────────────────────────────────────────────╯

The easiest thing to do is specify --to-secret-type and let the tool guide you through the rest. e.g.:

$ MIND_CASTLE_VAULT_HOST="http://127.0.0.1:8200" MIND_CASTLE_VAULT_TOKEN="<your_token>" python migration_tool.py "postgresql://<user>:<pass>@<host>:5432/<database>" --to-secret-type hashicorpvault

The tool will list tables and columns in the selected DB for you, and ask you to select each. Once a dry run is complete and you also have a backup of your database, you can add --no-dry-run to migrate the data for real.

SQLAlchemy Model

Once any existing data is migrated, you can update your SQLAlchemy model to include a SecretData column:

from mind_castle.sqlalchemy_type import SecretData

class MyDBModel(Base):
    name = Column(String, nullable=False)
    created_at = Column(DateTime, default=datetime.datetime.now)
    secret_data = Column(SecretData("hashicorpvault"))

Your secrets will then be safely stored in Vault (or AWS, or anywhere else you like)! The storage and retrieval of secrets will be completely transparent to your application.

Django Model

For a Django model, use mind_castle.django_type.SecretData instead - it's a JSONField subclass with the same encrypt/decrypt behavior. Install the django extra (pip install "mind-castle[django]") first.

from mind_castle.django_type import SecretData

class MyDBModel(models.Model):
    name = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)
    secret_data = SecretData("hashicorpvault", null=True)

Notes specific to the Django field:

  • Filtering or looking up by value (MyDBModel.objects.filter(secret_data=...), secret_data__somekey=...) raises FieldError on any store except "none" - the stored value is ciphertext with a fresh nonce per encryption, so a value-equality comparison could otherwise silently match nothing. isnull still works.
  • Admin and ModelForm rendering show the decrypted value (matching a normal JSONField's editable UX); dumpdata/serializers.serialize() always exports the encrypted envelope, never the decrypted value.

A note on mutating values in place

Reassign the whole field rather than mutating a retrieved value's contents in place, e.g. obj.secret_data = {**obj.secret_data, "key": "new"} rather than obj.secret_data["key"] = "new". The library detects "this value came straight from the DB, unchanged" by object identity of the wrapper it returns, not by diffing contents - an in-place mutation keeps that same wrapper, so it's saved as the original value with no error. This applies to both the SQLAlchemy and Django field types.

TODO

  • Make migration script work for non-json columns
  • Support deleting secrets when row is deleted
  • Implement prefixes/folders for secrets
  • Explain how secrets are stored
  • Enforce tests on PR / branch protections

Metadata

Release files for mind-castle 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mind-castle 0.7.0
File Size Uploaded
mind_castle-0.7.0.tar.gz 127.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mind-castle 0.7.0
File Interpreter ABI Platform
mind_castle-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 159.3 kB

Release files / mind_castle-0.7.0.tar.gz

Download URL mind_castle-0.7.0.tar.gz
Size 127.2 kB
Tags Source
SHA-256 checksum
How to use checksums
73832f09ba30ee13dae9392e9f877620343f78ac255143453d237417eed7bb2d
BLAKE2b-256 checksum
How to use checksums
19428a9a6fc0a74a3962a450d4a35859a4a8002764d786af7fd12661fe49e08c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","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 / mind_castle-0.7.0-py3-none-any.whl

Download URL mind_castle-0.7.0-py3-none-any.whl
Size 32.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7118e33a44e21140efe51ffa9352069d14b4939def76efb7d737aa57d6b5954c
BLAKE2b-256 checksum
How to use checksums
f6524287002ce394c84ee2b04b0bf8f5026fc0b6f73746f0bf283c5c443f03ca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","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 history Release notifications | RSS feed

0.7.1

2 release files

This release

0.7.0 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.9

2 release files

0.4.8

2 release files

0.4.7

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.7

2 release files

0.2.6

2 release files

0.2.5

2 release files

0.2.4

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page