RESPONSIBLE DISCLOSURE
Security at Broentech.
broen.tech is a commercial SaaS. The flagship product is ASMI — a reactive avatar that ships as an npm package and runs on our Gemini-backed infrastructure. That's two distinct attack surfaces, and I take both seriously. Below is what I do to defend them and how to tell me when I've got it wrong.
Last reviewed: 2026-04-22
The controls in place today.
- Authentication
- Firebase ID tokens on every cost-bearing write, with checkRevoked-on-admin + cost-bearing paths so a disabled account stops working within seconds rather than the default one-hour TTL. Admin role mirrored both server-side (Firestore role + email match) and in Firestore rules.
- Data isolation
- Drizzle ORM queries are always scoped by the caller's authenticated uid — never a client-supplied userId. Firestore rules deny-by-default and only permit owner or admin reads on per-user collections. Cloud Storage rules deny globally; the bucket is unused today but future writes can't accidentally be public.
- AI pipeline
- Every Gemini call goes through a single wrapper (`gemini-tracked.ts`) that enforces a model allowlist — any unlisted model is rejected before the network call. User-supplied text is wrapped in a defensive <UNTRUSTED_USER_INPUT> envelope at every concat site. LLM responses are scrubbed for secret-like patterns; a match redacts in place and trips a CRITICAL finding. Per-avatar daily USD budgets and per-user 30-day credit ceilings cap runaway spend.
- Session integrity
- Each avatar chat session carries an opaque server-issued secret returned once at creation; state-modifying calls against the session must echo it. Without the secret, a guessed sessionId gets you nothing.
- Rate limits
- Per-IP + per-session + per-user + per-avatar caps across chat, expression generation, and session creation. Firestore-backed so they survive deploys and Cloud Run scale-outs.
- Monitoring
- Anthropic Claude runs a daily (Haiku) and weekly (Sonnet) security scan over Postgres + Stripe + dependency signals, writing findings to /admin/security. HIGH and CRITICAL findings email me within the minute. Cross-vendor on purpose — the monitor shouldn't share a single vendor failure mode with the thing it monitors.
- Secrets
- All long-lived secrets live in GCP Secret Manager with the App Hosting compute service account scoped to read-only access. Nothing sensitive is shipped in the client bundle; NEXT_PUBLIC_* is reserved for intentionally-public Firebase config and the Stripe publishable key.
- Supply chain
- @avatar-state-machine-interface/* packages are published with a tight `files` allowlist — no source maps, no build intermediates. No postinstall scripts. Any researcher can `npm pack` a published version and diff against this repo to verify.
What's in bounds.
IN SCOPE
- broen.tech and all *.broen.tech subdomains
- Published npm packages: @avatar-state-machine-interface/runtime, @avatar-state-machine-interface/react
- The Next.js API surface under broen.tech/api/**
- The public widget (session create, chat, expression fetch) on public-allowlisted avatars only
OUT OF SCOPE
- Denial-of-service testing, load-testing, traffic floods — please don't. We'll consider you a bot and block.
- Social engineering of Broentech staff or partners
- Spam, bulk-submission bugs, rate-limit grinding without a novel exploit angle
- Third-party integrations (Stripe, SendGrid, Firebase, Google Cloud, Vertex) — those are in scope for their respective vendors
- Findings in our competitors or unrelated sites — only broen.tech
- Unpatched browsers, known CVEs in direct dependencies that we haven't had reasonable time to respond to
We won't sue you for helping.
If you operate in good faith within the scope above — minimizing data access to what's necessary for proof of concept, respecting user privacy, giving us 90 days before public disclosure — we will not pursue legal action against you. This includes CFAA / Computer Misuse Act analogues, Norwegian § 145, and any related civil claims.
If your activity accidentally causes damage (e.g. you rate-limited an endpoint too enthusiastically and took something down), tell us as soon as you notice. We default to understanding; we don't default to lawyers.
Get the fix started.
Send your report to security@broen.tech. Acknowledged within 48 hours. HIGH and CRITICAL fixes shipped within 14 days (or the disclosure window extended with your agreement). MEDIUM within a sprint. LOW triaged to the backlog.
For encrypted reports, fetch the public key at https://broen.tech/.well-known/pgp-key.asc (fingerprint: 5944 22AA 6AE0 71C0 40D5 DCD1 560C 12B9 0C52 735E). Plaintext is also fine — I'll agree a secure channel if the disclosure warrants one.
Machine-readable index: https://broen.tech/.well-known/security.txt
What to include
- What you found, where, and how you found it. Screenshots or curl commands welcome.
- Minimum proof of concept — no exfiltration beyond proving the bug exists.
- Whether you want to be credited in the Hall of Fame, and if so, how (name, handle, link).
Reproducible builds + open source.
Both @avatar-state-machine-interface/runtime and @avatar-state-machine-interface/react are open source under MIT and live at github.com/Stiander/broen.tech-webpage/tree/main/packages. The published tarballs contain the built `dist/`, a README, and the LICENSE — nothing else.
To verify a published version against source:
git clone https://github.com/Stiander/broen.tech-webpage cd broen.tech-webpage && npm ci cd packages/asmi-react && npm run build npm pack # produces a local tarball # diff the local tarball against `npm pack @avatar-state-machine-interface/react@<version>`
Researchers who've helped.
When a finding lands — valid, reasonable-scope, reported responsibly — I add the reporter here with their permission. Empty today; come help me fix that.
Questions? security@broen.tech