secure-enclave-unlocker #24
Reference in New Issue
Block a user
Delete Branch "secure-enclave-unlocker"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Code Review: PR #24 — secure-enclave-unlocker
Verdict: ❌ FAIL
Policy Divergences
1.
internal/secret/seunlocker_stub.go:panic()in library codeEvery method in this file panics instead of returning an error. This violates AGENTS.md Go-Specific Guideline #1: "No
panic,log.Fatal, oros.Exitin library code. Always propagate errors via return values."The existing
keychainunlocker_stub.gocorrectly returns errors for all methods and constructs a valid (but inert) struct fromNewKeychainUnlocker. The SE stub should follow this exact same pattern.CreateSecureEnclaveUnlockeralready has an error return — use it. The interface methods (GetIdentity,Remove) also have error returns.GetType,GetDirectory,GetID,GetMetadatacan return sentinel values.Files:
internal/secret/seunlocker_stub.golines 29-63, all methods.2.
internal/secret/seunlocker_darwin.go:280: Hardcoded derivation index 0getLongTermKeyForSEuses hardcoded derivation index0when deriving from the mnemonic env var. Compare withvault.go:GetOrDeriveLongTermKey()which correctly loadsmetadata.DerivationIndexfrom vault metadata. If the vault uses a derivation index other than 0, this will encrypt the wrong key into the SE unlocker, silently corrupting it. This is a correctness bug.3. Missing tests for new exported types in
secretpackageThe PR adds these new exports to
internal/secret/with zero test coverage:SecureEnclaveUnlocker(type)SecureEnclaveUnlockerMetadata(type)NewSecureEnclaveUnlocker(constructor)CreateSecureEnclaveUnlocker(function)The
macsepackage has good tests (macse_test.go), but thesecretpackage SE unlocker code has none. The existingkeychainunlocker_test.godemonstrates the expected pattern. At minimum,NewSecureEnclaveUnlockershould be tested (instantiation, getter methods) andCreateSecureEnclaveUnlockershould have a happy-path test (darwin-only, with SE availability skip guard).4. README inconsistency
The README has not been updated to reflect this implementation:
unlocker addtypes list doesn't includesecure-enclavesecret unlocker add secure-enclaveThe implementation uses
sc_auth/ CryptoTokenKit which bypasses the entitlement requirement, so the README's claim about needing Apple Developer Program membership is now inaccurate.Requirements Checklist
macsepackagesecretpackage SE unlockerBuild Result
docker build .— ✅ PASS (4.2s)Additional Notes
vault.gorefactoring (extractingunlockLongTermKey) is clean and the type-dispatch approach for SE vs. standard unlockers is reasonable.macseCGo bindings and Objective-C implementation look well-structured with proper memory management (@autoreleasepool,CFRelease).macse_stub.gocorrectly returns errors (not panics) — this is the right pattern.sc_auth list-ctk-identitiesoutput (secure_enclave.m lines 161-179) is fragile — it depends on the output format ofsc_auth. Consider a comment noting this.Summary
Four issues require resolution: (1) stub panics must be replaced with error returns per existing codebase pattern, (2) derivation index bug must be fixed, (3) tests needed for
secretpackage exports, (4) README must be updated.@clawbot pls rework
Rework agent dispatched to address review findings: (1) stub panics → error returns, (2) hardcoded derivation index fix, (3) missing tests for secret package SE exports, (4) README update.
b9aaf8ddactocc53469f90Rework Complete
All four review findings have been addressed:
1. Stub panics → error returns
internal/secret/seunlocker_stub.gonow follows thekeychainunlocker_stub.gopattern exactly:errSENotSupportedused forGetIdentity(),Remove(), andCreateSecureEnclaveUnlocker()GetType()returns"secure-enclave",GetMetadata()returns stored metadata,GetDirectory()returns stored directory,GetID()returns formatted stringNewSecureEnclaveUnlocker()returns a valid (inert) struct instance2. Hardcoded derivation index 0 → vault metadata
getLongTermKeyForSE()now readsvault-metadata.jsonto getmetadata.DerivationIndex, matching the pattern ingetLongTermPrivateKey()fromkeychainunlocker.go. This prevents silent key corruption when vaults use non-zero derivation indices.3. Tests for
secretpackage SE exportsAdded two test files:
seunlocker_stub_test.go(non-darwin): testsNewSecureEnclaveUnlockerinstantiation/getters, error returns fromGetIdentity/Remove/CreateSecureEnclaveUnlocker, and interface complianceseunlocker_test.go(darwin): tests instantiation, interface compliance,GetIDformat,generateSEKeyLabel, andGetIdentitywith missing encrypted key file4. README updated
sc_auth/ CryptoTokenKit)secure-enclaveto unlocker type list in CLI referencesecret unlocker add secure-enclaveto Advanced Authentication examplesdocker build .✅ PASSCode Review: PR #24 — secure-enclave-unlocker (post-rework)
Verdict: ✅ PASS
Previous Review Findings — Resolution Status
All four findings from the initial review have been properly addressed:
seunlocker_stub.gonow uses sentinelerrSENotSupported, matchingkeychainunlocker_stub.goexactlygetLongTermKeyForSE()readsvault-metadata.jsonformetadata.DerivationIndex, matchingkeychainunlocker.gopatternsecretpackage SE exportsseunlocker_stub_test.go(non-darwin) andseunlocker_test.go(darwin), covering constructor, getters, interface compliance, error pathsPolicy Divergences
No policy violations found in the changed files.
panic,log.Fatal, oros.Exitin library code ✅fmt.Errorf("context: %w", err)throughout ✅.golangci.yml,Makefile, or CI config ✅go.mod/go.sum(no new dependencies) ✅seKeyLabelPrefix,seUnlockerType,seLongtermFilename,p256UncompressedKeySize, etc.) ✅Requirements Checklist
unlockLongTermKeyrefactor)macsepackage (darwin)secretpackage SE unlocker (both platforms)Build Result
docker build .— ✅ PASS (4.3s)Rebase onto
main— ✅ Clean (already up to date)Code Quality Notes
vault.gorefactoring (unlockLongTermKeyextraction) is clean and the type-dispatch for SE vs. standard unlockers is architecturally sound.memguard.LockedBufferused for key material,decryptedDatazeroed immediately after parsing inGetIdentity().@autoreleasepoolblocks andCFReleasecalls — no memory leaks.GetID()omits hostname (returnstimestamp-secure-enclavevs darwin'stimestamp-hostname-secure-enclave) — this matches the existingKeychainUnlockerstub pattern exactly.sc_auth list-ctk-identitiesoutput parsing insecure_enclave.mis fragile (depends on whitespace-tokenized format) — previously noted, not blocking.