Release KSM CLI v1.5.0 - #1065
Open
stas-schaller wants to merge 49 commits into
Open
Conversation
Ticket IDs in test docstrings/comments rot when the tracker changes and distract readers into looking something up the comment should just explain inline; the substance (why each test exists) is kept, only the KSM-#### / REL-#### / URL references are removed.
_get_instance_region() constructed IMDSFetcher with botocore's default timeout/retries, so 'ksm profile setup -t aws' stalled on every one of the four region-lookup calls against an unreachable 169.254.169.254 before eventually surfacing a raw botocore NoRegionError. IMDSFetcher now uses a 1s/1-attempt timeout, and _get_client() raises a message naming the non-EC2 cause and the --fallback option instead. read_config() also logs AWS errors it previously swallowed silently.
…profiles KeyringUtilityStorage.__load_config() caught every exception, including keyring.errors.KeyringLocked, behind a blanket except-and-log-at-debug, so a locked keyring (e.g. gnome-keyring running but locked in an SSH session with no display server) was indistinguishable from no profile ever being configured. Reproduced empirically against the actual published keeper-secrets-manager-cli==1.3.0 package in a Docker container with a real dbus-daemon + gnome-keyring-daemon, locking a collection with a stored secret and confirming the silent None return. __load_config() now catches KeyringLocked specifically and raises the new KsmCliKeyringLockedException with an actionable message; existing KsmCliException handling in profile.py surfaces it without further changes. Keyring-less installs are unaffected (the guarded keyring import falls back to a tuple that matches nothing on ImportError).
The IMDS timeout fix and _get_client() error handling belong in the storage package (keeper-secrets-manager-storage), not the CLI release. These changes will land in storage v1.2.0 via KSM-977-aws-load-config-raise.
APPDIR is a Linux AppImage variable, not a standard Windows env var. Replace with APPDATA joined with "Keeper" so %APPDATA%\Keeper\keeper.ini is correctly included in the config path search, consistent with the existing PROGRAMDATA\Keeper and PROGRAMFILES\Keeper entries. KSM-1113
Relative ["etc"] entries in find_ksm_path resolved against CWD, not /etc. os.path.join produces /etc/keeper.ini only when the first component is an absolute path. No behavior change on Windows where /etc does not exist. KSM-1113
KSM-1114 dropped colorama, which left check_config_mode()'s Windows icacls permission warning uncolored (that function, in the Core SDK, duck-types its color_mod arg against colorama's Fore/Style shape). A small click.style()-backed shim gives it the same shape without reintroducing colorama as a dependency. KSM-1114
…ng entries Removes the KSM-859 entry (fix was reverted from this branch via 29c5997; CLI v1.5.0 ships without it, tracked as KSM-1115 for a future release). Adds KSM-1113, KSM-1107, and the installer-level KSM-1018/1105/1106 fixes, which were merged on this branch or the same release but missing from the changelog.
FieldType.__init__ in keeper-secrets-manager-helper crashes with IndexError when a complex field (name, address, host, etc.) has value: [] — the server's normal representation of an unpopulated field. Strip fields with empty value lists from the source record before passing to RecordV3.create_from_data so the helper never receives value: [] for a dict-typed field. Empty fields on the source are absent from the clone payload; the server still creates them as empty on the cloned record. A proper fix for the root cause in the helper is tracked in KSM-1119.
helper FieldType.__init__ crashes with IndexError when a dict-typed field (address, name, host, passkey, etc.) has value: [] -- the server's normal representation of an unpopulated complex field. The add-file path called Record.create_from_file with no filter, so any record script containing an unpopulated complex field crashed with an opaque "list index out of range" error naming neither the field nor the cause. add editor shares the same loader and is fixed by the same change. Fix: load the file via load_file, strip fields where value is empty before calling RecordV3.create_from_data, matching the pattern introduced for add clone in KSM-1118 (commit b0d660e). Also adds cleanup comments at both workaround sites (add file and add clone) so they can be removed once the helper ships the "if self.value:" guard in FieldType.__init__ (KSM-1119). Pre-existing since 1.4.0; not a 1.5.0 regression.
ksm secret download crashed with MissingSchema: Invalid URL 'None' when
called immediately after ksm secret upload in automation. The vault may
not have propagated the file download URL by the time the SDK returns.
CLI-layer workaround checks file.f.get("url") before calling
get_file_data() and raises a clear error with a retry prompt. Remove
when the CLI bumps Python Core past the KSM-1131 fix.
KSM_CONFIG bypasses the KSM-805 keyring integrity check entirely, since there is no persistent hash to compare an env-var config against. Now prints a stderr warning when a keyring profile exists and would otherwise be shadowed. Silent when keyring is unavailable or empty, so CI/container environments using KSM_CONFIG exclusively see no output.
…t (KSM-1156) (#1074) The ksm shell startup banner uses Unicode box-drawing characters echoed unconditionally, so any stdout that cannot encode them - cp1252 when output is piped or redirected on Windows, or a C-locale pipe on Linux - crashed the shell at startup with "UnicodeEncodeError: 'charmap' codec can't encode characters". Check the active stdout encoding before printing and use a plain-text banner when the logo cannot be represented. UTF-8 terminals keep the full logo.
…he shell (KSM-1157) click-repl re-invokes the main group callback for every line typed in ksm shell, parsing the inner line against the group's options. Options absent from the inner line parse as their defaults, so the callback rebuilt KeeperCli from scratch and session globals were silently dropped: `ksm --ini-file custom.ini shell` followed by `secret list` inside the shell resolved config as if --ini-file was never given (same for --profile-name, --output, --color, --cache and --log-level). Inner invocations are recognizable by ctx.parent being the session's group context (top-level runs have no parent). For those, fall back to the session's stored option values for every option not explicitly typed on the inner line, detected via ctx.get_parameter_source. Options typed on an inner line still override the session values, for that line only. KeeperCli is still rebuilt per line, so flows like profile init inside a shell keep picking up config changes. ctx.get_parameter_source requires click 8.0+, so declare click>=8.0 in install_requires and requirements.txt instead of an unpinned click.
…r (KSM-1155) (#1073) * fix(cli): raise KeeperError correctly in keyring storage fatal handler (KSM-1155) KeyringUtilityStorage.__fatal called KeeperError(message, error), but KeeperError.__init__ only accepts a message, so every storage failure path crashed with "TypeError: KeeperError.__init__() takes 2 positional arguments but 3 were given" instead of reporting the actual error. Raise KeeperError with the message and chain the original exception as the cause so the real storage error reaches the caller intact. * fix(cli): only chain keyring fatal cause when one exists (KSM-1155) Review follow-up: `raise ... from error` with error=None sets __suppress_context__, which hides the original ImportError from debug tracebacks when __fatal fires inside the `except ImportError` block (no keyring backend). Chain the cause only when one was passed, so the implicit exception context stays visible; adds a regression test asserting the ImportError context is preserved and not suppressed.
…#1081) * fix(cli): preserve backslash paths in ksm shell on Windows (KSM-1162) click_repl 0.2.0 tokenizes typed input with shlex in POSIX mode, where backslash is an escape character. Windows paths typed inside ksm shell were corrupted before click saw them (C:\dir\file.ini -> C:dirfile.ini), and paths with directory separators could even split into spurious extra tokens. Forward-slash paths were unaffected. The fix adds a _windows_safe_shlex() context manager that patches click_repl's shlex reference on Windows for the duration of the REPL loop. The replacement tokenizer uses posix=True + escape='' + whitespace_split=True: backslashes are treated as literal characters rather than escape characters, while quoted paths (including those with spaces) still have their quotes stripped normally. The --ini-file case became reachable in 1.5.0 after KSM-1157 made inner-line global options apply to each command; positional path arguments (e.g. add file -f) were affected all along.
…warning + _NOTSET_ sentinel (#1082) * fix(cli): KSM-1163 keeper.ini discovery — KSM_INI_DIR conflict warning and _NOTSET_ sentinel fix Defect A: when KSM_INI_DIR is set and a keeper.ini also exists in the current working directory, profile.py's _find_ini_file() was returning the CWD file without any indication that the env var was being ignored. The CWD file continues to load (non-breaking — three releases have shipped with CWD-first, so a silent flip would break setups that now rely on it), but a yellow warning is now emitted on stderr naming both paths, which one loaded, and the --ini-file override. Set KSM_INI_DIR_SKIP_CONFLICT_WARNING=TRUE to suppress the warning. Defect B: find_ksm_path() used "_NOTSET_" as a placeholder for unset environment variables and still joined + probed the resulting path. On any POSIX host where USERPROFILE/APPDATA/PROGRAMDATA/PROGRAMFILES are unset, those entries resolved to relative _NOTSET_/... paths, meaning a literal ./_NOTSET_/ directory in the CWD could be accidentally discovered and loaded. Entries whose environment variable is unset are now skipped entirely. Regression coverage: four new tests in IniDiscoveryTest.
…d (KSM-1165) shlex.shlex sets commenters='#' by default, so everything from '#' to end-of-line was silently dropped from arguments typed in ksm shell on Windows (e.g. p@ss#word -> p@ss). Mirror shlex.split(comments=False) semantics by clearing lex.commenters when comments is False. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…commenters fix(cli): clear shlex commenters in _win_split so '#' is not truncated (KSM-1165)
…n_non_windows (KSM-1166) The test read the live sys.platform value, causing it to fail on real Windows where _windows_safe_shlex() applies the patch. Decorate with @patch('sys.platform', 'linux') to make the test host-independent. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…platform-mock test(cli): mock sys.platform in test_windows_safe_shlex_not_applied_on_non_windows (KSM-1166)
…6 workaround in add file (KSM-1161) The KSM-1126 workaround in add_record_from_file() stripped value:[] custom fields to prevent a helper IndexError, but unlike the clone path (KSM-1136), it never re-attached them. Result: ksm secret add file and ksm secret add editor silently dropped every custom field whose value was empty. Save the stripped custom fields before filtering, then zip them back onto each record_create_obj.custom after create_from_data — the same pattern the clone path already uses.
…159/1160/1161/1164/1165)
…d-file-preserve-custom fix(cli): re-attach empty-value custom fields dropped by add file workaround (KSM-1161)
… (KSM-1169) The Azure (sync_azure) and GCP (sync_gcp) dry-run paths placed the current destination value into the output, and neither backend validated the destination name before writing. This mirrors the AWS issues in KSM-1168 on the other two backends; today they are reachable only through operator-supplied --map keys, but the dry-run value still lands in the sync log. - Add a shared _dst_status() helper and use it in both dry-run paths so the output reports dstExists/dstDiffers instead of the live value. - Validate Azure and GCP destination names (Azure Key Vault and GCP Secret Manager naming rules) before a write; an invalid name is reported per entry and skipped rather than sent to the backend.
…ented rules Address review feedback on KSM-1169. Azure Key Vault object names (secrets) have no first-character or hyphen-placement restriction; those rules apply to vault names. Sources: Microsoft Learn (object-name: a 1-127 character string containing only 0-9, a-z, A-Z, and -), the Key Vault REST spec SetSecret pattern ^[0-9a-zA-Z-]+$, and PSRule Azure.KeyVault.SecretName. A stricter regex would refuse names the service accepts, such as 123-secret. - Expand the validator docstring with the documented rule and sources. - Pin documented-valid edge shapes (123-secret, name-, a--b) and the 127/128 length boundary for Azure, and the 255/256 boundary plus 123_id for GCP, so any future tightening shows up as a deliberate test change.
…zure-gcp-dryrun-and-validate fix(cli): redact ksm sync Azure/GCP dry-run output and validate destination names (KSM-1169)
…me (KSM-1170) _resolve_records matched a token by record title and _resolve_folders matched by folder name or path, in addition to UID, with no signal to the operator. A title or folder name is mutable and settable by any shared-folder collaborator, so a scheduled sync pinned to one is fragile. Emit a stderr warning naming the resolved UID whenever a token resolves by title or by name/path rather than by UID. Resolution behavior is otherwise unchanged; ambiguous matches still error.
…test Address review feedback on KSM-1170. The folder test now resolves both by name and by UID in the same mock context and asserts exactly one warning, matching the shape of the records test, so a regression that made _resolve_folders warn on UID lookups would fail the test. Also asserts the warning names the resolved folder UID.
…esolve-by-uid-warning fix(cli): warn when ksm sync resolves --record/--folder by mutable name (KSM-1170)
…8s-validate-names fix(cli): build ksm init k8s manifest with a YAML serializer (KSM-1171)
stas-schaller
force-pushed
the
release/tool/cli/v1.5.0
branch
from
August 10, 2026 17:48
40628aa to
21b22ed
Compare
…ry-run output (#1097) * fix(cli): KSM-1168 require --prefix for ksm sync --type aws record/folder modes; redact dry-run output Require --prefix/-px when --record, --folder, or --folder-recursive is used with --type aws. The prefix is prepended to every AWS secret name derived from a record title, confining the sync to an operator-controlled namespace and preventing a folder member from targeting arbitrary secrets in the AWS account by controlling their record title. Replace dstValue in dry-run output with dstExists and dstDiffers. The live destination secret value is no longer fetched or printed, eliminating the CI-log exfiltration path identified in the Bugcrowd report. Breaking: users on --record/--folder since v1.2.0 must add --prefix to existing sync commands. Closes KSM-1168 * fix(cli): KSM-1168 reject path traversal in record titles used with --prefix Add _sanitize_title_for_prefix to strip leading slashes and reject '..' segments from record titles before prepending --prefix. Without this, a title like '../prod/db-password' with '--prefix keeper/' would produce 'keeper/../prod/db-password', potentially escaping the intended namespace. * fix(cli): KSM-1168 address review gaps — prefix separator, dstValue compat, test coverage Validate that --prefix ends with a non-alphanumeric character so the prefix forms a real namespace boundary (e.g. keeper/ or myapp-); a bare alphanumeric prefix would concatenate directly into the record title with no delimiter. Keep dstValue key present with null value in dry-run output instead of removing it, so consumers using entry["dstValue"] are not broken. Add tests: _sanitize_title_for_prefix (leading-slash strip, dotdot rejection), record-based dry-run redaction via sync_aws_json_with_client (json_key=None branch), and prefix prepend on the --folder path. * fix(cli): KSM-1168 fix dstDiffers always-true on record dry run; add prefix enforcement and traversal guard tests * chore(ci): trigger test.cli.yml on release/tool/cli/** branches
KSM-1171 merged immediately after KSM-1169 and KSM-1170; the merge resolved the README conflict correctly but left the trailing >>>>>>> end-marker in the file.
…oting (KSM-1182) (#1103)
…tored byte-for-byte (KSM-1186) (#1102) * fix(cli): disable click Windows argv expansion so secret values are stored byte-for-byte (KSM-1186) click >= 8.0 expands every command-line argument on Windows before parsing (BaseCommand.main -> _expand_args: os.path.expanduser, os.path.expandvars, then glob). ntpath.expandvars expands %VAR%/$VAR and collapses $$ to $, so 'password=a$$b' was stored as 'a$b' and 'login=x%OS%y' as 'xWindows_NTy' - silent data corruption of stored secrets, plus premature %VAR% expansion inside 'ksm exec' command arguments. Windows-only; pip and frozen surfaces. Pass windows_expand_args=False (the click 8.0.1+ opt-out) in main()'s group invocation, bump the click floor accordingly, and add a wiring test (CliRunner bypasses BaseCommand.main(), which is how this evaded the unit suite - the test asserts the opt-out is passed). * fix(cli): align requirements.txt click floor with setup.py (>=8.0.1) (KSM-1186) Review follow-up: requirements.txt still allowed click 8.0.0, which lacks the windows_expand_args parameter and raises TypeError when main() passes it. Both dependency declarations now require 8.0.1+.
) sync_aws_json_with_client, sync_aws_json, sync_aws_with_client and sync_aws declared maps: list = [], a mutable default argument shared and mutated across calls that omit it. Switched all four to Optional[list] = None with maps = maps or [] where the method iterates without an earlier guard. Originally found via Datadog SAST triage (VM-2868); ported here onto cli-1.5.0's current colorama-free sync.py. KSM-1191 Co-authored-by: Sergey Aldoukhov <[email protected]>
stas-schaller
force-pushed
the
release/tool/cli/v1.5.0
branch
from
August 12, 2026 19:42
343dd74 to
1b2f962
Compare
* fix(cli): validate RFC 1123 secret name in ksm init k8s (KSM-1183) --name was passed to kubectl as a positional argument with no validation, so a value beginning with '-' was consumed by kubectl as one of its own flags instead of as the secret name (CWE-88). KSM-1171 fixed the manifest branch by switching to yaml.safe_dump() but left the apply branch reading the name straight into argv. Validate --name against the RFC 1123 subdomain rule Kubernetes applies to Secret names, at the top of get_k8s() so the check covers both the apply and the manifest branch. A validated name can never begin with '-', which closes the argv surface. A '--' end-of-flags separator was considered and rejected: kubectl derives NAME with ArgsLenAtDash(), which counts only the positionals appearing before '--', so 'kubectl create secret generic -- my-secret' reports "exactly one NAME is required, got 0". Flags placed after '--' are also swallowed as positionals, so there is no argument order in which '--' both preserves NAME and keeps --from-literal parsed as a flag. The KSM-1171 injection test moves its newline payload to --namespace, which is still unvalidated and still relies on the YAML serializer; --name newlines are now rejected up front and are covered by the new test_k8s_rejects_invalid_names cases. * fix(cli): anchor RFC 1123 k8s name regex to end-of-string, not end-of-line Python re's $ matches before a trailing newline as well as at true end-of-string, so 'my-secret\n' passed the RFC 1123 check the k8s name validation is supposed to enforce. Switch to \Z, which has no such exception. Does not affect the leading-dash injection fix (KSM-1183): a trailing newline can never produce a flag-shaped name. * fix(cli): allow dots in k8s Secret names, validate before token redemption Mateo's review of the KSM-1183 fix found three issues in the RFC 1123 check and one in the manifest serializer it protects: - The regex implemented the label rule (no dots), but Kubernetes Secret names follow the subdomain rule, which permits them (tls.example.com, v1.2.3). Legal names were being rejected as a regression. - --name was validated inside get_k8s(), after Init.__init__() already redeemed the one-time token over the network, so a rejected name still burned the token. Validation now runs in a click callback at argument-parse time, before Init() is constructed, matching the validate_non_empty idiom already used elsewhere in this file. - Names that are legal Kubernetes Secret names but ambiguous in YAML 1.1 (y, n, 1e5) were emitted unquoted in the manifest branch; PyYAML itself round-trips them fine, but kubectl's stricter YAML 1.1 parser reads them as bool/number instead of string. They're now wrapped in a QuotedStr and forced through a SafeDumper subclass that quotes only that wrapper type. Also hoists subprocess.run mocking into _patched_cli, drops scaffolding nothing in the k8s command path exercises, and wraps the multi-case invalid-name test in subTest so one failure doesn't mask the rest.
mgallego-keeper
approved these changes
Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release branch for v1.5.0: 22 CLI bug fixes (including two security fixes: an authorization gap in
ksm sync --type awsand an argument-injection gap inksm init k8s --apply), a dependency cleanup, a behavior improvement to config-source conflict detection, and 10 installer and pipeline fixes from the cli-binaries repo that are user-facing for this release.Changes
Bug Fixes
--ini-file/KSM_CONFIGas a fallback.%APPDIR%\Keeper(a Linux AppImage variable, not a Windows one) instead of%APPDATA%\Keeper; on Linux, the/etcsearch entry resolved against the current working directory instead of/etc. Both are now resolved correctly.ksm secret add cloneexited 0 even when the source UID did not exist, masking the failure from scripts. Now exits non-zero with an error message.ksm secret add clonecrashed with "list index out of range" when the source record contained any unpopulated complex field (name, address, host, etc.) — fields the server legitimately returns asvalue: []. Empty-value fields are now skipped before the create payload is assembled. Root cause in the helper library is tracked as KSM-1119.ksm secret add fileandksm secret add editorcrashed with the same "list index out of range" error when the record script contained an unpopulated complex field withvalue: []. Empty-value fields are now stripped before the create payload is assembled. Root cause: KSM-1119.ksm secret downloadcrashed withMissingSchema: Invalid URL 'None'when called immediately afterksm secret uploadin automation, because the vault may not have propagated the file download URL yet. Now raises a clear error with a prompt to retry in a few seconds. Root cause in the Python SDK: KSM-1131.value: []fromksm secret add clone, so empty custom fields were silently missing from the cloned record. Empty custom fields are now re-attached to the clone payload with their type, label, and flags preserved. Regression within this release only; no shipped version affected.KeyringUtilityStoragecrashed withTypeError: KeeperError.__init__() takes 2 positional arguments but 3 were giveninstead of surfacing the actual error. Now raisesKeeperErrorwith the message and chains the original exception as the cause.ksm shellcrashed at startup withUnicodeEncodeErrorwhen stdout could not represent the Unicode box-drawing banner (e.g. piped/redirected output on Windows, or a C-locale pipe on Linux). The shell now checks the active stdout encoding and falls back to a plain-text banner.ksm shellre-resolved configuration from scratch, so global options set when launching (--ini-file,--profile-name,--output,--color/--no-color,--cache/--no-cache,--log-level) were ignored for inner commands. Inner commands now inherit the session's global options; an inner line can still override them for that line only. Requires click >= 8.0, now declared ininstall_requires.click-repltokenizes input withshlexin POSIX mode where backslash is an escape character, soC:\dir\file.inityped insideksm shellbecameC:dirfile.ini. The shell now uses a safe tokenizer on Windows that treats backslash as a literal character while still stripping quotes normally.#on all platforms (KSM-1165): The Windows-safe tokenizer introduced for KSM-1162 left#as a comment character, so anything after#in a shell argument was silently dropped — notation references likeUID#field/pathfailed inside the shell.#is now treated as a literal character.KSM_INI_DIRwas set and akeeper.inialso existed in the CWD, the CWD file loaded silently. The CLI now warns on stderr naming both paths; setKSM_INI_DIR_SKIP_CONFLICT_WARNING=TRUEto suppress. (B)find_ksm_path()probed_NOTSET_/…/keeper.inipaths when Windows environment variables were unset on POSIX hosts; entries with unset variables are now skipped entirely.ksm secret add file(andadd editor) silently dropped custom fields withvalue: []. The KSM-1126 workaround strips them before passing to the helper to avoid a crash; those custom fields are now re-attached to the create payload with their type, label, and flags preserved. Root cause: KSM-1119.ksm sync --type awswith--record,--folder, or--folder-recursivenow requires--prefix. The prefix confines the sync to an operator-defined namespace and prevents a shared-folder collaborator from targeting arbitrary AWS secrets by controlling their record title. Additionally,--dry-runoutput no longer includes the live destination secret value across all three cloud backends — it now reportsdstExistsanddstDiffersonly. See Breaking Changes below.ksm syncdry-run output no longer includes the live destination value for Azure and GCP backends, matching the AWS behavior introduced in KSM-1168. Azure Key Vault and GCP Secret Manager destination names supplied via--mapare now validated against provider naming rules before a write.ksm sync --record/--foldernow prints a warning on stderr when a token resolves by record title or folder name/path rather than by UID. Mutable identifiers are fragile in scheduled syncs; the warning names the resolved UID and suggests using UIDs instead.ksm init k8snow builds the Kubernetes Secret manifest with a YAML serializer instead of string formatting, so--nameand--namespaceare always properly encoded scalars and cannot inject additional manifest content.ksm sync --type aws|azure|gcpgave pip install advice on a missing cloud dependency even in a frozen binary install, where pip cannot fix anything. The three handlers now detect a frozen install and point at the installer's "Cloud Sync" component instead of pip; the AWS pip command is also single-quoted for zsh compatibility.%VAR%/$VARwere expanded against the environment and$$was collapsed to$before the CLI ever parsed them, sopassword=a$$bwas stored asa$b. The same expansion causedksm execto deliver raw, unresolved notation to the child process instead of the resolved secret. The CLI now disables this expansion; macOS and Linux are unaffected.maps: list = []), which Python evaluates once at function-definition time and shares across every call that omits it. Now usesOptional[list] = None, normalized per call.ksm init k8s --applypassed--nametokubectlas an unvalidated positional argument, so a value beginning with-was read bykubectlas one of its own flags (CWE-88).--nameis now validated against the RFC 1123 subdomain rule Kubernetes applies to Secret names before the one-time token is redeemed, and names that are legal in Kubernetes but ambiguous in YAML 1.1 (y,n,1e5) are now quoted in the manifest output. See Security Impact below.Behavior
KSM_CONFIGis set and a keyring profile exists that would otherwise take precedence, the CLI now warns on stderr naming the active config source. The warning is suppressed when the keyring is unavailable or empty, so CI/container environments usingKSM_CONFIGexclusively see no output.Dependency
coloramapackage withclick.style(), already available via the existingclick-help-colorsdependency. The Windows-only config-permission warning keeps its coloring via a smallclick.style()-backed shim.Maintenance
Deferred
Installer and pipeline fixes (cli-binaries repo — not in this diff, but user-facing for this release)
libssl.3.dylibpredated OpenSSL 3.2.0, causingImportError: Symbol not found: _SSL_get0_group_name. Both x64 and arm64 macOS builds now bundle an up-to-date libssl.%TEMP%was unsigned, causing EDR/AV to block the post-install launch ofksm.exe.ArchitecturesAllowedandArchitecturesInstallIn64BitModedirectives caused the 64-bit binary to land inC:\Program Files (x86)on 64-bit systems.PATH. The installer now detects and removes any pre-existing x86 installation before placing the 64-bit binary.{app}constant was not expanded before the PATH check, appending a duplicate entry on every install or upgrade.REG_EXPAND_SZentries toREG_SZ, preventing Windows from expanding%SystemRoot%and other variable references in PATH. The registry value type is now preserved.install.shinstalled silently on hosts below the documented libc minima; the firstksminvocation crashed with a cryptic loader error. The script now checks the host libc version before installing and exits with an actionable error. SetKSM_SKIP_PREFLIGHT=1to bypass.ksmcrashed at load time in its own image. The alpine image now uses Alpine 3.22 as its base, with binary self-tests added to the build./cli/glibc/ksmand/cli/musl/ksmbinaries were always the same amd64 ELF on both platforms, making the documented init-container pattern non-functional on arm64 hosts. The arm64 image legs now ship native arm64 binaries.Security Impact
KSM-1168 eliminates an authorization gap in
ksm sync --type aws: a Keeper user who can add records to a synced folder could control their record title to target arbitrary secrets in the AWS account. The--prefixrequirement confines every sync operation to an operator-defined namespace. KSM-1168/1169 also redact the live destination secret value from--dry-runoutput across all three cloud backends, removing a CI-log exfiltration path.KSM-1183 closes a CWE-88 argument-injection gap in
ksm init k8s --apply: an unvalidated--namebeginning with-was read bykubectlas one of its own flags rather than as the secret name. RFC 1123 validation now runs before--nameever reacheskubectl, and before the one-time token is redeemed, so a rejected name no longer burns the token.Breaking Changes
--record,--folder, or--folder-recursivesince v1.2.0 must add--prefix <value>to existing sync commands (e.g.--prefix keeper/). The prefix must end with a non-alphanumeric character to form a real namespace boundary.Related Issues