Skip to content

fix: 2026-07-02 audit remediation — date-safe redaction, DR incomplete flags, symlink-safe installs - #21

Merged
robotlearning123 merged 8 commits into
mainfrom
fix/audit-2026-07-02
Jul 3, 2026
Merged

fix: 2026-07-02 audit remediation — date-safe redaction, DR incomplete flags, symlink-safe installs#21
robotlearning123 merged 8 commits into
mainfrom
fix/audit-2026-07-02

Conversation

@robotlearning123

@robotlearning123 robotlearning123 commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Five audit fixes from the 2026-07-01 known-remaining list, plus the two P2 test gaps from the 2026-07-02 cx review of PRs #18/#20.

  • redact: _PHONE_RE matched calendar dates — "2026-05-26" came back as <PHONE> in memories, tasks, and conversation titles. Phone matches that start with a date shape are now left alone; real phone shapes still mask.
  • DR: deep_research / deep_research_heavy dropped the terminated_abnormally / timeout done-flags, so a truncated report was indistinguishable from a complete one. Both now append an explicit "⚠ Report may be incomplete" note.
  • install: _atomic_write rename()d over symlinked configs, replacing the link with a plain file and stranding the dotfile-repo target. Now resolves and writes through.
  • setup: write_mcp_config blindly write_text'd ~/.gpt2agent/config.toml. Now backs up (.bak-gpt2agent), writes atomically, and no-ops on identical content.
  • auth: removed the browser-use fallback that saved a NextAuth session cookie as access_token — that token 401s on every API call; extraction now fails visibly.

Tests

  • +17 tests: tests/test_audit_2026_07_02.py (14) + cx-review P2 gap coverage in test_install.py (combined commented header + commented child subtable) and test_none_guards.py (conversation/file/task None-contract).
  • Local: 163 passed, 9 skipped; ruff check gpt2agent tests clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_018FAemSFrVynaESb72jVGwP

Summary by CodeRabbit

  • Bug Fixes

    • Preserved date strings when redacting phone numbers, preventing calendar-style text from being altered.
    • Added clearer warnings when long-running research results end early or time out.
    • Improved login token handling by rejecting invalid pasted values and removing a broken fallback that could save unusable tokens.
    • Fixed config updates so changes write to the intended file, back up existing settings, and avoid unnecessary rewrites.
  • Tests

    • Expanded regression coverage for redaction, config writing, token validation, and safer empty-result handling.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR fixes date-swallowing phone redaction, adds incomplete-report markers to Deep Research tools on abnormal termination/timeout, resolves symlinks before atomic writes, backs up and atomically rewrites MCP config, and removes the invalid session-cookie fallback in browser auth, plus adds regression tests and minor TOML/None-guard tests.

Changes

Audit fixes: redaction, DR completeness, config writes, and auth

Layer / File(s) Summary
Changelog entries
CHANGELOG.md
Documents the fixes for redaction, DR incompleteness, symlink/atomic writes, and auth token validation.
Phone redaction preserves dates
gpt2agent/tools/_redact.py, tests/test_audit_2026_07_02.py
Adds date-prefix detection and _phone_repl to avoid swallowing calendar dates during phone redaction, with regression tests.
Deep Research incomplete-report marker
gpt2agent/server.py, tests/test_audit_2026_07_02.py
Adds _dr_incomplete_note and tracks truncated/timed_out in deep_research/deep_research_heavy to append a warning when the stream ends abnormally or times out.
Symlink-safe atomic writes
gpt2agent/install.py, tests/test_audit_2026_07_02.py
_atomic_write resolves symlinked target paths before rename to avoid replacing the symlink itself.
MCP config backup and atomic write
gpt2agent/setup.py, tests/test_audit_2026_07_02.py
write_mcp_config skips no-op writes, backs up existing config, and writes atomically instead of overwriting directly.
Stricter browser auth token validation
gpt2agent/auth.py, tests/test_audit_2026_07_02.py
Rejects non-JWT-shaped pasted tokens and removes cookie-based fallback extraction in the automatic flow.
TOML editor and None-guard tests
tests/test_install.py, tests/test_none_guards.py
Adds a TOML section editor test for commented headers/children and None-guard tests for conversation/file read tools.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant DeepResearchTool
  participant SSEStream

  Client->>DeepResearchTool: call deep_research/deep_research_heavy
  DeepResearchTool->>SSEStream: consume SSE events
  SSEStream-->>DeepResearchTool: done event (terminated_abnormally/timeout)
  DeepResearchTool->>DeepResearchTool: set truncated, timed_out
  DeepResearchTool->>DeepResearchTool: append citations/sources
  alt truncated
    DeepResearchTool->>DeepResearchTool: append _dr_incomplete_note(timed_out)
  end
  DeepResearchTool-->>Client: final response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main fixes: date-safe redaction, incomplete DR flags, and symlink-safe installs.
