Skip to content

Release KSM CLI v1.3.0#917

Open
stas-schaller wants to merge 59 commits intomasterfrom
release/tool/cli/v1.3.0
Open

Release KSM CLI v1.3.0#917
stas-schaller wants to merge 59 commits intomasterfrom
release/tool/cli/v1.3.0

Conversation

@stas-schaller
Copy link
Contributor

@stas-schaller stas-schaller commented Jan 21, 2026

Summary

Release branch for CLI v1.3.0 — adds OS-native keyring storage as the default credential store, with security hardening and dependency fixes.

Changes

New Features

  • OS-native keyring storage (KSM-800): new profiles stored in macOS Keychain, Windows Credential Manager, or Linux keyring by default; keyring is an optional install (pip install keeper-secrets-manager-cli[keyring]); --ini-file flag opts into explicit file-based storage
  • Profile delete command (KSM-810): new ksm profile delete <name> subcommand; completes the recovery path referenced by KsmCliIntegrityException

Bug Fixes

  • --ini-file flag respected by all subcommands (KSM-814): all profile and config subcommands now correctly use the --ini-file path — profile list, profile active, profile export, profile import, profile init, profile setup; config show, config color, config cache, config record-type-dir, config editor
  • Keyring integrity verification (KSM-805): SHA-256 hash now persisted as a separate Keychain entry and verified on every load; tampered entries raise KsmCliIntegrityException with a ksm profile delete recovery hint; backward-compatible (existing entries bootstrap silently on next save)
  • Active profile clearing on delete (KSM-810): delete_profile() now clears active_profile in the common config when the active profile is deleted, preventing a broken-keychain state on subsequent invocations
  • Upgrade-path warning (KSM-804): warn on stderr when keyring is active but empty and a legacy keeper.ini exists, with a --ini-file recovery hint; fixed duplicate warnings from redundant Profile instantiation in command handlers
  • INI file permissions (KSM-691): keeper.ini created atomically at 0600 via os.open() (eliminates TOCTOU window on Unix); set_config_mode always runs on every write so Windows ACLs (icacls) are applied to new files and pre-existing bad permissions are corrected on re-save
  • Env var config reload: _reload_config() now correctly re-applies KSM_CONFIG and KSM_CONFIG_BASE64_* environment variable configs on reload instead of falling through to disk discovery
  • CVE-2026-23949 (KSM-761): upgraded jaraco.context to >=6.1.0 (path traversal; build-time dependency only)
  • Record create payload (KSM-702): custom: [] now always included when creating records with no custom fields; previously the key was silently omitted, causing schema inconsistency with Vault and Commander
  • Profile name validation before OTT redemption (KSM-815): profile name is validated before the one-time token is redeemed; names containing whitespace or exceeding 64 characters are rejected immediately, preventing the token from being consumed on a failed init
  • Profile name strict validation (KSM-829): early profile name check now uses the same [a-zA-Z0-9_-]{1,64} pattern as keyring storage; previously path-traversal characters and special characters passed the early check, consuming the one-time token before the stricter validator fired
  • Shell crash with click>=8.2 (KSM-818): ksm shell crashed on any command when click-repl==0.3.0 was resolved alongside click>=8.2 (protected_args became read-only in Click 8.2); pinned click-repl to <0.3.0
  • JSON output key for custom fields (KSM-820): ksm secret get --json now outputs custom fields under "custom" (was "custom_fields"), matching the canonical V3 record format used by Commander and the Keeper Vault
  • Test keyring isolation (KSM-828): unit tests no longer write mock data to the real system keyring; added KeyringConfigStorage.is_available mock to all 19 Profile.init() call sites used as test scaffolding in secret_test.py, exec_test.py, and secret_inflate_test.py
  • boto3 import for non-AWS --ini-file profiles (KSM-831): AwsConfigProvider import deferred to inside the elif storage == "aws": branch; users without [aws] extra no longer hit Missing import dependencies: boto3 when loading any non-AWS profile via --ini-file
  • lkru utility removed (KSM-832): removed the lkru utility integration and KSM_CONFIG_KEYRING_UTILITY_PATH environment variable; lkru requires the same D-Bus Secret Service daemon as the Python keyring library and is not a headless alternative; is_available() now correctly returns False when keyring is not installed or no Secret Service daemon is running, falling back to keeper.ini file storage in both cases

