Security
Open by design.
Not by accident.
Petaluma Civic is a civic transparency project. Some of what it does would look unusual from a conventional security standpoint — so this page explains exactly what's open and why, what's protected and how, and what to do if you find something worth reporting.
01 · Design decisions
The MCP endpoint is intentionally public
Petaluma Civic exposes a public
Model Context Protocol
(MCP) endpoint at /mcp. MCP is an open standard that lets AI
assistants — Claude, Copilot, and others — connect directly to data sources.
Any resident can point their AI assistant at this endpoint and ask questions
about Petaluma city government without friction or signup.
This is a deliberate design choice, not an oversight. The data the endpoint serves is public civic records: council votes, meeting minutes, permit filings, ordinances, agenda items. There is no authentication requirement because requiring one would defeat the purpose — making civic data as accessible as possible to as many people as possible.
Rate limiting is applied per IP address to prevent runaway scripts and abuse without affecting normal AI assistant use.
02 · Data access
What can — and can't — be accessed
The public database role used by the MCP endpoint and the web query interface has read-only access to civic records only. Access to all other data is explicitly revoked at the database level — not just the application layer.
| Data | Access | Notes |
|---|---|---|
| Council votes & members | Open | Public record — all votes, motions, outcomes |
| Meeting minutes & agendas | Open | All City Council and SCPA meetings |
| Permit records | Open | Public permit data from the city's EnerGov system |
| Ordinances & municipal code | Open | All ordinances and municipal code sections |
| Agenda item descriptions | Open | AI-generated summaries of agenda items |
| Subscriber email addresses | Blocked | Revoked at the DB role level — inaccessible through any public interface |
The blocks above are enforced by PostgreSQL role privileges, not application logic. Even if application code were compromised, the database role used by public interfaces cannot read these tables.
03 · General posture
How the rest of the application is secured
Read-only database access. All public interfaces — the web query tool, the chat interface, and the MCP endpoint — connect to PostgreSQL through a role that can only read. No writes, no schema changes, no privilege escalation are possible through any public endpoint.
Rate limiting. Public query endpoints, including the MCP endpoint, enforce per-IP rate limits to prevent automated bulk extraction and scripted abuse.
XSS protections. The web interface escapes all user input and API response content before inserting it into the DOM. Output encoding is applied uniformly across the application.
Admin console. The administrative interface requires authentication and is not reachable without valid credentials. API route documentation is not publicly exposed.
Standard security headers. The application sets
X-Content-Type-Options, X-Frame-Options, and
Referrer-Policy on all responses.
HTTPS only. All traffic is served over HTTPS via Fly.io's infrastructure. HTTP connections are not accepted.
04 · Responsible disclosure
Found something? Please tell me.
This is a solo-maintained side project with no security team and no bug bounty program. But security findings are taken seriously, and I'll respond to every report.
If you find a vulnerability, please email a description of what you found and how to reproduce it. I'll acknowledge your report within a few days and aim to have a fix in place within two weeks for anything significant. For critical issues, I'll move faster.
janjirout@petalumacivic.org
Please include enough detail to reproduce the issue. If you'd like a specific disclosure timeline, mention that in your email and I'll commit to one. I won't take legal action against good-faith security research conducted in accordance with this page.
05 · Disclosure policy
What this page doesn't include
This page describes the application's security posture and design decisions — not specific vulnerability details. Specific findings, file locations, proof-of-concept code, and audit results are not published here until after issues are patched.
That's standard responsible disclosure practice: publishing exploitation details before a fix is in place helps attackers more than defenders. Once something is fixed, I'm happy to acknowledge it.
The internal security log — including a full remediation history — is maintained in the project's private documentation.