[{"data":1,"prerenderedAt":846},["ShallowReactive",2],{"blog-is-safe/appwrite":3},{"id":4,"title":5,"body":6,"category":816,"date":817,"dateModified":817,"description":818,"draft":819,"extension":820,"faq":821,"featured":819,"headerVariant":831,"image":832,"keywords":833,"meta":834,"navigation":269,"ogDescription":835,"ogTitle":832,"path":836,"readTime":837,"schemaOrg":838,"schemaType":839,"seo":840,"sitemap":841,"stem":842,"tags":843,"twitterCard":844,"__hash__":845},"blog/blog/is-safe/appwrite.md","Is Appwrite Safe? Open-Source BaaS Security Review (2026)",{"type":7,"value":8,"toc":806},"minimark",[9,18,21,34,41,46,105,109,112,181,190,196,211,223,298,302,305,312,346,360,397,412,418,422,428,463,466,469,535,538,547,551,554,589,592,595,599,602,647,663,667,674,680,688,698,702,712,719,775,794,802],[10,11,12,13,17],"p",{},"Roughly 20% of AI-built apps that use Appwrite as a backend have at least one collection or storage bucket with ",[14,15,16],"code",{},"any"," set as a write permission. That single misconfiguration lets anyone without an account create, update, or delete records in that collection. No authentication. No rate limiting from the permission layer. Just an open write endpoint sitting in production.",[10,19,20],{},"The platform itself is not the problem. The permission model is flexible by design. The issue is that \"any\" is a tempting shortcut during development, and AI code generators often leave it in place when scaffolding backend logic.",[22,23,24],"tldr",{},[10,25,26,27,29,30,33],{},"Appwrite Cloud is secure infrastructure. The risks are in your configuration: document and bucket permissions set to ",[14,28,16],{}," allow unauthenticated writes, server API keys belong only in server-side code (never in client bundles), and self-hosted deployments must replace the ",[14,31,32],{},"_APP_OPENSSL_KEY_V1"," default before going live. Get those three right and Appwrite is a solid production backend.",[35,36,38],"verdict-badge",{"verdict":37},"safe",[10,39,40],{},"Safe when permissions and API keys are configured correctly",[42,43,45],"h2",{"id":44},"our-verdict","Our Verdict",[47,48,49,54,79,83],"pros-cons",{},[50,51,53],"h4",{"id":52},"whats-good","What's Good",[55,56,57,61,64,67,73,76],"ul",{},[58,59,60],"li",{},"Client SDK uses sessions, not API keys, so there's no key to accidentally leak from the browser",[58,62,63],{},"SOC 2 aligned security controls on Appwrite Cloud",[58,65,66],{},"Fine-grained permission roles: user, team, label, and any combinations",[58,68,69,72],{},[14,70,71],{},"internalPermissions"," on individual documents override collection defaults",[58,74,75],{},"EU data residency available; GDPR data processing agreements provided",[58,77,78],{},"Active security disclosure program and regular CVE patches",[50,80,82],{"id":81},"what-to-watch","What to Watch",[55,84,85,90,93,96,99],{},[58,86,87,89],{},[14,88,16],{}," permission is dangerously easy to set and forgets to require authentication",[58,91,92],{},"Realtime subscriptions expose the same data as REST queries, so an open collection leaks via both channels",[58,94,95],{},"Self-hosting requires manual rotation of encryption secrets and manual updates",[58,97,98],{},"Server API keys have no expiry by default; compromised keys stay valid indefinitely until manually revoked",[58,100,101,102,104],{},"Storage buckets default to no permissions, which blocks all access, but switching to ",[14,103,16],{}," for convenience is common",[42,106,108],{"id":107},"the-permission-model-footgun","The Permission Model Footgun",[10,110,111],{},"Appwrite controls access to documents, collections, buckets, and functions through a permission system with these roles:",[113,114,115,128],"table",{},[116,117,118],"thead",{},[119,120,121,125],"tr",{},[122,123,124],"th",{},"Role",[122,126,127],{},"Who it allows",[129,130,131,141,151,161,171],"tbody",{},[119,132,133,138],{},[134,135,136],"td",{},[14,137,16],{},[134,139,140],{},"Every visitor, authenticated or not",[119,142,143,148],{},[134,144,145],{},[14,146,147],{},"users",[134,149,150],{},"Any logged-in user",[119,152,153,158],{},[134,154,155],{},[14,156,157],{},"guests",[134,159,160],{},"Only unauthenticated visitors",[119,162,163,168],{},[134,164,165],{},[14,166,167],{},"user:[userId]",[134,169,170],{},"One specific user",[119,172,173,178],{},[134,174,175],{},[14,176,177],{},"team:[teamId]",[134,179,180],{},"All members of a team",[10,182,183,184,186,187,189],{},"The problem is ",[14,185,16],{}," on write operations. During development, setting ",[14,188,16],{}," to create/update/delete is convenient: you can test writes without worrying about auth tokens. In production, it means any HTTP client can write to your database.",[10,191,192,193,195],{},"CheckYourVibe flags ",[14,194,16],{}," write permissions on collections and buckets as a critical finding. The actual scanner output looks like this:",[197,198,200],"finding-box",{"title":199},"Critical: Unauthenticated Write Access on 'posts' Collection",[10,201,202,203,206,207,210],{},"Appwrite collection permissions include ",[14,204,205],{},"create: any"," and ",[14,208,209],{},"update: any",". Any visitor can create or modify records without an account. Found in your Appwrite database configuration.",[10,212,213,214,216,217,219,220,222],{},"The fix is straightforward: replace ",[14,215,16],{}," with ",[14,218,147],{}," for operations that require a login, and ",[14,221,167],{}," for operations that should only affect the current user's own data.",[224,225,230],"pre",{"className":226,"code":227,"language":228,"meta":229,"style":229},"language-javascript shiki shiki-themes github-light github-dark","// Wrong: any visitor can create a post\nconst permissions = [\n  Permission.create(Role.any()),\n  Permission.read(Role.any()),\n];\n\n// Right: only the authenticated user can create, anyone can read (public content)\nconst permissions = [\n  Permission.create(Role.user(userId)),\n  Permission.read(Role.any()),\n];\n","javascript","",[14,231,232,240,246,252,258,264,271,277,282,288,293],{"__ignoreMap":229},[233,234,237],"span",{"class":235,"line":236},"line",1,[233,238,239],{},"// Wrong: any visitor can create a post\n",[233,241,243],{"class":235,"line":242},2,[233,244,245],{},"const permissions = [\n",[233,247,249],{"class":235,"line":248},3,[233,250,251],{},"  Permission.create(Role.any()),\n",[233,253,255],{"class":235,"line":254},4,[233,256,257],{},"  Permission.read(Role.any()),\n",[233,259,261],{"class":235,"line":260},5,[233,262,263],{},"];\n",[233,265,267],{"class":235,"line":266},6,[233,268,270],{"emptyLinePlaceholder":269},true,"\n",[233,272,274],{"class":235,"line":273},7,[233,275,276],{},"// Right: only the authenticated user can create, anyone can read (public content)\n",[233,278,280],{"class":235,"line":279},8,[233,281,245],{},[233,283,285],{"class":235,"line":284},9,[233,286,287],{},"  Permission.create(Role.user(userId)),\n",[233,289,291],{"class":235,"line":290},10,[233,292,257],{},[233,294,296],{"class":235,"line":295},11,[233,297,263],{},[42,299,301],{"id":300},"api-key-exposure-client-vs-server","API Key Exposure: Client vs. Server",[10,303,304],{},"Appwrite has two authentication modes and they are not interchangeable.",[10,306,307,311],{},[308,309,310],"strong",{},"Client SDK"," (for browser and mobile apps): Authenticates via session cookies after the user logs in. You initialize it with your Project ID and Endpoint only. No API key involved.",[224,313,315],{"className":226,"code":314,"language":228,"meta":229,"style":229},"// Client-side: no API key, this is correct\nimport { Client, Account } from 'appwrite';\n\nconst client = new Client()\n  .setEndpoint('https://cloud.appwrite.io/v1')\n  .setProject('your-project-id');  // Project ID is not a secret\n",[14,316,317,322,327,331,336,341],{"__ignoreMap":229},[233,318,319],{"class":235,"line":236},[233,320,321],{},"// Client-side: no API key, this is correct\n",[233,323,324],{"class":235,"line":242},[233,325,326],{},"import { Client, Account } from 'appwrite';\n",[233,328,329],{"class":235,"line":248},[233,330,270],{"emptyLinePlaceholder":269},[233,332,333],{"class":235,"line":254},[233,334,335],{},"const client = new Client()\n",[233,337,338],{"class":235,"line":260},[233,339,340],{},"  .setEndpoint('https://cloud.appwrite.io/v1')\n",[233,342,343],{"class":235,"line":266},[233,344,345],{},"  .setProject('your-project-id');  // Project ID is not a secret\n",[10,347,348,351,352,355,356,359],{},[308,349,350],{},"Server SDK"," (for backend services, functions, webhooks): Authenticates with an API key that has specific scopes like ",[14,353,354],{},"documents.write"," or ",[14,357,358],{},"users.read",".",[224,361,363],{"className":226,"code":362,"language":228,"meta":229,"style":229},"// Server-side only: API key must never reach the browser\nimport { Client, Users } from 'node-appwrite';\n\nconst client = new Client()\n  .setEndpoint('https://cloud.appwrite.io/v1')\n  .setProject(process.env.APPWRITE_PROJECT_ID)\n  .setKey(process.env.APPWRITE_API_KEY);  // Keep this server-side only\n",[14,364,365,370,375,379,383,387,392],{"__ignoreMap":229},[233,366,367],{"class":235,"line":236},[233,368,369],{},"// Server-side only: API key must never reach the browser\n",[233,371,372],{"class":235,"line":242},[233,373,374],{},"import { Client, Users } from 'node-appwrite';\n",[233,376,377],{"class":235,"line":248},[233,378,270],{"emptyLinePlaceholder":269},[233,380,381],{"class":235,"line":254},[233,382,335],{},[233,384,385],{"class":235,"line":260},[233,386,340],{},[233,388,389],{"class":235,"line":266},[233,390,391],{},"  .setProject(process.env.APPWRITE_PROJECT_ID)\n",[233,393,394],{"class":235,"line":273},[233,395,396],{},"  .setKey(process.env.APPWRITE_API_KEY);  // Keep this server-side only\n",[10,398,399,400,403,404,407,408,411],{},"When CheckYourVibe scans your deployed app, we look for ",[14,401,402],{},"NEXT_PUBLIC_APPWRITE_API_KEY",", ",[14,405,406],{},"VITE_APPWRITE_API_KEY",", or any variable with an Appwrite-shaped value (",[14,409,410],{},"standard_"," prefix, 40-character hex) appearing in your client JavaScript. Finding one means that key's full scope is available to every visitor.",[413,414,415],"warning-box",{},[10,416,417],{},"Appwrite API keys have no expiry date by default. If you suspect a key was leaked, go to Appwrite Console > Project > API Keys, delete the compromised key immediately, and generate a new one. There is no automatic rotation.",[42,419,421],{"id":420},"self-hosting-the-encryption-key-problem","Self-Hosting: The Encryption Key Problem",[10,423,424,425,427],{},"The most serious self-hosting misconfiguration is the ",[14,426,32],{}," environment variable. This key encrypts stored OAuth tokens, session secrets, and user credentials. Appwrite ships with a placeholder in its docker-compose.yml:",[224,429,433],{"className":430,"code":431,"language":432,"meta":229,"style":229},"language-yaml shiki shiki-themes github-light github-dark","# docker-compose.yml (self-hosted Appwrite)\nenvironment:\n  - _APP_OPENSSL_KEY_V1=your-secret-key  # THIS MUST BE REPLACED\n","yaml",[14,434,435,441,451],{"__ignoreMap":229},[233,436,437],{"class":235,"line":236},[233,438,440],{"class":439},"sJ8bj","# docker-compose.yml (self-hosted Appwrite)\n",[233,442,443,447],{"class":235,"line":242},[233,444,446],{"class":445},"s9eBZ","environment",[233,448,450],{"class":449},"sVt8B",":\n",[233,452,453,456,460],{"class":235,"line":248},[233,454,455],{"class":449},"  - ",[233,457,459],{"class":458},"sZZnC","_APP_OPENSSL_KEY_V1=your-secret-key",[233,461,462],{"class":439},"  # THIS MUST BE REPLACED\n",[10,464,465],{},"Hundreds of tutorials and GitHub repos show this exact default. If you deployed Appwrite without changing this value, every stored session token and OAuth credential in your database was encrypted with the string \"your-secret-key\". Anyone with database read access can decrypt them.",[10,467,468],{},"To generate a strong key before first run:",[224,470,474],{"className":471,"code":472,"language":473,"meta":229,"style":229},"language-bash shiki shiki-themes github-light github-dark","# Generate a 32-byte random key (base64 encoded)\nopenssl rand -base64 32\n\n# Set it in your .env before docker compose up\necho \"_APP_OPENSSL_KEY_V1=$(openssl rand -base64 32)\" >> .env\n","bash",[14,475,476,481,497,501,506],{"__ignoreMap":229},[233,477,478],{"class":235,"line":236},[233,479,480],{"class":439},"# Generate a 32-byte random key (base64 encoded)\n",[233,482,483,487,490,494],{"class":235,"line":242},[233,484,486],{"class":485},"sScJk","openssl",[233,488,489],{"class":458}," rand",[233,491,493],{"class":492},"sj4cs"," -base64",[233,495,496],{"class":492}," 32\n",[233,498,499],{"class":235,"line":248},[233,500,270],{"emptyLinePlaceholder":269},[233,502,503],{"class":235,"line":254},[233,504,505],{"class":439},"# Set it in your .env before docker compose up\n",[233,507,508,511,514,516,519,522,525,528,532],{"class":235,"line":260},[233,509,510],{"class":492},"echo",[233,512,513],{"class":458}," \"_APP_OPENSSL_KEY_V1=$(",[233,515,486],{"class":485},[233,517,518],{"class":458}," rand ",[233,520,521],{"class":492},"-base64",[233,523,524],{"class":492}," 32",[233,526,527],{"class":458},")\"",[233,529,531],{"class":530},"szBVR"," >>",[233,533,534],{"class":458}," .env\n",[10,536,537],{},"If you already have user data encrypted with the default key, rotating it requires a migration: export users, change the key, re-import. The Appwrite documentation covers this process, but it is disruptive. Get it right before the first deployment.",[539,540,541],"danger-box",{},[10,542,543,544,546],{},"Changing ",[14,545,32],{}," on a running deployment with existing users will invalidate all current sessions and OAuth tokens. Every user will be logged out and external OAuth connections will break. Do this before you have real users, or plan a maintenance window.",[42,548,550],{"id":549},"what-appwrite-cloud-handles-for-you","What Appwrite Cloud Handles for You",[10,552,553],{},"If you're using Appwrite Cloud rather than self-hosting, the platform manages:",[55,555,556,562,571,577,583],{},[58,557,558,561],{},[308,559,560],{},"TLS/SSL",": All traffic encrypted in transit, certificates managed automatically",[58,563,564,567,568,570],{},[308,565,566],{},"Encryption keys",": The ",[14,569,32],{}," equivalent is set and rotated by Appwrite",[58,572,573,576],{},[308,574,575],{},"Infrastructure updates",": Security patches applied without your intervention",[58,578,579,582],{},[308,580,581],{},"DDoS mitigation",": Handled at the network edge",[58,584,585,588],{},[308,586,587],{},"Backups",": Managed database backups with point-in-time recovery on higher plans",[10,590,591],{},"Appwrite Cloud is SOC 2 aligned and offers GDPR data processing agreements. EU data residency (Frankfurt region) is available on Pro and Scale plans for teams with regulatory requirements.",[10,593,594],{},"The tradeoff is that you do not have direct database access or control over the infrastructure configuration. For most founders building AI-assisted apps, that tradeoff is worth it.",[42,596,598],{"id":597},"realtime-subscriptions-and-the-double-exposure-risk","Realtime Subscriptions and the Double-Exposure Risk",[10,600,601],{},"Appwrite's Realtime feature lets clients subscribe to collection changes. It uses the same permission model as the REST API, which means an open collection is exploitable via Realtime as well as direct queries.",[224,603,605],{"className":226,"code":604,"language":228,"meta":229,"style":229},"// Subscribing to a collection with any:read permission\n// This stream is available to unauthenticated clients\nconst unsubscribe = client.subscribe(\n  'databases.production.collections.orders.documents',\n  (response) => {\n    console.log(response.payload); // Every new order, visible to anyone\n  }\n);\n",[14,606,607,612,617,622,627,632,637,642],{"__ignoreMap":229},[233,608,609],{"class":235,"line":236},[233,610,611],{},"// Subscribing to a collection with any:read permission\n",[233,613,614],{"class":235,"line":242},[233,615,616],{},"// This stream is available to unauthenticated clients\n",[233,618,619],{"class":235,"line":248},[233,620,621],{},"const unsubscribe = client.subscribe(\n",[233,623,624],{"class":235,"line":254},[233,625,626],{},"  'databases.production.collections.orders.documents',\n",[233,628,629],{"class":235,"line":260},[233,630,631],{},"  (response) => {\n",[233,633,634],{"class":235,"line":266},[233,635,636],{},"    console.log(response.payload); // Every new order, visible to anyone\n",[233,638,639],{"class":235,"line":273},[233,640,641],{},"  }\n",[233,643,644],{"class":235,"line":279},[233,645,646],{},");\n",[10,648,649,650,653,654,657,658,355,660,662],{},"If your ",[14,651,652],{},"orders"," collection has ",[14,655,656],{},"read: any"," set, every new document created fires a Realtime event that any connected client receives. Close the read permission to ",[14,659,147],{},[14,661,167],{}," to restrict it.",[42,664,666],{"id":665},"storage-buckets-same-model-different-default","Storage Buckets: Same Model, Different Default",[10,668,669,670,673],{},"Appwrite storage buckets default to ",[308,671,672],{},"no permissions",", which means all access is blocked until you set them. This is safer than an open default, but the fix is often too broad.",[10,675,676,677,679],{},"A bucket with ",[14,678,656],{}," makes every uploaded file publicly accessible via a direct URL, with no authentication required:",[224,681,686],{"className":682,"code":684,"language":685},[683],"language-text","https://cloud.appwrite.io/v1/storage/buckets/[bucketId]/files/[fileId]/view\n","text",[14,687,684],{"__ignoreMap":229},[10,689,690,691,694,695,697],{},"For user-generated content that should be private (profile photos, uploaded documents, medical records), use ",[14,692,693],{},"read: user:[userId]"," instead. For genuinely public assets like product images, ",[14,696,656],{}," is fine.",[42,699,701],{"id":700},"function-security","Function Security",[10,703,704,705,206,708,711],{},"Appwrite Functions run server-side and receive injected environment variables via ",[14,706,707],{},"process.env.APPWRITE_FUNCTION_PROJECT_ID",[14,709,710],{},"process.env.APPWRITE_FUNCTION_API_KEY",". These are provided by the platform and are not in your function code.",[10,713,714,715,718],{},"What you do control is what your function exposes. An HTTP-triggered function that returns sensitive data without checking ",[14,716,717],{},"req.headers['x-appwrite-trigger']"," or validating a webhook signature is an open endpoint. Add explicit caller verification to any function handling webhooks or sensitive operations.",[720,721,722,732,744,753,769],"faq-section",{},[723,724,726],"faq-item",{"question":725},"Is Appwrite safe for production apps?",[10,727,728,729,731],{},"Appwrite Cloud is safe for production when you configure permissions correctly. The platform handles SSL, managed infrastructure, and SOC 2 aligned security controls. The risks are in your configuration: document and bucket permissions set to ",[14,730,16],{}," expose data to unauthenticated users, and server API keys placed in client-side code give attackers full backend access.",[723,733,735],{"question":734},"What is the biggest security risk in Appwrite?",[10,736,737,738,740,741,743],{},"The ",[14,739,16],{}," shortcut in Appwrite's permission model. Setting create, update, or delete access to ",[14,742,16],{}," on a document collection or storage bucket means any visitor can write or overwrite that data without logging in. CheckYourVibe flags this as a critical finding when it appears on collections holding user or payment data.",[723,745,747],{"question":746},"Should I use Appwrite Cloud or self-host Appwrite?",[10,748,749,750,752],{},"Appwrite Cloud removes most infrastructure security concerns: managed TLS, automatic updates, and no default secrets to rotate. Self-hosting gives you full data control but requires you to set a strong ",[14,751,32],{}," before first run, restrict console access, manage updates yourself, and secure the Docker network. For most founders, Cloud is safer because the default is managed.",[723,754,756],{"question":755},"Can Appwrite API keys be exposed in the browser?",[10,757,758,759,762,763,355,766,768],{},"Yes, if you use a server API key in client-side code. Appwrite's client SDK does not use API keys: it authenticates via session cookies after a login flow. Server API keys (the ",[14,760,761],{},"standard_XXXXX"," format) are for backend services only. If you see ",[14,764,765],{},"APPWRITE_API_KEY",[14,767,402],{}," in your frontend code, that key is either already in your bundle or about to be.",[723,770,772],{"question":771},"Is Appwrite GDPR compliant?",[10,773,774],{},"Appwrite Cloud offers GDPR-compliant data processing with EU data residency available on Pro and Scale plans. The platform supports data deletion, export, and consent workflows. Self-hosted deployments put GDPR compliance on you: you own the infrastructure, the backups, and the data processing agreements.",[776,777,778,784,789],"related-articles",{},[779,780],"related-card",{"description":781,"href":782,"title":783},"RLS misconfiguration patterns, service role key exposure, and what to check before shipping a Supabase-backed app.","/blog/is-safe/supabase","Is Supabase Safe?",[779,785],{"description":786,"href":787,"title":788},"Firebase security rules gaps, exposed API keys, and how to audit what your AI-generated app actually exposes.","/blog/is-safe/firebase","Is Firebase Safe?",[779,790],{"description":791,"href":792,"title":793},"Public query exposure, missing auth checks in AI-generated code, and how to lock down Convex for production.","/blog/is-safe/convex-bk","Is the Convex Backend Safe?",[795,796,799],"cta-box",{"href":797,"label":798},"/","Scan Your Appwrite App",[10,800,801],{},"Check your deployed Appwrite app for exposed API keys in your JavaScript bundle, open collection permissions, and security header gaps. Free scan, no signup required.",[803,804,805],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}",{"title":229,"searchDepth":242,"depth":242,"links":807},[808,809,810,811,812,813,814,815],{"id":44,"depth":242,"text":45},{"id":107,"depth":242,"text":108},{"id":300,"depth":242,"text":301},{"id":420,"depth":242,"text":421},{"id":549,"depth":242,"text":550},{"id":597,"depth":242,"text":598},{"id":665,"depth":242,"text":666},{"id":700,"depth":242,"text":701},"is-safe","2026-06-28","Is Appwrite safe for your production app? Honest security review of Appwrite Cloud and self-hosted deployments: permission model risks, API key exposure, and encryption key pitfalls.",false,"md",[822,824,826,828,830],{"question":725,"answer":823},"Appwrite Cloud is safe for production when you configure permissions correctly. The platform handles SSL, managed infrastructure, and SOC 2 aligned security controls. The risks are in your configuration: document and bucket permissions set to 'any' expose data to unauthenticated users, and server API keys placed in client-side code give attackers full backend access.",{"question":734,"answer":825},"The 'any' shortcut in Appwrite's permission model. Setting create, update, or delete access to 'any' on a document collection or storage bucket means any visitor to your app can write or overwrite that data without logging in. CheckYourVibe flags this as a critical finding when it appears on collections holding user or payment data.",{"question":746,"answer":827},"Appwrite Cloud removes most infrastructure security concerns: managed TLS, automatic updates, and no default secrets to rotate. Self-hosting gives you full data control but requires you to set a strong _APP_OPENSSL_KEY_V1 before first run, restrict console access, manage updates yourself, and secure the Docker network. For most founders, Cloud is safer because the default is managed.",{"question":755,"answer":829},"Yes, if you use a server API key in client-side code. Appwrite's client SDK does not use API keys: it authenticates via session cookies after a login flow. Server API keys (the standard_XXXXX format) are for backend services only. If you see APPWRITE_API_KEY or NEXT_PUBLIC_APPWRITE_API_KEY in your frontend code, that key is either already in your bundle or about to be.",{"question":771,"answer":774},"amber",null,"is appwrite safe, appwrite security, appwrite permissions, appwrite api key exposure, appwrite self-hosted security, appwrite production",{},"Appwrite security review: the permission model footgun, client-bundle API key exposure, and the self-hosting default secret that breaks token encryption.","/blog/is-safe/appwrite","8 min read","[object Object]","Article",{"title":5,"description":818},{"loc":836},"blog/is-safe/appwrite",[],"summary_large_image","1h9Yrv6Z7qr62qaHgxhoRwOL462g936ofXj17V9XxCk",1784736378961]