openvibers/openvibe.bot Commits

Keep Actor browser storage private to Run’s leased desks (#347)

OP OpenVibers committed 4 days ago · 57902798b9 · 2 files, +16 −1

Changes

ddiff --git a/server/realtime.js b/server/realtime.js
iindex d2542df..9fac317 100644
---- a/server/realtime.js
++++ b/server/realtime.js
@@@ -801,7 +801,14 @@ function createRealtime({ config, keys, userAuth, log = console, now = () => Dat
};
const screen = msg.screen === true && kind === SCREEN_KIND;
if (!screen && !COMPUTER_CAP[kind]) return refuse('bot.invalid_input', `kind is ${COMPUTER_KINDS.join(', ')}`);
-- if (!screen) {
++ const profileAction = kind === 'browser' && ['profile_export', 'profile_import', 'profile_sign_out'].includes(value.action);
++ if (profileAction) {
++ const lease = /^desk-[a-z0-9][a-z0-9-]{0,23}$/.test(value.desk || '') ? await domain.deskLeases.get(deviceId, value.desk) : null;
++ if (conn.principal.kind !== 'service' || conn.principal.sub !== 'svc:run' || !lease || lease.owner !== `user:${subject}` || !capabilities.grants(conn.principal.cap, 'bot.desk.session')) return refuse('bot.forbidden', 'Saved sign-ins belong to Run and its leased desk.');
++ const keys = value.action === 'profile_import' ? ['action', 'desk', 'profile'] : value.action === 'profile_sign_out' ? ['action', 'desk', 'site'] : ['action', 'desk'];
++ if (Object.keys(value).some(k => !keys.includes(k)) || JSON.stringify(value).length > 48000 || (value.action === 'profile_import' && (!value.profile || !Array.isArray(value.profile.cookies) || !Array.isArray(value.profile.origins))) || (value.action === 'profile_sign_out' && !/^[a-z0-9.-]{1,253}$/.test(value.site || ''))) return refuse('bot.invalid_input', 'Invalid saved sign-ins.');
++ }
++ if (!screen && !profileAction) {
const check = validate('bot.command@1', { v: 1, seq: 0, ts: 0, type: 'command', id, kind, value });
if (!check.valid) return refuse('bot.invalid_input', `invalid ${kind}: ${(check.errors && check.errors[0] && (check.errors[0].message || JSON.stringify(check.errors[0]))) || 'bad value'}`.slice(0, 300));
}
ddiff --git a/test/desk-leases.test.js b/test/desk-leases.test.js
iindex d7d84ee..0f953a6 100644
---- a/test/desk-leases.test.js
++++ b/test/desk-leases.test.js
@@@ -95,6 +95,14 @@ const { boot, check, done } = require('./helpers/app');
assert.deepStrictEqual([r.status, r.json.result, r.json.desk], [200, 'ack', 'desk-s01'], r.text);
assert.strictEqual(seen.at(-1).value.desk, 'desk-s01', 'Run never names the desk: Bot does');
assert.strictEqual((await t.call('POST', url, { ...asRun(bob.subject), body: { kind: 'browser', value: { action: 'read', desk: 'desk-other' } } })).status, 422, 'a value naming a desk');
++ for (const value of [{ action: 'profile_export' }, { action: 'profile_import', profile: { cookies: [], origins: [] } }, { action: 'profile_sign_out', site: 'example.org' }]) {
++ const saved = await t.call('POST', url, { ...asRun(bob.subject), body: { kind: 'browser', value } });
++ assert.equal(saved.status, 200, saved.text); assert.equal(saved.json.result, 'ack', saved.text);
++ }
++ assert.equal((await t.call('POST', url, { ...asRun(carol.subject), body: { kind: 'browser', value: { action: 'profile_export' } } })).status, 404);
++ const privateCommand = await t.hub.machineCommand(own.deviceId, { kind: 'user', subject: bob.subject }, bob.subject, { kind: 'browser', value: { action: 'profile_export', desk: 'private' } });
++ assert.equal(privateCommand.result, 'refused');
++
assert.strictEqual((await t.call('POST', url, { ...asRun(bob.subject), body: { kind: 'exec', value: { argv: ['id'] } } })).status, 422, 'never the machine itself');
assert.strictEqual((await t.call('POST', url, { ...asRun(carol.subject), body: { kind: 'screen', value: {} } })).status, 404, 'another person');
assert.strictEqual((await t.call('GET', `/api/v1/machines/${host.deviceId}/desk-leases/desk-s01`, asRun(carol.subject))).status, 404);