Noctivault loads secrets from cloud Secret Managers into process memory without going through environment variables. Values are masked by default, and configuration is clearly separated from execution.
- Safe by default: in‑memory resolution, masked string representations
- Operational clarity: two files — references (refs) and mock values (mocks)
- Flexible: local encrypted store and remote (GCP) fetch supported
- Avoids env‑vars; secrets are handled via explicit API calls
- Lighter than Vault/SOPS for Python apps; DX focused
- Two‑file split clarifies “what to use” (refs) vs “where values come from” (mocks)
- Commit
.yaml.enc, distribute the key separately for safer, reproducible setups
- Python: >= 3.10
- Local encrypted store: extra
local-enc(cryptography,argon2-cffi) - Remote (GCP):
google-cloud-secret-manager(also available as extragcp) - Remote platform support: Google only for now (AWS/Azure on roadmap)
- pip:
pip install noctivault- Local encrypted store:
pip install 'noctivault[local-enc]' - GCP remote:
pip install google-cloud-secret-manager(orpip install 'noctivault[gcp]')
- Poetry:
poetry add noctivault- Local encrypted store:
poetry add "noctivault[local-enc]" - GCP remote:
poetry add google-cloud-secret-manager(orpoetry add "noctivault[gcp]")
- Mocks (plaintext or encrypted source):
noctivault.local-store.yaml
platform: google
gcp_project_id: demo
secret-mocks:
- name: db-password
value: s3cr3t
version: 2
- name: db-port
value: "5432"
version: 1- Refs (plaintext; do not encrypt):
noctivault.yaml
platform: google
gcp_project_id: demo
secret-refs:
- key: database
children:
- cast: password
ref: db-password
version: latest
type: str
- cast: port
ref: db-port
version: 1
type: int- Encrypt mocks (recommended)
- Generate key:
noctivault key gen(default~/.config/noctivault/local.key, chmod 600) - Create encrypted file:
noctivault local seal . --key-file ~/.config/noctivault/local.key --rm-plain- Commit
.yaml.enc; keep plaintext.yamlandlocal.keyout of VCS
- Commit
- Load (Python)
from noctivault import NoctivaultSettings
import noctivault
nv = noctivault.noctivault(settings=NoctivaultSettings(source="local"))
secrets = nv.load(local_store_path="./") # prefers .enc; falls back to .yaml
print(secrets.database.password) # -> ***
print(secrets.database.password.get()) # -> "s3cr3t"
print(secrets.database.port.get()) # -> 5432Restore plaintext from .enc when needed:
noctivault local unseal noctivault.local-store.yaml.enc --key-file ~/.config/noctivault/local.key > noctivault.local-store.yamlPrereqs
- Dependency:
poetry add google-cloud-secret-manager(orpoetry add "noctivault[gcp]") - Auth (ADC)
- Service account JSON:
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa.json - Or:
gcloud auth application-default login
- Service account JSON:
- Permission:
roles/secretmanager.secretAccessor, etc.
noctivault.yaml (refs only)
platform: google
gcp_project_id: my-gcp-project
secret-refs:
- key: database
children:
- cast: password
ref: db-password
version: latest
type: strLoad (Python)
from noctivault import NoctivaultSettings
import noctivault
nv = noctivault.noctivault(settings=NoctivaultSettings(source="remote"))
secrets = nv.load(local_store_path=".") # reads ./noctivault.yaml
print(secrets.database.password) # -> ***
print(secrets.database.password.get()) # -> UTF-8 string from GCPCommon errors and fixes
- MissingDependencyError: add
google-cloud-secret-manager - AuthorizationError: fix ADC/permissions
- MissingRemoteSecretError: check project/ref/version in
noctivault.yaml
- Key:
noctivault key gen [--out PATH] - Seal:
noctivault local seal <dir|file> (--key-file PATH | --passphrase PW | --prompt) [--out PATH] [--rm-plain] [--force] - Unseal:
noctivault local unseal <enc_file> (--key-file PATH | --passphrase PW | --prompt) - Verify:
noctivault local verify <enc_file> (--key-file PATH | --passphrase PW | --prompt)
When a directory is given, .yaml.enc is preferred; otherwise falls back to .yaml. You can pass file paths directly as well.
NoctivaultSettings(source="local"|"remote")- Local encrypted store key material (in this order):
settings.local_enc.key_file_pathNOCTIVAULT_LOCAL_KEY_FILE./local.keynext to.enc~/.config/noctivault/local.key
- Passphrase mode
settings.local_enc.passphraseorNOCTIVAULT_LOCAL_PASSPHRASE, or CLI--prompt
- Mocks (
noctivault.local-store.yaml)- Top‑level
platform,gcp_project_idare required; entries inherit if omitted secret-mocks:name,value(str),version(int)
- Top‑level
- Refs (
noctivault.yaml)- Top‑level
platform,gcp_project_idrequired secret-refs: entries or grouped bykey/childrentype:str|int(defaultstr),version:latest|int
- Top‑level
See docs/api.md for full details.
- Masked by default (
repr/str->***); call.get()to reveal - Encryption format (NVLE1)
- AES‑256‑GCM; Argon2id (for passphrase); KDF params stored in header
- Tamper detection via GCM tag; invalid tag -> decryption failure
- Operational guidance
- Keep plaintext
.yamlandlocal.keyout of VCS (use.gitignore) - Key files should be mode 600; distribute via secure channels
- Keep plaintext
- CombinedConfigNotAllowedError: don’t mix mocks and refs in one file
- MissingLocalMockError / MissingRemoteSecretError: name/version/project mismatch
- TypeCastError: e.g.,
type=intfor non‑numeric values - InvalidEncHeaderError / DecryptError: bad header / wrong key
- RemoteUnavailableError: transient GCP issues; retry later
- Why two files?
- Separate intent (refs) from value sources (mocks); simpler reviews and switching between local/remote
- Which file is preferred?
- Local:
.yaml.enc>.yaml; Remote usesnoctivault.yamlonly
- Local:
- How to migrate from a single file?
- Move
secret-mocksintonoctivault.local-store.yaml, andsecret-refsintonoctivault.yaml
- Move
- Remote drivers for AWS/Azure
- Cache/TTL/force‑refresh
- Pluggable providers; HMAC option for
display_hash
- PRs welcome. Please keep Ruff/Mypy clean and maintain high test coverage
- For security reports, please use responsible disclosure
MIT — see LICENSE.