personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

fix(tests): close the Oura egress guard's module-scope escape hatch

The guard allowlisted (oura_auth, <module>, module_network_import) and then
treated that single entry as permission for any module-scope capability except
dynamic_import. Because the check fell back to that broad rule, a future
module-level `import subprocess`, `import socket`, `import ssl`, `import
smtplib`, or an `os.system(...)` call in oura_auth.py would have been reported
as allowed — defeating the guard's stated contract of allowlisting by module,
function, and capability, and defeating its requirement to fail on sockets, TLS
primitives, mail clients, and subprocess escape hatches.

_allowed is now an exact (module, owner, capability) membership test with no
fallback, and the broad entry is replaced by exactly the three capabilities
oura_auth.py genuinely needs at module scope, matching its imports:
urllib.request (http_client), webbrowser (browser_open), and http.server
(loopback_http_server). urllib.parse remains covered by SAFE_IMPORTS. oura.py's
lazy-import discipline is unchanged.

Detection also missed several transports the boundary is supposed to cover, so
urllib3 and pycurl are now recognized as HTTP clients and imaplib and poplib as
mail clients.

The synthetic self-test — which exists to prove the guard can actually fail —
now feeds module-scope subprocess imports and calls, os.system, socket, smtplib,
and urllib3 through the harness and asserts each is reported as a violation.

Also corrects two design-doc statements that no longer matched the code: the
Oura sync backend is registered and implements save-mode sync rather than
raising as an unregistered skeleton, and oura.py keeps a lazy-import discipline
with egress confined to the allowlisted transport rather than having no network
imports at all.

