Security
Last updated: 14 September 2026
W AUTO POST holds credentials that can publish in your clients' names. This page describes the controls that protect them. It is a description of how the product is built, not a certification claim.
Authentication
- Authentication is handled by Supabase Auth. Passwords are hashed; we never store or transmit them in plaintext.
- Session tokens are held in HTTP-only cookies and are revalidated with the authentication server on every request rather than being trusted from the cookie alone.
- Every user carries exactly one role — agency administrator or client — and the role is stored server-side. A user cannot change their own role: the database rejects the attempt.
Client isolation
This is the control that matters most in a multi-client system, so it is enforced at the lowest level available rather than in application code.
- Every tenant-scoped table has PostgreSQL row level security enabled, with policies that resolve the caller’s client from the database session rather than from anything in the request.
- A client user is bound to exactly one client. Requesting another client’s record by its exact identifier returns nothing — there is no row to return, so there is nothing to leak.
- Client users have read access only. Approvals go through a single dedicated server-side function that re-checks authorisation; clients cannot edit captions, schedules or account settings by calling the API directly.
- Administrators can reach all clients, by design.
- Uploaded media sits in private storage buckets, partitioned by client, served only through short-lived signed URLs.
These properties are covered by an automated test suite that seeds two tenants, acts as a real client user at the database level, and attempts the obvious attacks — reading another client's posts by ID, editing them, self-promoting to administrator, reassigning tenancy, and approving another client's content. The suite lives in the repository and is run against the schema.
Platform credentials
- Accounts are connected only through official OAuth. We never receive a social media password, and we never ask anyone to disable two-factor authentication.
- Tokens live in a dedicated table with row level security enabled and no policies at all, and with database privileges revoked from every application role. No signed-in user can read it through the API — administrators included.
- Tokens are encrypted with AES-256-GCM before being stored, using a key that exists only in the server environment and never in the database or the repository.
- The encrypted format is versioned so the key can be rotated without downtime.
- Tokens are decrypted only inside server-side code, at the moment a platform call is made, and are never sent to the browser or written to logs.
- The OAuth flow uses single-use, expiring state values to prevent cross-site request forgery on the callback.
Publishing safety
Publishing the same post twice is a visible, embarrassing failure, so it is defended in several independent ways:
- A partial unique index allows at most one live job per post, so a double click, a duplicate trigger or an overlapping scheduler run cannot enqueue the same post twice.
- Workers claim jobs with SELECT … FOR UPDATE SKIP LOCKED under an expiring lease, so two workers can never hold the same job, and a crashed worker’s job is safely recovered rather than stranded.
- Each job carries a stable idempotency key, sent to platforms that accept one and recorded on every attempt.
- A unique constraint prevents the same platform post identifier being recorded twice for one account, so a retry after a lost response cannot create a second record.
- A database constraint makes it impossible to mark a post as published without recording proof of publication.
- Retries are limited to genuinely temporary failures. Permanent errors — revoked authorisation, invalid media, insufficient permissions — stop immediately and are reported.
Application security
- Secrets are held in server environment variables. The service role database key and all OAuth client secrets are confined to server-side modules that cannot be imported into browser code — attempting to do so fails the build.
- All input is validated server-side against explicit schemas. Authorisation is re-checked on the server for every action, never inferred from the interface state.
- Uploads are restricted by file type and size, and stored outside the web root in private buckets.
- The public contact form is validated and rate limited, and stores only a one-way hash of the submitting IP address.
- Security headers are applied to every response, and the application is served over HTTPS only.
Audit and accountability
Significant actions — connecting and disconnecting accounts, uploading content, approving and rejecting, scheduling, publishing, failing, retrying, changing settings — are written to an append-only audit log recording who acted, on what, when, and with what result. Audit entries are filtered before being written so that tokens, secrets and passwords can never appear in them.
Reporting a vulnerability
If you believe you have found a security issue, please report it to [CONTACT EMAIL NOT YET CONFIGURED] with enough detail to reproduce it. Please do not publicly disclose it before we have had a reasonable opportunity to respond, and please do not access, modify or delete data belonging to anyone else while investigating.