Valtik Studios
Back to blog
Appwritehigh2026-02-2711 min

Appwrite Attack Surface: Anonymous Sessions, Bucket Enumeration, and the Mistakes Developers Make

Appwrite is the open-source alternative to Firebase and Supabase. Over 100,000 developers, self-hosted deployments at thousands of companies. Also: a recurring finding on our platform audits. Projects commonly ship with permissive defaults, anonymous session access, enumerable buckets, and readable collections that expose user data. A practical walkthrough of the attack patterns.

TT
Tre Trebucchi·Founder, Valtik Studios. Penetration Tester

Founder of Valtik Studios. Pentester. Based in Connecticut, serving US mid-market.

Why Appwrite keeps showing up on audits

Appwrite positions itself as the open-source alternative to Firebase, Supabase. And other commercial Backend-as-a-Service platforms. It's developer-friendly: spin up a self-hosted instance in minutes, connect from any language's SDK, get databases, authentication, storage, functions. And real-time without writing backend code. For a startup shipping an MVP, it's a productivity multiplier.

It's also, in our penetration testing experience, one of the most commonly misconfigured platforms in production. Appwrite ships sensible defaults for developer experience. And those defaults are sometimes at odds with production security requirements. When developers skip the hardening documentation (which happens on most commercial engagements), the resulting deployment exposes APIs, data. And storage to anonymous users.

This post walks through the specific attack patterns we find when we audit Appwrite deployments, the misconfigurations that enable them. And the hardening guidance every Appwrite-based production should implement.

If you run Appwrite in production and haven't explicitly audited against this list, you probably have at least three of these findings live right now.

Anatomy of an Appwrite deployment

A typical Appwrite production deployment includes:

  • Appwrite Server. The core API, runs on Docker
  • Databases. NoSQL collections, organized into documents with attributes and indexes
  • Storage buckets. File storage, organized by bucket with permission rules
  • Authentication. Email/password, OAuth providers, magic links, anonymous sessions, phone
  • Functions. Serverless code execution in various runtimes
  • Realtime. WebSocket subscriptions for live data updates

Each of these has specific permission models, and the interactions create the attack surface.

Attack pattern 1: Project ID extraction

Appwrite deployments are identified by a project ID that must be included in every API request (via X-Appwrite-Project header). The project ID isn't sensitive by itself. It's intended to be public.

But the project ID is the starting point for enumeration. Finding the project ID gives you the key to query the rest of the API.

Where project IDs leak:

  • Page source. Appwrite SDK initialization typically includes the project ID in frontend JavaScript:
const client = new Appwrite.Client()
      .setEndpoint('https://appwrite.example.com/v1')
      .setProject('65f7a2b4c3d1e0f9a8b7c6d5'); // ← here

  • Response headers. Some Appwrite responses include X-Appwrite-Project or similar headers
  • Error messages. Malformed requests sometimes leak project metadata
  • Public documentation. Some projects publish their project ID in developer docs

During a pentest, finding the project ID is a 5-minute task. It's on the homepage JavaScript of every Appwrite-powered frontend.

Not a finding, but a precondition. Every subsequent attack starts here.

Attack pattern 2: Anonymous session creation

By default, Appwrite allows anonymous session creation. A user can request a session without credentials. Anonymous sessions enable access to whatever the anonymous role has permission to access.

The authentication flow:

POST /v1/account/sessions/anonymous
Headers:
  X-Appwrite-Project: <project-id>

If anonymous sessions are enabled, the response includes a valid session token. Everything the anonymous role can do is now accessible to you.

Common pattern: developers enable anonymous sessions during initial development for convenience, intending to tighten later. "Later" doesn't always happen. Production deployments continue accepting anonymous sessions for features that were never designed to be public.

What's often accessible via anonymous sessions:

  • User collection with email addresses (misconfigured permissions)
  • Product catalogs that were supposed to be authenticated-only
  • Forum posts, comments, and private threads
  • File storage buckets with overly broad read permissions
  • Real-time subscriptions to data that was meant to be private

The fix: if your application requires authentication, disable anonymous sessions:

Appwrite Console → Auth → Settings → Sessions → Allow Anonymous Sessions → Off

If you need anonymous access for specific features, scope permissions tightly so anonymous role only sees what's intended to be public.

Attack pattern 3: Database collection enumeration

Appwrite databases are organized into collections (similar to tables or MongoDB collections). Each collection has permission rules governing who can create, read, update, and delete documents.

Enumeration:

GET /v1/databases/{databaseId}/collections
Headers:
  X-Appwrite-Project: <project-id>
  X-Appwrite-Session: <session-id> (if authenticated)

