This document contains practical, copy-pasteable guidance for lo6: CSP headers, SSRF validation, Supabase RLS snippets, and LLM-output sanitization patterns.
Add these response headers (Next.js next.config.js or middleware) to reduce XSS and data exfiltration risk:
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'Content-Security-Policy', value: "default-src 'self'; img-src 'self' data: https:; connect-src 'self' https://api.example.com; script-src 'self'; style-src 'self' 'unsafe-inline'" },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' }
]
}
];
}
};
Adjust connect-src for allowed API domains. Use report-uri or report-to in staging to detect violations before enforcing in production.
Block private IPs and metadata endpoints and enforce an allowlist when possible.
import { URL } from 'url';
const PRIVATE_IP_RANGES = [
/^127\./, // loopback
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./
];
export function isAllowedUrl(raw: string, allowlist: string[] = []) {
try {
const u = new URL(raw);
const hostname = u.hostname;
// Allow explicit allowlist
if (allowlist.length && allowlist.includes(hostname)) return true;
// Prevent metadata services
if (hostname === '169.254.169.254') return false;
// Simple private IP check
for (const re of PRIVATE_IP_RANGES) {
if (re.test(hostname)) return false;
}
// Only http(s) allowed
if (!['http:', 'https:'].includes(u.protocol)) return false;
// Optionally perform a DNS lookup and validate the resolved IP is public.
return true;
} catch (err) {
return false;
}
}
Run external fetching from a sandboxed worker with egress controls and request-timeout limits.
Example flow:
import DOMPurify from 'isomorphic-dompurify';
import { z } from 'zod';
const LLMParagraph = z.object({
text: z.string(),
citations: z.array(z.string().url()).optional(),
});
export function sanitizeLLM(raw: unknown) {
const parsed = LLMParagraph.safeParse(raw);
if (!parsed.success) throw new Error('LLM output invalid');
const cleanText = DOMPurify.sanitize(parsed.data.text);
return { ...parsed.data, text: cleanText };
}
Store both raw and sanitized output in an llm_outputs audit table with actor_id and timestamp.
Assume stories table with owner_id and roles stored in auth.users or a user_roles table.
SQL snippet to enforce that only the owner or roles with editor/admin can update:
-- Enable RLS
ALTER TABLE public.stories ENABLE ROW LEVEL SECURITY;
-- Policy: owners can update
CREATE POLICY stories_update_owner
ON public.stories
FOR UPDATE
USING (auth.uid() = owner_id);
-- Policy: editors and admins can update
CREATE POLICY stories_update_roles
ON public.stories
FOR UPDATE
USING (
exists (
select 1 from public.user_roles ur where ur.user_id = auth.uid() and ur.role in ('editor','admin')
)
);
Test RLS policies by calling Supabase with a test JWT representing each role and asserting allowed/denied actions.
.secrets.baseline and run detect-secrets locally and in CI.Provide an environment-controlled flag that agents check before performing high-impact actions like publishing. Example env var: AGENTS_ENABLED=false. Also provide an Admin UI toggle that writes to a signed config row in the DB.