Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ab756b0
refactor(cursor): extracts token extraction into shared lib
wgordon17 Jul 31, 2026
b180295
feat(cursor): adds cursor-agent-backed token renewal orchestrator
wgordon17 Jul 31, 2026
d81334e
feat(cursor): proactively renews Cursor sessions in the sweep
wgordon17 Jul 31, 2026
73a2756
feat(cursor): adds local-only manual refresh route
wgordon17 Jul 31, 2026
e4bad4b
feat(cursor): surfaces a dismissible cursor-agent nudge
wgordon17 Jul 31, 2026
a193ae3
fix(cursor): wires manual refresh button to the new route
wgordon17 Jul 31, 2026
4bfe14a
fix(cursor): addresses Phase 4/4.5 review findings
wgordon17 Aug 1, 2026
8966f01
docs(cursor): adds changelog fragments for the renewal plan
wgordon17 Aug 1, 2026
bf91f13
fix(i18n): translates the new Cursor keys into Vietnamese
wgordon17 Aug 1, 2026
cd8c966
fix(cursor): addresses quality-gate Layer 1.5 findings
wgordon17 Aug 1, 2026
6feced9
fix(cursor): adds SIGKILL follow-up to the status-check spawn
wgordon17 Aug 1, 2026
060e07f
docs(cursor): fills in the PR number for changelog fragments
wgordon17 Aug 1, 2026
fd83827
fix(cursor): corrects changelog fragments to reference PR #9173
wgordon17 Aug 1, 2026
6b5b0a7
docs(cursor): regenerates the agent-skills catalog for the new route
wgordon17 Aug 2, 2026
9c26726
chore(quality): rebaselines file-size caps grown by agentrouter merges
wgordon17 Aug 2, 2026
4b1bd98
fix(sse): imports getModel helpers from db/models, not localDb
wgordon17 Aug 2, 2026
5b1b576
fix(sse): scopes CC-relay anthropic-beta to its own requestDefaults
wgordon17 Aug 2, 2026
5adfd8f
fix(sse): preserves bare CC-relay native treatment and context-1m
wgordon17 Aug 2, 2026
ec1d9ad
fix(sse): fills in remaining stale CLI version literals
wgordon17 Aug 2, 2026
551041a
fix(cursor): imports from db/ modules, not the localDb barrel
wgordon17 Aug 2, 2026
ed8a05e
fix(db): removes stale raw-SQL allowlist entry for cursor route
wgordon17 Aug 2, 2026
b2cd423
fix(test): registers cursor test files in stryker tap.testFiles
wgordon17 Aug 2, 2026
b044d94
chore(ci): retriggers checks (stuck GH Actions runner on shard 2/4)
wgordon17 Aug 2, 2026
933a46e
fix(sse): restores CC-relay context1m/redact-thinking test coverage
wgordon17 Aug 2, 2026
98542e8
ci: re-trigger checks after GitHub Actions incident (2026-08-07, reso…
wgordon17 Aug 7, 2026
fef00f0
ci: re-trigger checks (previous push event was dropped)
wgordon17 Aug 7, 2026
f25c091
fix(quality): restore dropped vi.json cursor-renewal keys + rebaselin…
wgordon17 Aug 8, 2026
fec3b00
chore(tests): drop explanatory comments on ALL_TARGETS_SKIPPED assert…
wgordon17 Aug 8, 2026
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
1 change: 1 addition & 0 deletions changelog.d/features/9173-cursor-agent-nudge-banner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- feat(cursor): surface a dismissible dashboard banner suggesting `cursor-agent` installation when it isn't available, so Cursor connections needing periodic manual reconnection aren't a silent surprise (#9173)
1 change: 1 addition & 0 deletions changelog.d/features/9173-cursor-proactive-renewal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- feat(cursor): proactively renew Cursor sessions before their ~24h token expires via the token health-check sweep, nudging `cursor-agent` and re-scraping IDE/agent credential sources so connections stop silently expiring (#9173)
1 change: 1 addition & 0 deletions changelog.d/fixes/9173-cursor-manual-refresh-502.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(cursor): the manual "Refresh" button on Cursor connections now calls the dedicated Cursor renewal route instead of silently returning a 502 every time (#9173)
5 changes: 0 additions & 5 deletions config/quality/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,6 @@
"count": 3
}
},
"open-sse/handlers/chatCore.ts": {
"no-restricted-imports": {
"count": 1
}
},
"open-sse/handlers/chatCore/codexFailover.ts": {
"no-restricted-imports": {
"count": 1
Expand Down
11 changes: 11 additions & 0 deletions config/quality/file-size-baseline.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,16 @@ paths:
"200":
description: Provider model list

/api/providers/cursor/agent-availability:
get:
tags: [Providers]
summary: Check cursor-agent availability
description: "Credential-free, informational check for whether cursor-agent is installed and authenticated on this host — backs the dashboard's dismissible install-nudge banner. Returns only cursorAgentAvailable (boolean); never tokens or machineId."
x-loopback-only: true
responses:
"200":
description: Availability result

/api/providers/test-batch:
post:
tags: [Providers]
Expand Down
61 changes: 35 additions & 26 deletions docs/security/ROUTE_GUARD_TIERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,24 @@ spawn-capable route: a leaked token over a tunnel still can't reach the spawn.
`check-route-guard-membership` gate enumerates every `route.ts` under the
spawn-capable prefixes and fails CI if any is not classified local-only.

| Prefix / pattern | Why it's local-only | Manage-scope bypassable? |
| ----------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------- |
| `/api/mcp/` | MCP server — spawns stdio bridges + SSE handlers | **Yes** (only one) |
| `/api/cli-tools/runtime/` | CLI tool runtime — executes arbitrary plugin code | No — spawn-capable |
| `/api/services/` | Embedded services (9router/CLIProxy) — `npm install` + spawn | No — spawn-capable |
| `/dashboard/providers/services/` | Reverse proxy to embedded-service UIs | No |
| `/api/copilot/` | Unauthenticated LLM driver — CLI-only by default | Operator opt-in: manage/admin |
| `/api/tools/agent-bridge/` | AgentBridge — spawns MITM server + DNS edits | No — spawn-capable |
| `/api/tools/traffic-inspector/` | Traffic Inspector — http-proxy listener + system proxy | No — spawn-capable |
| `/api/plugins/`, `/api/plugins` | Plugins — load/execute via `worker_threads` + `child_process` | No — spawn-capable |
| `/api/system/version` | Auto-update (POST only; GET/HEAD/OPTIONS exempt) — spawns `git checkout` + `npm install` | No |
| `/api/db-backups/exportAll` | Spawns `tar` for the export archive | No |
| `/api/local/` | 1-click local launchers (Redis today) — spawns podman/docker | No — spawn-capable |
| `/api/headroom/start`, `/stop` | Headroom proxy lifecycle — spawns python CLI / signals PID | No — spawn-capable |
| `/api/oauth/cursor/auto-import` | `execFile("which", ["cursor"])` before importing creds | No |
| `/api/providers/{id}/login` (regex) | Launches a headful Playwright Chromium for web-cookie login | No |
| Prefix / pattern | Why it's local-only | Manage-scope bypassable? |
| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| `/api/mcp/` | MCP server — spawns stdio bridges + SSE handlers | **Yes** (only one) |
| `/api/cli-tools/runtime/` | CLI tool runtime — executes arbitrary plugin code | No — spawn-capable |
| `/api/services/` | Embedded services (9router/CLIProxy) — `npm install` + spawn | No — spawn-capable |
| `/dashboard/providers/services/` | Reverse proxy to embedded-service UIs | No |
| `/api/copilot/` | Unauthenticated LLM driver — CLI-only by default | Operator opt-in: manage/admin |
| `/api/tools/agent-bridge/` | AgentBridge — spawns MITM server + DNS edits | No — spawn-capable |
| `/api/tools/traffic-inspector/` | Traffic Inspector — http-proxy listener + system proxy | No — spawn-capable |
| `/api/plugins/`, `/api/plugins` | Plugins — load/execute via `worker_threads` + `child_process` | No — spawn-capable |
| `/api/system/version` | Auto-update (POST only; GET/HEAD/OPTIONS exempt) — spawns `git checkout` + `npm install` | No |
| `/api/db-backups/exportAll` | Spawns `tar` for the export archive | No |
| `/api/local/` | 1-click local launchers (Redis today) — spawns podman/docker | No — spawn-capable |
| `/api/headroom/start`, `/stop` | Headroom proxy lifecycle — spawns python CLI / signals PID | No — spawn-capable |
| `/api/oauth/cursor/auto-import` | `execFile("which", ["cursor"])` before importing creds | No |
| `/api/providers/{id}/login` (regex) | Launches a headful Playwright Chromium for web-cookie login | No |
| `/api/providers/{id}/refresh-cursor` (regex) | Manual Cursor session renewal — nudges `cursor-agent` (`--list-models`/`status` via `src/lib/cursor/renewal.ts`); the rest of `/api/providers/`, including the generic `/refresh`, intentionally stays remote-reachable | No — spawn-capable |
| `/api/providers/cursor/agent-availability` | Dashboard install-nudge check — spawns `cursor-agent status --format json` via `checkCursorAgentAvailability()`/`getCachedCursorAgentAvailability()` (`src/lib/cursor/renewal.ts`); credential-free response (`{cursorAgentAvailable: boolean}` only) | No — spawn-capable |

**Response on violation:** `403 LOCAL_ONLY`

Expand Down Expand Up @@ -84,15 +86,15 @@ ever be added), and it is deliberately excluded from
carve-out exactly as before; `mcp:connect` is a lower-privilege alternative
for remote MCP-only callers who should not need broad management access.

| Request | Path | Result |
| ------------------------------------------------- | -------------------------- | ------------------- |
| Non-loopback, no Bearer | `/api/mcp/*` | 403 LOCAL_ONLY |
| Non-loopback, Bearer with `manage` scope | `/api/mcp/*` | Allow |
| Non-loopback, Bearer with `mcp:connect` scope | `/api/mcp/*` | Allow |
| Non-loopback, Bearer without `manage`/`mcp:connect` | `/api/mcp/*` | 403 LOCAL_ONLY |
| Non-loopback, Bearer with `mcp:connect` scope | `/api/cli-tools/runtime/*` | 403 LOCAL_ONLY |
| Non-loopback, Bearer with `manage` scope | `/api/cli-tools/runtime/*` | 403 LOCAL_ONLY |
| Loopback, any/no Bearer | any LOCAL_ONLY | Allow (gate passes) |
| Request | Path | Result |
| --------------------------------------------------- | -------------------------- | ------------------- |
| Non-loopback, no Bearer | `/api/mcp/*` | 403 LOCAL_ONLY |
| Non-loopback, Bearer with `manage` scope | `/api/mcp/*` | Allow |
| Non-loopback, Bearer with `mcp:connect` scope | `/api/mcp/*` | Allow |
| Non-loopback, Bearer without `manage`/`mcp:connect` | `/api/mcp/*` | 403 LOCAL_ONLY |
| Non-loopback, Bearer with `mcp:connect` scope | `/api/cli-tools/runtime/*` | 403 LOCAL_ONLY |
| Non-loopback, Bearer with `manage` scope | `/api/cli-tools/runtime/*` | 403 LOCAL_ONLY |
| Loopback, any/no Bearer | any LOCAL_ONLY | Allow (gate passes) |

#### Operator guidance & auditing

Expand All @@ -110,7 +112,14 @@ operator responsibilities remain:
only with a `manage`-scoped API key. The `SPAWN_CAPABLE_PREFIXES` can never be
added to the bypass list — the zod schema rejects them and
`isLocalOnlyBypassableByManageScope` denies them at runtime (defence-in-depth),
which is what the dashboard means by "cannot be made bypassable".
which is what the dashboard means by "cannot be made bypassable". Dynamic-segment
and static-path spawn-capable routes under `/api/providers/` (e.g. `/login`,
`/refresh-cursor`) are covered by the regex-based `SPAWN_CAPABLE_PATTERNS` /
`SPAWN_CAPABLE_PATTERN_ANCESTORS` companion in
`src/shared/constants/spawnCapablePrefixes.ts`, not by the flat
`SPAWN_CAPABLE_PREFIXES` array — the flat array would have to cover the
entire `/api/providers/` prefix to catch them, over-broadening a route tree
remote dashboards legitimately use for provider CRUD.

**Auditing access** — to verify nothing off-host is reaching these routes:

Expand Down
Loading
Loading