Skip to content

fix(knowledge-base): enforce published-only context filter + authenticate the internal chat proxy - #49

Open
plsrd wants to merge 3 commits into
mainfrom
fix/knowledge-base-review-bugs
Open

plsrd wants to merge 3 commits into
mainfrom
fix/knowledge-base-review-bugs

Conversation

@plsrd

@plsrd plsrd commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Fixes two reviewer-reported issues in the knowledge-base starter. The external customer-support Agent Context now enforces status == "published" in its groqFilter instead of only asking for it in prose, so the customer-facing chat agent and the help center's search apply the same rule. The internal chat proxy (dashboard-server) now requires a shared-secret bearer token on /api/chat, fails closed when that secret is unset, and never defaults CORS to *.

Changes

1. customer-support context could read draft and archived help content

What was wrong. The external context's groqFilter was _type in ["helpArticle", "faq", "product", "topic"]. Its instructions said "answer only from published help articles and FAQs", but instructions are advice to the model, not a boundary. Any helpArticle/faq with status set to draft or archived was retrievable by the public chat agent, while app/sanity/search.ts filters on status == "published". The shipped seed data already demonstrates it: helpArticle.data-exports is seeded with status: "draft" and was visible to the external agent.

What changed.

  • studio/scripts/generate-seed.ts: the filter is now _type in ["helpArticle", "faq", "product", "topic"] && (_type in ["product", "topic"] || status == "published"). helpArticle and faq are the only external types that carry statusField (checked in studio/schemaTypes/); product and topic have no status and are kept visible via the _type in [...] || branch rather than being silently dropped by a status clause. The expression is a top-level && so it composes safely with the agent's own query.
  • No !(_id in path("drafts.**")) clause was added: the Context MCP reference documents that the endpoint queries the published perspective by default, so Sanity drafts are already excluded. The problem here is the workflow status field on published documents, and the seed generator comment now says so.
  • The context's ## Content filter instructions text was updated to match, and studio/seed/data.ndjson was regenerated with pnpm --filter studio seed:generate (deterministic; only the agentContext.external line changed).
  • team-kb was left alone on purpose: staff are meant to see drafts and archived content, and its instructions already tell the agent to warn about stale material.

2. Internal chat proxy had no authentication

What was wrong. dashboard-server/src/index.ts holds SANITY_READ_TOKEN_INTERNAL and talks to the team-kb context (policies, playbooks, HR, Security & Compliance). Its only gate was cors({origin: process.env.DASHBOARD_ORIGIN ?? '*'}), which defaults to a wildcard and never stops a direct request. Anyone who could reach the port could POST to /api/chat and query the internal knowledge base.

