Skip to content

Release Python SDK v17.4.0 + Helper 1.1.3 - #1112

Draft
stas-schaller wants to merge 31 commits into
masterfrom
release/sdk/python/core/v17.4.0
Draft

Release Python SDK v17.4.0 + Helper 1.1.3#1112
stas-schaller wants to merge 31 commits into
masterfrom
release/sdk/python/core/v17.4.0

Conversation

@stas-schaller

Copy link
Copy Markdown
Contributor

Summary

Release branch for Python SDK v17.4.0 and Helper 1.1.3: NSF folder-decryption parity, cache and permission hardening, twelve bug fixes, and throttle improvements. PR #1096 (KSM-1140 PAM connection fields) is still in review against this branch.

Changes

New Features

  • PAM settings database and dbConnectionMethod (KSM-1127): added to PamSettings connection schema in Helper

Bug Fixes

  • get_secrets() deduplication (KSM-1145): records accessible via both a shared folder and an individual share no longer appear twice
  • Shared folder record decryption (KSM-747): records with innerFolderUid in the flat records[] array now use the folder key instead of the app key
  • get_folders() crash safety (KSM-1080): undecryptable folders are now skipped instead of raising; the remaining folders are returned normally
  • delete_secret()/delete_folder() partial failure (KSM-1085): both methods now raise KeeperError listing rejected UIDs
  • KSMCache file permissions (KSM-1122): the cache file is now written with mode 0600; existing files are corrected on the next write
  • KSMCache live response (KSM-1123): an HTTP 200 response is always returned to the caller even when the cache write fails
  • RecordCreate.to_dict() custom field (KSM-1152): "custom" is now always included as [] in the payload
  • get_file_data() null URL (KSM-1131): raises KeeperError with a retry message instead of a bare requests exception
  • Throttle retry jitter (KSM-1033): jitter is now one-sided (0 to +25%); server-supplied retry_after is capped at MAX_THROTTLE_DELAY_SEC (176s)
  • Key-rotation retry loop (KSM-1069): retries are bounded at 3; the SDK raises immediately for custom key deployments
  • KSMCache file name override (KSM-1019): an explicit kms_cache_file_name assignment now always takes precedence over KSM_CACHE_DIR
  • InMemoryKeyValueStorage malformed config (KSM-299): raises KeeperError with a clear message instead of a cryptic TypeError
  • FieldType.__init__ empty value list (KSM-1119): guards against an empty value list in Helper

Maintenance

  • Made KSMCache.get_cache_file_path a @staticmethod; bumped Helper core floor to 17.4.0 (KSM-1121)

Breaking Changes

None.

Related Issues

stas-schaller and others added 29 commits July 24, 2026 13:27
save_cache() used plain open(..., 'wb') which creates files at 0644,
exposing the cleartext transmission key to any user on the machine.
Switch to os.open with O_CREAT|O_TRUNC|0o600 and add os.chmod after
write to also correct permissions on files that already exist at a
wider mode.

Add two regression tests: one for a freshly created file, one for an
existing world-readable file being overwritten. Both are skipped on
Windows where permission bits carry different semantics.

Fixes KSM-1122.
…ure (KSM-1123)

The bare except in caching_post_function caught both the HTTP call and
save_cache. A FileNotFoundError from save_cache after a successful 200
caused the live response to be discarded and the fallback cache path to
be attempted instead, surfacing a FileNotFoundError to the caller.

Restructure so only the HTTP call is in the try block. If the network
call succeeds, save_cache failures are silently swallowed and the live
response is returned. Also tighten the except clause from bare except
to except Exception.

Add a regression test that mocks post_function to return 200 and
save_cache to raise FileNotFoundError, asserting the live response and
original transmission key are preserved.

Fixes KSM-1123.
…file-permissions

fix(python): cache file permissions and live-response masking (KSM-1122, KSM-1123)
…SM-1152)