If the database ID is accessible (and it usually is. It's in frontend JavaScript), and session permissions allow, the enumeration succeeds.

Common findings:

  • Collections named users, profiles, admins, secrets visible via enumeration
  • Permission rules allowing role:anonymous or role:users to read documents that weren't intended for those roles
  • Collections that appear hidden because the UI doesn't show them, but which are fully accessible via direct API

Real-world example from an audit: a SaaS product's Appwrite deployment had a collection named internal_notes that admins used to annotate customer accounts. Permissions were inherited from a parent collection, which was readable by any authenticated user. Any customer with a login could read every other customer's internal notes.

The fix: audit every collection's permission rules. For each collection, the right mental model is:

  • Who should be able to read each document?. Specific users (by document ownership), specific teams, specific roles, or nobody except admins
  • Who should be able to write each document?. Typically narrower than read access
  • Are the rules enforced, or inherited from a parent that's too permissive?

Appwrite's permission model is granular. Use it. Don't accept inherited "allow all" defaults.

Attack pattern 4: Storage bucket listing and download

Appwrite storage organizes files into buckets with per-bucket permission rules.

Bucket enumeration:

GET /v1/storage/buckets
Headers:
  X-Appwrite-Project: <project-id>

With appropriate session permissions, this returns bucket names. A common pattern is buckets named:

  • user-uploads
  • profile-pictures
  • documents
  • receipts
  • contracts
  • avatars

File listing within a bucket:

GET /v1/storage/buckets/{bucketId}/files
Headers:
  X-Appwrite-Project: <project-id>

If the bucket's read permission allows role:anonymous or role:users, the file listing succeeds. Each file includes:

  • File ID
  • Filename
  • Size
  • MIME type
  • Upload timestamp
  • Owner reference (sometimes)

File download:

GET /v1/storage/buckets/{bucketId}/files/{fileId}/view
Headers:
  X-Appwrite-Project: <project-id>

If file-level permissions allow, the file content returns.

Real-world findings from Appwrite audits:

  • A fitness app's storage bucket contained user workout videos with timestamp-based sequential IDs. Enumeration allowed downloading any user's workout videos
  • A SaaS startup's customer-contract upload bucket had role:users read permissions (any logged-in user could read every customer's contracts)
  • A medical app had scanned document uploads with readable permissions for anonymous users
  • A hiring platform's resume uploads bucket allowed any authenticated user to download every candidate's resume

