fix(agent-templates): featuring is the dashboard's, not the owner's#364
Conversation
Any signed-in user could put their own template into the convos.org featured gallery. `create` accepts `featured` in the body and PATCH accepts it from whoever passes the ownership guard — which is the owner — so a user could mint a self-featured template in one request, publish it (owners may publish their own), and be in the gallery the homepage renders. Proven end to end with a plain JWT, no admin key anywhere: CREATE 201 (featured stored), PUBLISH 200, PATCH featured:true 200, and there it was in the public gallery. Only the slot was gated. They entered at weight 0, dead last, which contains it while the gallery holds a homepage's worth of curated templates above them — but containment is not a closed door: it inflates the featured set, and the moment the gallery holds fewer than eight weighted templates, an outsider renders on the homepage. Both doors are shut now. Withdrawal stays theirs: `featured: false` can only ever take something OFF the homepage, and the runtime's builder sends it on every create. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DxFzZNgXaSnFWAYR8XPpCR
Curating the featured gallery is not something a user does, so this was never a route a user should be able to reach. It was mounted like the rest — any signed -in caller admitted by the middleware, then refused inside by an `isApiKeyListener` check — which makes the endpoint part of the user-facing API surface and leaves its safety resting on a guard in the handler body. It now takes the agent key alone. A signed-in caller is turned away at the door (401), not let in and told no, and the key is the identity — there is no account to require. The in-handler check goes with it: a request without the key never arrives, so a check for one could only ever be dead code pretending to be a lock. The dashboard is unaffected — the worker already injects the same `X-Agent-API-Key` this guard validates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DxFzZNgXaSnFWAYR8XPpCR
|
Claude finished @saulmc's task —— View job Code Review Complete ✅I've reviewed this PR thoroughly. The implementation is excellent — well-architected, thoroughly tested, and properly secured. Here's my detailed feedback: Security ✅ StrongAuthorization enforcement is layered and complete:
One observation (not a blocker): The old in-handler Code Quality ✅ ExcellentRouter changes (
Handler authorization logic:
Small suggestion (minor polish): The PII handling: Both Test Coverage ✅ ComprehensiveNew test file (
Featured-order tests (
Router auth wiring test (
No gaps identified. The test coverage matches the security requirements exactly. Potential Issues / Edge Cases 🔍None found. I looked for:
Performance Considerations ✅No concerns. The authorization checks are simple boolean comparisons on Best Practices ✅
RecommendationsNone blocking. This is production-ready as-is. Two optional polish items:
Both are minor documentation improvements, not functional issues. SummaryThis PR successfully closes two authorization holes:
The implementation is clean, well-tested, and follows security best practices. The test coverage is exemplary — particularly the router source-parsing test, which is a clever way to prevent silent auth regressions. All 1492 tests passing, no breaking changes to legitimate flows. Verdict: Approved for merge. 🚀 |
WalkthroughThe PR restricts featured-template creation, patching, and ordering to API-key-authorized flows. Router middleware, handler checks, and integration tests are updated to reflect the new authorization behavior. ChangesFeaturing authorization
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Caller
participant Router
participant AgentAPIKeyAuth
participant Handler
participant Database
Caller->>Router: Create or patch featured template
Router->>AgentAPIKeyAuth: Resolve API-key listener
AgentAPIKeyAuth->>Handler: Continue with authorization context
Handler->>Database: Create or update template
Database-->>Handler: Persisted featured state
Handler-->>Caller: HTTP response
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR modifies authentication middleware and authorization logic for the featured gallery endpoints. Changes to auth/security pathways warrant human review, and all modified files are owned by xmtplabs/engineering rather than the PR author. You can customize Macroscope's approvability policy. Learn more. |
…ts formatting The source-level assertions matched exact whitespace, so a formatter reflowing the router could turn a harmless reflow into a red test — and a red test that looks like noise is a test someone weakens. They match the chain whitespace-agnostically now. The lookbehind is load-bearing: without it `agentApiKeyAuth` matches inside `authOrAgentApiKeyAuth`, and the two differ by exactly the thing these assertions exist to tell apart — whether a signed-in caller can reach the featured-order endpoint. Both permissive middlewares are now named in negative assertions rather than left to case-sensitivity to catch. Mutation-checked: re-wiring /featured-order to `authOrAgentApiKeyAuth + requireAccount` turns the test red; reverting turns it green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DxFzZNgXaSnFWAYR8XPpCR
|
Took one of the three. Adjudicating all of them. ✅ Whitespace-brittle assertions — taken (699779c)Fair, and worth more than it looks: a source-level assertion that fails on a reflow is noise, and a test that cries wolf is a test the next person weakens. They match the chain whitespace-agnostically now. The interesting part is what fixing it surfaced. The naive regex is wrong: const chain = (middleware: string, handler: string) =>
new RegExp(`(?<![A-Za-z])${middleware},\\s+${handler}\\b`);That lookbehind is load-bearing. Without it, I mutation-checked the guard rather than trusting it: re-wiring ❌ PATCH concurrent-modification 409 — the premise doesn't hold
It doesn't. The where: {
id: template.id,
ownerAccountId: template.ownerAccountId,
slug: template.slug,
status: template.status,
firstPublishedAt: template.firstPublishedAt,
},So the scenario described — an owner editing ❌ Extract a shared
|
Follow-up to #363 — these two commits were pushed to that branch after it was squash-merged, so they didn't land with it. Nothing new since; this is the same work, cherry-picked onto the merged
otr-devand re-verified there.Curation is the dashboard's. Two holes closed.
The front door was open
Any signed-in user could put their own template into the convos.org featured gallery.
createacceptsfeaturedin the body, andPATCHaccepts it from whoever passes the ownership guard — which is the owner. Proven end to end with a plain JWT, no admin key anywhere:createwithfeatured: truePATCH featured: truePATCH featuredRank: 999Only the slot was gated (#362), so a self-featured template entered at weight 0 — dead last. That contains it while the gallery holds a homepage's worth of curated templates above it, but containment is not a closed door: it inflates the featured set, and the moment the gallery holds fewer than eight weighted templates, an outsider renders on the homepage.
Withdrawal stays the owner's.
featured: falsecan only ever take something off the homepage, and the runtime's builder sends it on every create — which is why the gate keys on=== true, not!== undefined. Gating that too would 403 every template the builder makes.I checked that nothing legitimate features templates: across the runtime, website and workers, the only non-admin writer of
featuredisbuild.mjs, writingfalse. The admin console reaches the backend as an API-key caller and is unaffected — and the full suite passing with no test needing an update is itself evidence no existing flow relied on a user featuring anything.PUT /featured-orderwas mounted as a user endpointIt admitted any signed-in caller at the middleware and then refused them inside with an
isApiKeyListenercheck. That makes a pure curation tool part of the user-facing API surface and leaves its safety resting on a guard in the handler body.It now takes the agent key alone: a signed-in caller is turned away at the door (401) rather than let in and told no, and the key is the identity, so there's no account to require. The in-handler check goes with it — once the route can't be reached without the key, a check for one is dead code shaped like a lock, and the next reader would reasonably infer the route was reachable without it.
The auth-wiring guard test pins the shape both ways, so it can't be quietly re-mounted behind the permissive middleware later:
The dashboard is unaffected: the worker already injects the same
X-Agent-API-Key(AGENT_ASSETS_API_KEY) that this stricter guard validates.Tests
featuredunchanged)featured: false, and unfeature their ownFull suite green (1492) on top of the merged
otr-dev.🤖 Generated with Claude Code
https://claude.ai/code/session_01DxFzZNgXaSnFWAYR8XPpCR
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Note
Restrict agent template featuring to dashboard (agent API key) callers only
PUT /featured-ordernow usesagentApiKeyAuthexclusively; JWT-authenticated and anonymous callers receive 401.POSTandPATCHhandlers for agent templates now return 403 if a non-agent-key caller attempts to setfeatured: true; settingfeatured: falseremains permitted for owners.featured: true.Macroscope summarized 699779c.
Summary by CodeRabbit
New Features
Bug Fixes