HeyPuter/puter · error · HttpError
bad_request
bad_request
Error message
Exactly one of `app` and `origin` is required
What it means
Thrown by GET /app-feedback/target when the XOR check `!app === !origin` fails — i.e. both app and origin are provided, or neither is. The endpoint takes exactly one target identifier: an app uid/name OR an origin URL. readTargetParam also returns undefined for values over 2048 chars or non-strings, so an over-long origin looks like 'not provided'.
Source
Thrown at src/backend/controllers/feedback/AppFeedbackController.ts:77
* app accepts feedback, plus its canonical title/name for display. Reveals
* nothing that `puter.apps.get` doesn't already.
*/
@Get('/target', {
subdomain: 'api',
requireUserActor: true,
requireVerified: true,
rateLimit: {
scope: 'app-feedback-target',
limit: 60,
window: 60_000,
key: 'user',
},
})
async target(req: Request, res: Response): Promise<void> {
const app = readTargetParam(req.query.app);
const origin = readTargetParam(req.query.origin);
if (!app === !origin) {
throw new HttpError(
400,
'Exactly one of `app` and `origin` is required',
{ legacyCode: 'bad_request' },
);
}
const service = this.services.appFeedback as AppFeedbackService;
res.json(await service.getTarget({ app, origin }));
}
/**
* POST /app-feedback — store one feedback message and email the app's
* developer. Strict limits: the route limits below are the cheap first
* line; AppFeedbackService enforces durable per-user/per-app daily caps
* from the database (the route limiter fails open, the DB caps don't).
*/
@Post('/', {
subdomain: 'api',View on GitHub (pinned to 908ec23eda)
Solutions
- Send exactly one of ?app=<app> or ?origin=<origin>, not both, not neither.
- Ensure origin (if used) is a real URL under 2048 characters — longer values are dropped by readTargetParam and treated as absent.
- In client code, branch: pick app when you have an app id, otherwise origin, never concatenate.
Example fix
// before
fetch('/app-feedback/target?app=' + appId + '&origin=' + origin)
// after
fetch('/app-feedback/target?' + (appId ? 'app=' + appId : 'origin=' + origin)) Defensive patterns
Strategy: validation
Validate before calling
const params = appId ? { app: appId } : (origin ? { origin } : null);
if (!params) throw new Error('need app or origin');
// origin must be <= 2048 chars
if ('origin' in params && params.origin.length > 2048) throw new Error('origin too long'); Type guard
const hasOneTarget = (app?: string, origin?: string): boolean => !!app !== !!origin; // exactly one
Prevention
- Send exactly one of app or origin.
- Keep origin under 2048 chars or readTargetParam drops it.
- Build the query string from a single chosen field.
When it happens
Trigger: GET /app-feedback/target?app=X&origin=Y (both), or /app-feedback/target with neither; an origin longer than 2048 chars (TARGET_PARAM_MAX_LENGTH) that readTargetParam silently drops to undefined, making an otherwise-present origin count as missing.
Common situations: Dialog that fills both fields from context; client always sending origin as a fallback alongside app; an origin with a huge tracking suffix exceeding the 2048 cap and being silently ignored.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/9844ad8ce26dbfe4.
Report an issue: GitHub.