Audit remediation: security hardening, trust docs, discoverability (v0.0.5) - #10
Conversation
ChatGPT moved heavy Deep Research to the "Deep Research App" connector
(connectors://connector_openai_deep_research), which renders the report in an
embedded widget and never writes it as an assistant text node. The old
_poll_dr_completion scanned only assistant text, so heavy runs timed out at
1800s with an empty report even though the research completed server-side.
The report lives in the hidden widget state (widget_state.report_message).
_poll_dr_completion now fetches the conversation with
?include_visually_hidden_messages=true&include_widget_state=true and recovers
the report (text + content_references) from either widget-state carrier — a
"The latest state of the widget is: {…}" tool node, or
message.metadata.chatgpt_sdk.widget_state — via the new
_dr_report_from_widget_state helper. Existing citation-extraction is untouched.
Verified by recovering three real completed reports headlessly
(45.6K / 52.4K / 51.5K chars, with citations). Adds 4 fixture-based tests
(real oracle, no network). Light deep_research uses a different (SearchGPT)
mechanism and is unchanged; a dedicated light-mode fix is tracked as a follow-up.
Bumps version 0.0.3 -> 0.0.4; updates deep-research skill doc + CHANGELOG.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… parser Security: - Default bind to 127.0.0.1; refuse non-loopback HTTP unless GPT2AGENT_ALLOW_REMOTE=1 (the HTTP transport is unauthenticated and proxies a full ChatGPT account). config.example.toml now ships loopback. (_http_bind_decision helper, unit-tested) - Route backend.py + streaming SSE error bodies through redact_error. - Broaden _log_redact: bare Bearer, access_token/session_token JSON fields, auth/session cookies, token query params (was: quoted header pairs only). Correctness (DR widget recovery, PR #9 follow-up): - _dr_report_from_widget_state now requires author.role in (tool,assistant), the prefix to START the part (not substring), and a finished report status — blocks DR-report spoofing via a user message and premature in-progress emission. - stream() typed AsyncIterator[str | dict]; complete() skips non-str sentinels. - agent/chat/gpt_chat/memory tools return "(no response)" instead of "" on timeout. Lint: drop dead `done` var + unused `groups`; fix E402 import order; assert on live /backend-api/me result. Tests: +14 (tests/test_security_hardening.py) for redaction, bind gate, parser guards, and the widget-state query-flag assertion. Full suite 60 passed, 9 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019UxBmhmJvjdh3qA9FKBNZR
…staller - Remove docs/goals/*.md internal agent-orchestration briefings from the public repo (leaked writer/reviewer roles + DR quota usage). NOTE: still in git history — a history purge would need a separate, explicit force-push. - Honor CODEX_HOME in auth.py and setup.py (mirrors backend.py) so multi-account users read/save the intended login, not always ~/.codex. - install.sh: on PyPI install failure, auto-fall back to git+https://github.com/robotlearning123/gpt2agent.git instead of exiting — the one-line install works today (pre-PyPI-publish) and switches to PyPI seamlessly once the package is published. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019UxBmhmJvjdh3qA9FKBNZR
…le claims; 0.0.5 - README: add a prominent "Security & risk" section (ToS/account-ban from web-client impersonation, unauthenticated HTTP transport + loopback default, limited PII redaction, RAW_DUMP warning). Honest "(emails/phones redacted)" wording. Reconcile the "auto-publish to PyPI" claim with the required one-time Trusted Publisher setup; default config example to 127.0.0.1; de-stale version refs. - Add SECURITY.md (private reporting + documented risk model), issue templates (bug/feature + security contact link), and a PR template. - CLAUDE.md test count 38 -> 60. - Bump to 0.0.5 with CHANGELOG covering the security/correctness hardening. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019UxBmhmJvjdh3qA9FKBNZR
- setup.py: generated config now writes host = "127.0.0.1" (was 0.0.0.0). With the new bind gate, `gpt2agent setup` had been self-generating a config it would then refuse to start. (cx P1) - sse.py: guard `chatgpt_sdk` with isinstance(dict) before .get() — a non-dict scalar carrier no longer aborts _dr_report_from_widget_state / DR polling. (cx P2) - test: regression for the malformed-sdk carrier. cx final review (writer=Opus, reviewer=cx GPT-5.5): otherwise clean — bind gate conservative + stdio bypasses it, redaction bounded to error strings (not response JSON), legitimate completed widget fixtures still pass. Suite: 61 passed, 9 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019UxBmhmJvjdh3qA9FKBNZR
📝 WalkthroughWalkthroughRelease 0.0.5 adds loopback-only HTTP bind enforcement with an opt-in env var, expands the secret-redaction pipeline to cover bearer tokens, cookies, and query params, adds Changesgpt2agent v0.0.5: Security Hardening and Deep Research Widget-State Recovery
Sequence Diagram(s)sequenceDiagram
participant MCP_Client
participant server_main
participant _http_bind_decision
participant ConversationClient
participant _poll_dr_completion
participant BackendGet
MCP_Client->>server_main: start streamable-http
server_main->>_http_bind_decision: check host + GPT2AGENT_ALLOW_REMOTE
alt non-loopback, no opt-in
_http_bind_decision-->>server_main: refuse
server_main-->>MCP_Client: SystemExit
else loopback or opt-in
_http_bind_decision-->>server_main: ok
server_main-->>MCP_Client: server started
MCP_Client->>ConversationClient: deep_research_heavy(prompt)
ConversationClient->>_poll_dr_completion: poll for completion
loop poll cycle
_poll_dr_completion->>BackendGet: GET ?include_visually_hidden_messages=true&include_widget_state=true
BackendGet-->>`_poll_dr_completion`: conversation JSON
alt widget_state has finished report
`_poll_dr_completion`->>ConversationClient: emit progress + done (early return)
else no widget report yet
`_poll_dr_completion`->>ConversationClient: emit progress, continue
end
end
ConversationClient-->>MCP_Client: final done event with report + content_references
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2317307cc0
ℹ️ 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".
| _TOKEN_FIELD_RE = re.compile( | ||
| r'"((?:access|session|id|refresh|bearer)[_-]?token|accessToken)"\s*:\s*"[^"]*"', | ||
| re.IGNORECASE, |
There was a problem hiding this comment.
Redact Python-repr token fields
When callers pass a Python dict representation (for example sentinel.get_tokens() uses _redact_error(str(resp)) on the no-token branch), this new pattern does not match single-quoted keys, so {'tokens': {'access_token': 'SECRET'}} is returned with SECRET intact. That leaves the redaction hardening incomplete for an existing error path; handle single-quoted reprs too or serialize dicts to JSON before redacting.
Useful? React with 👍 / 👎.
| if widget_text != last_emitted: | ||
| yield {"type": "progress", "text": widget_text} |
There was a problem hiding this comment.
Emit only widget report deltas
When the SSE phase has already yielded partial report text and then falls back to polling, last_emitted is initialized from that seed text, but this branch emits the entire widget_text as a progress event whenever it differs. Direct consumers that concatenate progress chunks, matching the delta semantics used by the assistant-text polling path below, will see seed_text + widget_text and duplicate the prefix of the final report; emit only the suffix when widget_text.startswith(last_emitted).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/setup.py`:
- Around line 43-44: The CODEX_HOME environment variable path is not being
expanded, which means literal tilde characters in the path are not being
converted to the user's home directory. In setup.py where codex_home is
retrieved from the environment and used to construct the Path object (lines
43-44), apply expanduser() to the codex_home string before converting it to a
Path to properly resolve any tilde characters. Apply this same fix consistently
to gpt2agent/auth.py at lines 24-25 where a similar CODEX_HOME path resolution
pattern exists.
🪄 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: e24ae320-6dd7-4b05-ab47-e667bc803373
📒 Files selected for processing (26)
.github/ISSUE_TEMPLATE/bug_report.md.github/ISSUE_TEMPLATE/config.yml.github/ISSUE_TEMPLATE/feature_request.md.github/pull_request_template.mdCHANGELOG.mdCLAUDE.mdREADME.mdSECURITY.mdconfig.example.tomldocs/goals/heavy-dr-citations-RESULT.mddocs/goals/heavy-dr-citations.mdgpt2agent/_log_redact.pygpt2agent/auth.pygpt2agent/backend.pygpt2agent/sentinel.pygpt2agent/server.pygpt2agent/setup.pygpt2agent/skills/deep-research/SKILL.mdgpt2agent/skills/deep-research/bin/deep_research.pygpt2agent/sse.pyinstall.shpyproject.tomltests/fixtures/heavy_dr_widget_state.jsontests/test_backend_tools.pytests/test_heavy_dr_parser.pytests/test_security_hardening.py
💤 Files with no reviewable changes (2)
- docs/goals/heavy-dr-citations-RESULT.md
- docs/goals/heavy-dr-citations.md
| codex_home = os.environ.get("CODEX_HOME") | ||
| p = (Path(codex_home) if codex_home else Path.home() / ".codex") / "auth.json" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify current CODEX_HOME path handling and pathlib expansion behavior.
rg -n 'Path\(codex_home\)|CODEX_HOME' gpt2agent/setup.py gpt2agent/auth.py
python - <<'PY'
from pathlib import Path
raw = Path("~/.codex-cx2") / "auth.json"
expanded = Path("~/.codex-cx2").expanduser() / "auth.json"
print("raw :", raw)
print("expanded :", expanded)
PYRepository: robotlearning123/gpt2agent
Length of output: 703
Expand CODEX_HOME with expanduser() before resolving auth.json.
Path(codex_home) keeps a literal ~ in common env-file/quoted setups, so token lookup can miss a valid Codex login path.
Suggested fix
- codex_home = os.environ.get("CODEX_HOME")
- p = (Path(codex_home) if codex_home else Path.home() / ".codex") / "auth.json"
+ codex_home = os.environ.get("CODEX_HOME")
+ codex_base = (
+ Path(codex_home).expanduser() if codex_home else Path.home() / ".codex"
+ )
+ p = codex_base / "auth.json"Apply the same fix to gpt2agent/auth.py (lines 24–25) for consistency.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| codex_home = os.environ.get("CODEX_HOME") | |
| p = (Path(codex_home) if codex_home else Path.home() / ".codex") / "auth.json" | |
| codex_home = os.environ.get("CODEX_HOME") | |
| codex_base = ( | |
| Path(codex_home).expanduser() if codex_home else Path.home() / ".codex" | |
| ) | |
| p = codex_base / "auth.json" |
🤖 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 43 - 44, The CODEX_HOME environment variable
path is not being expanded, which means literal tilde characters in the path are
not being converted to the user's home directory. In setup.py where codex_home
is retrieved from the environment and used to construct the Path object (lines
43-44), apply expanduser() to the codex_home string before converting it to a
Path to properly resolve any tilde characters. Apply this same fix consistently
to gpt2agent/auth.py at lines 24-25 where a similar CODEX_HOME path resolution
pattern exists.
Full remediation of the 2026-06-18 cross-model audit (cx + cx2 + 4× ccz waves, Opus-verified). Stacked on PR #9 — includes the heavy-DR widget fix (2807473) plus the hardening below. Reviewer: cx (GPT-5.5) ran a final pass on the complete diff → clean after two follow-up fixes (now included).
Security
127.0.0.1(was0.0.0.0). The HTTP transport is unauthenticated and proxies a full ChatGPT account; the server now refuses non-loopback HTTP unlessGPT2AGENT_ALLOW_REMOTE=1, with a loud warning.config.example.toml+gpt2agent setupnow generate loopback. stdio (theinstalldefault) is unaffected.redact_errornow also strips bareBearer, named JSON token fields, auth/session cookies, and token query params;backend.py+ streaming SSE error paths route through it (were raw).docs/goals/*.md) from the repo.Correctness
author.role ∈ {tool,assistant}, prefix must start the part, and only afinished_successfullyreport is emitted → closes a DR-report-spoofing vector and premature-doneon in-progress drafts.stream()typedAsyncIterator[str | dict];agent/chat/gpt_chat/memoryreturn"(no response)"on timeout (not"").CODEX_HOMEhonored inauth.py/setup.py.install.shfalls back togit+httpson PyPI 404 → the one-liner works pre-publish.Docs / trust / discoverability
SECURITY.md, issue templates, PR template. Bumped to 0.0.5 + CHANGELOG. GitHub topics + description updated.Configure a PyPI Trusted Publisher (project
gpt2agent, ownerrobotlearning123, workflowrelease.yml, envpypi) or do a first manualtwine upload. Until then every Release run fails withinvalid-publisherandpip install gpt2agent404s (the installer git-fallback mitigates for users).Verification
ruff checkclean ·pytest -q→ 61 passed, 9 skipped (+15 hardening tests intests/test_security_hardening.py).🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes v0.0.5
Security
Bug Fixes
Documentation