Labels: Technical-Track · security · good-first-issue · v0.5.2
Priority: Medium — Known security gap, ~1h fix
Estimated effort: ~30–50 lines of Python
Skill level required: Beginner-Intermediate Python, basic understanding of memory security
The problem
After SensorCertificationAuthority.full_handshake() completes (implemented in v0.5.1, item J3.2), the derived X25519 session key is returned as a plain Python bytes object:
# src/keros/sensor_channel.py — current behavior
session_key: bytes = hkdf_derive(shared_secret, salt, info, length=32)
return session_key # ← lives on CPython heap, NOT mlock'd
A Python bytes object on the CPython heap is not covered by mlock(2). This means:
- The OS can swap it to disk during memory pressure
- A memory forensics tool could extract it from a swap partition
- The key may persist in memory after the session ends (GC is non-deterministic)
This is documented in the v0.5.1 backlog as [OI-ECDH-persistence]:
"After full_handshake(), the derived session_key is a Python bytes object on the CPython heap. mlock does not cover it without wrapping in SecureKeyBuffer. To fully close: wrap session_key in SecureKeyBuffer and pass as export_bytearray() to callers. Estimate: ~1h."
The fix
SecureKeyBuffer already exists in src/sal/state_buffer_secure.py. It provides:
mlock(2) pinning (prevents swap)
ctypes volatile zeroing on __exit__ (deterministic destruction)
- Constant-time comparison via
hmac.compare_digest
export_bytearray() for use with hmac module
The fix is to use it in sensor_channel.py:
# BEFORE (current — insecure)
def full_handshake(self, ...):
...
session_key: bytes = hkdf_derive(...)
return {"session_key": session_key, "status": "ok"}
# AFTER (target)
from src.sal.state_buffer_secure import SecureKeyBuffer
def full_handshake(self, ...):
...
raw_key: bytes = hkdf_derive(...)
secure_key = SecureKeyBuffer(raw_key)
# caller is responsible for secure_key.__exit__() when session ends
return {"session_key": secure_key, "status": "ok"}
All callers that currently use session_key as bytes for HMAC operations must be updated to use secure_key.export_bytearray():
# BEFORE
hmac.new(session_key, data, hashlib.sha256).digest()
# AFTER
with secure_key.export_bytearray() as key_bytes:
hmac.new(bytes(key_bytes), data, hashlib.sha256).digest()
Files to modify
| File |
Change |
src/keros/sensor_channel.py |
Wrap HKDF output in SecureKeyBuffer in perform_ecdh_handshake() and full_handshake() |
src/sal/cognitive_shield_v2.py |
Update any caller that unpacks result["session_key"] as bytes |
tests/ (SAL vectors) |
Update test vectors that assert isinstance(session_key, bytes) — should now assert isinstance(session_key, SecureKeyBuffer) |
Constraints
SecureKeyBuffer.__exit__() zeroes the key — ensure the buffer's lifetime covers all HMAC operations in the session
- The existing 92 SAL test vectors and 70 governance tests must still pass
- All tests must pass with
python -W error (zero warnings)
- If
mlock is unavailable (non-Linux or no permissions), SecureKeyBuffer gracefully degrades — this behavior is already implemented and must be preserved
How to verify the fix
After your change, run:
python src/sal/cognitive_shield_v2.py --test # 92 vectors, 0 failures
python tests/test_cognitive_neutrality.py # 70 tests, 0 failures
python -m src.keros.sensor_channel # 7 tests, 0 failures
And verify that session_key is no longer a plain bytes in the return value of full_handshake().
Resources
src/sal/state_buffer_secure.py — SecureKeyBuffer implementation (lines ~6379–6450)
src/keros/sensor_channel.py — perform_ecdh_handshake(), full_handshake() (lines ~8986+)
docs/security/SECURITY.md §3 — Anatomical Privacy and memory zeroing policy
- Python
hmac.compare_digest docs for constant-time comparison
Acceptance criteria
This is an ideal first security contribution — well-scoped, with a clear fix path and existing infrastructure to build on.
Questions? Comment here with tag [Technical-Track].
Labels:
Technical-Track·security·good-first-issue·v0.5.2Priority: Medium — Known security gap, ~1h fix
Estimated effort: ~30–50 lines of Python
Skill level required: Beginner-Intermediate Python, basic understanding of memory security
The problem
After
SensorCertificationAuthority.full_handshake()completes (implemented in v0.5.1, item J3.2), the derived X25519 session key is returned as a plain Pythonbytesobject:A Python
bytesobject on the CPython heap is not covered bymlock(2). This means:This is documented in the v0.5.1 backlog as
[OI-ECDH-persistence]:The fix
SecureKeyBufferalready exists insrc/sal/state_buffer_secure.py. It provides:mlock(2)pinning (prevents swap)ctypesvolatile zeroing on__exit__(deterministic destruction)hmac.compare_digestexport_bytearray()for use withhmacmoduleThe fix is to use it in
sensor_channel.py:All callers that currently use
session_keyasbytesfor HMAC operations must be updated to usesecure_key.export_bytearray():Files to modify
src/keros/sensor_channel.pySecureKeyBufferinperform_ecdh_handshake()andfull_handshake()src/sal/cognitive_shield_v2.pyresult["session_key"]asbytestests/(SAL vectors)isinstance(session_key, bytes)— should now assertisinstance(session_key, SecureKeyBuffer)Constraints
SecureKeyBuffer.__exit__()zeroes the key — ensure the buffer's lifetime covers all HMAC operations in the sessionpython -W error(zero warnings)mlockis unavailable (non-Linux or no permissions),SecureKeyBuffergracefully degrades — this behavior is already implemented and must be preservedHow to verify the fix
After your change, run:
And verify that
session_keyis no longer a plainbytesin the return value offull_handshake().Resources
src/sal/state_buffer_secure.py—SecureKeyBufferimplementation (lines ~6379–6450)src/keros/sensor_channel.py—perform_ecdh_handshake(),full_handshake()(lines ~8986+)docs/security/SECURITY.md §3— Anatomical Privacy and memory zeroing policyhmac.compare_digestdocs for constant-time comparisonAcceptance criteria
full_handshake()returnsSecureKeyBuffernotbytesforsession_keyexport_bytearray()— no plainbytesextractionSecureKeyBuffer.__exit__()is called at session end (verify in test)python -W errorOPEN_ISSUES.mdentry[OI-ECDH-persistence]updated toCLOSED — v0.5.2