From 98f18faf3601c1a42f70d0dc174eac1ed1ce4170 Mon Sep 17 00:00:00 2001 From: ProgrammerIn-wonderland Date: Fri, 29 May 2026 18:18:21 -0400 Subject: [PATCH] tighten RAO policy --- src/backend/controllers/apps/AppController.js | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/backend/controllers/apps/AppController.js b/src/backend/controllers/apps/AppController.js index 48debed57..d2ae385b7 100644 --- a/src/backend/controllers/apps/AppController.js +++ b/src/backend/controllers/apps/AppController.js @@ -17,6 +17,7 @@ * along with this program. If not, see . */ +import { isAccessTokenActor, isAppActor } from '../../core/actor.js'; import { HttpError } from '../../core/http/HttpError.js'; import { driversContainers } from '../../exports.js'; import { @@ -89,6 +90,16 @@ export class AppController extends PuterController { // actor calls this, the app id is already on the token — clients // don't re-send it in the body. Fall back to `actor.app.uid` // before 400-ing for a missing body field. + // + // Authorization: only two callers are trusted to report opens — + // 1. a root user actor (plain session, no `.app` and no access + // token), e.g. the GUI launching apps on behalf of the user; + // 2. the app-under-user actor for the app being reported, i.e. + // `actor.app.uid === app_uid`. + // Everything else — access tokens (regardless of issuer), asset + // tokens, app actors reporting for a *different* app — is denied, + // otherwise any authenticated party could inflate another app's + // open count. router.post( '/rao', { @@ -96,8 +107,9 @@ export class AppController extends PuterController { requireAuth: true, }, async (req, res) => { + const actor = req.actor; const bodyAppUid = req.body?.app_uid; - const actorAppUid = req.actor?.app?.uid; + const actorAppUid = actor?.app?.uid; const app_uid = typeof bodyAppUid === 'string' && bodyAppUid.length > 0 ? bodyAppUid @@ -108,6 +120,26 @@ export class AppController extends PuterController { }); } + // Access tokens (and any other non-user/non-app identity, + // e.g. asset tokens) are not allowed to report opens — + // they're shared / scoped credentials and shouldn't drive + // analytics counters. + if (isAccessTokenActor(actor)) { + throw new HttpError( + 403, + 'Access tokens cannot report app opens', + { legacyCode: 'forbidden' }, + ); + } + + if (isAppActor(actor) && app_uid !== actorAppUid) { + throw new HttpError( + 403, + 'App actors can only report opens for their own app', + { legacyCode: 'forbidden' }, + ); + } + const app = await this.appStore.getByUid(app_uid); if (!app) throw new HttpError(404, 'App not found', {