The fix:

  • Buckets should have default-deny permissions at the bucket level
  • Individual file permissions should grant access only to the specific owner
  • File IDs should be random (Appwrite's defaults are fine here. Custom sequential IDs are a mistake)
  • Sensitive buckets (PII, financial, medical) should require specific team/role membership, not authentication

Attack pattern 5: User collection exposure

Appwrite manages users internally, but many applications also create parallel user collections in their databases. To store additional profile info, user-specific settings, relationships, etc.

Common misconfigurations:

The custom user collection has permission rules like:

read: ['role:users'] // any authenticated user can read any user document

This exposes every user's:

  • Email address (if stored)
  • Phone number
  • Full name
  • Profile photo URL
  • Internal user ID
  • Preferences and settings
  • Sometimes: hashed passwords, API keys, internal role flags

Real-world finding: a startup's profiles collection was readable by any authenticated user. A single query returned 45,000 users' emails + phone numbers, which the attacker could use for credential-stuffing attacks against the application itself. Account takeover on hundreds of users followed from a weekend of attack.

The fix: per-user document read permissions:

read: ['user:{ownerId}'] // only the owner can read their own document

Appwrite supports this pattern via document-level permissions. Use them for any collection containing user-specific data.

Attack pattern 6: Function enumeration and execution

Appwrite Functions are serverless code executions. Each function has:

  • Execution permissions (who can trigger it)
  • Associated environment variables (often containing API keys)
  • Deployment-specific runtime

Enumeration:

GET /v1/functions
Headers:
  X-Appwrite-Project: <project-id>

Execution:

POST /v1/functions/{functionId}/executions
Headers:
  X-Appwrite-Project: <project-id>
Body: {
  "data": "<user-provided-input>"
}

If function execution permissions allow role:users or role:anonymous. And the function accepts user input, vulnerabilities in the function become externally accessible.

Real-world attack vectors:

  • Functions that hit internal APIs with user-provided inputs → SSRF
  • Functions that write to databases based on user-provided data → integrity attacks
  • Functions with logic bugs that can be exploited via crafted inputs
  • Functions that leak environment variables via error messages

The fix: function execution permissions should default to team-specific or admin-only. User-triggered functions should validate inputs strictly and minimize side effects.

Attack pattern 7: Realtime subscription leakage

Appwrite's real-time feature allows WebSocket subscriptions to databases and buckets for live updates.

The subscription:

client.subscribe('databases.{databaseId}.collections.{collectionId}.documents', response => {
  console.log(response); // every change to the collection is streamed here
});

If the collection's read permissions allow the subscriber, every document change streams in real-time. Every create, update, delete.

Problem: a collection that's "mostly private" might still be subscribable if permissions allow broader read than intended. An attacker who subscribes gets a live feed of changes including:

  • User activity patterns
  • Private messages being sent
  • Admin operations happening
  • Support ticket updates
  • Anything that writes to the collection

Real-world finding: a customer support SaaS exposed its tickets collection real-time to any authenticated user. Competitors subscribed and received live streams of support ticket content.

The fix: real-time permissions follow the collection's read permissions. Tighten the collection's reads, and real-time inherits the restrictions.

Attack pattern 8: Webhook and API key exposure

Appwrite supports webhooks (outbound notifications on events) and API keys (for server-to-server integration).

Common exposure:

  • Webhook URLs leaked in error messages or frontend JavaScript
  • API keys stored in environment variables that functions leak via error output
  • API keys stored in publicly-readable collection documents
  • API keys in git repositories

The fix:

  • Never store API keys in client-side code or publicly-readable collections
  • Use server-only environment variables for API keys
  • Rotate keys on any suspected exposure
  • Use separate keys per integration so compromises are contained

Attack pattern 9: Exposed admin console

Appwrite's admin console is typically accessible at /console/ on the Appwrite server. Some deployments leave this accessible to the public internet without additional access controls.

What's exposed if the console is public:

  • Login form (credential-stuffing target)
  • Potentially cached session state
  • Project enumeration
  • Error messages that leak infrastructure details

The fix:

  • Restrict console access to VPN or admin IPs
  • Use strong admin credentials with MFA
  • Monitor for brute-force login attempts
  • Consider putting the console behind a reverse proxy with additional authentication

Attack pattern 10: Self-hosted deployment issues

Self-hosted Appwrite deployments frequently have infrastructure misconfigurations beyond Appwrite itself:

  • Docker exposure: Appwrite runs as a Docker stack. Docker socket exposure can enable container breakout.
  • Traefik misconfiguration: Appwrite's default reverse proxy (Traefik) can be exposed administratively if not configured carefully.
  • Redis/MariaDB exposure: the underlying datastores can be exposed to the internet if Docker networking is misconfigured.
  • Backup exposure: backup files containing full database contents can end up publicly accessible on misconfigured S3 buckets or FTP servers.

These aren't Appwrite-specific issues but frequently co-occur with Appwrite deployments.

The hardening checklist

If you're running Appwrite in production, work through this list:

1. Authentication

  • [ ] Anonymous sessions disabled (unless specifically needed)
  • [ ] OAuth providers configured only for identity providers you trust
  • [ ] Session duration appropriate for your threat model
  • [ ] MFA enabled for admin accounts
  • [ ] Rate limiting on authentication endpoints

2. Collections

  • [ ] Every collection has explicit permission rules
  • [ ] No collection uses role:all or role:guest for write access
  • [ ] User-specific data uses document-level permissions (user:{ownerId})
  • [ ] Admin-only collections are accessible only to admin role
  • [ ] Sensitive fields (passwords, API keys, PII) not in publicly-readable collections

3. Storage

  • [ ] Every bucket has explicit permission rules
  • [ ] Bucket-level permissions default-deny
  • [ ] File-level permissions for per-user files
  • [ ] Sensitive buckets require specific team membership
  • [ ] File naming avoids sequential/predictable patterns for sensitive content

4. Functions

  • [ ] Function execution permissions appropriate to use case
  • [ ] Functions validate inputs
  • [ ] Functions don't leak environment variables in errors
  • [ ] Functions that accept user input have rate limiting
  • [ ] Functions that access external APIs use minimum-privilege credentials

5. Real-time

  • [ ] Real-time subscriptions inherit appropriate read permissions
  • [ ] Sensitive collections aren't subscribable by unauthorized roles

6. Infrastructure

  • [ ] Admin console not exposed to public internet
  • [ ] Docker socket not exposed
  • [ ] Redis / MariaDB not exposed publicly
  • [ ] Backups stored securely
  • [ ] Appwrite updated to latest supported version (security patches)
  • [ ] Logging enabled for security-relevant events

7. Operations

  • [ ] API keys rotated on a schedule
  • [ ] Webhook signatures verified on receipt
  • [ ] Monitoring for anomalous authentication patterns
  • [ ] Penetration testing by qualified third party at least annually

For Valtik clients

Valtik's Appwrite security audits include every pattern above, plus the underlying Docker/Traefik/Redis infrastructure review. Our typical Appwrite engagement produces:

  • Per-collection and per-bucket permission audit with specific remediation
  • Storage file-listing enumeration results
  • Authentication flow analysis
  • Function security review
  • Infrastructure security review
  • Comparison against Appwrite's official hardening documentation

If you run Appwrite in production and haven't had an independent security review, the findings above are likely waiting in your deployment. Reach out via https://valtikstudios.com for scoping.

Sources

  1. Appwrite Official Documentation
  2. Appwrite Security Best Practices
  3. Appwrite Permission Model
  4. Appwrite Self-Hosted Installation
  5. OWASP API Security Top 10
  6. Appwrite GitHub Repository
  7. Appwrite Security Advisories
  8. BaaS Security Research. Valtik
  9. Docker Security Best Practices. Docker
  10. Traefik Documentation. Security
appwritebaas securitypenetration testingbackend as a serviceapplication securityvulnerability assessmentopen sourceoss securityresearch

Want us to check your Appwrite setup?

Our scanner detects this exact misconfiguration. plus dozens more across 38 platforms. Free website check available, no commitment required.

Get new research in your inbox
No spam. No newsletter filler. Only new posts as they publish.