Description check ✅ Passed The description covers the change summary and test results, though it omits the template's explicit Type and Checklist sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/audit-2026-07-02

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e611c29d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread gpt2agent/tools/_redact.py Outdated

def _phone_repl(m: re.Match) -> str:
text = m.group(0)
return text if _DATE_PREFIX_RE.match(text) else "<PHONE>"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Redact phone numbers that follow preserved dates

When a date and phone number are adjacent in one field, e.g. 2026-05-26 617-555-0123, _PHONE_RE greedily matches the entire digit/space/hyphen run. This branch then returns the whole match because it starts with a date, leaving the phone number unmasked in memories/tasks/conversation titles. Preserve only the date portion or continue redacting the remainder instead of returning the full match.

Useful? React with 👍 / 👎.

cx (GPT-5.5) review P1: _phone_repl returned the entire _PHONE_RE match
unchanged when it started with a date, so 'appt 2026-05-26 617-555-0123'
leaked the phone. Now only the date prefix is preserved and the remainder
of the match is re-scanned.

Also cx P2: the manual _from_browser fallback no longer suggests pasting
the __Secure-next-auth.session-token cookie and rejects non-3-segment-JWT
pastes instead of saving a value that can only 401.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GsBLZ8EE6o9x4KtJ5WhHbX
@robotlearning123
robotlearning123 merged commit f8b737c into main Jul 3, 2026
9 of 10 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
gpt2agent/setup.py (1)

162-168: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Backup location can diverge from real target if MCP_CONFIG_PATH is ever a symlink.

