How we wrote this page. The practices below describe how the Patronly platform is actually built. They reflect the current codebase; as the platform evolves we keep this page in step. For sensitive specifics not appropriate to publish, contact
security@patronly.ai.
Overview
Nonprofit galas involve sensitive information — guest lists, donor and sponsor details, and the payments that fund a mission. Security is built into how Patronly is engineered, not bolted on afterward. This page describes the concrete measures in place across authentication, data isolation, input handling, file uploads, and payments.
Password protection
Passwords are never stored in readable form. We hash every password using bcrypt through PHP's native password hashing, and we automatically re-hash to stronger parameters over time as standards advance.
- One-way hashing: passwords are stored only as bcrypt hashes; we cannot see or recover your plaintext password.
- Constant-time verification: login always runs a full password check — even for an email that doesn't exist — so attackers can't discover which emails are registered by measuring response time.
- Generic errors: a failed login says only "invalid email or password," never which field was wrong.
- Automatic upgrades: when a stronger hashing standard becomes the default, existing passwords are transparently re-hashed on the user's next successful login.
Sessions & CSRF protection
- Hardened session cookies: session cookies are set HttpOnly (unreadable by JavaScript) and SameSite=Lax (not sent on cross-site requests), and are scoped to the application path so they can't collide with other apps on the same domain.
- Session-fixation defense: the session identifier is regenerated the moment you log in, so a pre-login identifier can't be reused to hijack an authenticated session.
- CSRF tokens: every state-changing form is protected by a cryptographically random, per-session token verified with a constant-time comparison. Requests without a valid token are rejected.
Injection defense
Database access uses parameterized queries (prepared statements) throughout — hundreds of them across the codebase — with real server-side prepared statements rather than emulated ones. User input is passed as bound parameters, never concatenated into SQL. In the rare places values are used inline, they are strictly cast to integers first. This is the primary defense against SQL injection.
Tenant isolation
Patronly is multi-tenant: each Organization's data is logically separated so one Organization can never see another's. Every data query is scoped to the authenticated Organization's tenant identifier, and that scope is re-verified on each request from the server-side session — never from anything the browser can tamper with. Logins, password resets, and record lookups all enforce this boundary.
Access control
- Authentication required: protected pages verify a valid, active session on every request; stale or suspended accounts are logged out immediately.
- Role-based permissions: sensitive actions require a specific role (for example, owner or admin). Users without the required role receive a hard "Forbidden" response.
- Least privilege: access to data and actions is limited to what each role legitimately needs.
File uploads
Uploads (such as logos and event documents) are handled defensively:
- Extension allowlist: only known-safe file types are accepted; executable types are rejected.
- Server-determined type: the file's type is decided server-side, not trusted from the browser.
- Randomized filenames: stored files get random, unguessable names, preserving the original name only for display.
- No code execution: the uploads directory is configured to refuse to execute scripts, so an uploaded file can never run as code.
- Path-traversal defense: file operations are confined to the owning Organization's own upload directory.
- Size limits: uploads are capped to reasonable maximums.
Password reset
Password resets use single-use tokens that are cryptographically random and expire after one hour. To avoid revealing whether an email is registered, the reset flow always returns the same neutral response regardless of whether an account exists.
Payment security
When payment processing is enabled, it is handled by PCI-DSS-compliant third-party processors. Full payment card numbers are handled by those specialized, certified systems and are not stored on Patronly's servers — keeping the most sensitive financial data out of our environment entirely.
AI features & third parties
Patronly's AI-assisted features (such as drafting sponsor descriptions and captain emails) call the AI provider only from our servers. API credentials live in server-side configuration and are never exposed to your browser or embedded in any page. Requests to third-party services time out and fail safely.
Encryption & transport
- In transit: traffic between your browser and Patronly is protected with industry-standard TLS (HTTPS). Look for the lock icon in your browser's address bar.
- Configuration secrets (database credentials, API keys) are kept in server-side configuration outside the web-accessible code and are excluded from version control.
Protecting your account
Security is a shared responsibility. To help keep your account safe:
- Use a strong, unique password.
- Give each volunteer their own login instead of sharing one account — Patronly's roles are designed for this.
- Be alert to phishing — Patronly will never ask for your password by email.
- Log out on shared devices, especially at event check-in stations.
Reporting a vulnerability
We welcome responsible disclosure from security researchers. If you believe you've found a vulnerability, email security@patronly.ai with details so we can investigate. Please give us a reasonable opportunity to address the issue before any public disclosure, and avoid accessing or modifying data that isn't yours while testing.
Questions about security at Patronly? Reach our team at security@patronly.ai.
>