Maintenance

  • Updated prompt-toolkit from ~=2.0 to >=3.0 (fixes pip dependency resolution conflicts)
  • Updated keeper-secrets-manager-core to >=17.2.0, keeper-secrets-manager-helper to >=1.1.0
  • Keyring integration tests (KSM-830): Docker-based integration tests against a real Secret Service backend (dbus + gnome-keyring); new test-cli-keyring CI job across Python 3.10–3.13; test.cli.yml path-filtered to CLI and Python SDK paths

Breaking Changes

  • Python 3.7–3.9 dropped (KSM-799, KSM-817): minimum supported version is now Python 3.10
  • boto3 now optional (KSM-817): boto3 moved out of the base install; AWS sync users must install the [aws] extra: pip install keeper-secrets-manager-cli[aws]

Related Issues

@socket-security
Copy link

socket-security bot commented Jan 21, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedpypi/​prompt-toolkit@​2.0.10 ⏵ 3.0.5290 +1100100100100
Addedpypi/​keyring@​25.7.0100100100100100
Updatedpypi/​click-repl@​0.3.0 ⏵ 0.2.0100100100100100
Addedpypi/​click-help-colors@​0.2100100100100100

View full report

@stas-schaller stas-schaller force-pushed the release/tool/cli/v1.3.0 branch 2 times, most recently from acffcd5 to 31e606c Compare January 23, 2026 18:33
pvagare-ks and others added 16 commits February 24, 2026 15:25
- Update prompt-toolkit from ~=2.0 to >=3.0 (fixes pip backtracking)
- Pin boto3>=1.20.0 for IMDSFetcher support in AWS integrations
- Update keeper-secrets-manager-storage to >=1.0.3
- Raise minimum Python version from 3.7 to 3.9
- Bump CLI version to 1.3.0

This fixes the test-cli (3.9) failure caused by pip resolving to
ancient boto3 1.7.84 due to prompt-toolkit~=2.0 dependency conflict.
Revert minimum Python version from 3.9 back to 3.7 to give clients
time to migrate. Python SDK v17.1.0 is researching deprecation timeline
for EOL Python versions with migration guidance coming soon.

This aligns CLI deprecation timeline with the Python SDK v17.1.0
approach of providing advance notice before removing support.