_backup writes .bak-gpt2agent next to MCP_CONFIG_PATH itself, while _atomic_write resolves symlinks and writes to the real target's directory. Since MCP_CONFIG_PATH is a fixed, non-symlinked path in practice, this is unlikely to matter today.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gpt2agent/setup.py` around lines 162 - 168, The backup path handling in
_backup can diverge from the actual config target if MCP_CONFIG_PATH is a
symlink, since _atomic_write resolves the real destination but _backup currently
writes next to the path object itself. Update the backup logic used in setup.py
alongside _atomic_write so both operations resolve the same final target
directory before creating .bak-gpt2agent, and keep the existing MCP_CONFIG_PATH
existence/read_text flow unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gpt2agent/server.py`:
- Around line 167-177: The incomplete-note gate in deep_research and
deep_research_heavy only checks terminated_abnormally via truncated, so
timeout-only completions never append the warning. Update the done-event
handling in both paths to treat timed_out as part of the same incomplete
condition, and make the final append decision use the timeout flag as well as
abnormal termination. Verify the logic around the deep_research event loop, the
deep_research_heavy flow, and the final note construction uses the existing
timed_out variable consistently.

In `@gpt2agent/tools/_redact.py`:
- Around line 14-20: _recursive_repl in _phone_repl currently recurses through
date-shaped prefixes, which can overflow the stack on long token chains. Replace
the recursive re-entry with an iterative loop that keeps peeling off the
_DATE_PREFIX_RE prefix and applying _PHONE_RE until no prefix remains, while
preserving the same redaction behavior for redact().

---

Nitpick comments:
In `@gpt2agent/setup.py`:
- Around line 162-168: The backup path handling in _backup can diverge from the
actual config target if MCP_CONFIG_PATH is a symlink, since _atomic_write
resolves the real destination but _backup currently writes next to the path
object itself. Update the backup logic used in setup.py alongside _atomic_write
so both operations resolve the same final target directory before creating
.bak-gpt2agent, and keep the existing MCP_CONFIG_PATH existence/read_text flow
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 245d9624-263f-4f34-9eff-635131d32fa9

📥 Commits

Reviewing files that changed from the base of the PR and between 6f5d5e0 and 333f2a5.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • gpt2agent/auth.py
  • gpt2agent/install.py
  • gpt2agent/server.py
  • gpt2agent/setup.py
  • gpt2agent/tools/_redact.py
  • tests/test_audit_2026_07_02.py
  • tests/test_install.py
  • tests/test_none_guards.py

Comment thread gpt2agent/server.py
Comment on lines +167 to +177
truncated = False
timed_out = False

async for event in conv.deep_research(q):
if event["type"] == "tool":
tool_calls.append(event["call"])
elif event["type"] == "done":
final_text = event["text"]
refs = event.get("content_references", [])
truncated = bool(event.get("terminated_abnormally"))
timed_out = bool(event.get("timeout"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Incomplete note never fires for timeout-only case.

The PR objective is to warn when a report "may be incomplete due to timeout or abnormal termination," but the gate on line 192 (if truncated:) and line 255 only checks terminated_abnormally. If a done event sets timeout: true without terminated_abnormally, timed_out is computed but never consulted for the append decision — the note is silently skipped. Same bug in both deep_research (line 192) and deep_research_heavy (line 255).

This matches the regression tests too: only terminated_abnormally=True is exercised; there's no test asserting the note appears when only timeout is set.

🐛 Proposed fix (apply to both tools)
-                truncated = bool(event.get("terminated_abnormally"))
-                timed_out = bool(event.get("timeout"))
+                timed_out = bool(event.get("timeout"))
+                truncated = bool(event.get("terminated_abnormally")) or timed_out

Also applies to: 192-194, 214-225, 255-257

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gpt2agent/server.py` around lines 167 - 177, The incomplete-note gate in
deep_research and deep_research_heavy only checks terminated_abnormally via
truncated, so timeout-only completions never append the warning. Update the
done-event handling in both paths to treat timed_out as part of the same
incomplete condition, and make the final append decision use the timeout flag as
well as abnormal termination. Verify the logic around the deep_research event
loop, the deep_research_heavy flow, and the final note construction uses the
existing timed_out variable consistently.

Comment on lines +14 to +20
def _phone_repl(m: re.Match) -> str:
text = m.group(0)
dm = _DATE_PREFIX_RE.match(text)
if not dm:
return "<PHONE>"
prefix = dm.group(1)
return prefix + _PHONE_RE.sub(_phone_repl, text[len(prefix):])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Where is redact() invoked, and on what kind of content?
rg -nP '\bredact\s*\(' -g '!tests/**' -C2

Repository: robotlearning123/gpt2agent

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- gpt2agent/tools/_redact.py ---'
cat -n gpt2agent/tools/_redact.py

echo
echo '--- usages of redact / _phone_repl / _DATE_PREFIX_RE / _PHONE_RE ---'
rg -n "_phone_repl|_DATE_PREFIX_RE|_PHONE_RE|\bredact\b" gpt2agent -g '!tests/**' -C 2

Repository: robotlearning123/gpt2agent

Length of output: 12486


🏁 Script executed:

python3 - <<'PY'
import re, sys

_PHONE_RE = re.compile(r"\+?\d[\d ()\-]{8,}\d")
_DATE_PREFIX_RE = re.compile(r"^(\d{4}-\d{2}-\d{2}|\d{1,2}-\d{1,2}-\d{4})(?=$|[^\d])")

