fix: review follow-ups from #4 — proxy credential strip, Vary append, ledger hardening#5
Conversation
…0600 + corruption tolerance Addresses the four real findings from PR #4's review threads (3562990747, 3562990748, 3562990752, 3562990757). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jkvh4uxSi6AsBJYPjiurze
PR Summary by QodoFix proxy credential leakage, preserve Vary, and harden profile ledger parsing/perms
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
9 rules 1. Ledger chmod can crash
|
| path = ledger_path() | ||
| created = not path.exists() | ||
| with path.open("a", encoding="utf-8") as handle: | ||
| handle.write(json.dumps(row, ensure_ascii=False)) | ||
| handle.write("\n") | ||
| if created: | ||
| os.chmod(path, _LEDGER_MODE) |
There was a problem hiding this comment.
1. Ledger chmod can crash 🐞 Bug ☼ Reliability
append_ledger() calls os.chmod() without handling OSError, so the first ledger write can raise and break learn record/sync on filesystems that reject chmod (unlike auth.json, which is best-effort).
Agent Prompt
### Issue description
`learn.profile._ledger.append_ledger()` calls `os.chmod()` unguarded. On platforms/filesystems that reject chmod, this can raise `OSError` and fail the command even though the ledger write itself succeeded.
### Issue Context
`learn.profile._auth.save_auth()` already treats chmod as best-effort (it catches `OSError` and continues), with a comment noting some environments reject chmod.
### Fix Focus Areas
- learn/profile/_ledger.py[31-41]
- learn/profile/_auth.py[76-85]
### Suggested fix
- Wrap the ledger chmod in `try: ... except OSError: pass` (mirroring `save_auth`).
- (Optional) If you want the test to remain strict while still being portable, conditionally skip/relax `test_ledger_file_is_created_private` when chmod is unsupported (platform/filesystem-specific).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| path = ledger_path() | ||
| created = not path.exists() | ||
| with path.open("a", encoding="utf-8") as handle: | ||
| handle.write(json.dumps(row, ensure_ascii=False)) | ||
| handle.write("\n") | ||
| if created: | ||
| os.chmod(path, _LEDGER_MODE) |
There was a problem hiding this comment.
2. Ledger mode not enforced 🐞 Bug ⛨ Security
append_ledger() only applies 0600 when the ledger file is newly created, so upgrading to this version will not harden an existing permissive ledger.jsonl and learner data may remain readable by other users.
Agent Prompt
### Issue description
The new `0600` hardening is only applied when the file does not exist prior to append. If an existing `ledger.jsonl` was created with broader permissions (older versions, manual copy, different umask), this change will not correct it.
### Issue Context
The module docstring states the ledger is learner data and should be private (0600), matching the intent for `auth.json`.
### Fix Focus Areas
- learn/profile/_ledger.py[12-16]
- learn/profile/_ledger.py[31-41]
### Suggested fix
- After writing (or before writing), check `path.is_file()` and ensure the mode is `0600`; if not, attempt to `chmod` it (best-effort, catching `OSError`).
- Alternatively, run `chmod` unconditionally after every append (still best-effort), which also covers upgrades/migrations.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| try: | ||
| parsed = json.loads(stripped) | ||
| except json.JSONDecodeError: | ||
| continue | ||
| if isinstance(parsed, dict): | ||
| rows.append(parsed) |
There was a problem hiding this comment.
3. Sync misses repaired row 🐞 Bug ≡ Correctness
Because read_ledger() now drops malformed/non-dict lines, SyncState.cursor (a count over the filtered list) no longer maps to a stable file position; if a previously malformed line is manually repaired into a valid dict later, push_pending() can treat it as already pushed and never upload it.
Agent Prompt
### Issue description
`read_ledger()` now filters out invalid lines. Sync uses `rows = read_ledger()` and slices by `SyncState.cursor`, which means the cursor is tied to the *filtered* list index, not a physical file line number. If an invalid line is later edited into a valid dict, it can be inserted before the cursor index and skipped forever by sync.
### Issue Context
The ledger docstring mentions manual edits as a source of corruption; manual edits are also the most likely way a corrupted line could be repaired.
### Fix Focus Areas
- learn/profile/_ledger.py[43-64]
- learn/profile/_sync.py[58-66]
- learn/profile/_syncstate.py[1-10]
### Suggested fix options
- Make the sync cursor track physical positions (e.g., file line count or byte offset) rather than `len(read_ledger())`.
- Or, explicitly document/enforce that malformed lines are permanently ignored for sync unless the user resets `sync.json` (and optionally surface a warning when corruption is detected).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



What
The four real findings from #4's review threads, fixed with regression tests (user-approved follow-up-PR lane):
/learnzone-mount proxy no longer forwardsCookie/Authorizationto the Pages origin — learner sessions stay with the API.withCorsappendsOriginto an existingVaryinstead of overwriting it.ledger.jsonlis created0600, same asauth.json.progress/next/sync.Worker tests 37 → 39; Python suite 353 → 355. Version 0.5.0 → 0.5.1.
The other three threads (agentfront runtime dep, parallel App registry, json defaults) are deliberate/defused — pushback replies on the threads with evidence.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Jkvh4uxSi6AsBJYPjiurze