KSM-819 fixed the empty-list case (custom=[]) by switching from a
truthiness check to `is not None`. That left the never-set case
(custom=None) still omitting the key entirely. The vault API requires
custom to be present on every create payload.

Move custom into the base dict with a None→[] default so it is always
serialized. Replace the test_none_custom_omitted regression test from
KSM-819 with test_unset_custom_serializes_as_empty_list that asserts
the corrected behavior.

Fixes KSM-1152.
…-create-custom

fix(python): always serialize custom as [] in RecordCreate.to_dict (KSM-1152)
get_file_data() passed None directly to requests.get() when the vault
had not yet propagated the download URL after an upload, producing an
untyped MissingSchema exception. Guard on a falsy url and raise
KeeperError with a retry message.

Jira: KSM-1131
…rl-guard

fix(python): KSM-1131 raise KeeperError when file download URL is null
…nit__

For complex fields (address, name, host, paymentCard, etc.), __init__
accessed self.value[0] unconditionally after the is-not-None check. When
the server returns an unpopulated field as "value": [], the empty list
passes the None check but raises IndexError on index access.

Change `if self.value is not None` to `if self.value` so an empty list
is treated the same as None -- attribute variables default to None.

Jira: KSM-1119
…-empty-field

fix(python): KSM-1119 guard against IndexError in FieldType.__init__ for empty value list
Two throttle backoff defects, both ported from KSM-1030 (Java) and
KSM-1035 (JS):

1. Jitter was two-sided: random.uniform(-0.25, 0.25) could reduce the
   delay below its computed floor, causing an immediate re-throttle.
   Changed to random.uniform(0, 0.25) so the floor is always respected.

2. _parse_throttle had no upper bound on retry_after: a misbehaving
   backend could supply an arbitrarily large value. Added
   MAX_THROTTLE_DELAY_SEC = 176 (= BASE * 2**4, the last-retry ceiling)
   and capped with min(..., MAX_THROTTLE_DELAY_SEC).

Jira: KSM-1033
…le-jitter-cap

fix(python): KSM-1033 one-sided throttle jitter and retry_after cap
… custom keys (KSM-1069)

The error='key' retry path in _post_query had no upper bound, looping
indefinitely if the server kept suggesting a key the SDK could not resolve.
For IL5 deployments with a pinned custom server public key, this was
effectively unbounded by design: the server would keep suggesting a standard
key that the deployment cannot use.

- Add MAX_KEY_ROTATION_RETRIES = 3 to keeper_globals
- Add _parse_key_rotation() static method mirroring _parse_throttle()
- In _post_query, intercept error='key' inline before handler_http_error:
  raise immediately when a custom key is pinned (IL5); cap standard
  key-rotation retries at MAX_KEY_ROTATION_RETRIES
- Update il5_test to reflect the new correct behavior (raise immediately
  on key-rotation when custom key is pinned, not silently accept rotation)
- Add key_rotation_test.py with unit tests for _parse_key_rotation and
  integration tests for all three exit paths
…-key-rotation-cap

fix(python): bound key-rotation retries; raise immediately for IL5 custom keys (KSM-1069)
…elper core floor to 17.4.0

get_cache_file_path was declared @classmethod but never used cls -- all
other KSMCache methods are @staticmethod. Converts the decorator to
match. (KSM-1121)

Helper setup.py core floor raised from >=17.3.0 to >=17.4.0, required
before the 17.4.0 PyPI publish since KSM-1119 and KSM-1127 both depend
on behavior introduced in this release's core.
…static-consistency

fix(python): KSMCache staticmethod consistency + helper core floor bump
…s connection schema

Both fields were present in the Java SDK (PamSettingsConnection) but absent
from the Python helper's v3 field_type.py schema for pamDatabase records.
Adds them alongside the existing connection fields so they appear in template
generation output and are available for record construction. (KSM-1127)
…ttings-db-fields

fix(python/helper): add database and dbConnectionMethod to PamSettings connection schema
…-747)

