Summary
Static validation confirms an expensive API abuse path in /api/news/v1/summarize-article: anonymous/basic-session callers can trigger LLM provider calls on cache misses. The current premium check only controls the optional systemAppend field and does not gate the LLM call itself. The route is also missing from the endpoint-specific fail-closed rate-limit registry.
Validated against revision 52cc48fa2d91d5851cb04db77bc5424794c7ccc9.
Affected surface
- Gateway-routed Sebuf endpoint:
/api/news/v1/summarize-article
- Source: user-controlled
headlines, bodies, geoContext, mode, provider, variant, and lang
- Sink: paid/expensive outbound LLM provider request inside
summarizeArticle()
- Policy basis:
SECURITY.md lists Edge function abuse controls and rate limiting as security considerations
Static evidence
api/news/v1/[rpc].ts routes the News service through createDomainGateway(...).
server/worldmonitor/news/v1/handler.ts exposes summarizeArticle.
server/worldmonitor/news/v1/summarize-article.ts calls isCallerPremium(ctx.request), but only uses the result to decide whether req.systemAppend is honored:
const isPremium = await isCallerPremium(ctx.request);
const systemAppend = isPremium && typeof req.systemAppend === 'string' ? req.systemAppend : '';
- The LLM call inside
cachedFetchJsonWithMeta(..., async () => { ... fetch(apiUrl, ...) }) is unconditional on cache miss when provider credentials exist and the provider health gate passes.
- The cache key includes user-controlled headline/body/context inputs, so callers can force cache misses with unique headline text.
src/shared/premium-paths.ts does not include /api/news/v1/summarize-article.
server/_shared/entitlement-check.ts does not include /api/news/v1/summarize-article in ENDPOINT_ENTITLEMENTS.
server/_shared/rate-limit.ts includes /api/news/v1/summarize-article-cache, but not /api/news/v1/summarize-article, in ENDPOINT_RATE_POLICIES.
api/wm-session.js intentionally mints anonymous wms_ session tokens behind a broad per-IP cap; those tokens satisfy the gateway's basic non-premium auth path.
Compensating controls checked
- The sibling
api/chat-analyst.ts is Pro-gated with isCallerPremium() and calls checkRateLimit(..., { failClosed: true }) before LLM work. That stronger pattern is not applied here.
- The endpoint uses a 24h cache, sanitization, max headline/body sizes, and provider health checks, but those do not prevent unbounded unique cache misses.
Exploit sketch
- Call
/api/wm-session to obtain a basic anonymous session cookie.
- Call
/api/news/v1/summarize-article with a valid provider and unique headline/body text per request.
- Each unique input generates a fresh cache key and can trigger a fresh provider call.
- The route inherits only the global per-IP fallback limiter, not a fail-closed LLM-specific policy.
No dynamic exploit probe was run; this is static validation only.
Recommended fix
Pick one product decision and enforce it consistently:
- If article summarization is premium: add
/api/news/v1/summarize-article to PREMIUM_RPC_PATHS and/or ENDPOINT_ENTITLEMENTS, and require a Pro/API entitlement before the LLM fetcher runs.
- If some summarization remains free: add an explicit endpoint policy for
/api/news/v1/summarize-article with a low budget and fail-closed semantics, and consider a per-session/user/IP cache-miss budget.
- Keep
systemAppend premium-only regardless.
Acceptance criteria
- Anonymous
wms_ sessions cannot drive unlimited LLM cache misses.
- The endpoint has an explicit
ENDPOINT_RATE_POLICIES entry or an equivalent scoped limiter with fail-closed behavior.
- Add focused tests for anonymous/basic-session behavior, Pro behavior, and Redis-degraded rate-limit behavior.
- Ensure the cache-read sibling
/api/news/v1/summarize-article-cache remains a read-only cache lookup and does not call providers.
Summary
Static validation confirms an expensive API abuse path in
/api/news/v1/summarize-article: anonymous/basic-session callers can trigger LLM provider calls on cache misses. The current premium check only controls the optionalsystemAppendfield and does not gate the LLM call itself. The route is also missing from the endpoint-specific fail-closed rate-limit registry.Validated against revision
52cc48fa2d91d5851cb04db77bc5424794c7ccc9.Affected surface
/api/news/v1/summarize-articleheadlines,bodies,geoContext,mode,provider,variant, andlangsummarizeArticle()SECURITY.mdlists Edge function abuse controls and rate limiting as security considerationsStatic evidence
api/news/v1/[rpc].tsroutes the News service throughcreateDomainGateway(...).server/worldmonitor/news/v1/handler.tsexposessummarizeArticle.server/worldmonitor/news/v1/summarize-article.tscallsisCallerPremium(ctx.request), but only uses the result to decide whetherreq.systemAppendis honored:cachedFetchJsonWithMeta(..., async () => { ... fetch(apiUrl, ...) })is unconditional on cache miss when provider credentials exist and the provider health gate passes.src/shared/premium-paths.tsdoes not include/api/news/v1/summarize-article.server/_shared/entitlement-check.tsdoes not include/api/news/v1/summarize-articleinENDPOINT_ENTITLEMENTS.server/_shared/rate-limit.tsincludes/api/news/v1/summarize-article-cache, but not/api/news/v1/summarize-article, inENDPOINT_RATE_POLICIES.api/wm-session.jsintentionally mints anonymouswms_session tokens behind a broad per-IP cap; those tokens satisfy the gateway's basic non-premium auth path.Compensating controls checked
api/chat-analyst.tsis Pro-gated withisCallerPremium()and callscheckRateLimit(..., { failClosed: true })before LLM work. That stronger pattern is not applied here.Exploit sketch
/api/wm-sessionto obtain a basic anonymous session cookie./api/news/v1/summarize-articlewith a valid provider and unique headline/body text per request.No dynamic exploit probe was run; this is static validation only.
Recommended fix
Pick one product decision and enforce it consistently:
/api/news/v1/summarize-articletoPREMIUM_RPC_PATHSand/orENDPOINT_ENTITLEMENTS, and require a Pro/API entitlement before the LLM fetcher runs./api/news/v1/summarize-articlewith a low budget and fail-closed semantics, and consider a per-session/user/IP cache-miss budget.systemAppendpremium-only regardless.Acceptance criteria
wms_sessions cannot drive unlimited LLM cache misses.ENDPOINT_RATE_POLICIESentry or an equivalent scoped limiter with fail-closed behavior./api/news/v1/summarize-article-cacheremains a read-only cache lookup and does not call providers.