Repository navigation
Feat/python sdk cipher - #1453
Conversation
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
repr and str show [sealed] for sealed fields. Cipher(cls) checks the class again and finds the credentials, with no request and no HTTP client. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Credentials are found without building an HTTP client, and each refusal names the variable or file to fix. Redaction reads the class's own dict, covers rich, and runs no descriptor that may do work. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
repr and str show [sealed] for sealed fields. Cipher(cls) checks the class again and finds the credentials, with no request. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
5b792d5 to
11ee027
Compare
cipherstash-agent
left a comment
There was a problem hiding this comment.
Review attestation — commit reviewed: 11ee027aa1b1c5fbce339a6a0c2a23c1017d0a23. Host: Claude Code (scheduled routine). Model: claude-sonnet-5-5. Skills: code-review (medium, performed inline), review-cross-check (done manually: no existing review threads; only changeset-bot and CodeRabbit-skipped notices), review-post.
Blocking issues: none.
Findings: 1 inline comment (non-blocking, medium confidence) on hand_written in _redact.py.
Not reviewed in depth: Rust engine/refusal/plan changes and test files were skimmed from the file list only; tests were not run.
Generated by Claude Code
cipherstash-bot
left a comment
There was a problem hiding this comment.
Recommendation: 🟡 merge after changes (1 of 8 review job(s) failed)
infracode — claude (claude-opus-5-5)
Nothing in the infrastructure part of this change needs to be fixed before merge. This PR changes no Terraform, CloudFormation, mise tasks, CI workflows or observability configs. Its only build-config changes are a new stack-auth path dependency in languages/python/Cargo.toml, crates/core/Cargo.toml and one line in languages/python/Cargo.lock.
I checked three things:
- CI path filter.
tests-python.ymlalready listspackages/stack-auth/**, becausestack-kms'sprofilefeature pulls in that crate. A change tostack-authalready reruns the Python suite. httpfeature stays off. The new dependency isdefault-features = false.stack-encryptis built withdefault-features = false, features = ["dynamic"], anddynamicdoes not turn onstack-auth/httporstack-kms/http. Cargo's feature unification does not compile reqwest or TLS into the wheel, so the comments inCargo.tomlandcredentials.rsare correct.- Lock file.
stack-authwas already in the workspace'sCargo.lock, throughstack-kms. The diff adds only the new edge fromstackencrypt-core, socargo ... --lockedstill resolves.
I found no infrastructure issues.
python — claude (claude-opus-5-5)
Nothing must change before merging. Two small follow-ups can wait: hand_written can replace some hand-written methods with no error, and Pydantic's devtools hook still prints plaintext.
I did not repeat the open thread on reprlib.recursive_repr. The first finding below is a different input with a related cause.
| Severity | Finding | Location |
|---|---|---|
| Follow-up | A hand-written method with no __code__, or from another module, is replaced with no error |
languages/python/python/stackencrypt/_redact.py:73 |
| Optional | Pydantic's __pretty__ (devtools) still prints sealed fields |
languages/python/python/stackencrypt/_redact.py:60 |
I could not run the toolchain: this checkout has no .venv, and Pydantic and SQLAlchemy are not installed. I ran hand_written and install alone on Python 3.12, outside the package. They gave the results below. Rust files were out of scope.
rust — claude (claude-opus-5-5)
No Rust change has to be fixed before merging. The two follow-ups are both about the messages a Python user reads. The new Rust code does not use unsafe, has no panic sites, and holds no lock across an .await. The no-request transport keeps stack-auth's http feature off, as the PR description says. The one gap is that cargo clippy and cargo test were not run, because this environment has no Rust toolchain.
| Severity | Finding | Location |
|---|---|---|
| Fix in a follow-up | Refusal::of joins the message and the help with a space only, so the text reads as one broken sentence. This now applies to plan refusals too. |
languages/python/crates/core/src/refusal.rs:24 |
| Fix in a follow-up | When a profile exists but its token has no region or client id, the user sees "no credentials were found" | languages/python/crates/core/src/credentials.rs:47 |
| Optional | The default message is cloned on every refusal, even when a mapped message replaces it | languages/python/crates/core/src/credentials.rs:37 |
test-gap — claude (claude-opus-5-5)
Two coverage gaps should be fixed before merging: repr on a real SQLAlchemy instance with a column that is not loaded, and a Cipher built from a valid stash auth login profile. Both are the main promises of this PR, and today only a stand-in or the refusal path is tested. The other four comments are optional and can wait.
The tests that exist are clear and follow the repo's pytest style. The sketches below use the same helpers (declared, refusal, the credentials fixture, SECRET). I could not run the sketches here, because no .venv is built in this checkout.
Not repeated here: the existing review thread on hand_written and reprlib.recursive_repr already asks for a test of a hand-written @reprlib.recursive_repr() __repr__.
Out of scope, noted only: hand_written returns False for a method whose __module__ differs from the class's module. So a __repr__ defined in a helper module and assigned in the class body (__repr__ = helpers.show) is replaced with no error. This is the same kind of misclassification as the recursive_repr thread.
infracode — codex (gpt-5.6-terra)
This pull request contains no infrastructure-as-code changes. No infrastructure review findings.
python — codex (gpt-5.6-terra)
Redaction must avoid custom attribute access before merging. The remaining issue can wait only after this access is made storage-only.
| Severity | Finding | Location |
|---|---|---|
| request-changes | Reading __dict__ can run user code and trigger I/O. |
languages/python/python/stackencrypt/_redact.py:122 |
Focused tests were not run because this checkout has no Python or pytest executable.
rust — codex (gpt-5.6-terra)
I found no Rust issues that require changes before merging. The Rust credential detection builds a no-request transport and only performs local credential and profile validation.
| Severity | Findings |
|---|---|
| — | None |
Other findings not posted as comments
languages/python/crates/core/src/credentials.rs:56— Optional: the Python messages forInvalidCrn,RegionandInvalidAccessKeyare not checked by any test.languages/python/crates/core/src/credentials.rs:29— Change before merge: no test builds aCipherfrom a validstash auth loginprofile.languages/python/tests/test_cipher.py:151— Optional: this test is named "names the name", but it does not check which field is named.languages/python/python/stackencrypt/_redact.py:122— Change before merge:getattr(instance, "__dict__")runs a custom__getattribute__method.
How this review was made
| Agent | Model | Review type | Result |
|---|---|---|---|
| claude | claude-opus-5-5 | test-gap | 6 found, 3 posted |
| claude | claude-opus-5-5 | rust | 3 found, 3 posted |
| claude | claude-opus-5-5 | python | 2 found, 2 posted |
| claude | claude-opus-5-5 | infracode | 0 found, 0 posted |
| codex | gpt-5.6-terra | test-gap | failed |
| codex | gpt-5.6-terra | rust | 0 found, 0 posted |
| codex | gpt-5.6-terra | python | 1 found, 0 posted |
| codex | gpt-5.6-terra | infracode | 0 found, 0 posted |
Plain language: the reader test did not run; only the word-and-shape check was applied.
Stack: position 7 of 15 (#1405, #1406, #1414, #1415, #1433, #1437, *️⃣ #1453, #1499, #1509, #1510, #1511, #1513, #1516, #1517, #1582). *️⃣ marks this pull request.
Context loaded: the description, 2 linked issue(s) and 4 discussion entries.
Summary
A declared class no longer prints plaintext:
repr,strand rich's__rich_repr__show<sealed>for every sealed field.Cipher(User)now exists. It checks that the class is declared and unchanged, then finds the credentials the way every SDK does, with no request.encrypt,decryptandquerycome in later tickets.Changes
Redaction (
_redact.py).@stackencrypt.modelreplaces__repr__,__str__and__rich_repr__. A passthrough field shows its value, a sealed field shows<sealed>, and anIgnorefield is left out. A value is read from the instance's__dict__or a storage slot only, so a SQLAlchemy column that is not loaded runs no SELECT. A generated method is replaced. A hand-written one is refused withdeclaration_repr_present, unless the decorator is givenredact=False.Cipher(_cipher.py).Cipher(cls)refuses an undeclared class, a subclass of a declared class, or a non-class withdeclaration_not_decorated. A class changed after decoration isdeclaration_class_changed, with reasonmethods,fieldsorconstructor. Then it calls the engine for the credentials. A refusal is anAuthenticationErrorwith the engine's code, such asstack_auth_not_authenticated.Credentials (
crates/core/src/credentials.rs). stack-auth'sAutoStrategyreadsCS_CLIENT_ACCESS_KEYandCS_WORKSPACE_CRN, then the profilestash auth loginwrites. It is given a transport that sends nothing, so no HTTP client is built and thehttpfeature stays off. Each refusal names the variable or file to fix.Placeholder. Python prints
<sealed>, not Go's[sealed].[sealed]is a valid tag in rich's markup, sorich.print(repr(user))would show the field as empty. The ADR and the spec record it.Verification
NamedTupleand SQLAlchemy.cargo test --locked, clippy,cargo fmt --check, mypy strict, and pyright are clean.pnpm run test:scripts: 83 of 83 files pass.Related
Closes #1388. Part of #1341.
Review notes
_redact.hand_writtenand_value, thencheck_unchangedin_declaration.py.dataclasses.asdict,model_dumpandvarsstill return plaintext, by design.Ignorefield underAnnotatedloads as one object, not a list. That is a spec problem, noted on Python SDK: core call over psycopg, on EQL v4 and Stack Encrypt #1341.🤖 Generated with Claude Code