Make Wayland capture honest and replace the broad input group with least privilege
Template tests / tests (pull_request) Failing after 33s
Template tests / tests (pull_request) Failing after 33s
Phase 3 of the improvement plan (PR 10 of the sequence): honest Wayland behavior and a least-privilege alternative to the broad `input` group the audit flagged as a keylogging surface. Least-privilege input access: - packaging/linux/common/60-stepforge-input.rules grants the ACTIVE session read access to MOUSE devices only (ID_INPUT_MOUSE, excluding ID_INPUT_KEYBOARD) via a systemd uaccess ACL — session-scoped, device- scoped, and never keyboards. This replaces `usermod -aG input`, which grants every input device (keyboards included) to the user permanently. - scripts/linux/enable-click-capture.sh installs it opt-in: it prints the exact rule, requires confirmation, and documents the security tradeoff. It never runs the broad-group command. Honest trigger reporting: - New chooseCaptureTrigger() (app/platform/linux/diagnostics.js, re-exported from app/platform/index.js) maps a capability profile to the real trigger: per-click with a marker on X11+xinput; per-click WITHOUT coordinates or a marker on Wayland evdev (the platform exposes no pointer position); and an honest hotkey/interval fallback otherwise. It never promises per-click capture with coordinates on Wayland. - platform:capabilities now includes the active trigger for the machine and the user's fallback setting, for the diagnostics UI. Docs: - GETTING_STARTED_WITH_LINUX.md no longer instructs every Wayland user to join the `input` group; it presents that as a warning, documents the least- privilege script, corrects the capability table (per-click is opt-in, mice only; hotkey/interval is the default Wayland trigger), and points at Settings → Diagnostics. Tests: trigger decisions for X11/xinput, Wayland evdev (no coordinates/marker), Wayland fallback (interval/hotkey with an honest note), the platform facade wiring, Windows; the udev rule (mouse-only, excludes keyboards, uaccess); the opt-in enable script (confirms, installs the rule, never runs usermod -aG input as a command); and docs guards. 289 unit tests pass; startup smoke and click self-test unchanged (stream, markers 3/3, burst 8/8). Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
+8
-2
@@ -900,8 +900,14 @@ function setupIpc() {
|
||||
platform: process.platform,
|
||||
}));
|
||||
// Platform capture-capability profile (session type, portal/PipeWire,
|
||||
// xinput, click source, actionable messages) for the diagnostics UI.
|
||||
h('platform:capabilities', () => require('./platform').detectCapabilities());
|
||||
// xinput, click source, actionable messages) for the diagnostics UI, plus
|
||||
// the honest active trigger for this machine and settings.
|
||||
h('platform:capabilities', () => {
|
||||
const platform = require('./platform');
|
||||
const caps = platform.detectCapabilities();
|
||||
const activeTrigger = platform.chooseCaptureTrigger(caps, settings.get('capture.fallbackTrigger') || 'interval');
|
||||
return { ...caps, activeTrigger };
|
||||
});
|
||||
}
|
||||
|
||||
// ---- lifecycle --------------------------------------------------------------
|
||||
|
||||
+16
-1
@@ -63,4 +63,19 @@ function detectCapabilities({ platform = process.platform, env = process.env } =
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { detectPlatform, createWindowContextProvider, detectCapabilities };
|
||||
/**
|
||||
* The honest capture-trigger decision for the current capabilities. On Linux
|
||||
* this defers to the diagnostics helper (which never promises per-click
|
||||
* capture with coordinates on Wayland); other platforms have a fixed answer.
|
||||
*/
|
||||
function chooseCaptureTrigger(capabilities, userTriggerPreference = 'interval') {
|
||||
if (capabilities && capabilities.os === 'linux') {
|
||||
return require('./linux/diagnostics').chooseCaptureTrigger(capabilities, userTriggerPreference);
|
||||
}
|
||||
if (capabilities && capabilities.os === 'windows') {
|
||||
return { trigger: 'click', clickSource: 'windows-hook', coordinates: true, marker: true, note: '' };
|
||||
}
|
||||
return { trigger: 'click', clickSource: capabilities ? capabilities.os : 'unavailable', coordinates: true, marker: true, note: '' };
|
||||
}
|
||||
|
||||
module.exports = { detectPlatform, createWindowContextProvider, detectCapabilities, chooseCaptureTrigger };
|
||||
|
||||
@@ -96,4 +96,61 @@ function detectLinuxCapabilities({
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { detectLinuxCapabilities, detectSessionType };
|
||||
/**
|
||||
* Decide the honest capture trigger for a Linux capability profile. StepForge
|
||||
* must never *promise* per-click capture with coordinates on Wayland, because
|
||||
* the platform does not expose pointer position to apps. Returns the trigger,
|
||||
* whether clicks carry coordinates, whether a marker can be drawn, and a
|
||||
* user-facing note. `userTriggerPreference` is the capture.fallbackTrigger
|
||||
* setting ('interval' | 'hotkey') used only when no click source exists.
|
||||
*/
|
||||
function chooseCaptureTrigger(capabilities, userTriggerPreference = 'interval') {
|
||||
const caps = capabilities || {};
|
||||
const click = caps.clickCapture;
|
||||
|
||||
if (click === 'x11-xinput') {
|
||||
return {
|
||||
trigger: 'click',
|
||||
clickSource: 'x11',
|
||||
coordinates: true,
|
||||
marker: true,
|
||||
note: 'Per-click capture with an accurate marker (X11 + xinput).',
|
||||
};
|
||||
}
|
||||
if (click === 'evdev-x11') {
|
||||
return {
|
||||
trigger: 'click',
|
||||
clickSource: 'evdev-x11',
|
||||
coordinates: true,
|
||||
marker: true,
|
||||
note: 'Per-click capture via kernel input devices (X11, no xinput).',
|
||||
};
|
||||
}
|
||||
if (click === 'evdev-wayland') {
|
||||
// Wayland exposes button presses (via evdev, if permitted) but NOT pointer
|
||||
// position, so a step is captured per click but without a marker. This is
|
||||
// only reached when the user opted into the least-privilege device rule.
|
||||
return {
|
||||
trigger: 'click',
|
||||
clickSource: 'evdev-wayland',
|
||||
coordinates: false,
|
||||
marker: false,
|
||||
note: 'Per-click capture on Wayland has no pointer position, so no marker is drawn.',
|
||||
};
|
||||
}
|
||||
|
||||
// No global click source: the safe baseline is the user's chosen fallback.
|
||||
const trigger = userTriggerPreference === 'hotkey' ? 'hotkey' : 'interval';
|
||||
return {
|
||||
trigger,
|
||||
clickSource: trigger,
|
||||
coordinates: false,
|
||||
marker: false,
|
||||
note: caps.isWayland
|
||||
? 'Wayland does not expose global clicks; recording uses your ' + trigger + ' trigger. '
|
||||
+ 'Screen sharing is requested once per recording via the portal.'
|
||||
: 'No global click source available; recording uses your ' + trigger + ' trigger.',
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { detectLinuxCapabilities, detectSessionType, chooseCaptureTrigger };
|
||||
|
||||
Reference in New Issue
Block a user