+48 -17
+2 -2
docs/design/oura-import.md
··· 172 172 173 173 ## 5. (d) Sync design 174 174 175 - **Backend.** `OuraSyncBackend` (skeleton class exists; `sync()` raises with a pointer here). Registered in `SYNCABLE_REGISTRY` **only at phase O3** — the skeleton deliberately leaves it unregistered so no runtime flow (CLI `--sync`, export tooling) can reach a half-built path; a test pins that until O3 flips both together. 175 + **Backend.** `OuraSyncBackend` is registered in `SYNCABLE_REGISTRY["oura"]` and implements save-mode sync. Save runs validate the pre-save gate before taking the per-journal import lock, recheck the gate inside the lock, then fetch and persist only after the gate and lock both hold. Cursor-only quiet runs intentionally advance `imports/oura.json` without creating an import bundle. 176 176 177 177 **Cursor state** at `imports/oura.json` via `sync.load_sync_state`/`save_sync_state`: 178 178 ··· 415 415 416 416 ## 10. What landed with this doc (phase O0 inventory) 417 417 418 - - `solstone/think/importers/oura.py` — parse layer (`parse_oura_bundle`, `parse_endpoint_document`, `parse_oura_day`), normalizer (`normalize_bundle` → rows + `HealthDedupeRecord`s via `health_schema`), §13 copy reference (`render_day_summary`), `OuraImporter` (detect/preview/dry-run live; save gated then seamed), `OuraSyncBackend` + OAuth seams (all raise, pointing here). Zero network imports, test-enforced. 418 + - `solstone/think/importers/oura.py` — parse layer (`parse_oura_bundle`, `parse_endpoint_document`, `parse_oura_day`), normalizer (`normalize_bundle` → rows + `HealthDedupeRecord`s via `health_schema`), §13 copy reference (`render_day_summary`), `OuraImporter` (detect/preview/dry-run live; save gated then seamed), `OuraSyncBackend` + OAuth seams. Network egress follows a lazy-import discipline: no module-level network imports, with live egress confined to the allowlisted transport path enforced by tests. 419 419 - `solstone/think/importers/health_schema.py` — `SOURCE_OURA_API`, `KNOWN_SOURCE_FAMILIES` entry, friendly names for the seven `oura.*` record types. 420 420 - `solstone/think/importers/pre_save_gate.py` — `"oura"` joins `SENSITIVE_IMPORTERS`. 421 421 - `solstone/think/importers/file_importer.py` — registry entry (preview/dry-run-only paths active).
+46 -15
tests/test_oura_egress_guard.py
··· 13 13 ALLOWED_EGRESS: frozenset[tuple[str, str, str]] = frozenset( 14 14 { 15 15 ("oura", "_default_transport", "http_client"), 16 - ("oura_auth", "<module>", "module_network_import"), 16 + ("oura_auth", "<module>", "browser_open"), 17 + ("oura_auth", "<module>", "http_client"), 18 + ("oura_auth", "<module>", "loopback_http_server"), 17 19 ("oura_auth", "_default_http_transport", "http_client"), 18 20 ("oura_auth", "_post_token_request", "http_client"), 19 21 ("oura_auth", "_authorization_url", "authorization_url"), ··· 32 34 "http.client": "http_client", 33 35 "http.server": "loopback_http_server", 34 36 "httpx": "http_client", 37 + "imaplib": "mail_client", 35 38 "importlib": "dynamic_import", 39 + "poplib": "mail_client", 40 + "pycurl": "http_client", 36 41 "requests": "http_client", 37 42 "smtplib": "mail_client", 38 43 "socket": "socket", ··· 41 46 "urllib": "http_client", 42 47 "urllib.error": "http_client", 43 48 "urllib.request": "http_client", 49 + "urllib3": "http_client", 44 50 "webbrowser": "browser_open", 45 51 } 46 52 ··· 52 58 "http.server.BaseHTTPRequestHandler": "loopback_http_server", 53 59 "http.server.HTTPServer": "loopback_http_server", 54 60 "httpx": "http_client", 61 + "imaplib": "mail_client", 55 62 "importlib": "dynamic_import", 56 63 "os.popen": "process_escape", 57 64 "os.system": "process_escape", 65 + "poplib": "mail_client", 66 + "pycurl": "http_client", 58 67 "requests": "http_client", 59 68 "smtplib": "mail_client", 60 69 "socket": "socket", 61 70 "ssl": "tls", 62 71 "subprocess": "process_escape", 63 72 "urllib.request": "http_client", 73 + "urllib3": "http_client", 64 74 "webbrowser.open": "browser_open", 65 75 } 66 76 ··· 155 165 156 166 157 167 def _allowed(module: str, owner: str, capability: str) -> bool: 158 - if (module, owner, capability) in ALLOWED_EGRESS: 159 - return True 160 - return ( 161 - owner == "<module>" 162 - and capability != "dynamic_import" 163 - and (module, owner, "module_network_import") in ALLOWED_EGRESS 164 - ) 168 + return (module, owner, capability) in ALLOWED_EGRESS 165 169 166 170 167 171 def _violation( ··· 256 260 257 261 def test_oura_egress_guard_reports_synthetic_violation() -> None: 258 262 source = """ 263 + import os 264 + import smtplib 259 265 import socket 266 + import subprocess 267 + import urllib3 260 268 261 - def added_escape_hatch(): 262 - import subprocess 263 - socket.socket() 264 - __import__("ssl") 265 - subprocess.run(["true"]) 269 + socket.socket() 270 + smtplib.SMTP("localhost") 271 + subprocess.run(["true"]) 272 + os.system("true") 273 + urllib3.PoolManager() 274 + __import__("ssl") 266 275 """ 267 276 268 277 violations = _egress_violations("oura_future", source) 269 278 270 - assert any("socket" in violation for violation in violations) 279 + assert any( 280 + "oura_future.<module>: process_escape: import subprocess" in violation 281 + for violation in violations 282 + ) 283 + assert any( 284 + "oura_future.<module>: process_escape: call subprocess.run" in violation 285 + for violation in violations 286 + ) 287 + assert any( 288 + "oura_future.<module>: process_escape: call os.system" in violation 289 + for violation in violations 290 + ) 291 + assert any( 292 + "oura_future.<module>: socket: import socket" in violation 293 + for violation in violations 294 + ) 295 + assert any( 296 + "oura_future.<module>: mail_client: import smtplib" in violation 297 + for violation in violations 298 + ) 299 + assert any( 300 + "oura_future.<module>: http_client: import urllib3" in violation 301 + for violation in violations 302 + ) 271 303 assert any("dynamic_import" in violation for violation in violations) 272 - assert any("process_escape" in violation for violation in violations)