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
- Confirm the subject path/uid still exists for the owning user.
- Remove stale share/ACL records that reference deleted entries.
- 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
- Remove share/ACL records that reference deleted entries.
- Re-stat as the owner to distinguish missing-subject from genuine ACL 404.
- Watch for ACL/entry cleanup races after deletion.
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
- 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/a291afe70ca5122b.
Report an issue: GitHub.