Skip to content

fix(codex): keep Cookbook off bearer APIs#5679

Closed
RaresKeY wants to merge 1934 commits into
odysseus-dev:devfrom
RaresKeY:fix/cookbook-bearer-boundary
Closed

fix(codex): keep Cookbook off bearer APIs#5679
RaresKeY wants to merge 1934 commits into
odysseus-dev:devfrom
RaresKeY:fix/cookbook-bearer-boundary

Conversation

@RaresKeY

Copy link
Copy Markdown
Member

Summary

Keep Cookbook model installation, launch, host control, task logs, saved commands, and deployment credentials inside the existing trusted, operator-controlled in-app/admin boundary. The duplicate external-integration /api/codex/cookbook/* family now fails closed for bearer and internal-tool identities before state, configuration, credential, filesystem, network, SSH, tmux, process, or endpoint access.

The obsolete Cookbook token scopes and shipped Codex/Claude bearer commands are removed. Existing token rows are not rewritten; any legacy Cookbook scope strings simply become inert. Generic chat/model APIs, configured inference endpoints, unrelated Codex/Claude tools, and existing trusted in-app/admin Cookbook/model routes remain unchanged.

Target branch

  • This PR targets dev, not main.

Linked Issue

Fixes #5676

Part of #5674

Depends on #5678. After that mounted-path fix merges, this branch should be
rebased to use its shared application-route-path helper without changing the
Cookbook boundary.

Type of Change

  • Bug fix (restores a coherent authorization boundary)
  • New feature (non-breaking — adds new behaviour)
  • Breaking change (retires the advertised Cookbook bearer scopes)
  • Refactor / cleanup (behaviour unchanged)
  • Documentation only
  • CI / tooling / configuration

Checklist

  • I searched open issues and open PRs — this is not a duplicate.
  • This PR targets dev.
  • My changes are limited to the Cookbook bearer boundary, scope catalog/UI, shipped integration wording, and focused regressions.
  • I ran the full app interactively. This remains a draft until maintainer review and deployment-level verification are complete.

How to Test

  1. Run python -m pytest -q tests/test_codex_cookbook_admin_gate.py tests/test_api_token_routes.py tests/test_codex_ssh_host_validation.py tests/test_cors_preflight.py. The isolated focused run passes all 115 tests.
  2. Verify bearer and internal-tool requests to every /api/codex/cookbook/* route receive the same non-enumerating 403 before any mocked state, credential, network, shell, process, or endpoint operation is reached.
  3. Verify cookbook:read and cookbook:launch are absent from the token profiles/API and both token-management interfaces, and attempts to mint them are rejected.
  4. Verify the Codex and Claude bundles no longer expose Cookbook administration commands.
  5. Verify existing trusted in-app/admin callers can still use the Cookbook and direct /api/model/* routes.
  6. Run python -m pytest -q. The isolated full suite completes with 4,737 passing and 5 skipped tests. Its 12 failures exactly match the clean current-dev runner baseline: the omitted .env.example, one offline URL-join check, two backup-recovery checks, and eight offline web-fetch checks.

Visual / UI changes — REQUIRED if you touched anything that renders

The API-token forms no longer show the two retired Cookbook scope options. No
layout, styling, component, or model-management UI changes are included.

  • Screenshot or short clip of the change in the running app, attached below. This remains pending while the PR is a draft.
  • Style match: this only removes two existing scope options and adds no visual values, classes, icons, spacing, or typography.
  • No new component patterns. No component is added or replaced.
  • I am not an LLM agent submitting a bulk PR. The focused issue Codex Cookbook bearer routes have an inconsistent admin boundary #5676 was opened first and this is not represented as a human-authored interactive verification.

Screenshots / clips

Pending interactive verification. The visible change is limited to removal of
the cookbook:read and cookbook:launch choices from both existing API-token
forms.

pewdiepie-archdaemon and others added 30 commits June 27, 2026 21:23
Keep an unhealthy MemoryVectorStore instance available for health reporting instead of discarding it as disabled. This lets health checks report a degraded/down vector-store state while preserving focused regression coverage for initializer behavior.
* fix(security): prevent ReDoS in XML and args tool-call parsers

Four py/polynomial-redos sinks in tool_parsing.py ran lazy/greedy regexes over
untrusted model output (tool-call markup is attacker-influenced via prompt
injection). When the closing delimiter was absent, each rescanned to
end-of-string from every opener -> O(n^2):

  - args => { ... } in _parse_tool_call_block: greedy \{([\s\S]*)\} restarted
    from every `args:{` opener. Now finds the opener once and takes through the
    last `}` (rfind) — equivalent capture, O(n).
  - _XML_INVOKE_RE: lazy <invoke ...>([\s\S]*?)</invoke>. Now _iter_xml_invoke
    pairs each opener with the first reachable </invoke> and stops when none is.
  - _XML_DIRECT_TOOL_RE and the <tag>([\s\S]*?)</\1> param scan in
    _parse_tool_code_block: lazy backreference patterns. Now _iter_backref_blocks
    pairs each opener with the nearest matching closer and memoizes tag names
    with no remaining closer, so an opener flood stays O(n).

All four are output-equivalent to the originals on well-formed tool-call markup;
the lazy patterns remain defined (still re-exported via agent_tools) but no
longer drive a finditer over untrusted text. Adds tests/test_redos_xml_tool_parsers.py
pinning correctness and bounding the opener-flood inputs (old paths took 4-15s).

* fix(security): harden invoke-parameter and distinct-name tag scans

Forward-only the two residual ReDoS paths in the XML/tool parsers that the
outer-delimiter fix left quadratic:

- _parse_xml_invoke parsed <parameter> with _XML_PARAM_RE.finditer, so a
  closed <invoke> body full of unclosed <parameter> openers rescanned the
  body from every opener (O(n^2), ~11s at 8k openers). Now scans forward-only
  via _iter_named_blocks, factored out of _iter_xml_invoke.
- _iter_backref_blocks only memoized repeated missing tag names; a flood of
  distinct unclosed names searched the suffix once per name (O(n^2)). It now
  indexes every closer by name in one linear pass and binary-searches per
  opener (O(n log n)). Covers the direct and tool_code backref scans.

Output-equivalent to the prior scanners (200k randomized trials match the
memoized version for both the direct ci=True and tool_code ci=False configs).
Adds regressions for the closed-invoke parameter flood and the distinct-name
floods (45k openers now run in ~0.05s, were 5-6s).
ap0ught and others added 22 commits July 17, 2026 16:18
chore: sync upstream changes from odysseus-dev/odysseus
fix(docker): bump Docker CLI to a patched release
fix(docker): bump Docker CLI to a patched release
* feat(models): define capability schema and readers

* fix(models): harden Google catalog probing

Restrict native catalog probing to the Gemini host, keep provider keys out of request URLs, filter non-chat model resources, and preserve the manual refresh default in the built-in Google add flow.
…(#5474)

* security(url-safety): reject RFC 6598 shared address space in strict mode

Strict mode (block_private=True) is a full SSRF lockdown, but it only
rejected is_private and is_loopback targets. CPython does not classify RFC
6598 shared/CGNAT space (100.64.0.0/10) as is_private (it is "shared", not
"private"), so a public redirect into 100.64.0.1 passed the per-hop guard
and still issued the request to a potentially internal CGNAT service.

not is_global would also exclude it, but only on CPython 3.11.10+/3.12.4+/
3.13+; the CI matrix runs 3.11/3.12, so reject the range explicitly to stay
correct across patch levels and the 3.14 runtime image. Default local-first
mode is unchanged. Adds strict-mode coverage for shared, non-global, and
public targets.

* docs(url-safety): correct CGNAT is_global rationale in strict-mode comment

The prior comment claimed `not is_global` catches 100.64.0.0/10 only on
CPython 3.11.10+/3.12.4+/3.13+. That is inaccurate for CGNAT: is_global
is False for 100.64.0.1 on every supported version (verified 3.10-3.14).
The version-fragility applies to other ranges gh-113171 touched, not CGNAT.
The explicit range reject is still the right choice; restate the reason as
is_private not covering shared space, and not coupling strict mode to
is_global's broader, cross-version definition. No behavior change.
…… (#5491)

* fix(llm): enhance fallback logic to handle empty completions and improve metadata handling

* fix(llm): stream tool call deltas immediately
Slice 2f of the route-domain reorganization (#4082/#4071, per
specs/architecture-runtime-inventory.md §6.3). Moves note_routes.py into
routes/note/, leaving a backward-compat sys.modules shim at the old path.
Pure file reorganization, no behavior change.

The shim uses sys.modules replacement (same pattern as the merged gallery
#4903, research #4975, memory #5007, history #5090, and contacts #5227
slices) so that `import routes.note_routes`, `from routes.note_routes import
X`, `importlib.import_module(...)`, and the `import ... as note_routes` +
`monkeypatch.setattr(note_routes, "SessionLocal", ...)` pattern used by
test_note_reminder_fire_scope.py / test_notes_fail_closed_auth.py all
operate on the same module object the application uses.

The canonical module does NOT depend on the shim — routes/note/note_routes.py
imports only from core/, src/, and stdlib. The outbound email cross-domain
imports (routes.email_routes._get_email_config, routes.email_helpers.
_send_smtp_message) are function-local lazy imports that keep resolving
through the email module's own path (email is not yet migrated).

One source-introspection test site repointed to the new canonical path:
- test_model_helper_owner_scope.py (shared with history; history entry
  already repointed in #5090, note entry repointed here)

Adds tests/test_note_routes_shim.py to pin the sys.modules shim contract
(legacy and canonical paths resolve to the same module object; monkeypatch
via legacy alias reaches the canonical module).

Verified: compileall clean; full suite 4487 passed, 3 skipped.
… (#5658)

Slice 2g of the route-domain reorganization (#4082/#4071). Moves
cleanup_routes.py into routes/cleanup/, leaving a backward-compat
sys.modules shim at the old path. Pure file reorganization, no behavior
change.

The shim uses sys.modules replacement so string-targeted
monkeypatch.setattr("routes.cleanup_routes.*", ...) in
test_cleanup_owner_scope.py reaches the canonical module.

Canonical module imports only from src/ and stdlib (zero internal
routes/ coupling). Zero source-introspection landmines.

Adds tests/test_cleanup_routes_shim.py to pin the sys.modules shim
contract. Verified: compileall clean; targeted tests pass.
…ackage (#5659)

Slice 2h of the route-domain reorganization (#4082/#4071). Moves
admin_wipe_routes.py into routes/admin_wipe/, leaving a backward-compat
sys.modules shim at the old path. Pure file reorganization, no behavior
change.

The shim uses sys.modules replacement so the `import ... as
admin_wipe_routes` + `monkeypatch.setattr(admin_wipe_routes, "SessionLocal",
...)` / `"require_admin"` pattern in test_admin_wipe_gallery.py reaches
the canonical module.

Canonical module imports only from core/, src/, and stdlib (zero internal
routes/ coupling). Zero source-introspection landmines.

Adds tests/test_admin_wipe_routes_shim.py to pin the sys.modules shim
contract. Verified: compileall clean; targeted tests pass.
… (#5660)

Slice 2i of the route-domain reorganization (#4082/#4071). Moves
compare_routes.py into routes/compare/, leaving a backward-compat
sys.modules shim at the old path. Pure file reorganization, no behavior
change.

The shim uses sys.modules replacement so the `import ... as cr` +
`monkeypatch.setattr(cr, "SessionLocal", ...)` / `"_owned_endpoint_by_url"`
/ `"_owned_endpoint_by_id"` pattern in test_endpoint_owner_scope_followup.py
reaches the canonical module.

Canonical module imports only from core/, src/, and routes.session_routes
(zero dependency on the legacy shim). One source-introspection test site
repointed: test_endpoint_owner_scope_followup.py (shared with other domains;
only the compare entry repointed here).

Adds tests/test_compare_routes_shim.py to pin the sys.modules shim
contract. Verified: compileall clean; targeted tests pass.
fix(docker): detect snap+WSL2 GPU passthrough incompatibility
* ci: add CodeQL advanced setup to scan pull requests before merge

* ci(codeql): preserve scheduled scans

Add a weekly advanced-setup scan and update the security CI guide so default setup remains disabled.

---------

Co-authored-by: RaresKeY <158580472+RaresKeY@users.noreply.github.com>
@RaresKeY RaresKeY added the bug Something isn't working label Jul 21, 2026
@RaresKeY RaresKeY added this to the Stabilization milestone Jul 21, 2026
@github-actions github-actions Bot added the ready for review Description complete — ready for maintainer review label Jul 21, 2026
@RaresKeY RaresKeY removed this from the Stabilization milestone Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ready for review Description complete — ready for maintainer review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex Cookbook bearer routes have an inconsistent admin boundary