Records created by non-SDK clients (Commander, Vault UI) inside shared
folders appear in response.records[] with innerFolderUid set and their
record key encrypted with the folder key, not the app key. The SDK was
always using the app key, silently failing AES-GCM authentication and
moving every such record to bad_records.

Fix: before the records loop, build a folder_key_map from folders_resp
(the same data already used by the folder path). For any flat record
with innerFolderUid present in the map, decrypt its record key with the
folder key; fall back to the app key for records without innerFolderUid.

Regression test in shared_folder_test.py constructs a raw response with
a folder key encrypted under the app key and a flat record whose record
key is encrypted under the folder key, confirming the fix.

Jira: KSM-747
…folder-key-decryption

fix(python): use folder key for flat records with innerFolderUid (KSM-747)
When a record has individual share access and also belongs to a shared
folder, the vault returns it in both response.records[] and
response.folders[].records[]. After the folder-key decryption fix, both
copies decrypt correctly, causing get_secrets() to return two entries for
the same UID. Deduplication by recordUid is applied after both response
paths are processed.

Jira: KSM-1145
…-dedup-records

fix(python): deduplicate records returned via both flat and folder paths (KSM-1145)
…ries

Fix semicolons, passive voice, overlong sentences, and participial
endings in the new changelog bullets. No content changes.
…h (KSM-1019)

* fix(sdk/python/core): honor kms_cache_file_name override in KSMCache cache path

PR #1034 (KSM-1004) routed cache operations through get_cache_file_path(), which
re-derived the path from KSM_CACHE_DIR and ignored the kms_cache_file_name class
attribute. Assigning KSMCache.kms_cache_file_name was silently dropped.

Honor an explicit kms_cache_file_name override again (detected via an import-time
default snapshot) while keeping lazy KSM_CACHE_DIR resolution. Add a regression test
and bump to 17.3.1 (including the keeper_globals hardcoded fallback and smoke-test
assertions).

Fixes #1044.

* fix(sdk/python/core): detect kms_cache_file_name override by identity (KSM-1019)

Replace the value-equality override check in KSMCache.get_cache_file_path with
an identity comparison against a _DefaultCachePath(str) sentinel, so an explicit
kms_cache_file_name assignment is honored even when its text equals the
import-time default (e.g. "ksm_cache.bin" when KSM_CACHE_DIR was unset at
import). The previous "!=" check silently dropped such an override once
KSM_CACHE_DIR was set, a narrower case of the KSM-1004 regression this line
fixes.

Add a collision-case regression test and the 17.3.1 README changelog entry.
KSM-1004 lazy KSM_CACHE_DIR resolution and all default behavior are preserved.

* chore(sdk/python/core): drop 17.3.1 version bump and changelog (folded into 17.4.0)