Related: Python SDK v17.1.0 release (PR #886)
Version 1.0.3 is not published to PyPI. CLI already has boto3>=1.20.0 pinned directly.
Ansible tests are broken on master due to PSModulePath detection.
Fix already exists on release/integration/ansible/v1.2.7.
Skip ansible tests for CLI releases until ansible v1.2.7 merges.
- Upgrade jaraco.context dependency to fix CVE-2026-23949 path traversal vulnerability
- Add clarifying comments for SHA-256 usage in keyring config (change detection, not password hashing)
- Update CLI README changelog with CVE fix details
CLI publishing now happens via secrets-manager-cli-binaries repo using publish.ksm.cli.yml workflow. This workflow is no longer used.

style: restore blank line in storage.py to match master
- Update keeper-secrets-manager-core from >=17.0.0 to >=17.1.0
- Requires Python SDK v17.1.0 for security fixes and compatibility
- Includes CVE-2026-23949, CVE-2026-24049, duplicate UID fix (KSM-732)
- Includes file upload/download SSL/proxy fixes (KSM-763) needed by CLI
- Includes transmission key #18 (KSM-740)
@stas-schaller stas-schaller force-pushed the release/tool/cli/v1.3.0 branch from de67025 to fd8c787 Compare February 24, 2026 20:33
KSM-804: warn on stderr when keyring empty and legacy keeper.ini found
… config

Persist a SHA-256 integrity hash as a separate Keychain entry on every
save and verify it on every load. Tampered entries raise
KsmCliIntegrityException with a recovery hint. Deletion bypasses the
check so compromised profiles can always be removed.

Backward compatible: existing entries without an integrity key load
silently; the key is bootstrapped on the next save.
KSM-805: add SHA-256 cross-session integrity verification for keyring config
Use os.open() for atomic 0600 creation on POSIX, and always call
set_config_mode() so Windows icacls runs on both new and existing
files. Adds unit tests covering new file creation, bad-permission
correction, and the Windows set_config_mode call path.
_reload_config fell through to _find_ini_file when config was loaded
from KSM_CONFIG or KSM_CONFIG_BASE64_* env vars, causing profile list
to load from a keeper.ini on disk instead of the env var config.
Add missing `ksm profile delete <name>` subcommand. Fix
`KeyringConfigStorage.delete_profile()` to clear `active_profile` when
the deleted profile was active, preventing a broken-keychain state on
subsequent CLI invocations.

Also fix profile command handlers to reuse the already-initialised
`Profile` instance from `KeeperCli` instead of creating a second one,
eliminating duplicate upgrade-path warnings on macOS Keychain systems.

Changes:
- keyring_config.py: clear active_profile after removing profile from list
- profile.py: add delete() method routing keyring vs INI storage
- __main__.py: add profile_delete_command; reuse ctx.obj["cli"].profile
  in all five profile sub-command handlers
- keyring_test.py: extend test_delete_profile() as failing regression test
- profile_test.py: add test_delete_command_clears_active_profile()
…ommand

KSM-810: add ksm profile delete command and fix active profile clearing
…name-validation

KSM-815: validate profile name before redeeming one-time token
KSM-814: fix --ini-file flag ignored by profile and config subcommands
KSM-818: pin click-repl<0.3.0 to fix shell crash with click>=8.2
Add _reload_config() to show_config, set_color, set_cache,
set_record_type_dir, and set_editor so they read/write the correct
storage (ini file or keyring) rather than stale in-memory state.

Forward global --ini-file to profile_setup_command when no subcommand
flag is set, matching the fix already applied to profile_init_command.

Add 7 regression/coverage tests covering:
- profile setup, profile active, profile delete, profile import
- config show, config color, config cache
all with global --ini-file only (no subcommand flag)
…payload

Python Core SDK to_dict() uses `if self.custom:` which treats an empty
list as falsy, dropping the custom key even after the null guard sets it
to []. Monkey-patch to_dict() at the class level to inject custom:[] when
absent. Updated test now asserts on the serialized payload rather than the
object attribute, correctly catching the serialization-layer bug.
Remove patch when keeper-secrets-manager-core >= 17.2.1 is released.
…y-fix

KSM-820: fix JSON output key custom_fields → custom
…lation

KSM-828: prevent unit tests from writing mock data to real system keyring
Python 3.10+ includes importlib.metadata in the standard library, making
the importlib_metadata backport unnecessary.
Comment on lines +72 to +129
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install system deps (dbus + gnome-keyring)
run: sudo apt-get install -y gnome-keyring dbus-x11

########## KSM Python SDK (from source)

- name: Install SDK dependencies
working-directory: ./sdk/python/core
run: |
python3 -m pip install --upgrade pip
python3 -m pip install setuptools
python3 -m pip install -r requirements.txt
python3 -m pip install -e .

- name: Install SDK for integrations
working-directory: ./sdk/python/core
run: |
python3 setup.py build install

########## KSM Python Helper (from source)

- name: Install SDK Helper dependencies
working-directory: ./sdk/python/helper
run: |
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
python3 -m pip install -e .

- name: Install SDK Helper for integrations
working-directory: ./sdk/python/helper
run: |
python3 setup.py build install

########## CLI with keyring extra

- name: Install CLI with keyring extra
working-directory: ./integration/keeper_secrets_manager_cli
run: pip install -e ".[keyring]" pytest

- name: Run keyring integration tests
working-directory: ./integration/keeper_secrets_manager_cli
run: |
dbus-run-session -- bash -c "
echo '' | gnome-keyring-daemon --unlock --components=secrets,keyring
KSM_KEYRING_INTEGRATION=1 python -m pytest tests/keyring_integration_test.py -v --tb=short
"

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium test

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 4 days ago

To fix the problem, explicitly define a permissions block that limits GITHUB_TOKEN to the minimal required rights. In this workflow, the jobs only need to check out code and run Python tests, so read access to repository contents is sufficient; no write or PR/issue permissions are needed.

The best minimal change is to add a workflow-level permissions block near the top of .github/workflows/test.cli.yml, just after the name (or before on:). This block will apply to both test-cli and test-cli-keyring since they do not define their own permissions. We’ll set contents: read, which is the recommended minimal baseline and matches CodeQL’s suggested starting point.

Concretely:

  • Edit .github/workflows/test.cli.yml.
  • Insert:
permissions:
  contents: read
  • Place it between line 2 and line 3 (after name: Test-CLI and the blank line), keeping indentation at the root level.
  • No imports, methods, or other definitions are needed because this is a YAML configuration change only.
Suggested changeset 1
.github/workflows/test.cli.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/test.cli.yml b/.github/workflows/test.cli.yml
--- a/.github/workflows/test.cli.yml
+++ b/.github/workflows/test.cli.yml
@@ -1,5 +1,8 @@
 name: Test-CLI
 
+permissions:
+  contents: read
+
 on:
   pull_request:
     branches: [ master ]
EOF
@@ -1,5 +1,8 @@
name: Test-CLI

permissions:
contents: read

on:
pull_request:
branches: [ master ]
Copilot is powered by AI and may make mistakes. Always verify output.
…le-import

KSM-831: fix boto3 import error for non-AWS --ini-file profiles
Two related fixes in keyring_config.py:

1. KeyringConfigStorage.is_available(): was returning False when keyring
   was installed but returned fail.Keyring (no Secret Service daemon),
   preventing lkru from being detected. Now falls through to lkru check
   on both ImportError and fail.Keyring backend.

2. KeyringUtilityStorage.__init__(): was setting use_python_keyring=True
   unconditionally on successful import, even when get_keyring() returned
   fail.Keyring. The lkru detection block (except ImportError) was never
   reached. Now raises ImportError when fail.Keyring is detected, routing
   into the existing lkru detection path. Broadened to except Exception
   for consistency with is_available().

Also adds:
- 3 regression tests in keyring_test.py (mocked, ImportError path)
- keyring_lkru_headless_test.py: 5 unmocked integration tests that run
  in Docker with real fail.Keyring backend, including
  test_storage_routes_to_lkru_not_fail_backend which catches the __init__
  bug independently of is_available()
- test-cli-lkru-headless CI job in test.cli.yml using Docker to guarantee
  a headless environment (no D-Bus)
- Dockerfile.keyring-test moved to tests/docker/
- README and docs/keyring.md updated
lkru communicates with the D-Bus Secret Service API — the same daemon
required by the Python keyring library. It provides no benefit when the
daemon is absent and solves a scenario (daemon running, keyring library
not installed) that pip install keeper-secrets-manager-cli[keyring]
trivially handles. The repo has 1 star and 0 forks.

Removes:
- lkru detection from KeyringConfigStorage.is_available()
- KSM_CONFIG_KEYRING_UTILITY_PATH env var support
- keyring_utility / keyring_utility_path params from KeyringUtilityStorage
- use_python_keyring flag and __run_keyring_utility() method
- subprocess import (no longer used)
- keyring_lkru_headless_test.py and 3 lkru unit tests
- test-cli-lkru-headless CI job

Also skips test_config_mode_access_denied when running as root (Docker),
where chmod 0o0000 does not restrict file access.
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.

2 participants