Skip to content

fix(tui): harden the TUI surface — markup, audit, rate limit, bounded context - #42

Closed
CryptoJones wants to merge 2 commits into
fix/tasks-reliabilityfrom
fix/tui-hardening
Closed

CryptoJones wants to merge 2 commits into
fix/tasks-reliabilityfrom
fix/tui-hardening

Conversation

@CryptoJones

Copy link
Copy Markdown
Owner

Resolves the tui/app.py hardening cluster (six issues). The shared RequestGuard ties the rate-limit and audit controls together so every network surface goes through the same gate.

Stacked on #34 (fix/tasks-reliability) because #16 also edits tasks.py. The base is set to that branch so this diff is clean; GitHub will retarget it to main once #34 merges.

#14 — untrusted titles rendered as Rich markup

_refresh_tabbar now escape()s the page title, so a title like [2026] Best [GPU] deals is shown literally instead of raising MarkupError mid-render or injecting styling.

#19 — TUI emitted no audit events

New security.RequestGuard encapsulates the bucket.acquire() + audit.record() pairing that server.py::_guard did inline. server.py now delegates to it (behavior preserved), and the TUI constructs one and calls it from _command/_assist — so TUI activity is finally audited.

#16 — background probes bypassed the rate limiter

TaskManager takes an optional guard; _probe acquires a token per run, so recurring /task monitors are metered and audited like interactive calls instead of hammering the API unmetered.

#21 — unbounded per-turn context

Only the most-recent _MAX_CONTEXT_TABS (3) open tabs are sent as assistant context, and Store.tabs() now caps (LIMIT) and dedups by URL (newest wins), so a long-lived space no longer reloads or re-uploads unbounded page text. (Index/pruning follow-up tracked in #30.)

#32 — sequential search round trips

/search runs the ranked-results and grounded-answer calls via asyncio.gather.

#33 — duplicated task-kind tuple + type: ignore

/task derives its valid kinds from get_args(TaskKind) and casts, removing the hand-maintained ("search","fetch") tuple and the suppression.

Tests (88 passed, +6)

RequestGuard audit + rate-limit-then-raise; tabs() dedup-by-URL and limit; a probe rate-limited through the guard; tab bar escaping a bracketed title. ruff + mypy --strict clean, coverage 88.5%.

Closes #14, closes #16, closes #19, closes #21, closes #32, closes #33.

🤖 Generated with Claude Code

… context

Six fixes to the interactive TUI and the controls it shares:

- Escape untrusted page titles before they reach the tab bar (#14): a title
  with Rich-markup metacharacters (e.g. "[2026] deals") no longer raises
  MarkupError mid-render or injects styling.

- Route the TUI through a shared RequestGuard (#19): the bucket+audit pairing
  that server.py's _guard implemented inline now lives in security.RequestGuard,
  used by the MCP server, the TUI commands/assist path, and the background
  tasks — so the TUI surface finally emits audit events.

- Meter background monitor probes (#16): TaskManager takes an optional guard and
  acquires a token per probe, so recurring watches can't make unmetered,
  unaudited API/web calls. Bucket enforcement is no longer per-call-site only.

- Bound per-turn assistant context (#21): only the most-recent _MAX_CONTEXT_TABS
  open tabs are sent as context, and Store.tabs() caps + dedups by URL so a
  long-lived space doesn't reload (or re-upload) unbounded page text.

- Run /search's ranked-results and grounded-answer calls concurrently (#32)
  instead of paying both round trips in series.

- Derive /task's valid kinds from get_args(TaskKind) and cast (#33), dropping
  the duplicated literal tuple and the type: ignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…guard.record

Audit follow-up on the TUI hardening:

- _cmd_search's asyncio.gather now uses return_exceptions=True and re-raises the
  first failure, so if one of the two concurrent calls fails the other still
  completes instead of being orphaned with an unretrieved exception.

- _loop only notifies on a *new* error: a persistent failure (e.g. a drained
  shared rate-limit bucket starving background probes) is reported once instead
  of spamming the same line every interval. It still keeps retrying.

- server.py's tool_result audits now go through RequestGuard.record() instead of
  the raw AuditLogger, so the guard is the single audit chokepoint its docstring
  claims (and record() is no longer dead production-wise).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CryptoJones

Copy link
Copy Markdown
Owner Author

Superseded by #49 (TUI + background-task hardening, re-targeted onto v0.2.0).

@CryptoJones
CryptoJones deleted the fix/tui-hardening branch June 12, 2026 16:58
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