Repository navigation
fix(knowledge-base): enforce published-only context filter + authenticate the internal chat proxy - #49
Open
plsrd wants to merge 3 commits into
Open
fix(knowledge-base): enforce published-only context filter + authenticate the internal chat proxy#49plsrd wants to merge 3 commits into
plsrd wants to merge 3 commits into
Conversation
…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
had a problem deploying
to
agentic-localization
September 3, 2026 19:29 — with
GitHub Actions
Failure
plsrd
had a problem deploying
to
agentic-localization
September 3, 2026 19:29 — with
GitHub Actions
Error
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What this PR does
Fixes two reviewer-reported issues in the
knowledge-basestarter. The externalcustomer-supportAgent Context now enforcesstatus == "published"in itsgroqFilterinstead 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-supportcontext could read draft and archived help contentWhat was wrong. The external context's
groqFilterwas_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. AnyhelpArticle/faqwithstatusset todraftorarchivedwas retrievable by the public chat agent, whileapp/sanity/search.tsfilters onstatus == "published". The shipped seed data already demonstrates it:helpArticle.data-exportsis seeded withstatus: "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").helpArticleandfaqare the only external types that carrystatusField(checked instudio/schemaTypes/);productandtopichave 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.!(_id in path("drafts.**"))clause was added: the Context MCP reference documents that the endpoint queries thepublishedperspective by default, so Sanity drafts are already excluded. The problem here is the workflowstatusfield on published documents, and the seed generator comment now says so.## Content filterinstructions text was updated to match, andstudio/seed/data.ndjsonwas regenerated withpnpm --filter studio seed:generate(deterministic; only theagentContext.externalline changed).team-kbwas 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.tsholdsSANITY_READ_TOKEN_INTERNALand talks to theteam-kbcontext (policies, playbooks, HR, Security & Compliance). Its only gate wascors({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/chatand query the internal knowledge base.What changed.
dashboard-server/src/index.ts:/api/*now requiresAuthorization: Bearer <DASHBOARD_API_TOKEN>(constant-time compare). Missing/wrong token returns401; ifDASHBOARD_API_TOKENis unset the server logs a clear error at startup and every request returns500— fail-closed in every environment, not only production. CORS accepts onlyDASHBOARD_ORIGIN; it falls back tohttp://localhost:3333in development and to no origin at all in production (with a startup error), never*.Authorizationwas added toallowHeaders, and the auth middleware runs aftercors()so preflights still succeed.dashboard/src/components/ChatPanel.tsxsends the header fromSANITY_APP_DASHBOARD_API_TOKEN;dashboard/src/env.d.tstypes it and explains why this one browser-bundled value is deliberate.studio/scripts/bootstrap.tsgains step 9d: generates the secret locally (randomBytes(32)), reuses an existing one on re-run, and writes it to bothdashboard-server/.env.localanddashboard/.env.localsopnpm bootstrap && pnpm devstill works end to end..env.examplefiles, README (security model + env vars), AGENT.md, and theadd-scoped-agent-surfaceskill 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 (afternpx oxfmt .from the repo root; unrelated reformatting it produced outsideknowledge-base/was reverted to keep this PR scoped)pnpm run lint— passSANITY_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 itsanity schema extractfails before my change too)pnpm run validate— "Template validated successfully"pnpm --filter studio seed:generate— regenerated ndjson, diff limited to the one context documentdashboard-serverwith two live instances (node --import tsx src/index.ts):401; wrong token ->401; correct token -> passes auth and hits the pre-existingSANITY_AGENT_CONTEXT_URL_INTERNAL is not set500; preflight fromhttp://localhost:3333getsaccess-control-allow-origin, preflight from another origin gets noneNODE_ENV=production: startup logs both config errors; any request ->500 DASHBOARD_API_TOKEN is not set; preflight from localhost gets noaccess-control-allow-origingroqFilteragainst a deployed Context MCP endpoint, the dashboard UI round-trip with a real Anthropic key, and a fullpnpm bootstraprun (no Sanity project in this environment). The filter uses only standard GROQ operators listed in the Context MCP reference.🤖 Generated with Claude Code