Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ Use this index to choose the smallest document that matches your goal.
model for scans and reviews.
- [Security Backlog](maintainers/security-backlog.md) points to security
backlog issues and routes supply-chain review through `@codex-security`.
- [Remote Mobile Host Boundary Review](maintainers/remote-mobile-host-boundary-review.md)
records the host-state matrix for remote-control and Codex mobile review.
- [Agentic Maintenance Policy](policies/agentic-maintenance.md) explains what
belongs in tracked docs, what belongs in agent policy, and what should remain
local session evidence.
Expand Down
82 changes: 82 additions & 0 deletions docs/maintainers/remote-mobile-host-boundary-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Remote Mobile Host Boundary Review

This review note records the current review gate for the `remote-control-ui`
and `remote-mobile-control` port integrations. It is issue #59's durable
evidence surface for the local host boundary; GitHub comments should point back
here instead of carrying the only copy of the matrix.

## Scope

The review covers fork-owned Linux behavior around:

- generated bundle patch descriptors for remote-control and Codex mobile
surfaces;
- app-server or managed remote-control daemon startup and config preservation;
- the Linux software device-key provider under XDG config;
- host identity selection for remote-control auto-connect;
- docs and review evidence that distinguish local fork behavior from
OpenAI-hosted account, enrollment, MFA, mobile-client authorization, and
remote-access policy.

Generated app output, local state, and mobile-client behavior are inspection
evidence. Durable changes belong in source patchers, port integration tests,
maintainer docs, or the issue/PR evidence trail.

## Host-State Matrix

| Review row | Repo-local evidence | Live evidence required before issue closure |
| --- | --- | --- |
| Enrollment state shown in UI | `PRODUCT.md` and `DESIGN.md` require that connected-looking UI not imply unverified enrollment, host liveness, thread visibility, authorization, remote environment state, or service availability. | A live account/mobile check records the enrollment state shown in the generated app and confirms it matches current account/mobile enrollment state. |
| App-server or daemon liveness | `port-integrations/remote-mobile-control/cold-start-hook.sh` starts the managed daemon only when the Desktop app-server does not own remote-control, and tests cover the ownership marker and stale daemon PID cleanup. | A live run records the intended Desktop app-server or managed daemon process as alive and reachable. |
| Linux device-key store | `port-integrations/remote-mobile-control/test.js` covers key creation, signing, deletion, and `0600` mode for `${XDG_CONFIG_HOME:-$HOME/.config}/codex-app/remote-control-device-keys-v1.json`. | A live run records the configured key path and owner-only mode without exposing key material. |
| Mobile side sees intended thread/session | Fork-side tests cannot prove OpenAI-hosted account/mobile discovery semantics. | The mobile side records the intended host thread/session as visible. |
| First mobile action reaches intended host thread/session | Fork-side tests cannot prove mobile action routing through OpenAI-hosted services. | A first mobile action or message is applied to the intended live host thread/session. |
| Stale, revoked, unauthorized, or mismatched hosts are rejected | `port-integrations/remote-mobile-control/test.js` covers auto-connecting only the local installation, leaving hosts disconnected when no local identity is available, and refreshing empty connection snapshots before selecting the intended host. | A live run records stale, revoked, unauthorized, or mismatched hosts rejected instead of displayed as connected. |

## Scoped Security Review Evidence

- `remote-control-ui` patches expose Linux remote-control UI surfaces and Linux
copy. They do not authorize a host, mint device keys, or prove account/mobile
enrollment.
- `remote-mobile-control` keeps OpenAI-hosted account, enrollment, step-up, MFA,
mobile-client authorization, and remote-access decisions outside the local
fork. Local patches preserve those checks and only adapt Linux host plumbing.
- Linux device keys are exportable software keys. The provider stores them under
per-user XDG config, writes the key store with `0600` mode, uses a `0700` lock
directory, and fails when no user config root can be resolved.
- The Desktop app-server path owns remote-control when the generated app carries
the ownership marker. The standalone daemon path is a local fallback and must
not imply OpenAI-hosted enrollment or mobile reachability.
- Auto-connect is limited to `remote-control:` host records whose installation
id matches the local `electron-local-remote-control-installation-id`. Empty
connection snapshots are refreshed before selection, and missing local
identity leaves every remote host disconnected.

