{"record":{"id":"a291afe70ca5122b","repo":"HeyPuter/puter","slug":"access-denied-a291af","errorCode":null,"errorMessage":"Access denied","messagePattern":"Access denied","errorType":"http","errorClass":"HttpError","httpStatus":404,"severity":"error","filePath":"src/backend/controllers/fs/legacyFsHelpers.ts","lineNumber":212,"sourceCode":"        typeof safe?.message === 'string' && safe.message.length > 0\n            ? safe.message\n            : 'Access denied';\n    const code =\n        typeof safe?.fields?.code === 'string' ? safe.fields.code : undefined;\n    const legacyCode = code === 'forbidden' ? 'access_denied' : code;\n\n    // App-under-user actors see denials as 404 \"subject_does_not_exist\"\n    // so existence of a sibling user's / other-app's files isn't leaked\n    // through the error code. User-actor denials keep the real 403.\n\n    if (isAppActor(actor)) {\n        throw new HttpError(404, `Entry not found: path=${path}`, {\n            legacyCode: 'subject_does_not_exist',\n        });\n    }\n\n    if (status === 404) {\n        throw new HttpError(404, message, {\n            ...(legacyCode ? { legacyCode } : {}),\n        });\n    }\n    throw new HttpError(403, message, {\n        legacyCode: legacyCode ?? 'access_denied',\n    });\n}\n\n/**\n * Authorize creation of a new entry at `targetPath`. The standard rule is write\n * on the parent, but we also allow it when the actor has explicit write on the\n * target itself — this covers an app creating its own\n * `/<user>/AppData/<app_uid>` folder (parent `AppData` is off-limits, but the\n * target is the app's own subtree per ACLService's short-circuit) and shares\n * granted directly on a not-yet-created path.\n *\n * On failure, delegates to `assertAccess` on the parent so the error shape\n * stays identical to the previous parent-only check.","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/fs/legacyFsHelpers.ts#L194-L230","documentation":"`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.","triggerScenarios":"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.","commonSituations":"A share/permission record points at a deleted entry; ACL cache/table inconsistency; race between entry deletion and ACL cleanup.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await fs.read(path); }\ncatch (e) { if (e.status === 404) { await pruneStaleShares(path); throw e; } throw e; }","preventionTips":["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."],"tags":["acl","authz","not-found","v1-api"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}