{"record":{"id":"ecdcff59a3886b3b","repo":"HeyPuter/puter","slug":"subject-does-not-exist-ecdcff","errorCode":"subject_does_not_exist","errorMessage":"Entry not found: path=${path}","messagePattern":"Entry not found: path=(.+?)","errorType":"http","errorClass":"HttpError","httpStatus":404,"severity":"error","filePath":"src/backend/controllers/fs/legacyFsHelpers.ts","lineNumber":206,"sourceCode":"        status?: unknown;\n        message?: unknown;\n        fields?: { code?: unknown };\n    };\n    const status = Number(safe?.status);\n    const message =\n        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","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/fs/legacyFsHelpers.ts#L188-L224","documentation":"`assertAccess` masks ACL denials for app-under-user actors as 404 `subject_does_not_exist`. The deliberate intent (documented inline) is to avoid leaking the existence of another user's or another app's private files through a distinct error code. So an app that lacks read/write on a path gets 'not found' even when the entry exists.","triggerScenarios":"An app-scoped actor attempts to read/write a path it has no ACL grant on — including paths that genuinely exist but are owned by a sibling user or a different app's AppData subtree.","commonSituations":"App tries to access a file outside its granted scope without a share; missing share/permission grant to the app; app assumes a path exists and gets a misleading 404.","solutions":["Grant the app explicit access (share or ACL rule) to the path, then retry.","Do not infer from this 404 that the entry is absent — for app actors it can mean 'forbidden'.","Verify as the owning user that the path exists before debugging app access."],"exampleFix":"// before — app has no grant on the file\nawait appClient.fs.read('/alice/secret.txt'); // -> 404 subject_does_not_exist\n\n// after — owner grants the app read access first\nawait ownerClient.acl.grant({ subject: '/alice/secret.txt', app: appUid, mode: 'read' });\nawait appClient.fs.read('/alice/secret.txt');","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isAppActorDenied404(e) { return e?.status === 404 && e?.code === 'subject_does_not_exist'; }","tryCatchPattern":"try { await appClient.fs.read(path); }\ncatch (e) {\n  if (isAppActorDenied404(e)) { await grantAccess(path, appUid); return retry(); }\n  throw e;\n}","preventionTips":["Grant the app explicit ACL access before it touches the path.","Do not treat this 404 as 'absent' for app actors — it often means forbidden.","Verify existence as the owning user when debugging."],"tags":["acl","authz","app","not-found","security"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}