Kind: Service
Source: atloria-monorepo/apps/api/src/technical-docs/playground-proxy.service.ts
Server-side try-it proxy (Tier 3.4), extracted from the technical-docs MCP controller so the PUBLIC docs site can share the exact same execution path. Lets the playground call APIs that block CORS. Two callers:
- owner playground (technical-docs//playground/proxy): open target, SSRF-guarded.
- public playground (public/p//playground/proxy): additionally PINNED via
allowedHoststo the hosts the project itself declares (spec servers + Live-API base), so an anonymous visitor can't use the proxy as a generic fetch service.
PlaygroundProxyService executes server-side API requests for the technical documentation playground, allowing browser-based users to call APIs that would otherwise be blocked by CORS. It is shared by owner and public playground routes, applying SSRF protections for all requests and additional allowedHosts pinning for anonymous public requests.
Methods
| Method | Signature | Returns |
|---|---|---|
proxyRequest | proxyRequest(target: PlaygroundProxyTarget, opts: { allowedHosts?: string[] }) | Promise<PlaygroundProxyResult> |
Where it refuses work
PlaygroundProxyServicestops the work with an early return whenbytes > MAX_PROXY_UPLOAD_BYTES, in 2 places.PlaygroundProxyServicestops the work with an early return when!allowed.has(host).PlaygroundProxyServicestops the work with an early return whenprivateHost.PlaygroundProxyServicestops the work with an early return whenresolved.some((a) => isPrivateIp(a.address)).PlaygroundProxyServicestops the work with an early return when'error' in built.PlaygroundProxyServicestops the work with an early return whenbytes.byteLength > MAX_PROXY_UPLOAD_BYTES.
When something fails
PlaygroundProxyServicehandles failure in 4 places: it turns it into a return value in 3, and discards it silently in 1. A failure discarded silently leaves no trace for whoever debugs this later.
Diagram
mermaidsequenceDiagram participant Browser as Playground Browser participant Controller as Playground Controller participant Proxy as PlaygroundProxyService participant Guard as SSRF / Host Validation participant API as Target API Browser->>Controller: Submit playground request Controller->>Proxy: proxyRequest(request, options) Proxy->>Guard: Validate target URL alt Owner playground Guard-->>Proxy: SSRF-safe target accepted else Public playground Guard->>Guard: Verify host is in allowedHosts Guard-->>Proxy: SSRF-safe pinned host accepted end Proxy->>API: Execute server-side HTTP request API-->>Proxy: HTTP response Proxy-->>Controller: PlaygroundProxyResult Controller-->>Browser: Return response for display
Usage
tsimport { Injectable } from '@nestjs/common';
import { PlaygroundProxyService } from './playground-proxy.service';
@Injectable()
export class PlaygroundExecutionService {
constructor(
private readonly playgroundProxyService: PlaygroundProxyService,
) {}
async executePublicRequest() {
return this.playgroundProxyService.proxyRequest({
url: 'https://api.example.com/v1/users',
method: 'GET',
headers: {
accept: 'application/json',
},
allowedHosts: ['api.example.com'],
});
}
}
AI Coding Instructions
- Route both owner and public playground execution through
proxyRequest()so they share identical request handling and response formatting. - Always preserve SSRF validation when adding request options, redirects, URL parsing, or new HTTP methods.
- For public playground callers, derive
allowedHostsonly from project-controlled sources such as OpenAPI spec servers and the configured Live API base URL. - Do not accept arbitrary client-provided host allowlists; doing so would turn the public endpoint into a generic anonymous proxy.
- Keep controller code focused on authentication, project lookup, and caller context; place outbound request and validation behavior in this service.
Referenced By
PublicProjectController(DEPENDS_ON)TechnicalDocsMcpController(DEPENDS_ON)TechnicalDocsModule(MODULE_PROVIDES)TechnicalDocsModule(MODULE_EXPORTS)
Was this page helpful?