HeyPuter/puter · error · HttpError
access_denied
access_denied
Error message
Access denied
What it means
The default `assertAccess` failure for a user actor: the ACL check denied access (not a 404-from-ACL case) and the helper throws 403 with `legacyCode: 'access_denied'` (or the ACL layer's own code, with `forbidden` mapped to `access_denied`). This is the standard 'permission denied' for user-actor requests.
Source
Thrown at src/backend/controllers/fs/legacyFsHelpers.ts:216
typeof safe?.fields?.code === 'string' ? safe.fields.code : undefined;
const legacyCode = code === 'forbidden' ? 'access_denied' : code;
// App-under-user actors see denials as 404 "subject_does_not_exist"
// so existence of a sibling user's / other-app's files isn't leaked
// through the error code. User-actor denials keep the real 403.
if (isAppActor(actor)) {
throw new HttpError(404, `Entry not found: path=${path}`, {
legacyCode: 'subject_does_not_exist',
});
}
if (status === 404) {
throw new HttpError(404, message, {
...(legacyCode ? { legacyCode } : {}),
});
}
throw new HttpError(403, message, {
legacyCode: legacyCode ?? 'access_denied',
});
}
/**
* Authorize creation of a new entry at `targetPath`. The standard rule is write
* on the parent, but we also allow it when the actor has explicit write on the
* target itself — this covers an app creating its own
* `/<user>/AppData/<app_uid>` folder (parent `AppData` is off-limits, but the
* target is the app's own subtree per ACLService's short-circuit) and shares
* granted directly on a not-yet-created path.
*
* On failure, delegates to `assertAccess` on the parent so the error shape
* stays identical to the previous parent-only check.
*/
export async function assertCanCreate(
aclService: ACLService,
fsService: FSService,View on GitHub (pinned to 908ec23eda)
Solutions
- Have the owner share the path with the user at the required mode (read or write).
- Switch to the owning account if you own the resource.
- Verify the share mode matches the operation (write ops need write share).
Example fix
// before — user lacks write on a shared folder
await puter.fs.write('/shared/doc.txt', content); // 403 access_denied
// after — owner grants write, then retry
await owner.acl.grant({ subject: '/shared/doc.txt', user: userId, mode: 'write' });
await puter.fs.write('/shared/doc.txt', content); Defensive patterns
Strategy: try-catch
Validate before calling
async function canAccess(acl, actor, path, mode) { return await acl.check(actor, { path }, mode); }
if (!await canAccess(acl, actor, path, 'write')) throw new Error('request write share first'); Type guard
function isAccessDenied(e) { return e?.status === 403 && e?.code === 'access_denied'; } Try / catch
try { await fs.write(path, data); }
catch (e) { if (isAccessDenied(e)) { await requestShare(path, 'write'); return retry(); } throw e; } Prevention
- Request a share at the correct mode (read/write) before operating.
- Switch to the owning account if you own the resource.
- Re-verify share mode matches the operation before retry.
When it happens
Trigger: A user reads/writes a path they don't own and that hasn't been shared with them; an attempt to write into another user's home or a read-only share.
Common situations: No share granted; share is read-only but the op needs write; user switched accounts and lost access; share expired or was revoked.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/894acdb3eed0af4a.
Report an issue: GitHub.