calls = 0
max_depth = 0
depth = 0

def _phone_repl(m: re.Match) -> str:
    global calls, max_depth, depth
    calls += 1
    depth += 1
    max_depth = max(max_depth, depth)
    try:
        text = m.group(0)
        dm = _DATE_PREFIX_RE.match(text)
        if not dm:
            return "<PHONE>"
        prefix = dm.group(1)
        return prefix + _PHONE_RE.sub(_phone_repl, text[len(prefix):])
    finally:
        depth -= 1

def redact(s: str) -> str:
    return _PHONE_RE.sub(_phone_repl, s)

print("recursionlimit=", sys.getrecursionlimit())
for n in [1, 2, 3, 10, 50, 100, 200, 500, 900, 1000, 1100]:
    calls = max_depth = depth = 0
    s = " ".join([f"2026-05-{i:02d}" for i in range(1, n+1)])
    try:
        out = redact(s)
        print(n, "ok", "calls=", calls, "max_depth=", max_depth, "len(out)=", len(out))
    except RecursionError as e:
        print(n, "RecursionError", "calls=", calls, "max_depth=", max_depth, type(e).__name__)
        break
PY

Repository: robotlearning123/gpt2agent

Length of output: 683


🏁 Script executed:

python3 - <<'PY'
import re, sys

_PHONE_RE = re.compile(r"\+?\d[\d ()\-]{8,}\d")
_DATE_PREFIX_RE = re.compile(r"^(\d{4}-\d{2}-\d{2}|\d{1,2}-\d{1,2}-\d{4})(?=$|[^\d])")

calls = 0
max_depth = 0
depth = 0

def _phone_repl(m: re.Match) -> str:
    global calls, max_depth, depth
    calls += 1
    depth += 1
    max_depth = max(max_depth, depth)
    try:
        text = m.group(0)
        dm = _DATE_PREFIX_RE.match(text)
        if not dm:
            return "<PHONE>"
        prefix = dm.group(1)
        return prefix + _PHONE_RE.sub(_phone_repl, text[len(prefix):])
    finally:
        depth -= 1

def redact(s: str) -> str:
    return _PHONE_RE.sub(_phone_repl, s)

def valid_dates(n):
    # Cycle within valid month/day ranges to keep every token date-shaped.
    months = [f"{m:02d}" for m in range(1, 13)]
    days = [f"{d:02d}" for d in range(1, 29)]
    out = []
    i = 0
    for _ in range(n):
        out.append(f"2026-{months[i % 12]}-{days[i % 28]}")
        i += 1
    return " ".join(out)

print("recursionlimit=", sys.getrecursionlimit())
for n in [1, 2, 3, 10, 50, 100, 200, 500, 800, 900, 950, 1000, 1050, 1100]:
    calls = max_depth = depth = 0
    s = valid_dates(n)
    try:
        out = redact(s)
        print(n, "ok", "calls=", calls, "max_depth=", max_depth, "len(out)=", len(out))
    except RecursionError as e:
        print(n, "RecursionError", "calls=", calls, "max_depth=", max_depth, type(e).__name__)
        break
PY

Repository: robotlearning123/gpt2agent

Length of output: 549


Avoid recursive re-entry in _phone_repl
A long chain of date-shaped tokens can recurse once per token and eventually raise RecursionError, which breaks redact() on large conversation/memory blobs. An iterative loop would keep the same behavior without the stack risk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gpt2agent/tools/_redact.py` around lines 14 - 20, _recursive_repl in
_phone_repl currently recurses through date-shaped prefixes, which can overflow
the stack on long token chains. Replace the recursive re-entry with an iterative
loop that keeps peeling off the _DATE_PREFIX_RE prefix and applying _PHONE_RE
until no prefix remains, while preserving the same redaction behavior for
redact().

@robotlearning123
robotlearning123 deleted the fix/audit-2026-07-02 branch July 3, 2026 02:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant