HeyPuter/puter · error · HttpError

unauthorized

unauthorized

Error message

Unauthorized

What it means

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.

Source

Thrown at src/backend/controllers/fs/FSController.ts:1604

        await this.#assertAccess(actor, target.path, 'read');
        await this.#assertAccess(actor, parent.path, 'write');

        const shortcut = await this.services.fs.mkshortcut(userId, {
            parent,
            name,
            target,
            dedupeName: this.#toBoolean(body.dedupe_name) ?? true,
        });
        this.#emitGuiItemAdded(shortcut);
        res.json(this.#toClientEntry(shortcut));
    }

    // -- Read-side helpers -----------------------------------------------

    #requireActor(req: Request): Actor {
        const actor = req.actor;
        if (!actor) {
            throw new HttpError(401, 'Unauthorized', {
                legacyCode: 'unauthorized',
            });
        }
        return actor;
    }

    #isRootPathRef(source: Record<string, unknown>): boolean {
        if (typeof source.path !== 'string') return false;
        if (source.uid !== undefined || source.uuid !== undefined) return false;
        if (source.id !== undefined) return false;
        return source.path.trim() === '/';
    }

    async #resolveEntryForRequest(source: Record<string, unknown>) {
        const mod = await import('../../services/fs/resolveNode.js');
        const username = Context.get('actor')?.user?.username;
        const rawPath =
            typeof source.path === 'string' ? source.path : undefined;

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Send a valid session token or app access token in the request (Authorization header or puter-auth cookie).
  2. Verify the auth middleware is mounted on the api subdomain route and runs before FSController.
  3. Confirm the token has not expired and the user/app it represents still exists.
  4. If reproducing in tests, use setupPuterTestEnv() helpers that authenticate the request rather than calling the handler with a bare Request.

Example fix

// before
await fetch('https://api.puter.com/mkshortcut', { method:'POST', body: JSON.stringify({...}) });
// after
await fetch('https://api.puter.com/mkshortcut', { method:'POST', headers:{ Authorization:`Bearer ${token}` }, body: JSON.stringify({...}) });
Defensive patterns

Strategy: validation

Validate before calling

function withAuth(token, init = {}) {
  if (!token) throw new Error('Auth token required for FS mutation');
  return { ...init, headers: { ...(init.headers||{}), Authorization: `Bearer ${token}` } };
}

Type guard

/** @param {any} req */
function hasActor(req) { return !!req?.actor; }

Try / catch

try { await api(); }
catch (e) {
  if (e?.status === 401) { /* redirect to login / refresh token */ }
  else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


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