Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
closes the pipe, never linger deaf and silently discard requests (the
failure mode diagnosed in obsidian-mcp, omind#49). Verified the server
already behaves correctly; the test locks it in.
- **Configurable per-Space store retention** — `PERPLEXITY_MAX_TABS_PER_SPACE`
(default 50, the existing recent-tab cache) and `PERPLEXITY_MAX_HISTORY_PER_SPACE`
(default unbounded). Conversation history is never auto-deleted unless an
operator opts in; a blank value is treated as "unbounded" rather than crashing
startup.

### Security

- The token-bucket rate limit and the audit log are now wired together for the
TUI surface via a shared `RequestGuard`: the TUI commands/assist path and the
background `/task` monitors go through it, so the TUI is audited and background
probes are metered (previously the TUI built an audit logger it never called
and probes bypassed the limiter). The MCP server keeps its own correlation-id
guard.
- Untrusted page titles are escaped before the tab bar renders them, so a title
with console-markup metacharacters can neither crash the bar nor inject styling.

### Changed

- CI runs lint, type-check, and dependency-audit across the 3.11/3.12/3.13 matrix
again (they had been narrowed to 3.12 only), adds a job that exercises the
tui-less core install, and no longer cancels in-progress runs on `main`. The
v0.2.0 CI hardening (pinned action SHAs, full-history gitleaks) is preserved.
- The local store indexes its per-Space lookups (`conversations`/`tabs` by
`space`), and the dead `facts` table is dropped on open.

### Fixed

- `TaskManager.aclose()` awaits cancelled probe handles before returning, so
quitting the TUI doesn't race `on_unmount` closing the shared fetcher/client.
- `/search` runs its two independent calls concurrently with `return_exceptions`,
so a failure in one no longer orphans the other.
- `perplexity-agent --transport http tui` now errors instead of silently
ignoring `--transport` and launching the interactive UI.
- `group_tabs` tolerates a malformed or schema-nonconforming model response
instead of raising.

## [0.2.0] - 2026-06-10

Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ physics. Everything else maps onto terminal-feasible equivalents:
| Chat with your tabs / synthesis | `/summary` across all open tabs; bare chat is tab-aware |
| AI tab grouping | `/group` clusters open tabs into named groups |
| Deep research | `/research <q>` runs the full validated, cited pipeline |
| Memory & Spaces | Local SQLite store (owner-only 0600; tabs dedupe per space+URL, newest 50 kept); `/space [name]` switches workspaces |
| Memory & Spaces | Local SQLite store (owner-only 0600; tabs dedupe per space+URL, recent-tab cap and optional history retention are configurable); `/space [name]` switches workspaces |
| Background / scheduled tasks | `/task search\|fetch <seconds> <target>` monitors and alerts on change; `/untask <id>` stops it |
| Agentic task planning | Research-only planning (decompose a goal); **no real web actions** |

Expand All @@ -282,8 +282,11 @@ it reaches Sonar. See [`SECURITY.md`](SECURITY.md). The MCP tool surface is unch

All optional knobs are environment variables (see [`.env.example`](.env.example)):
timeouts, response-size cap, retry count, rate limits, an optional JSON audit-log
path, and (for the TUI) the fetch User-Agent, `PERPLEXITY_FETCH_ALLOW_PRIVATE`, and
`PERPLEXITY_STORE_PATH`.
path, and (for the TUI) the fetch User-Agent, `PERPLEXITY_FETCH_ALLOW_PRIVATE`,
`PERPLEXITY_STORE_PATH`, and the per-Space store retention caps
(`PERPLEXITY_MAX_TABS_PER_SPACE`, default 50; `PERPLEXITY_MAX_HISTORY_PER_SPACE`,
default unbounded — set a positive integer to keep only the N most-recent rows
per Space, or leave blank to keep everything).

## Development

Expand Down
11 changes: 8 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ operating-system account the server runs under.
| **Filter & monitor outputs / chained execution** | Perplexity responses are treated as **untrusted input** to the next stage. `deep_research` scans retrieved snippets for indirect-prompt-injection patterns and surfaces them in `security_flags`. Citations are taken only from API metadata, never from model free-text. |
| **Instrument for logging & detection** | A structured JSON audit log (`security.py`) records every tool call and result with redacted parameters, a result hash, validation status, a per-call **correlation id** (shared by the `tool_call`/`tool_result` pair), **latency** (`duration_ms`), and **token usage** when the API reports it — suitable for SIEM ingestion. |
| **Track & patch vulnerabilities** | Pinned deps + `uv.lock`; CI runs `pip-audit` on every push and Dependabot is enabled. GitHub Actions are **pinned to commit SHAs** (not retargetable tags), and the gitleaks secret scan covers the **full git history**, not just the worktree. |
| **DoS / fatigue resistance** | A token-bucket rate limiter (`rate_per_minute` / `rate_burst`), bounded sub-question counts, input-size caps, and request timeouts. |
| **DoS / fatigue resistance** | A token-bucket rate limiter (`rate_per_minute` / `rate_burst`) that — via `RequestGuard` — meters the MCP tools, the TUI commands, and background `/task` probes alike, plus bounded sub-question counts, input-size caps, and request timeouts. The TUI's local store is bounded by per-Space retention caps (`PERPLEXITY_MAX_TABS_PER_SPACE` / `PERPLEXITY_MAX_HISTORY_PER_SPACE`). |
| **Access control / token security** | `PERPLEXITY_API_KEY` is loaded server-side from the environment only; the server fails fast if it is absent. No token passthrough. |

## Interactive TUI page fetcher (added egress surface)
Expand Down Expand Up @@ -63,7 +63,10 @@ fetcher applies SSRF + DoS controls mirroring the API client:
- **Untrusted-output handling** — extracted page text is run through the same
indirect-prompt-injection scan as `deep_research`; matches are surfaced to the user
before the text is sent to Sonar.
- **Audit** — fetches share the TUI's `TokenBucket` rate limiter and audit logger.
- **Audit & rate limit** — the TUI's commands, page fetches, and background
`/task` probes pass through one `RequestGuard` that charges the `TokenBucket`
and writes a redacted audit record, so the interactive surface is metered and
logged like the MCP tools.

**Residual risk:** with the connection pinned to the validated IP, the classic
DNS-rebinding TOCTOU window is closed. What remains is by design: the fetcher will
Expand All @@ -73,7 +76,9 @@ who fetch untrusted URLs should still run the TUI behind a filtering egress prox
The TUI's local SQLite store (history/tabs/Spaces) holds only the user's own
artifacts — no secrets. It is still browsing history, so the file is created
owner-only (0600, directory 0700), tabs are deduplicated per space+URL, and each
space retains only the newest 50 tabs.
space retains only the most-recent tabs (default 50, configurable). Conversation
history is unbounded by default — it is never auto-deleted — but an operator can
cap it with `PERPLEXITY_MAX_HISTORY_PER_SPACE`.

## Secret handling

Expand Down
Loading