#1290 added a guard so the bridge's attribution identity is derived from the caller instead of accepted from the client. The rejection half works; the derive half is skipped whenever liveRelay arrives as anything other than a literal boolean true, because the check is strict and the schema field is loosely cast.
backend/routes/integrations.ts:402-406 (main e35d89e6):
if (config && 'linkedUserId' in config && String(config.linkedUserId) !== String(req.user?.id)) {
return res.status(400).json({ message: 'linkedUserId is derived from the authenticated caller and cannot be set' });
}
if (config && config.liveRelay === true) nextConfig.linkedUserId = req.user?.id;
config.liveRelay === true is strict. liveRelay is declared { type: Boolean }, so Mongoose casts 'true' and 1 to true on write. Send either and the relay switches on while linkedUserId keeps whatever it already held.
Measured
Against the existing route harness at e35d89e6, caller user-1, on a row whose stored config.linkedUserId is VICTIM-USER-ID:
| body |
status |
stored liveRelay |
stored linkedUserId |
{ liveRelay: true, linkedUserId: 'VICTIM-USER-ID' } |
400 |
— |
— |
{ liveRelay: true } |
200 |
true |
user-1 ✅ derived |
{ liveRelay: 'true' } |
200 |
true |
VICTIM-USER-ID ❌ |
{ liveRelay: 1 } |
200 |
true |
VICTIM-USER-ID ❌ |
And end-to-end through Mongoose (mongodb-memory-server, raw-driver readback) to confirm the string is not merely stored as a string:
stored liveRelay : true (typeof "boolean")
stored linkedUserId : "VICTIM"
findLiveIntegration hit : true
So the bridge activates and relays under the stale identity.
Reachability — narrower than it looks, and worth stating
The 400 means a caller cannot introduce someone else's id. A stale linkedUserId can only be a previous authorized caller's, so exploiting this needs two parties authorized on the same integration — canDeleteIntegration admits instance admins, the pod's creator, and the integration's creator, so pod-creator + integration-creator is the realistic pair.
The correctness defect stands independently of that: the invariant #1290 set out to establish — liveRelay true implies linkedUserId is whoever turned it on — does not hold, and any future caller that sends a form-encoded or JSON-stringified "true" breaks it without malice.
Suggested fix
Key the derive off the coerced result rather than the raw input, so it cannot drift from what Mongoose will store:
if (config && 'liveRelay' in config && Boolean(config.liveRelay)) {
nextConfig.linkedUserId = req.user?.id;
}
Worth a test asserting { liveRelay: 'true' } derives, since that is the arm that is currently green and wrong.
Not verified
Whether any live integration currently carries a linkedUserId that is not its most recent enabler — I checked the code path, not production data. Nothing on main writes liveRelay outside the new Connectors page, which only sends a real boolean, so the string path likely has no live traffic yet.
#1290 added a guard so the bridge's attribution identity is derived from the caller instead of accepted from the client. The rejection half works; the derive half is skipped whenever
liveRelayarrives as anything other than a literal booleantrue, because the check is strict and the schema field is loosely cast.backend/routes/integrations.ts:402-406(maine35d89e6):config.liveRelay === trueis strict.liveRelayis declared{ type: Boolean }, so Mongoose casts'true'and1totrueon write. Send either and the relay switches on whilelinkedUserIdkeeps whatever it already held.Measured
Against the existing route harness at
e35d89e6, calleruser-1, on a row whose storedconfig.linkedUserIdisVICTIM-USER-ID:liveRelaylinkedUserId{ liveRelay: true, linkedUserId: 'VICTIM-USER-ID' }{ liveRelay: true }trueuser-1✅ derived{ liveRelay: 'true' }trueVICTIM-USER-ID❌{ liveRelay: 1 }trueVICTIM-USER-ID❌And end-to-end through Mongoose (
mongodb-memory-server, raw-driver readback) to confirm the string is not merely stored as a string:So the bridge activates and relays under the stale identity.
Reachability — narrower than it looks, and worth stating
The 400 means a caller cannot introduce someone else's id. A stale
linkedUserIdcan only be a previous authorized caller's, so exploiting this needs two parties authorized on the same integration —canDeleteIntegrationadmits instance admins, the pod's creator, and the integration's creator, so pod-creator + integration-creator is the realistic pair.The correctness defect stands independently of that: the invariant #1290 set out to establish —
liveRelaytrue implieslinkedUserIdis whoever turned it on — does not hold, and any future caller that sends a form-encoded or JSON-stringified"true"breaks it without malice.Suggested fix
Key the derive off the coerced result rather than the raw input, so it cannot drift from what Mongoose will store:
Worth a test asserting
{ liveRelay: 'true' }derives, since that is the arm that is currently green and wrong.Not verified
Whether any live integration currently carries a
linkedUserIdthat is not its most recent enabler — I checked the code path, not production data. Nothing onmainwritesliveRelayoutside the new Connectors page, which only sends a real boolean, so the string path likely has no live traffic yet.