Require authentication on all rag-analytics routes - #208
Open
collinschreyer-dev wants to merge 2 commits into
Open
Require authentication on all rag-analytics routes#208collinschreyer-dev wants to merge 2 commits into
collinschreyer-dev wants to merge 2 commits into
Conversation
23 of the 24 /api/rag-analytics/* routes were registered with no middleware at all. SRT applies authentication per route rather than through a blanket middleware, and these were never given any, so they were reachable unauthenticated from the internet. The handlers perform no authorization of their own. This exposed more than analytics reads. saveStage, deleteStage, savePipeline and deletePipeline are unauthenticated writes to pipeline configuration, and execute-pipeline, execute-stage, test-completion, test-embeddings, generate-prompt and package-synthesis all invoke LLM and embedding backends, so an anonymous caller could both alter pipeline config and spend model budget. Guards follow what the UI already enforces on the client side. Every analytics page is behind AdminGuardFn in app.routing.ts, so those routes get token(), admin_only(). The two endpoints the regular home page calls, playground/analyze and playground/package-synthesis, get token() only, since requiring admin there would break the normal user workflow. The cause of the gap is visible in how the calls are written. art-lookup was the one guarded route and the one called through Angular HttpClient, which attaches a bearer token via TokenInterceptor. Every unguarded route was called with raw fetch(), which bypasses the interceptor. The companion srt-ui change attaches the header explicitly at those call sites. Verified no tests and no other services call these endpoints. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both declared 2048M while the running apps were on 4G, so the file had drifted from reality. The image is around 3.3G, mostly the Python virtualenv, and does not unpack inside 2048M. Pushing production with its own manifest therefore lowered the quota below what the image needs and crash-looped the app: uncompressed layer size exceeds quota disk limit is smaller than volume size That took the production API down for roughly two minutes on 2026-09-01 until the quota was restored with cf scale. Staging carried the same fault and was only spared because it had been deployed with cf restage, which does not apply the manifest. Both now declare 4096M, matching dev and matching what the apps actually run on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
23 of the 24
/api/rag-analytics/*routes were registered with no middleware. SRT authenticates per route rather than through a blanket middleware, so these were reachable unauthenticated. The handlers do no authorization of their own.Must merge together with GSA/srt-ui#191. On its own, this change breaks the analytics pages.
Why it matters beyond analytics reads
POST /stages,DELETE /stages/:idPOST /pipelines,DELETE /pipelines/:idPOST /playground/execute-pipeline,/execute-stagePOST /playground/test-completion,/test-embeddingsGET /agency-leaderboard,/tri-state,/posture, ...So this was an anonymous config-write and model-spend surface, not only a data read.
Guards chosen
Matched to what the UI already enforces client-side in
app.routing.ts:token(), admin_only()for 21 routes. Everyanalytics/*page is behindAdminGuardFn.token()only forplayground/analyzeandplayground/package-synthesis. Both are called by the regularhomepage, which is behindAuthGuardFn. Requiring admin would break the normal user workflow.art-lookupalready hadtoken()and is unchanged.Root cause
art-lookupwas the only guarded route and also the only one called via AngularHttpClient, which attaches a bearer token throughTokenInterceptor. Every unguarded route was called with rawfetch(), which bypasses the interceptor. Adding a guard would have 401'd them, so none was added.Verification
node --checkpasses.manual_upload_pipelinematches are an April reference bundle, not a deployed service.🤖 Generated with Claude Code