Release Python Oracle KMS Storage v1.1.0#965
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
81b1e69 to
4599784
Compare
4599784 to
060c76a
Compare
060c76a to
fe4556f
Compare
fe4556f to
0e862c1
Compare
| cryptography==46.0.5 | ||
| idna==3.10 | ||
| importlib_metadata==8.6.1 | ||
| keeper-secrets-manager-core>=16.6.6 | ||
| keeper-secrets-manager-core>=17.2.0 | ||
| oci==2.146.0 | ||
| pycparser==2.22 | ||
| pycryptodome==3.21.0 | ||
| pyOpenSSL==24.3.0 |
There was a problem hiding this comment.
🔴 requirements.txt now pins cryptography==46.0.5 (for CVE-2026-26007) alongside pyOpenSSL==24.3.0, but pyOpenSSL 24.3.0's metadata declares Requires-Dist: cryptography<45,>=41.0.5. pip install -r requirements.txt will fail with ResolutionImpossible on modern pip, or silently downgrade cryptography on older pip — defeating the CVE fix that motivates this PR. Fix by bumping pyOpenSSL to a 46.x-compatible release (e.g. >=25.3.0) or dropping the pin altogether (it is a transitive of oci, not in pyproject.toml, and not imported in source).
Extended reasoning...
The conflict. The PR raises cryptography from 44.0.1 to 46.0.5 in requirements.txt to address CVE-2026-26007, but leaves pyOpenSSL==24.3.0 in place. pyOpenSSL 24.3.0's PyPI metadata declares Requires-Dist: cryptography<45,>=41.0.5, so the two pins are mutually unsatisfiable.
How it manifests. Running pip install -r requirements.txt against this file behaves one of two ways:
- Modern pip (resolver enabled, the default since 20.3): fails with
ERROR: ResolutionImpossibleand aborts the install. - Older pip or
--use-deprecated=legacy-resolver: silently downgradescryptographyto a<45release, re-introducing CVE-2026-26007 — the exact vulnerability this release was created to patch.
Step-by-step proof.
- Inspect pyOpenSSL 24.3.0's METADATA on PyPI:
Requires-Dist: cryptography<45,>=41.0.5. requirements.txt(lines 5–11) pinscryptography==46.0.5andpyOpenSSL==24.3.0.pip install --dry-run pyOpenSSL==24.3.0 cryptography==46.0.5(verified by a reviewer) yields: 'Cannot install cryptography==46.0.5 and pyopenssl==24.3.0 because these package versions have conflicting dependencies. ERROR: ResolutionImpossible'.- pyOpenSSL 25.0.0+ relaxes the bound to support cryptography 46; 25.3.0 is a known-good pin.
Why existing guards do not catch it. The publish workflow in this PR no longer runs pip install -r requirements.txt — it builds the wheel and installs the wheel using pyproject.toml dependencies (which only lower-bound cryptography>=46.0.5 and do not list pyOpenSSL). So CI green does not exercise this file, and end users installing the published wheel from PyPI are not affected. But the file is shipped in the source tree and is the only frozen-environment recipe in the package — any developer setting up a dev environment by running pip install -r requirements.txt will hit the resolver failure (or the silent downgrade if they're on an older pip).
Related observation (not required to fix this bug, but worth flagging). oci==2.146.0 itself declares Requires-Dist: cryptography<46.0.0,>=3.2.1, so requirements.txt is doubly inconsistent with cryptography==46.0.5. A clean pip freeze regeneration after installing the package against pyproject would resolve both at once (e.g. by bumping oci to 2.174.0 and pyOpenSSL to 25.3.0+).
Suggested fix. Either (a) bump pyOpenSSL to 25.3.0 (or later) and oci to a release that allows cryptography 46.x; or (b) drop pyOpenSSL from requirements.txt entirely — grep confirms it is not imported in the package source, and it is not declared in pyproject.toml, so it is only there as a frozen transitive of oci. Regenerating requirements.txt via pip freeze after a fresh install would naturally pick a compatible set.
| run: | | ||
| python3 -m build | ||
| python3 -m twine upload --verbose dist/* No newline at end of file | ||
| python3 -m build --no-isolation |
There was a problem hiding this comment.
🔴 The 'Build and Publish' step runs python3 -m build --no-isolation (line 131), but pyproject.toml declares setuptools_scm[toml]>=6.2 in build-system.requires and the 'Install build tools' step never installs it. pypa/build performs an unconditional dependency check under --no-isolation and exits with code 1 on missing requirements, so this workflow will fail every time it is dispatched. Fix by either dropping setuptools_scm[toml]>=6.2 from build-system.requires (it is unused — version is hard-coded to "1.1.0" and there is no [tool.setuptools_scm] section) or pre-installing it in the build-tools step.
Extended reasoning...
What breaks
sdk/python/storage/keeper_secrets_manager_storage_oracle_kms/pyproject.toml declares:
[build-system]
requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"The new publish workflow at .github/workflows/publish.pypi.sdk.storage.oracle.kms.yml runs:
- name: Install build tools
run: |
python3 -m pip install --upgrade pip setuptools
python3 -m pip install build twine 'wheel>=0.46.3' # wheel pin: CVE-2026-24049
- name: Build and Publish
working-directory: ./sdk/python/storage/keeper_secrets_manager_storage_oracle_kms
...
run: |
python3 -m build --no-isolation
...Note: setuptools_scm is never installed. actions/setup-python@v5 on a fresh ubuntu-latest runner does not provide it either.
Why it fails (not just warns)
The refutation argues python -m build only emits a warning on missing build-system.requires deps. That is incorrect for --no-isolation. pypa/build runs check_dependency() against build-system.requires whenever isolation is disabled, and skip_dependency_check defaults to False. When any requirement is unsatisfied, build calls _cprint("ERROR ...") and raise SystemExit(1) before invoking the backend. Three independent verifiers reproduced this with current build releases — all observed exit code 1 with the error ERROR Missing dependencies: setuptools_scm[toml]>=6.2. Whether setuptools_scm is actually used by the backend is irrelevant because the check happens first.
Step-by-step proof
- Workflow is dispatched on
workflow_dispatch. Install build toolsstep installspip,setuptools,build,twine,wheel>=0.46.3.setuptools_scmis not installed.Build and Publishstep runspython3 -m build --no-isolationfromsdk/python/storage/keeper_secrets_manager_storage_oracle_kms.buildparsespyproject.toml, readsbuild-system.requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"].buildwalks the requirements and resolves them against the host env.setuptools✓,wheel✓,setuptools_scm[toml]>=6.2✗.buildprintsERROR Missing dependencies: setuptools_scm[toml]>=6.2and exits 1.- No
dist/*.whlis produced. The subsequentpip install dist/*.whl, the import smoke test, andtwine uploadnever run. The publish job fails. v1.1.0 cannot ship.
Impact
The entire purpose of this PR is to publish v1.1.0 (CVE-2026-26007 fix, Python floor bump, core dep bump). The workflow as written will fail on first dispatch — no wheel reaches PyPI.
Fix (pick one)
- (Preferred) Drop
setuptools_scm[toml]>=6.2frombuild-system.requiresinpyproject.toml. The package does not use it:version = "1.1.0"is statically declared, there is no[tool.setuptools_scm]section, andversionis not indynamic. - Or pre-install it in the build-tools step:
python3 -m pip install build twine setuptools_scm[toml] 'wheel>=0.46.3'. - Or pass
--skip-dependency-checktopython -m build(worst option — masks future drift).
Why the refutation is wrong
The refutation claims the dep check only warns. That was true for very old build versions but is not true for any release in the past several years; the check is gated by skip_dependency_check (default False) and raises SystemExit(1) when failed. The original bug submitter hedged ("may still succeed in practice"), but the three verifiers who actually ran python -m build --no-isolation against a matching pyproject.toml all observed a hard failure.
| python3 -m twine upload --verbose dist/* No newline at end of file | ||
| python3 -m build --no-isolation | ||
| python3 -m pip install dist/*.whl | ||
| python3 -c "import keeper_secrets_manager_storage_oracle_kms; print('import OK')" |
There was a problem hiding this comment.
🔴 The post-build import sanity check resolves to the source tree, not the installed wheel. The step runs with working-directory: ./sdk/python/storage/keeper_secrets_manager_storage_oracle_kms, which contains a same-named source package subdirectory; python3 -c prepends CWD to sys.path[0], so the import always finds the local source even if the wheel is broken. Fix by running the check from a neutral directory (e.g. cd /tmp && python3 -c ...) or using python3 -P -c ... to disable CWD-on-sys.path.
Extended reasoning...
What the bug is
The new "Build and Publish" step (introduced in commit b033909, "ci: add post-build import check") does:
working-directory: ./sdk/python/storage/keeper_secrets_manager_storage_oracle_kms
run: |
python3 -m build --no-isolation
python3 -m pip install dist/*.whl
python3 -c "import keeper_secrets_manager_storage_oracle_kms; print('import OK')"
python3 -m twine upload --verbose dist/*The working directory ./sdk/python/storage/keeper_secrets_manager_storage_oracle_kms itself contains a subdirectory of the same name (keeper_secrets_manager_storage_oracle_kms/ with __init__.py and the source modules). Per the Python docs for -c, sys.path[0] is set to '' (the current directory) and searched first. So the import resolves to the on-disk source package, not the wheel that was just installed via pip install dist/*.whl.
Step-by-step proof
- Workflow's
working-directoryfor the step is./sdk/python/storage/keeper_secrets_manager_storage_oracle_kms. - Inside that directory there is a subdirectory
keeper_secrets_manager_storage_oracle_kms/(verified — it contains__init__.py,constants.py,oci_kms_client.py, etc.). python3 -c 'import keeper_secrets_manager_storage_oracle_kms; ...'runs withsys.path = ['', ...site-packages...].- Python's import system finds
./keeper_secrets_manager_storage_oracle_kms/__init__.py(a regular package onsys.path[0]) before it reaches site-packages where the wheel was installed. print('import OK')succeeds even if the wheel is broken (missing files, wrong package name in metadata, bad[tool.setuptools]config, missing package-data, etc.).
Why this matters
The commit message "ci: add post-build import check" explicitly states the intent: catch wheel build problems before twine upload ships them to PyPI. Concrete failure modes the check will silently miss:
[tool.setuptools] packages = [...]typo dropping the package from the wheel- Missing package-data (so the wheel imports but is functionally broken)
- Wrong package name in built metadata
- A stale wheel left over in
dist/thatdist/*.whlglobs and pip installs alongside the new one
pip install dist/*.whl itself would still fail on a totally malformed archive, so the check isn't entirely useless — but for the class of bugs it was added to catch (structural problems in the wheel that import-time would reveal), it provides false confidence.
Fix
One-line. Either:
(cd /tmp && python3 -c "import keeper_secrets_manager_storage_oracle_kms; print('import OK')")or use Python's -P flag to skip CWD-on-sys.path:
python3 -P -c "import keeper_secrets_manager_storage_oracle_kms; print('import OK')"Either option makes the import resolve through site-packages, which is what the check was meant to test. Worth fixing before this workflow gets copied to the other storage SDKs.
| persist-credentials: false | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 |
There was a problem hiding this comment.
🟡 Nit / supply-chain hardening consistency: at lines 52 and 108 is referenced by mutable tag, while every other action in this workflow (actions/checkout, Keeper-Security/ksm-action, actions/upload-artifact) is SHA-pinned with a version comment per the recent hardening in 310fb4f / 0e862c1. Since this PR explicitly touches both lines (bumping @v4 → @v5), it's the natural opportunity to complete the pattern by pinning to a commit SHA (e.g., actions/setup-python@<sha> # v5.x.x).
Extended reasoning...
What's inconsistent
Within .github/workflows/publish.pypi.sdk.storage.oracle.kms.yml, four distinct uses: references are present after this PR:
actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2(lines 14, 47, 103) — SHA-pinnedactions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0(line 88) — SHA-pinnedKeeper-Security/ksm-action@37abbada28637b6478ba845e73d3f8b9676572fe # v1.3.0(line 114) — SHA-pinnedactions/setup-python@v5(lines 52 and 108) — mutable tag, no SHA
The recent commits 310fb4f ci: harden publish workflow and 0e862c1 ci: add version comment to ksm-action SHA pin establish a clear @<sha> # <version> convention in this file. setup-python is the one outlier.
Why this matters
Git tags are mutable: the action publisher can move v5 to point at a different commit at any time, including after a credential compromise. SHA pins eliminate that risk because the SHA is content-addressable. This is the well-known OpenSSF Scorecard / GitHub Hardening third-party-actions guidance. Even though actions/setup-python is GitHub-owned (lower base risk than a random third-party action), the official guidance and this file's existing pattern both call for SHA pinning.
Why this PR is the right time
The PR diff at lines 52 and 108 changes actions/setup-python@v4 → actions/setup-python@v5 — so this PR is already updating these exact lines as part of the v1.1.0 release hardening. Pinning the SHA at the same time is a one-line further change with no additional review/testing cost; deferring it means a separate future PR to touch the same lines again.
Step-by-step proof of the inconsistency
- Open
.github/workflows/publish.pypi.sdk.storage.oracle.kms.ymlat line 52:uses: actions/setup-python@v5— tag only. - Same file, line 108:
uses: actions/setup-python@v5— tag only. - Same file, line 14 (and 47, 103):
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2— SHA + version comment. - Same file, line 88:
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0— SHA + version comment. - Same file, line 114:
uses: Keeper-Security/ksm-action@37abbada28637b6478ba845e73d3f8b9676572fe # v1.3.0— SHA + version comment. git log -- .github/workflows/publish.pypi.sdk.storage.oracle.kms.ymlshows commits 310fb4f / 0e862c1 introducing/standardizing the@<sha> # <version>pattern.
Caveats / acknowledgments
- This is not a runtime bug — the workflow runs correctly today. Severity is nit.
- Repo-wide, many other workflows still reference setup-python by tag (e.g.
test.python.yml,publish.pypi.sdk.yml), so the repo-wide convention is mixed. The inconsistency being flagged is specifically within this file, which has otherwise been hardened to a uniform SHA-pin standard. - Fix is mechanical: pick the current
actions/setup-pythonv5.x commit SHA, replace both@v5references with@<sha> # v5.x.y, no other code changes needed.
| STATUS=$(curl -sS -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/keeper-secrets-manager-storage-oracle-kms/${VERSION}/json") | ||
| if [ "$STATUS" = "200" ]; then | ||
| echo "::error::keeper-secrets-manager-storage-oracle-kms ${VERSION} is already on PyPI. Aborting." | ||
| exit 1 | ||
| fi | ||
| echo "keeper-secrets-manager-storage-oracle-kms $VERSION not yet on PyPI - ok to proceed" |
There was a problem hiding this comment.
🟡 The new job (lines 35-40) only fails when PyPI returns HTTP 200, so 5xx errors, 3xx redirects, or any non-404 response silently pass the check. Since twine itself rejects duplicate uploads this is defense-in-depth rather than a correctness bug, but the fix is one line — treat anything other than 404 as ambiguous: if [ "$STATUS" != "404" ]; then echo "::error::Unexpected status $STATUS"; exit 1; fi (and add --fail-with-body to the curl).
Extended reasoning...
What the bug is
The validate-versions job added in this PR (.github/workflows/publish.pypi.sdk.storage.oracle.kms.yml lines 35-40) probes PyPI to make sure the version about to be released does not already exist:
STATUS=$(curl -sS -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/keeper-secrets-manager-storage-oracle-kms/${VERSION}/json")
if [ "$STATUS" = "200" ]; then
echo "::error::keeper-secrets-manager-storage-oracle-kms ${VERSION} is already on PyPI. Aborting."
exit 1
fi
echo "keeper-secrets-manager-storage-oracle-kms $VERSION not yet on PyPI - ok to proceed"The gate fires only when STATUS == "200". Any other HTTP code — including 5xx server errors, 3xx redirects, or unexpected values — is treated as "ok to proceed". curl is invoked with -sS (no --fail/--fail-with-body), so HTTP-level errors do not change its exit code.
Why existing safeguards don't fully cover this
GitHub Actions defaults to bash --noprofile --norc -eo pipefail, so set -e is active. That means a pure network-level curl failure (DNS error, connection refused) would abort the script because the STATUS=$(curl ...) substitution would inherit curl's non-zero exit code. So the bug isn't as broad as "any transient error fails open" — but HTTP-level failures (5xx, redirects) do slip through, because -sS without --fail returns exit 0 for those.
Step-by-step proof (PyPI 503 outage scenario)
- Maintainer dispatches the workflow to re-publish
keeper-secrets-manager-storage-oracle-kms 1.0.0(already on PyPI). - PyPI is having an incident and
https://pypi.org/pypi/.../1.0.0/jsonreturnsHTTP/1.1 503 Service Unavailable. curl -sS -o /dev/null -w "%{http_code}"writes503to stdout and exits with status0(no--fail).STATUS="503". The conditional[ "$STATUS" = "200" ]is false.- The script prints
... not yet on PyPI - ok to proceedand the job succeeds. generate-sbomandpublishjobs run, wasting CI time and the SBOM publish quota.twine uploadultimately calls PyPI's upload endpoint, which returns400 File already exists, and the publish job fails with a confusing error several minutes later instead of the intended fast-fail.
Impact
Limited. Twine + PyPI remain the authoritative duplicate-version safeguard (PyPI rejects re-uploads with 400 File already exists), so this is not a correctness/security issue — there is no path that causes a wrong package to actually publish. The real cost is:
- Wasted CI minutes and a misleading failure point in the SBOM/publish steps during a PyPI outage.
- A weakened early-fail signal: the whole point of the validate-versions job is to fast-fail before doing 5+ minutes of build/SBOM work.
Suggested fix
Invert the check to treat only 404 as success, and let curl fail on transport errors:
STATUS=$(curl -sS --fail-with-body -o /dev/null -w "%{http_code}" \
"https://pypi.org/pypi/keeper-secrets-manager-storage-oracle-kms/${VERSION}/json" || echo "$?")
if [ "$STATUS" = "200" ]; then
echo "::error::Version ${VERSION} is already on PyPI. Aborting."
exit 1
fi
if [ "$STATUS" != "404" ]; then
echo "::error::Unexpected status \"$STATUS\" from PyPI — cannot verify version. Aborting."
exit 1
fi
echo "Version $VERSION not yet on PyPI - ok to proceed"This makes the check unambiguous: 200 means "already published" (fail), 404 means "new version" (proceed), anything else means "cannot determine" (fail, retry the workflow once PyPI is healthy).
There was a problem hiding this comment.
An organization admin can view or raise the cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.
Once the cap resets or is raised, push a new commit or reopen this pull request to trigger a review.
There was a problem hiding this comment.
An organization admin can view or raise the cap at claude.ai/admin-settings/claude-code. The cap resets at the start of the next billing period.
Once the cap resets or is raised, push a new commit or reopen this pull request to trigger a review.
…ge-oracle-kms v1.1.0
- Raise Python floor to 3.9.2; Core SDK dependency to >=17.2.1
- Fix AES-GCM nonce from 128-bit to 96-bit per NIST SP 800-38D (KSM-954)
- Replace MD5 with SHA-256 for config change detection (KSM-954)
- Fix plaintext {} written before encryption at init (KSM-950)
- Fix encrypt/decrypt silently returning empty on KMS error (KSM-951)
- Fix delete_all() leaving credential file on disk when KMS unavailable (KSM-952)
- Fix set() silently diverging on read-only config files (KSM-953)
- Fix read_storage() returning live reference; correct autosave default (KSM-955)
- Add thread-safety lock to OracleKeyValueStorage (KSM-956)
- Fix load_config() leaving self.config = None on empty bootstrap (KSM-957)
- Fix requires-python floor to >=3.9.2 (KSM-958)
- Upgrade cryptography to >=46.0.5; fixes CVE-2026-26007 (KSM-834)
- Upgrade urllib3 to 2.6.3, requests to 2.32.4
- Port 23-test suite from GCP patterns; all pass on Python 3.9-3.13
- Rewrite publish workflow with GCP v1.1.0 parity hardening
8ec2a4b to
2608edf
Compare
Summary
Release branch for v1.1.0: security hardening (CVE-2026-26007, AES-GCM nonce correction), eight correctness fixes from a GCP-parity audit, and a Python floor raise to 3.9.
Changes
Bug Fixes
cryptographyfloor raised to>=46.0.5— ECDH subgroup attack on SECT curves (CVSS 8.2)__init__no longer writes{}to disk before encryption succeeds; KMS failures during initialget_key/encrypt_buffernow propagateencrypt_buffer()anddecrypt_buffer()now raise on KMS error instead of returning empty bytes/stringdelete_all()left file on disk (KSM-952): now callsos.remove()instead of re-encrypting an empty configset()silently diverged on read-only file (KSM-953):PermissionErrornow propagates; in-memory and disk state stay consistentread_storage()(KSM-955): returns a defensive copy;decrypt_config()default changed fromautosave=Truetoautosave=FalseOracleKeyValueStoragenow uses an internalthreading.RLockforset(),delete(),change_key(), anddecrypt_config()self.config = Noneafter empty bootstrap (KSM-957):load_config()no longer leaves config asNoneafter parsing a plaintext{}init file; subsequentget/set/deleteno longer crash withTypeErrorrequires-pythontightened to>=3.9.2;cryptography>=46.0.5excludes 3.9.0 and 3.9.1 via PyPI markers — pip now rejects those versions with a clear error instead of an opaque resolver failureMaintenance
keeper-secrets-manager-corefloor raised from>=16.6.6to>=17.2.1ocifloor updated with version markers:>=2.167.3,<2.168.2on Python 3.9,>=2.174.0on 3.10+; required to unlockcryptography>=46.0.5(olderocicappedcryptography<46.0.0)pycryptodomefloor raised to>=3.21.0;requeststo>=2.32.0(CVE-2026-47081);urllib3to>=2.6.3publishtoggle, SBOM generationBreaking Changes
keeper-secrets-manager-storage-oracle-kms<1.1.0to remain on a supported version.decrypt_config()default changed:autosavenow defaults toFalse(wasTrue). Passautosave=Trueexplicitly to preserve the previous behavior (KSM-955).Related Issues
KSM-834, KSM-949, KSM-950, KSM-951, KSM-952, KSM-953, KSM-954, KSM-955, KSM-956, KSM-957, KSM-958