This fix is being consolidated into the 17.4.0 release. The single version
bump and the KSM-1019 changelog entry now live in the 17.4.0 release PR
(#1057), so revert _version.py, keeper_globals.py, smoke_test.py and the
README changelog back to the release-branch baseline. This PR now carries
only the KSMCache code fix and its regression tests.

* refactor(sdk/python/core): single-source default cache path and clarify restore note (KSM-1019)

Address second review round:
- extract _default_cache_path() so the import-time sentinel and the lazy
  branch of get_cache_file_path build the default path from one expression,
  removing the silent-drift risk between the two copies
- reword the restore-default note: restore by assigning
  KSMCache.kms_cache_file_name = KSMCache._default_cache_file_name, not by
  re-assigning the sentinel itself
- add test_kms_cache_file_name_override_can_be_restored_to_default locking
  in the documented restore path
- drop a stray blank line left in the README by the 17.3.1 changelog removal

* test(sdk/python/core): cover plain default cache path and sentinel text consistency (KSM-1019)

Close two gaps in the path-resolution coverage matrix:
- the untouched default (no override, no KSM_CACHE_DIR) resolving to
  ksm_cache.bin in the current working directory, exercised end to end
  through save_cache
- the import-time sentinel text staying equal to _default_cache_path()
  output, so editing or re-inlining either copy of the expression fails a
  test instead of silently changing what the public attribute advertises
…orage config (KSM-299)

* fix(sdk/python/core): raise clear KeeperError for malformed InMemoryKeyValueStorage config (KSM-299)

InMemoryKeyValueStorage given a config string that is not valid JSON (or
base64-encoded JSON) raised a cryptic "TypeError: object of type 'NoneType'
has no len()". The error path called len() on the json_to_dict() result
(None) instead of the source string, and %-formatting evaluated that len()
argument before the KeeperError could even be constructed.

Preserve the original config string in config_str and report its length, so
a malformed config now raises a clear KeeperError explaining the problem.
Add regression tests mirroring the existing "must raise KeeperError, not
TypeError" pattern in this suite.

* chore(sdk/python/core): bump to 17.4.0 and consolidate changelog

Bump python-core from 17.3.0 to 17.4.0 across the three coupled locations
(_version.py, the keeper_globals.py hardcoded fallback, and the smoke_test
version assertions) and add a 17.4.0 README changelog entry covering
KSM-299, KSM-1019, KSM-1080 and KSM-1085.
…chema (KSM-1140) (#1096)

* feat(python): add full PAM connection settings fields to CLI helper schema

Expands PamSettings.connection with the complete PamSettingsConnection
field set (RDP, SSH/Terminal, VNC, database, Telnet, Kubernetes, nested
SFTP sub-settings), plus allowSupplyHost at the PamSetting level.
PamSettingsPortForward gains useSpecifiedLocalPort, localPort, and
allowKeeperDBProxy. PamRemoteBrowserSettings gains audio control fields
and browser session fields sourced from vault TypeScript definitions.

Jira: KSM-1140

* fix(python): address KSM-1140 review feedback

- Move changelog entry from core README to helper README Version 1.1.3 section
- Update changelog text to accurately reflect live additions only
- Strip inert sub-schemas from list-typed portForward and connection fields
  (value_type: list fields are terminal leaves; nested schemas are never read)
- Bump helper setup.py from 1.1.2 to 1.1.3

* fix(python): fix allowSupplyHost depth and restore KSM-1127 fields

- Move allowSupplyHost to pamSettings top-level schema (value[0].allowSupplyHost)
  so it is live; previously nested inside connection made it value[0].connection.allowSupplyHost
  which no vault type has
- Restore database and dbConnectionMethod to connection.schema (accidentally removed
  when stripping inert keys in round-1 review pass, reverting KSM-1127)

* docs(python): fix KSM-1140 changelog - allowSupplyHost is at PamSettings level

The previous entry said "PamSettings.connection schema" which is the
location where values are silently discarded. allowSupplyHost is a
sibling of connection, not inside it.
@stas-schaller stas-schaller changed the title feat(python): Python SDK v17.4.0 + Helper 1.1.3 Release Python SDK v17.4.0 + Helper 1.1.3 Aug 18, 2026
* fix(python): bump cryptography floor to >=46.0.7 (CVE-2026-39892)

Passing a non-contiguous buffer to APIs accepting Python buffers, such as
Hash.update(), can overflow. Affects 45.0.0 through 46.0.6, fixed in
46.0.7. The open floor of >=46.0.5 resolves happily to 46.0.5 or 46.0.6,
both vulnerable, and this is the published keeper-secrets-manager-core
package - a fresh install could land on an affected version.

Cherry-picked from ba6b0678 (Sergey Aldoukhov), scoped to sdk/python/core
only; the same commit's Oracle KMS storage bump is handled separately
under KSM-1215 since storage packages release independently.

KSM-1213

* docs(python): add KSM-1213 security changelog entry for CVE-2026-39892 cryptography floor bump

---------

Co-authored-by: Sergey Aldoukhov <saldoukhov@gmail.com>
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.

Python core: KSMCache ignores kms_cache_file_name override after KSM-1004

2 participants