{"record":{"id":"b4c33fbf8f67a195","repo":"HeyPuter/puter","slug":"unauthorized-b4c33f","errorCode":"unauthorized","errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"exception","errorClass":"HttpError","httpStatus":401,"severity":"error","filePath":"src/backend/controllers/fs/FSController.ts","lineNumber":1604,"sourceCode":"        await this.#assertAccess(actor, target.path, 'read');\n        await this.#assertAccess(actor, parent.path, 'write');\n\n        const shortcut = await this.services.fs.mkshortcut(userId, {\n            parent,\n            name,\n            target,\n            dedupeName: this.#toBoolean(body.dedupe_name) ?? true,\n        });\n        this.#emitGuiItemAdded(shortcut);\n        res.json(this.#toClientEntry(shortcut));\n    }\n\n    // -- Read-side helpers -----------------------------------------------\n\n    #requireActor(req: Request): Actor {\n        const actor = req.actor;\n        if (!actor) {\n            throw new HttpError(401, 'Unauthorized', {\n                legacyCode: 'unauthorized',\n            });\n        }\n        return actor;\n    }\n\n    #isRootPathRef(source: Record<string, unknown>): boolean {\n        if (typeof source.path !== 'string') return false;\n        if (source.uid !== undefined || source.uuid !== undefined) return false;\n        if (source.id !== undefined) return false;\n        return source.path.trim() === '/';\n    }\n\n    async #resolveEntryForRequest(source: Record<string, unknown>) {\n        const mod = await import('../../services/fs/resolveNode.js');\n        const username = Context.get('actor')?.user?.username;\n        const rawPath =\n            typeof source.path === 'string' ? source.path : undefined;","sourceCodeStart":1586,"sourceCodeEnd":1622,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/fs/FSController.ts#L1586-L1622","documentation":"Thrown by FSController.#requireActor when `req.actor` is falsy at handler entry. The actor is set by upstream authentication middleware (session or app token), so reaching a handler without it means authn never ran or failed silently and the request slipped past the route gate. Every mutating FS handler opens with this check, so it is the first thing that fires when the actor context is absent.","triggerScenarios":"Calling any FS mutation endpoint (mkshortcut, copy, write, batchWrite, etc.) with no Authorization header / session cookie; a misconfigured route that drops the auth middleware; an internal call that bypasses the express pipeline and constructs a Request manually without attaching `actor`; token verification disabled in dev but the handler still requires an actor.","commonSituations":"Local dev with `requireVerified` or auth middleware disabled; a proxy stripping the Authorization header; using a service-to-service client that forgets to forward the user session; hitting the route during a session-expired race where the actor was cleared.","solutions":["Send a valid session token or app access token in the request (Authorization header or puter-auth cookie).","Verify the auth middleware is mounted on the api subdomain route and runs before FSController.","Confirm the token has not expired and the user/app it represents still exists.","If reproducing in tests, use setupPuterTestEnv() helpers that authenticate the request rather than calling the handler with a bare Request."],"exampleFix":"// before\nawait fetch('https://api.puter.com/mkshortcut', { method:'POST', body: JSON.stringify({...}) });\n// after\nawait fetch('https://api.puter.com/mkshortcut', { method:'POST', headers:{ Authorization:`Bearer ${token}` }, body: JSON.stringify({...}) });","handlingStrategy":"validation","validationCode":"function withAuth(token, init = {}) {\n  if (!token) throw new Error('Auth token required for FS mutation');\n  return { ...init, headers: { ...(init.headers||{}), Authorization: `Bearer ${token}` } };\n}","typeGuard":"/** @param {any} req */\nfunction hasActor(req) { return !!req?.actor; }","tryCatchPattern":"try { await api(); }\ncatch (e) {\n  if (e?.status === 401) { /* redirect to login / refresh token */ }\n  else throw e;\n}","preventionTips":["Always attach Authorization on FS mutation calls; gate them on a resolved token.","Use the SDK's session-aware client rather than hand-rolled fetch.","In tests, drive requests through the authenticated test-env helpers."],"tags":["fs","auth","api","unauthorized"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}