{"record":{"id":"9b15f45b5b1a3456","repo":"HeyPuter/puter","slug":"account-suspended-9b15f4","errorCode":"account_suspended","errorMessage":"Account suspended","messagePattern":"Account suspended","errorType":"http","errorClass":"HttpError","httpStatus":401,"severity":"error","filePath":"src/backend/controllers/fs/LegacyFSController.ts","lineNumber":1408,"sourceCode":"        );\n\n        const uid = typeof query.uid === 'string' ? query.uid : '';\n        const targetEntry = await resolveV1Selector(this.stores.fsEntry, {\n            uid,\n        });\n        if (!targetEntry)\n            throw new HttpError(404, 'Item not found', {\n                legacyCode: 'not_found',\n            });\n\n        // Owner suspension check.\n        const owner = await this.stores.user.getById(targetEntry.userId);\n        if (!owner)\n            throw new HttpError(500, 'Owner not found', {\n                legacyCode: 'internal_error',\n            });\n        if ((owner as { suspended?: unknown }).suspended)\n            throw new HttpError(401, 'Account suspended', {\n                legacyCode: 'account_suspended',\n            });\n\n        const userId = targetEntry.userId;\n        const operation =\n            typeof query.operation === 'string' ? query.operation : 'write';\n\n        // A valid write signature authorises overwriting the file's bytes,\n        // not structural changes. Restrict copy/move/mkdir/rename/delete/trash\n        // to a caller authenticated as the owner — otherwise a recipient of a\n        // write-share could relocate or destroy the source via this endpoint.\n        if (operation !== 'write' && req.actor?.user?.id !== userId) {\n            throw new HttpError(\n                403,\n                `'${operation}' via signed URL requires owner authentication`,\n                { legacyCode: 'forbidden' },\n            );\n        }","sourceCodeStart":1390,"sourceCodeEnd":1426,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/fs/LegacyFSController.ts#L1390-L1426","documentation":"Thrown by POST /writeFile when the target entry's owner has the `suspended` flag set. Even though the signature is cryptographically valid, a suspended owner's files must not be mutated. Because signed URLs are long-lived by default, this guard blocks signatures minted before the suspension.","triggerScenarios":"POST /writeFile (any operation) where the file owner's account was suspended after the URL was signed; writing to a shared file whose owner got suspended.","commonSituations":"Account suspended for abuse/non-payment while collaborators hold valid signed URLs; reusing an old signed URL after an account action.","solutions":["Stop using signed URLs for the suspended owner's content; resolve the suspension with the account admin first.","If you are a collaborator, ask the owner to restore their account, then re-sign.","Surface a clear 'account suspended' message to end users rather than retrying."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await fetch(writeUrl, { method:'POST', body: form }); }\ncatch (e) {\n  if (e.code === 'account_suspended') {\n    // stop retrying; surface 'owner account suspended' to the user\n  } else throw e;\n}","preventionTips":["Do not retry on account_suspended — it is not transient.","Show a clear message and a path to resolve the suspension.","Invalidate locally cached signed URLs for that owner."],"tags":["auth","suspended","writefile","account"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}