## Review Rules

- Do not treat a connected-looking local UI as proof of account/mobile
authorization, host liveness, or thread/session reachability.
- Do not claim general-ready status until `@codex-security` review evidence and
the host-state matrix are both recorded.
- Keep local fork behavior distinct from OpenAI-hosted services in docs, PR
text, and issue closure comments.
- Do not persist screenshots, key material, private account identifiers,
private hostnames, private paths, tokens, or mobile-client state that is not
necessary for review.

## Local Validation

Run these local checks after source or review-doc changes in this area:

```bash
node --test port-integrations/remote-control-ui/test.js
node --test port-integrations/remote-mobile-control/test.js
```

If shared patching behavior changes, also run:

```bash
node --test scripts/patch-linux-window-ui.test.js
```

If shell hooks change, run `bash -n` on the touched shell files.
4 changes: 3 additions & 1 deletion docs/maintainers/security-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ Medium priority:

- [Review generated-app Electron IPC and file-manager handling](https://github.com/nisavid/codex-app-linux/issues/57)
- [Review Linux Computer Use desktop-control boundary](https://github.com/nisavid/codex-app-linux/issues/58)
- [Review experimental remote-control and Codex mobile host boundary](https://github.com/nisavid/codex-app-linux/issues/59)
- [Review experimental remote-control and Codex mobile host boundary](https://github.com/nisavid/codex-app-linux/issues/59):
see [Remote Mobile Host Boundary Review](remote-mobile-host-boundary-review.md)
for the host-state matrix and repo-local evidence.
- [Review bundled browser and Chrome native-host boundary](https://github.com/nisavid/codex-app-linux/issues/60)
- [Require trusted metadata for non-default DMG sources](https://github.com/nisavid/codex-app-linux/issues/61)
- [Pin executable build inputs outside the Nix path](https://github.com/nisavid/codex-app-linux/issues/62)
Expand Down
17 changes: 12 additions & 5 deletions docs/maintainers/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,12 +456,17 @@ end-to-end authorization path.
plumbing without fabricating OpenAI enrollment, connected-client, or MFA
state; patches are descriptor-scoped and fail soft; Linux device keys are stored
in a per-user XDG config file with `0600` mode; and tests cover key creation,
signing, deletion, visibility gating, and Linux-specific copy.
signing, deletion, visibility gating, local host auto-connect selection,
missing local host identity, refreshed connection snapshots, and Linux-specific
copy.

**Gaps:** Linux keys are software-only and same-user readable; fork-side tests
cannot prove OpenAI account/mobile authorization semantics; remote-control
patches need fresh security review before being treated as general-ready
functionality.
cannot prove OpenAI account/mobile authorization semantics; connected-looking
UI is not proof that the intended live host, app-server or managed daemon, and
thread/session are current, reachable, and authorized; remote-control patches
need fresh security review and the host-state matrix in
[Remote Mobile Host Boundary Review](remote-mobile-host-boundary-review.md)
before being treated as general-ready functionality.

**Priority:** High when touching remote-control/mobile behavior; Medium
otherwise.
Expand Down Expand Up @@ -584,7 +589,9 @@ still contain arbitrary sensitive values.
- `port-integrations/remote-control-ui/` and
`port-integrations/remote-mobile-control/`: port integrations for
remote-control/mobile UI gates, app-server config preservation, Linux
device-key storage, and generated-copy patches.
device-key storage, generated-copy patches, and host-state evidence. See
[Remote Mobile Host Boundary Review](remote-mobile-host-boundary-review.md)
for the host-state matrix.
- `scripts/lib/dmg.sh`: installer DMG download and version extraction.
- `scripts/lib/native-modules.sh`: native dependency version floors and
Electron-specific temporary source compatibility patches.
Expand Down
101 changes: 101 additions & 0 deletions port-integrations/remote-mobile-control/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,107 @@ test("Linux remote-control enablement bridge auto-connects only this Desktop hos
assert.equal(calls[3].params.autoConnect, false);
});

test("Linux remote-control enablement bridge leaves remote hosts disconnected without a local host identity", async () => {
const source = syntheticAppMainEnablementBridgeBundle();
const patched = applyLinuxRemoteControlEnablementBridgePatch(source);

const calls = [];
const context = {
DF: "[remote-connections/slingshot-gate-bridge]",
navigator: { userAgent: "X11; Linux x86_64" },
Promise,
q: { warning() {} },
Q: {
useEffect(callback) {
callback();
},
},
sc: () => false,
Z: { c: () => [] },
$o: (method, { params }) => {
calls.push({ method, params });
if (method === "set-remote-control-connections-enabled") {
return Promise.resolve({
remoteControlConnections: [
{ hostId: "remote-control:env_one", installationId: "install_one" },
{ hostId: "remote-control:env_two", installationId: "install_two" },
],
});
}
if (method === "get-global-state") {
return Promise.resolve({ value: null });
}
return Promise.resolve({});
},
};
vm.runInNewContext(`${patched};OF();`, context);
await new Promise((resolve) => setImmediate(resolve));

assert.equal(calls.length, 4);
assert.equal(calls[0].method, "set-remote-control-connections-enabled");
assert.equal(calls[0].params.enabled, true);
assert.equal(calls[1].method, "get-global-state");
assert.equal(calls[1].params.key, "electron-local-remote-control-installation-id");
assert.equal(calls[2].method, "set-remote-connection-auto-connect");
assert.equal(calls[2].params.hostId, "remote-control:env_one");
assert.equal(calls[2].params.autoConnect, false);
assert.equal(calls[3].method, "set-remote-connection-auto-connect");
assert.equal(calls[3].params.hostId, "remote-control:env_two");
assert.equal(calls[3].params.autoConnect, false);
});
Comment thread
greptile-apps[bot] marked this conversation as resolved.

test("Linux remote-control enablement bridge refreshes empty connection snapshots before auto-connect", async () => {
const source = syntheticAppMainEnablementBridgeBundle();
const patched = applyLinuxRemoteControlEnablementBridgePatch(source);

const calls = [];
const context = {
DF: "[remote-connections/slingshot-gate-bridge]",
navigator: { userAgent: "X11; Linux x86_64" },
Promise,
q: { warning() {} },
Q: {
useEffect(callback) {
callback();
},
},
sc: () => false,
Z: { c: () => [] },
$o: (method, { params }) => {
calls.push({ method, params });
if (method === "set-remote-control-connections-enabled") {
return Promise.resolve({ remoteControlConnections: [] });
}
if (method === "refresh-remote-control-connections") {
return Promise.resolve({
sharedObjects: {
local_remote_control_installation_id: "install_local",
remote_control_connections: [
{ hostId: "remote-control:env_local", installation_id: "install_local" },
{ hostId: "remote-control:env_stale", installation_id: "install_stale" },
],
},
});
}
return Promise.resolve({});
},
};
vm.runInNewContext(`${patched};OF();`, context);
await new Promise((resolve) => setImmediate(resolve));

assert.equal(calls.length, 4);
assert.equal(calls[0].method, "set-remote-control-connections-enabled");
assert.equal(calls[0].params.enabled, true);
assert.equal(calls[1].method, "refresh-remote-control-connections");
Comment thread
greptile-apps[bot] marked this conversation as resolved.
assert.deepEqual(structuredClone(calls[1].params), {});
Comment thread
nisavid marked this conversation as resolved.
assert.equal(calls[2].method, "set-remote-connection-auto-connect");
assert.equal(calls[2].params.hostId, "remote-control:env_local");
assert.equal(calls[2].params.autoConnect, true);
assert.equal(calls[3].method, "set-remote-connection-auto-connect");
assert.equal(calls[3].params.hostId, "remote-control:env_stale");
assert.equal(calls[3].params.autoConnect, false);
});

test("patched Linux device-key provider can create, sign with, and delete a key", async () => {
const configHome = fs.mkdtempSync(path.join(os.tmpdir(), "codex-remote-mobile-key-store-"));
try {
Expand Down