HeyPuter/puter · error · HttpError

forbidden

forbidden

Error message

kv: `${method}` is not available on another app's data

What it means

Thrown by `#assertCrossAppKvAccess` when a KV method is invoked against another app's namespace but the method has no mapped cross-app operation in `APP_DATA_KV_METHOD_OPS` (value is `null` or `undefined`). `flush` maps to `null` (namespace-wide, not an entry op), so it can never run cross-app; any unmapped method also fails closed. Returns HTTP 403 with legacyCode `forbidden`.

Source

Thrown at src/backend/drivers/kv/KVStoreDriver.ts:205

                { legacyCode: 'bad_request' },
            );
        }

        await this.#assertCrossAppKvAccess(actor!, appUuid, method, args);
        return { actor, namespaceAppUuid: appUuid };
    }

    async #assertCrossAppKvAccess(
        actor: Actor,
        targetAppUid: string,
        method: string,
        args: KvCallArgs,
    ): Promise<void> {
        // `null` = no scope reaches it (`flush` is namespace-wide, not an
        // entry op); `undefined` = unmapped method. Both fail closed.
        const op = APP_DATA_KV_METHOD_OPS[method];
        if (!op) {
            throw new HttpError(
                403,
                `kv: \`${method}\` is not available on another app's data`,
                { legacyCode: 'forbidden' },
            );
        }

        const target = await this.stores.app.getByUid(targetAppUid);
        if (!target) {
            throw new HttpError(404, `entity_not_found: app:${targetAppUid}`, {
                legacyCode: 'subject_does_not_exist',
            });
        }
        if (!appDataSharingAllowed(target)) {
            throw new HttpError(
                403,
                'kv: this app does not share its data with other apps',
                { legacyCode: 'forbidden' },
            );

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Do not flush another app's namespace; flush is own-namespace only.
  2. For cross-app data, restrict to mapped entry ops (get/set/list/del/incr/decr/update/expire/expireAt/add/batchPut).
  3. Remove the `appUuid` override to operate on your own namespace.

Example fix

// before
kv.flush({ appUuid: otherAppUid }); // 403

// after
// flush only your own namespace
kv.flush();
// or target specific keys cross-app
await kv.del('k', { appUuid: otherAppUid });
Defensive patterns

Strategy: validation

Validate before calling

const CROSS_APP_OK = new Set(['get','set','list','del','remove','incr','decr','add','update','batchPut','expire','expireAt']);
if (appUuid && appUuid !== ownAppUid && !CROSS_APP_OK.has(method)) {
  throw new Error(`${method} is not available on another app's data`);
}

Type guard

/** @param {string} method @param {string|undefined} appUuid @param {string} ownUid @returns {boolean} */
function isAllowedCrossAppMethod(method, appUuid, ownUid) {
  if (!appUuid || appUuid === ownUid) return true;
  return new Set(['get','set','list','del','remove','incr','decr','add','update','batchPut','expire','expireAt']).has(method);
}

Try / catch

try {
  await kv.flush({ appUuid });
} catch (e) {
  if (e.code === 'forbidden' && e.message.includes('not available on another app')) { /* flush own namespace only */ await kv.flush(); return; }
  throw e;
}

Prevention

When it happens

Trigger: Calling `kv.flush({ appUuid: otherApp })` (the canonical case), or any future/unmapped method, while targeting another app's namespace. The map defines which ops are even eligible for cross-app access.

Common situations: Admin/tooling trying to flush another app's namespace; a generic KV wrapper that forwards arbitrary method names plus an appUuid; assuming flush is permitted because get/set are.

Related errors


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