Full application build: landing page, auth, chat with MCP tool use, harness management, and OAuth#8
Full application build: landing page, auth, chat with MCP tool use, harness management, and OAuth#8cole-ramer merged 162 commits intomainfrom
Conversation
… Header component
…ing - should've been in the staging already
…ng related deps to apps/web
…d per conversation, add usersettings logic
Feat/landing page
Feat/login and onboarding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
skills.sh implementation
…or switching back to the default.
The skills merge overwrote deployment fixes. This restores: - frontend-cd.yml: use --name flag instead of --env for correct worker targeting - main.py: add harness.nz and workers.dev CORS origins - wrangler.jsonc: add public env vars for both staging and production Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deployment fixes: CORS, wrangler vars, CD worker targeting
…en creating/editing a Harness
…ness into feat/model_switching
…asis or the entire Harness itself.
Feat/model switching
…laim
The frontend was calling getToken() without a template, which returns
Clerk's default session token (no email claim). The backend's
extract_princeton_netid() needs the email to derive the netid.
Fix: use getToken({ template: "convex" }) which includes the email
claim configured in the Clerk JWT template.
Also add debug logging when netid extraction fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix: netid not passing to Junction Engine
The JWT email is the user's primary email (e.g. Gmail), not necessarily their Princeton email. When the JWT email isn't @princeton.edu, the backend now calls the Clerk Backend API to check all verified emails on the account. - Add resolve_princeton_netid() async function with Clerk API fallback - Add 5-minute TTL cache so email removals propagate without restart - Requires CLERK_SECRET_KEY env var on the backend Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix netid: Clerk API fallback with TTL cache
|
@claude, review this. |
|
Claude finished @cole-ramer's task in 6m 57s —— View job PR ReviewThis is an incremental review on top of the prior Claude review from March 22. I re-read the key files and note what's been fixed, what remains open, and flag new issues introduced by the additions since then (sandbox tools, skills system, etc.). Tasks
What's Been Fixed
🔴 Critical — Still Open from Previous Review1. JWT issuer read from unverified token ( unverified_claims = jwt.decode(token, options={"verify_signature": False})
issuer = unverified_claims.get("iss", "")
jwks = await _get_jwks(http_client, issuer)The JWKS URL is still derived from the attacker-controlled 2. Audience verification disabled ( options={"verify_aud": False}Tokens issued for any Clerk application are accepted. Fix this → 3. window.opener.postMessage({json.dumps(message_data)}, "*");The OAuth result (including access token provenance) is broadcast to any origin. Pass 🟠 High — Still Open from Previous Review4.
5. OAuth PKCE state in-process only (
🟠 High — New Findings6. Redirect URI built from unvalidated forwarded_proto = request.headers.get("x-forwarded-proto", request.url.scheme)
forwarded_host = request.headers.get("x-forwarded-host") or request.headers.get("host", "localhost:8000")
redirect_uri = f"{forwarded_proto}://{forwarded_host}/api/mcp/oauth/callback"If the FastAPI server is directly internet-exposed (without a trusted reverse proxy stripping these headers), a client can inject an arbitrary 7. Skill content fetching allows arbitrary GitHub requests (
🟡 Medium — Still Open from Previous Review8. JWKS cache has no TTL (
9. Duplicate model IDs in {"id": "anthropic/claude-sonnet-4", "name": "Claude Sonnet 4"},
{"id": "anthropic/claude-sonnet-4", "name": "Claude Sonnet 4 (Thinking)"},Two entries share the same 🟡 Medium — New Findings10. GitHub OAuth PKCE code_verifier generated but never sent ( code_verifier, code_challenge = _generate_pkce_pair()For the standalone GitHub OAuth flow, 11. The tools list cache is not user-scoped. For most servers this is fine (tool schemas are the same for all users), but for any server that returns per-user tool definitions, user A's cached tools will be served to user B. Low risk currently, worth a comment. 12. No message content size limit ( The full 🔵 Low / Nits13. def _extract_summary(detail: str, max_chars: int = 300) -> str:
import re
14. Same pattern — 15. Same pattern — move to top-level imports. 16. Bearer tokens stored in plaintext in Convex (
17. userId: v.optional(v.string()),
18. Harness delete doesn't cascade to conversations ( Deleting a harness leaves conversations with a stale SummaryThe codebase has grown considerably and remains well-structured. The session cache isolation fix (item 5 from the prior review) is a meaningful improvement. The two highest-priority unfixed issues remain JWT issuer pinning (#1) and postMessage wildcard (#3). The new redirect URI from unvalidated forwarded headers (#6) is worth fixing before deploying behind anything less than a fully trusted reverse proxy, and the skill content SSRF vector (#7) deserves a sanity-check on the |
|
@claude, review this, focus on notifying REAL issues |
|
Claude finished @cole-ramer's task in 1m 43s —— View job PR ReviewTasks
What's Been Fixed Since Prior Reviews
🔴 Critical — Still Open1. Audience verification disabled ( options={"verify_aud": False}This accepts JWTs issued for any Clerk application, not just this one. A token from a completely different Clerk app (e.g. a user's personal project) will pass verification. Set 2. window.opener.postMessage({json.dumps(message_data)}, "*");The OAuth result — including the server URL that was just authorized — is broadcast to every open window/tab, regardless of origin. Any malicious page the user has open can intercept it and learn which MCP servers they authenticated with. Replace 🟠 High — Still Open3.
Fix: add an ownership check in the FastAPI route before the agentic loop starts, or add 4. OAuth PKCE state stored in process memory only ( _pending_oauth: dict[str, "PendingOAuth"] = {}Under multi-worker deployments ( 🟡 Medium5. Duplicate model IDs in {"id": "anthropic/claude-sonnet-4", "name": "Claude Sonnet 4"},
{"id": "anthropic/claude-sonnet-4", "name": "Claude Sonnet 4 (Thinking)"},
{"id": "anthropic/claude-opus-4", "name": "Claude Opus 4"},
{"id": "anthropic/claude-opus-4", "name": "Claude Opus 4 (Thinking)"},Any frontend code that deduplicates by 🔵 Nits (3 in-function imports that should be hoisted)
SummaryItems 1 (audience verification) and 2 (postMessage wildcard) are the two unfixed security issues from prior reviews. Item 3 (conversation ownership) is a real data isolation bug — the |
|
Investigating the issues flagged, they appear not to be real issues (sorry if I am wrong) |
Summary
This PR represents the full initial build of Harness, bringing the application from skeleton to a functional AI chat
platform with MCP (Model Context Protocol) integration. Key additions include:
creation, TanStack Router auth guards for protected routes
visualization, chat interrupts, auto-scroll, and per-conversation state management
decoupled harnesses from conversations with last-used tracking
checks with UI status indicators, and error surfacing to frontend
token storage and refresh via Convex
service, structured logging, config validation, connection pooling via
httpx.AsyncClientOAuth tokens, and user settings; seed data; composite indexes for recency queries
management, Geist font, toast notifications, dark theme with Clerk appearance customization
thinking model variants with reasoning parameter support
Changes
packages/backendin favor ofpackages/convex-backendHeadercomponent and Clerk integration files, replaced with route-level auth guards and chromeless routesupport
PRs included