Architecture
Security model
Security in Glozr is layered, not conventional. Every tenant query is filtered, every widget request validates its origin, every outbound HTTP fetch is guarded against SSRF, every retrieved chunk is wrapped as data rather than instructions, and every public endpoint is rate-limited.
Overview
Seven defences run continuously: workspace isolation, origin verification, SSRF protection, input sanitization, prompt-injection containment, rate limiting, and encryption at rest. Browser-side, all responses ship with hardened security headers.
Workspace isolation
Every tenant-scoped model uses BelongsToWorkspace or BelongsToAgent, which apply global query scopes automatically. Workspace identity is resolved from the authenticated user or the verified JWT — never from request parameters. See Multi-tenancy.
Origin allow-listing
The widget validates at two checkpoints: when the JWT is issued, and again on every privileged endpoint the JWT can reach. The match is strict: an empty allowed_origins list denies everywhere, otherwise the request's scheme://host must appear in the list exactly. No subdomain inference, no wildcard, no port stripping.
SSRF protection
The crawler is the largest SSRF surface. It defends in depth:
- Scheme allow-list — only
httpandhttps. - Hostname pattern blocks — private IPv4 ranges, IPv6 unique-local, link-local, loopback.
- Cloud metadata block —
169.254.169.254,metadata.google.internal, etc. - DNS rebind detection — re-resolves at fetch time and rejects if the resolved IP shifts into a private range.
- Redirect re-validation — each hop runs through the full set of checks; redirects can't smuggle the crawler into an internal host.
Content sanitization & prompt injection
Knowledge-base markdown is run through a hardened sanitizer that strips raw HTML and unsafe URI schemes (javascript:, data:, etc.). System prompts wrap retrieved chunks inside source tags and instruct the model explicitly that this is data, not instructions. The widget refuses to render assistant output that contains script tags or event handlers.
Encryption & secrets
API keys, OAuth refresh tokens, and database passwords on sources are encrypted at rest using Laravel's encrypted cast (AES-256-GCM). The widget JWT signing secret is hashed before comparison. Application secrets live only in env vars or the platform-admin App Settings vault.
Rate limiting
Public endpoints carry per-route throttles, ranging from 5/min (forgot-password) to 600/min (widget message stream). The identifier is chosen per route — IP, JWT subject, or email — to defeat the most common abuse pattern for that endpoint.
Browser defences
Every response sets:
Content-Security-Policywith nounsafe-inlineorunsafe-eval.Strict-Transport-Securitywith a long max-age.X-Content-Type-Options: nosniff.Referrer-Policy: strict-origin-when-cross-origin.X-Frame-Options: DENYon the dashboard (the widget is iframe-friendly by design).
Note. Report a vulnerability privately at [email protected]. Please don't open public issues for security reports.