What changed.

  • dashboard-server/src/index.ts: /api/* now requires Authorization: Bearer <DASHBOARD_API_TOKEN> (constant-time compare). Missing/wrong token returns 401; if DASHBOARD_API_TOKEN is unset the server logs a clear error at startup and every request returns 500 — fail-closed in every environment, not only production. CORS accepts only DASHBOARD_ORIGIN; it falls back to http://localhost:3333 in development and to no origin at all in production (with a startup error), never *. Authorization was added to allowHeaders, and the auth middleware runs after cors() so preflights still succeed.
  • dashboard/src/components/ChatPanel.tsx sends the header from SANITY_APP_DASHBOARD_API_TOKEN; dashboard/src/env.d.ts types it and explains why this one browser-bundled value is deliberate.
  • studio/scripts/bootstrap.ts gains step 9d: generates the secret locally (randomBytes(32)), reuses an existing one on re-run, and writes it to both dashboard-server/.env.local and dashboard/.env.local so pnpm bootstrap && pnpm dev still works end to end.
  • .env.example files, README (security model + env vars), AGENT.md, and the add-scoped-agent-surface skill document the new variable, the CORS requirement, and that this is a minimal gate a real deployment should replace with its own SSO/auth (or by verifying the caller's Sanity session).

Testing

Run from knowledge-base/ unless noted:

  • pnpm run format:check — pass (after npx oxfmt . from the repo root; unrelated reformatting it produced outside knowledge-base/ was reverted to keep this PR scoped)
  • pnpm run lint — pass
  • SANITY_STUDIO_PROJECT_ID=abc12345 SANITY_STUDIO_DATASET=production pnpm run typecheck — pass for all 5 workspaces (typegen needs the project ID that CI injects; without it sanity schema extract fails before my change too)
  • pnpm run validate — "Template validated successfully"
  • pnpm --filter studio seed:generate — regenerated ndjson, diff limited to the one context document
  • Smoke test of dashboard-server with two live instances (node --import tsx src/index.ts):
    • token set, dev mode: no header -> 401; wrong token -> 401; correct token -> passes auth and hits the pre-existing SANITY_AGENT_CONTEXT_URL_INTERNAL is not set 500; preflight from http://localhost:3333 gets access-control-allow-origin, preflight from another origin gets none
    • token unset, NODE_ENV=production: startup logs both config errors; any request -> 500 DASHBOARD_API_TOKEN is not set; preflight from localhost gets no access-control-allow-origin
  • Not verified live: the new groqFilter against a deployed Context MCP endpoint, the dashboard UI round-trip with a real Anthropic key, and a full pnpm bootstrap run (no Sanity project in this environment). The filter uses only standard GROQ operators listed in the Context MCP reference.

🤖 Generated with Claude Code

plsrd and others added 3 commits September 3, 2026 12:29
…content

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… proxy gate

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@plsrd
plsrd had a problem deploying to agentic-localization September 3, 2026 19:29 — with GitHub Actions Failure
@plsrd
plsrd deployed to knowledge-base September 3, 2026 19:29 — with GitHub Actions Active
@plsrd
plsrd deployed to commerce-pdp-management September 3, 2026 19:29 — with GitHub Actions Active
@plsrd
plsrd deployed to ai-shopping-assistant September 3, 2026 19:29 — with GitHub Actions Active
@plsrd
plsrd deployed to commerce-plp-management September 3, 2026 19:29 — with GitHub Actions Active
@plsrd
plsrd had a problem deploying to agentic-localization September 3, 2026 19:29 — with GitHub Actions Error
@plsrd
plsrd deployed to knowledge-base September 3, 2026 19:29 — with GitHub Actions Active
@plsrd
plsrd deployed to commerce-pdp-management September 3, 2026 19:29 — with GitHub Actions Active
@plsrd
plsrd deployed to commerce-plp-management September 3, 2026 19:29 — with GitHub Actions Active
@vercel

vercel Bot commented Sep 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agentic-localization Ready Ready Preview Sep 3, 2026 7:31pm UTC
ai-shopping-assistant Ready Ready Preview Sep 3, 2026 7:31pm UTC
commerce-pdp-starter Ready Ready Preview Sep 3, 2026 7:31pm UTC
commerce-plp-starter Ready Ready Preview Sep 3, 2026 7:31pm UTC
content-analytics-starter Ready Ready Preview Sep 3, 2026 7:31pm UTC

Request Review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Cursor Bugbot was not present after the first check poll, so that signal was skipped; no applicable approval policy required human review. Reviewers were not assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Approver

This branch was successfully deployed

9 active deployments
Preview – ai-shopping-assistant — 757aa30a Deployed Sep 3, 2026 by vercel[bot]
Preview – content-analytics-starter — 757aa30a Deployed Sep 3, 2026 by vercel[bot]
Preview – commerce-plp-starter — 757aa30a Deployed Sep 3, 2026 by vercel[bot]
Preview – agentic-localization — 757aa30a Deployed Sep 3, 2026 by vercel[bot]
Preview – commerce-pdp-starter — 757aa30a Deployed Sep 3, 2026 by vercel[bot]
agentic-localization — 757aa30a Deployed Sep 3, 2026 by plsrd via agentic-localization (Node 22) #195
knowledge-base — 757aa30a Deployed Sep 3, 2026 by plsrd via knowledge-base (Node 20) #195
ai-shopping-assistant — 757aa30a Deployed Sep 3, 2026 by plsrd via ai-shopping-assistant #195
commerce-pdp-management — 757aa30a Deployed Sep 3, 2026 by plsrd via commerce-pdp-management (Node 22) #195
commerce-plp-management — 757aa30a Deployed Sep 3, 2026 by plsrd via commerce-plp-management (Node 22) #195
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