Skip to content

Releases: cdds-ab/vaultctl

v0.12.0

14 Mar 14:50

Choose a tag to compare

v0.12.0 (2026-03-14)

Features

  • cli: Add --raw and --base64 flags for clean SSH key import/export (#33, 10a7ed8)

Summary

SSH private keys and certificates stored in Ansible Vault suffer from whitespace corruption during YAML multiline formatting. This PR adds clean import/export modes to prevent these issues.

  • get --raw: Outputs value without Type: headers or field labels, strips trailing whitespace per line, ensures single trailing newline. Ideal for vaultctl get key --field privateKey --raw > key.pem - get --base64: Outputs value as a single base64-encoded line, suitable for environments that cannot handle multiline values - set --base64: Accepts an inline base64-encoded value, decodes before storing - set --base64-file: Reads base64 from a file or stdin (-), decodes before storing - set --file: Now applies whitespace cleanup (trailing space removal) on import - clean_multiline_value(): New helper that strips trailing whitespace per line and ensures exactly one trailing newline

Problem

When SSH keys are stored in YAML via ansible-vault, the multiline formatting introduces trailing spaces on lines. Extracting these keys with vaultctl get ... --json | jq -r produces keys that SSH rejects. There was no way to get a clean, whitespace-safe export or to import base64-encoded values.

Changed Files

| File | Change | |------|--------| | src/vaultctl/cli.py | Added --raw and --base64 flags to get, --base64 and --base64-file options to set, mutual exclusivity validation, _output_raw() and _output_base64_encoded() helpers | | src/vaultctl/yaml_util.py | Added clean_multiline_value() helper | | tests/test_cli.py | 17 new integration tests covering all new flags and edge cases | | tests/test_yaml_util.py | 7 unit tests for clean_multiline_value() | | tests/conftest.py | Added ssh_key fixture entry with trailing whitespace for testing |

Design Decisions

  1. clean_multiline_value in yaml_util.py — It is a value formatting utility closely related to YAML handling, keeping it here avoids a new module for one function 2. Mutual exclusivity of --json, --raw, --base64 — Validated at runtime with a clear error message rather than Click's cls=MutuallyExclusiveOption to keep it simple 3. --file now cleans whitespace on import — Prevents storing corrupted values at the source. This is a minor behavioral change but strictly an improvement 4. --base64-file - for stdin — Follows Unix convention, enables piping: cat key.pem | base64 | vaultctl set key --base64-file -

Test Plan

  • get --raw on plain strings outputs clean value - [x] get --raw on multiline values strips trailing whitespace - [x] get --raw --field extracts single field without headers - [x] get --raw on structured entries outputs YAML without Type: header - [x] get --base64 produces valid single-line base64 - [x] get --base64 on multiline values cleans before encoding - [x] get --base64 --field works on individual fields - [x] --json, --raw, --base64 are mutually exclusive - [x] set --base64 decodes and stores correctly - [x] set --base64 rejects invalid input - [x] set --base64-file reads from file - [x] set --base64-file - reads from stdin - [x] set --file cleans trailing whitespace - [x] Multiple input sources rejected - [x] clean_multiline_value unit tests (7 cases) - [x] All 319 tests pass, coverage 88%

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com


Detailed Changes: v0.11.1...v0.12.0

v0.11.1

14 Mar 13:26

Choose a tag to compare

v0.11.1 (2026-03-14)

Bug Fixes

  • cli: Format nested values as YAML and add --json flag to get (#32, 899ab2d)

Problem

vaultctl get on structured entries (dicts, lists) outputs Python repr() format — single quotes, no indentation, not parseable by jq or other tools. Example:


domains: [{'name': 'docker build hosts', 'credentials': [{'type': 'x509ClientCert', ...

This makes credentialStore entries with 50+ nested credentials completely unreadable.

Solution

1. Human-readable output: YAML formatting

Added _format_value() helper (cli.py:35-46) that formats nested values: - Strings: returned as-is (no change to existing behavior) - Dicts/Lists: formatted as YAML via yaml.dump(default_flow_style=False) - Other types: converted via str()

The get command now calls _format_value(value[f]) instead of directly printing value[f], so nested structures render as readable YAML with proper indentation.

2. Machine-readable output: --json flag

New --json flag on the get command outputs the value as JSON:

bash vaultctl get vault_jenkins_credentials --json | jq '.global.credentials | length'

Works with --field too:

bash vaultctl get db_creds --field username --json

Uses json.dumps(indent=2, ensure_ascii=False) for readable JSON that pipes cleanly to jq.

Files changed

  • src/vaultctl/cli.py: - Added _format_value() helper (lines 35-46) - Added --json / output_json option to get command - Changed dict field output from value[f] to _format_value(value[f]) - JSON output path for both full value and --field access

Test plan - [ ] vaultctl get <dict-key> shows readable YAML (not Python repr) - [ ] vaultctl get <dict-key> --json | jq . parses correctly - [ ] vaultctl get <string-key> unchanged (plain string output) - [ ] vaultctl get <dict-key> --field username unchanged - [ ] All 298 existing tests pass

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com


Detailed Changes: v0.11.0...v0.11.1

v0.11.0

14 Mar 11:05

Choose a tag to compare

v0.11.0 (2026-03-14)

Features

  • cli: Add shell completion for bash, zsh, and fish (#31, 7d860d8)

Summary

  • New vaultctl completion <shell> command (bash, zsh, fish)
  • Uses Click's shell_completion API
  • Works without .vaultctl.yml config

Install

bash eval "$(vaultctl completion bash)" # bash eval "$(vaultctl completion zsh)" # zsh vaultctl completion fish > ~/.config/fish/completions/vaultctl.fish # fish

Test plan

  • vaultctl completion bash outputs valid bash completion
  • vaultctl completion zsh outputs valid zsh completion
  • vaultctl completion fish outputs valid fish completion
  • Tab completion works after eval

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com


Detailed Changes: v0.10.0...v0.11.0

v0.10.0

14 Mar 10:39

Choose a tag to compare

v0.10.0 (2026-03-14)

Features

  • search: Add --context flag to show parent object of matches (#30, 47a015e)

Summary

  • Add --context / -c flag to vaultctl search that shows the parent dict of each matched field
  • Sibling fields are redacted by default (****), matched field shows first 4 chars + ...
  • Combine with --show-match to display all field values in cleartext
  • Multiple matches in the same parent object are grouped into a single block

Closes #20

Test plan

  • Unit tests for search_values(include_context=True) covering nested dicts, lists, top-level strings, multiple matches
  • CLI integration tests for --context, --context --show-match, and top-level string fallback
  • All 298 tests pass, 88% coverage
  • mypy strict, ruff, bandit clean

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com


Detailed Changes: v0.9.0...v0.10.0

v0.9.0

14 Mar 10:09

Choose a tag to compare

v0.9.0 (2026-03-14)

Features

  • cli: Add search command and list --filter (#29, 007f505)

Summary

  • vaultctl list --filter/-f PATTERN: Regex filter on key names, descriptions, and consumers from vault-keys.yml metadata. No additional decryption beyond what list already does. - vaultctl search PATTERN: New subcommand that decrypts the vault and recursively searches all values (strings in nested dicts/lists). Output shows only key names and dot-path locations — never values unless --show-match is explicitly used. - --keys-only / -k: Search only key names and metadata (no vault decryption needed) - --show-match: Display matched values (with security warning) - Exit code 0 if matches found, 1 if not (scripting-friendly)

Security considerations - Search pattern is never logged or included in error output - Values are never shown without explicit --show-match flag - --show-match displays a yellow WARNING to stderr - Recursive search is depth-limited (max 20 levels) - All search logic is in a pure-function module (search.py) — no side effects

Architecture - New module src/vaultctl/search.py with search_values() and filter_keys() — pure functions, fully unit-testable - CLI wiring in cli.py follows existing command patterns - 100% test coverage on search.py, 87% overall

Closes #TBD

Test plan

  • Unit tests for search_values() — flat values, nested dicts, nested lists, depth limit, include_values toggle - [x] Unit tests for filter_keys() — key name, description, consumer matching, regex, case insensitivity - [x] Integration tests for vaultctl list --filter — name match, description match, regex, no match, invalid regex - [x] Integration tests for vaultctl search — value found, not found, nested, show-match, keys-only, invalid regex - [x] All 272 tests pass, 87.45% coverage - [x] ruff, mypy --strict, bandit all clean

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com


Detailed Changes: v0.8.2...v0.9.0

v0.8.2

14 Mar 08:50

Choose a tag to compare

v0.8.2 (2026-03-14)

Bug Fixes

  • detect: Support lists as top-level vault values in recursive detection (#28, 9bc77b4)

Summary

  • _collect_nested_credential_types() now handles list values directly (not only lists inside dicts)
  • detect_type_heuristic() checks isinstance(value, (dict, list)) for credential store detection
  • Fixes detection for vault entries that are credential lists at the top level
  • 5 new tests for list-based credential structures

Test plan

  • uv run pytest — 237 tests green
  • vaultctl detect-types on vaults with list-based credential entries

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com

Documentation

  • Add troubleshooting section to README (#27, 1384c43)

Summary

Adds troubleshooting section covering the most common issues:

  • Decryption failures from missing/misconfigured password source
  • Config file not found
  • init overwriting password config on re-run
  • self-update on pip/uv installs

Test plan

  • README renders correctly on GitHub

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com


Detailed Changes: v0.8.1...v0.8.2

v0.8.1

14 Mar 07:20

Choose a tag to compare

v0.8.1 (2026-03-14)

Bug Fixes

  • security: Add recursion limits, redaction runtime guard, and security docs (#26, 9bcf6e2)

Summary

  • F-04: Recursion depth limit (max 50) in _collect_nested_credential_types() and redact_value() - A-01: Runtime redaction guard in build_payload() using contains_unredacted() — aborts AI detection if redaction fails - F-05: Trust-boundary comments on shell=True in password.py and ai_detect.py - docs/SECURITY.md: Comprehensive security architecture documentation covering data flow, triple-layer AI protection, trust boundaries, and verification steps - 7 new tests for recursion limits and redaction guard

Based on findings from cybersecurity audit of #24.

Test plan - [ ] uv run pytest — all 233+ tests green - [ ] Review docs/SECURITY.md for completeness - [ ] vaultctl detect-types --show-payload still works correctly

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com


Detailed Changes: v0.8.0...v0.8.1

v0.8.0

14 Mar 06:33

Choose a tag to compare

v0.8.0 (2026-03-14)

Features

  • detect: Recursive type detection for nested credential structures (#25, aafbfed)

Summary

  • Adds recursive scanning of nested dict/list structures for credential type fields
  • Detects Jenkins JCasC-style credential stores
  • New credentialStore type with sub-type summary
  • 10 new tests

Closes #24

Test plan

  • uv run pytest — all tests green
  • vaultctl detect-types on real Jenkins JCasC vault shows nested types
  • Existing detection behavior unchanged

Co-authored-by: Fred Thiele 8555720+f3rdy@users.noreply.github.com


Detailed Changes: v0.7.2...v0.8.0

v0.7.2

13 Mar 12:40

Choose a tag to compare

v0.7.2 (2026-03-13)

Bug Fixes

  • self-update: Improve module docstring for clarity (7832596)

Detailed Changes: v0.7.1...v0.7.2

v0.7.1

13 Mar 12:11

Choose a tag to compare

v0.7.1 (2026-03-13)

Bug Fixes

  • ci: Drop macos-amd64 binary build (unreliable macos-13 runner) (59bf347)

Detailed Changes: v0.7.0...v0.7.1