ZTA Developer Guide
ZTA Developer Guide
Practical guide for contributors working on the bloqr-backend. Follow these patterns to maintain Zero Trust Architecture compliance.
Adding a New Worker Endpoint
Every new endpoint must follow this pattern:
// 1. Auth gate — verify before any business logicconst authGuard = requireAuth(authContext);if (authGuard) return authGuard;
// 2. Rate limiting — enforce per-tier limitsconst rateLimit = await checkRateLimitTiered(env, ip, authContext);if (!rateLimit.allowed) { analytics.trackSecurityEvent({ eventType: 'rate_limit', path: '/your/endpoint', method: request.method, clientIpHash: AnalyticsService.hashIp(ip), tier: authContext.tier, reason: 'rate_limit_exceeded', }); return Response.json({ error: 'Rate limit exceeded' }, { status: 429 });}
// 3. Validate input with Zodconst parsed = YourInputSchema.safeParse(await request.json());if (!parsed.success) { return Response.json({ error: 'Invalid input', details: parsed.error.issues }, { status: 400 });}
// 4. Execute business logicconst result = await doWork(parsed.data);
// 5. Response — CORS is applied automatically by the fetch() wrapperreturn Response.json(result);CORS: Which Function to Use
| Scenario | Function | Returns |
|---|---|---|
| Public read-only endpoint | getPublicCorsHeaders() | Access-Control-Allow-Origin: * |
| Authenticated/write endpoint | getCorsHeaders(request, env) | Origin-reflected from allowlist |
| OPTIONS preflight | handleCorsPreflight(request, env) | Full preflight response |
Never add Access-Control-Allow-Origin: * directly. Import from worker/utils/cors.ts.
D1 Database Queries
Always use parameterized queries:
// ✅ Correctconst result = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first();
// ❌ Wrong — SQL injection riskconst result = await env.DB.prepare(`SELECT * FROM users WHERE id = '${userId}'`).first();Frontend API Consumption
All API responses must be Zod-validated before use:
import { validateResponse } from '../schemas/api-responses';import { YourResponseSchema } from '../schemas/api-responses';
// In an Observable pipereturn this.http.get<unknown>(url).pipe( map(raw => validateResponse(YourResponseSchema, raw, 'YourService.method')));
// With async/awaitconst raw = await firstValueFrom(this.http.get<unknown>(url));return validateResponse(YourResponseSchema, raw, 'YourService.method');Adding Zod Schemas
Add new schemas to frontend/src/app/schemas/api-responses.ts:
export const NewResponseSchema = z.object({ success: z.boolean(), data: z.object({ // your fields }),}).passthrough(); // Allow extra fields for forward compatibilitySecrets Management
| Value Type | Storage | Example |
|---|---|---|
| Secret / credential | wrangler secret put | CLERK_SECRET_KEY, ADMIN_KEY |
| Public config | wrangler.toml [vars] | CORS_ALLOWED_ORIGINS, COMPILER_VERSION |
| Local dev | .env.local (gitignored) | All values |
Never commit secrets to source or put them in wrangler.toml [vars].
Auth Token Management (Frontend)
// ✅ Correct — use Clerk SDKconst token = await clerk.session?.getToken();
// ❌ Wrong — never store tokens manuallylocalStorage.setItem('token', jwt);The HTTP interceptor (auth.interceptor.ts) automatically attaches Bearer tokens. Do not attach tokens manually in service code.
PR Checklist
Every PR touching worker/ or frontend/ must complete the ZTA checklist in the PR template. The CI zta-lint workflow runs automated checks, but the checklist covers items that require human review.
PRs touching docs/api/openapi.yaml or resource endpoint handlers (endpoints with path parameters like /{id}) must also complete the API Shield / Vulnerability Scanner section of the PR template.
BOLA Prevention (Broken Object Level Authorization)
The API Shield Vulnerability Scanner detects BOLA — accessing another user’s resources via ID manipulation. Every resource endpoint must scope queries to the authenticated user:
// ✅ Correct — query scoped to authenticated userconst row = await env.DB .prepare('SELECT * FROM api_keys WHERE user_id = ? AND id = ?') .bind(authContext.userId, keyId) .first();
// Return 404 (not 403) if not found — avoid leaking resource existenceif (!row) { return JsonResponse.notFound('Not found');}
// ❌ Wrong — unscoped lookup; any user can access any key by IDconst row = await env.DB .prepare('SELECT * FROM api_keys WHERE id = ?') .bind(keyId) .first();Why 404 and not 403? Returning 403 when a resource exists (but belongs to another user) leaks the existence of that resource. Always return 404 for missing-or-unauthorized resources on user-scoped endpoints.
See API Shield Vulnerability Scanner for the full guide and CI setup.
Common Mistakes
- Forgetting
requireAuth()— Every write endpoint needs an auth gate - Using
Response.json()with manual CORS — Thefetch()wrapper adds CORS; don’t duplicate - Flushing empty objects in tests — Zod validation means mock data must match schemas
- String interpolation in SQL — Always
.prepare().bind() - Storing auth state in components — Use
ClerkServicesignals - Unscoped resource queries — Always bind
authContext.userIdin WHERE clauses on user-owned tables
Bloqr AI™ — The privacy you didn't know you needed.
© 2026 Bloqr AI™, a trademark of Bloqr Systems™. Created by Bloqr Systems™, founded by Jayson Knight.
Internet Hygiene (n.) — the ongoing practices that keep your digital life clean, private, and safe.
Our product repos live in the BloqrAI org, part of the Bloqr Systems GitHub Enterprise. Product repos are internal-visibility — enterprise membership is required to view them.