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

  1. Have the owner share the path with the user at the required mode (read or write).
  2. Switch to the owning account if you own the resource.
  3. 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

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

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/894acdb3eed0af4a. Report an issue: GitHub.