HeyPuter/puter · error · HttpError

Access denied

Error message

Access denied

What it means

`assertAccess` for a non-app actor re-throws the underlying ACL error. When the safe-ACL error reports `status === 404` (the subject itself is reported missing by the ACL layer), it surfaces as a 404 with that message and whatever legacyCode the ACL layer supplied — which may be empty/undefined (hence the error has no `code` in the metadata). This is the 'denial that looks like not-found for user actors' branch.

Source

Thrown at src/backend/controllers/fs/legacyFsHelpers.ts:212

        typeof safe?.message === 'string' && safe.message.length > 0
            ? safe.message
            : 'Access denied';
    const code =
        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.

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Confirm the subject path/uid still exists for the owning user.
  2. Remove stale share/ACL records that reference deleted entries.
  3. Retry after the ACL layer reconciles; if persistent, investigate the ACL service's subject resolution.
Defensive patterns

Strategy: try-catch

Try / catch

try { await fs.read(path); }
catch (e) { if (e.status === 404) { await pruneStaleShares(path); throw e; } throw e; }

Prevention

When it happens

Trigger: A user-actor request to a path the ACL layer cannot find in its own tables (e.g. a share descriptor whose subject was removed), causing the ACL check itself to return 404 rather than 403.

Common situations: A share/permission record points at a deleted entry; ACL cache/table inconsistency; race between entry deletion and ACL cleanup.

Understand the failure class

Related errors


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