relmio 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +126 -36
- package/docs/architecture.md +57 -0
- package/docs/images/examples/gpt-56-model-selector.png +0 -0
- package/docs/images/examples/n8n-openai-credential-connected.png +0 -0
- package/docs/images/examples/telegram-model-results.png +0 -0
- package/docs/images/examples/telegram-n8n-workflow-execution.png +0 -0
- package/docs/images/setup/00-install-methods.png +0 -0
- package/docs/images/setup/01-local-sign-in-ready.png +0 -0
- package/docs/images/setup/02-vps-identity-confirmed.png +0 -0
- package/docs/images/setup/03-n8n-detected.png +0 -0
- package/docs/images/setup/04-install-plan.png +0 -0
- package/docs/images/setup/05-bridge-ready.png +0 -0
- package/docs/local-endpoints-spec.md +370 -0
- package/docs/local-endpoints.md +374 -0
- package/docs/npm-publish.md +121 -201
- package/docs/security.md +92 -3
- package/package.json +2 -2
- package/src/domain/local-endpoints.js +460 -0
- package/src/gateway/openai.js +834 -0
- package/src/infrastructure/local-process.js +375 -0
- package/src/services/codex-login.js +711 -0
- package/src/services/local-installer.js +1120 -0
- package/src/ui/app.js +4 -0
- package/src/ui/index.html +119 -108
- package/src/ui/local.css +262 -0
- package/src/ui/local.html +442 -0
- package/src/ui/local.js +550 -0
- package/src/ui/styles.css +577 -278
- package/src/web/server.js +247 -12
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# Spec: Policy-safe local endpoints
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Approved for implementation on `codex/local-openai-endpoint` on 2026-08-13.
|
|
6
|
+
|
|
7
|
+
This spec is product and engineering guidance based on the current official
|
|
8
|
+
OpenAI documentation. It is not a legal opinion. Relmio must not claim that
|
|
9
|
+
OpenAI has endorsed, certified, or pre-approved the project.
|
|
10
|
+
|
|
11
|
+
## Objective
|
|
12
|
+
|
|
13
|
+
Add a local Docker installation path to the Relmio browser wizard without
|
|
14
|
+
weakening the existing VPS/n8n safety boundary.
|
|
15
|
+
|
|
16
|
+
Relmio offers two intentionally different local providers:
|
|
17
|
+
|
|
18
|
+
1. `openai-api` is an OpenAI-compatible HTTP gateway backed by the user's
|
|
19
|
+
OpenAI Platform API key.
|
|
20
|
+
2. `codex-chatgpt` is the official Codex App Server protocol backed by the
|
|
21
|
+
user's ChatGPT/Codex sign-in.
|
|
22
|
+
|
|
23
|
+
Relmio must never exchange, translate, or present a ChatGPT/Codex credential as
|
|
24
|
+
a general OpenAI API bearer credential. The Codex provider must not expose an
|
|
25
|
+
OpenAI-shaped `/v1` compatibility surface.
|
|
26
|
+
|
|
27
|
+
## Official-source boundary
|
|
28
|
+
|
|
29
|
+
- General OpenAI API requests use a Platform API key (or an officially
|
|
30
|
+
supported workload identity credential).
|
|
31
|
+
- ChatGPT subscription access stays inside the official Codex CLI/App Server
|
|
32
|
+
workflow.
|
|
33
|
+
- Codex App Server uses its native JSON-RPC thread, turn, approval, and event
|
|
34
|
+
protocol.
|
|
35
|
+
- Codex App Server WebSocket transport is experimental and unsupported for
|
|
36
|
+
production workloads. Relmio must display that limitation before install and
|
|
37
|
+
in the result screen.
|
|
38
|
+
- Codex for Open Source benefits remain personal, limited, and governed by the
|
|
39
|
+
program terms. Program acceptance does not become a generic credential-scope
|
|
40
|
+
waiver.
|
|
41
|
+
|
|
42
|
+
Primary references:
|
|
43
|
+
|
|
44
|
+
- <https://learn.chatgpt.com/docs/app-server>
|
|
45
|
+
- <https://learn.chatgpt.com/docs/auth>
|
|
46
|
+
- <https://learn.chatgpt.com/docs/enterprise/access-tokens>
|
|
47
|
+
- <https://developers.openai.com/api/reference/overview#authentication>
|
|
48
|
+
- <https://learn.chatgpt.com/docs/codex-for-oss-terms>
|
|
49
|
+
|
|
50
|
+
## User experience
|
|
51
|
+
|
|
52
|
+
The existing VPS/n8n wizard remains a separate legacy setup path. The wizard
|
|
53
|
+
landing experience adds a prominent **Local endpoints** option which opens a
|
|
54
|
+
dedicated local installer.
|
|
55
|
+
|
|
56
|
+
The local installer starts with two provider cards:
|
|
57
|
+
|
|
58
|
+
### OpenAI API
|
|
59
|
+
|
|
60
|
+
- Label: **OpenAI API — compatible clients**
|
|
61
|
+
- Default HTTP port: `12435`
|
|
62
|
+
- Requires an OpenAI Platform API key beginning with `sk-`.
|
|
63
|
+
- Accepts zero or more exact browser origins. No wildcard origin is allowed.
|
|
64
|
+
- Result:
|
|
65
|
+
- Base URL: `http://127.0.0.1:<port>/v1`
|
|
66
|
+
- A newly generated Relmio bearer key, displayed once
|
|
67
|
+
- A warning that the upstream Platform API key is seeded over stdin into a
|
|
68
|
+
private labeled Docker volume, never written to a host file, and never
|
|
69
|
+
returned by the wizard
|
|
70
|
+
|
|
71
|
+
### Codex with ChatGPT
|
|
72
|
+
|
|
73
|
+
- Label: **Codex with ChatGPT — agent clients**
|
|
74
|
+
- Default WebSocket port: `14500`
|
|
75
|
+
- Uses pinned official `@openai/codex@0.147.0`.
|
|
76
|
+
- Result:
|
|
77
|
+
- Endpoint: `ws://127.0.0.1:<port>`
|
|
78
|
+
- A newly generated capability token, displayed once
|
|
79
|
+
- A device-code sign-in action using `account/login/start` with
|
|
80
|
+
`{ "type": "chatgptDeviceCode" }`
|
|
81
|
+
- An explicit statement that this is Codex JSON-RPC, not OpenAI `/v1`
|
|
82
|
+
- An explicit experimental/non-production notice
|
|
83
|
+
|
|
84
|
+
Both flows show a review screen and require a final confirmation before any
|
|
85
|
+
filesystem or Docker write.
|
|
86
|
+
|
|
87
|
+
This release supports macOS, Linux, and Linux under WSL2. Native Windows is
|
|
88
|
+
unsupported because its filesystem permission model does not provide the POSIX
|
|
89
|
+
owner-only protection this installer requires. The UI and documentation must
|
|
90
|
+
say so, and the installer must reject native Windows before any write.
|
|
91
|
+
|
|
92
|
+
## Wizard API contract
|
|
93
|
+
|
|
94
|
+
Every wizard API route continues to require the existing `X-Setup-Token` and
|
|
95
|
+
same-origin protections.
|
|
96
|
+
|
|
97
|
+
### `GET /api/local/docker/status`
|
|
98
|
+
|
|
99
|
+
Returns local Docker and Compose availability. It never returns filesystem
|
|
100
|
+
paths containing the user's home directory.
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"dockerAvailable": true,
|
|
105
|
+
"dockerVersion": "27.0.0",
|
|
106
|
+
"composeVersion": "2.29.0"
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `POST /api/local/plan`
|
|
111
|
+
|
|
112
|
+
Request:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"target": "openai-api",
|
|
117
|
+
"port": 12435,
|
|
118
|
+
"allowedOrigins": ["http://localhost:3000"]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The request never contains an upstream credential. The response contains an
|
|
123
|
+
opaque, single-use `planId` and the validated binding, managed path alias,
|
|
124
|
+
compatibility type, authentication type, and caveats.
|
|
125
|
+
|
|
126
|
+
### `POST /api/local/install`
|
|
127
|
+
|
|
128
|
+
Request fields:
|
|
129
|
+
|
|
130
|
+
- `planId`: the opaque identifier returned by the most recent reviewed plan
|
|
131
|
+
- `apiKey`: required only for `openai-api`; accepted only in request memory
|
|
132
|
+
- `confirmed`: must be exactly `true`
|
|
133
|
+
|
|
134
|
+
The server consumes the plan before attempting installation, so callers cannot
|
|
135
|
+
change the reviewed target, port, or origins or replay a failed attempt.
|
|
136
|
+
The response never includes the upstream Platform API key or ChatGPT
|
|
137
|
+
credential. It includes the new local capability once.
|
|
138
|
+
|
|
139
|
+
Only one installation may execute in a wizard process at a time. A concurrent
|
|
140
|
+
attempt receives `409` without consuming its reviewed plan. The in-flight lock
|
|
141
|
+
is released in a `finally` path after both success and failure.
|
|
142
|
+
|
|
143
|
+
### `POST /api/local/codex/login`
|
|
144
|
+
|
|
145
|
+
Starts an official Codex App Server device-code login through a one-shot stdio
|
|
146
|
+
App Server process attached to the same persistent Codex home volume.
|
|
147
|
+
|
|
148
|
+
Every login attempt resolves the managed Codex directory and attests its
|
|
149
|
+
schema-2 marker and matching Docker resources, even in a fresh wizard process.
|
|
150
|
+
The server passes only the attested Docker host and unique project name to the
|
|
151
|
+
stdio login service. An in-memory "installed" flag is not sufficient. Preview
|
|
152
|
+
mode and rate-limit guards run before attestation.
|
|
153
|
+
|
|
154
|
+
Response:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"verificationUrl": "https://auth.openai.com/...",
|
|
159
|
+
"userCode": "ABCD-EFGH"
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### `GET /api/local/codex/login/status`
|
|
164
|
+
|
|
165
|
+
Returns `idle`, `pending`, `success`, or `error`. Errors are sanitized; raw
|
|
166
|
+
App Server output is never returned.
|
|
167
|
+
|
|
168
|
+
## Local OpenAI gateway contract
|
|
169
|
+
|
|
170
|
+
### Listener
|
|
171
|
+
|
|
172
|
+
- Container listener: `0.0.0.0:10531`
|
|
173
|
+
- Host publication: `127.0.0.1:<selected-port>:10531`
|
|
174
|
+
- A generated Compose file containing `0.0.0.0:<port>` or an unqualified
|
|
175
|
+
`<port>:<port>` mapping is invalid.
|
|
176
|
+
|
|
177
|
+
### Authentication
|
|
178
|
+
|
|
179
|
+
- Relmio generates 32 random bytes and returns the base64url capability once.
|
|
180
|
+
- Only the SHA-256 verifier is persisted.
|
|
181
|
+
- Every operation that can reach `/v1` upstream requires
|
|
182
|
+
`Authorization: Bearer <Relmio capability>`. An exact-origin `OPTIONS`
|
|
183
|
+
preflight is the sole unauthenticated, non-forwarding metadata exception.
|
|
184
|
+
- Comparison uses a constant-time operation.
|
|
185
|
+
- The upstream OpenAI key replaces, and is never combined with, the client's
|
|
186
|
+
Authorization header.
|
|
187
|
+
|
|
188
|
+
### Proxy behavior
|
|
189
|
+
|
|
190
|
+
- The upstream origin is fixed to `https://api.openai.com`.
|
|
191
|
+
- Only `GET /v1/models`, `POST /v1/responses`, and
|
|
192
|
+
`POST /v1/chat/completions` are forwarded.
|
|
193
|
+
- `CONNECT`, `TRACE`, absolute-form URLs, protocol-relative URLs, invalid Host
|
|
194
|
+
headers, and oversized headers are rejected.
|
|
195
|
+
- Hop-by-hop, cookie, forwarding, proxy-authorization, origin, and referrer
|
|
196
|
+
headers are not forwarded upstream.
|
|
197
|
+
- Response status, supported end-to-end headers, streaming bodies, client
|
|
198
|
+
cancellation, and backpressure are preserved.
|
|
199
|
+
- Upstream `429` responses and `Retry-After` are passed through unchanged.
|
|
200
|
+
- Local overload responses use `429` and never retry upstream automatically.
|
|
201
|
+
|
|
202
|
+
### Browser origin policy
|
|
203
|
+
|
|
204
|
+
- Requests without `Origin` are accepted after bearer authentication.
|
|
205
|
+
- Browser requests require an exact configured `http` or `https` origin.
|
|
206
|
+
- Wildcards, `null`, credentials, paths, queries, and fragments are rejected.
|
|
207
|
+
- Preflight allows only the configured origin and a small documented header
|
|
208
|
+
list.
|
|
209
|
+
- Browser credentials are still caller secrets; Relmio must not encourage
|
|
210
|
+
embedding the local key in a public frontend bundle.
|
|
211
|
+
|
|
212
|
+
### Health
|
|
213
|
+
|
|
214
|
+
- `GET /health` is the only unauthenticated gateway route.
|
|
215
|
+
- It returns only local process readiness and no provider/account details.
|
|
216
|
+
|
|
217
|
+
## Codex App Server contract
|
|
218
|
+
|
|
219
|
+
The container command is equivalent to:
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
codex app-server
|
|
223
|
+
--strict-config
|
|
224
|
+
--listen ws://0.0.0.0:4500
|
|
225
|
+
--ws-auth capability-token
|
|
226
|
+
--ws-token-sha256 <sha256-verifier>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The host mapping is exactly `127.0.0.1:<selected-port>:4500`.
|
|
230
|
+
|
|
231
|
+
- `CODEX_HOME` is a private named Docker volume.
|
|
232
|
+
- Credential storage is forced to file mode inside the container so refreshed
|
|
233
|
+
credentials remain in the private volume.
|
|
234
|
+
- Login mode is forced to ChatGPT.
|
|
235
|
+
- Root-owned managed requirements allow only Relmio's network-disabled
|
|
236
|
+
permission profile (which extends Codex's built-in workspace profile),
|
|
237
|
+
on-request/user-reviewed approvals, disabled web search, no login shell, and
|
|
238
|
+
a closed set of optional features. Clients cannot request
|
|
239
|
+
`danger-full-access` or approval policy `never`.
|
|
240
|
+
- An empty named workspace volume is mounted; no host source directory, Docker
|
|
241
|
+
socket, SSH key, browser profile, or home directory is mounted.
|
|
242
|
+
- `GET /readyz` is the Docker readiness probe.
|
|
243
|
+
- The client sends `Authorization: Bearer <capability>` during WebSocket
|
|
244
|
+
upgrade, then `initialize`, `initialized`, `account/read`, and the normal
|
|
245
|
+
thread/turn protocol.
|
|
246
|
+
- Relmio does not inject or return raw ChatGPT OAuth tokens through the wizard.
|
|
247
|
+
Raw App Server is a high-trust surface: possession of its capability can
|
|
248
|
+
control the isolated container and may expose the signed-in ChatGPT session.
|
|
249
|
+
The capability is therefore password-equivalent and limited to a trusted,
|
|
250
|
+
same-owner native client.
|
|
251
|
+
|
|
252
|
+
## Local filesystem and process boundary
|
|
253
|
+
|
|
254
|
+
Managed roots:
|
|
255
|
+
|
|
256
|
+
- `~/.relmio/local/openai-api`
|
|
257
|
+
- `~/.relmio/local/codex-chatgpt`
|
|
258
|
+
|
|
259
|
+
`RELMIO_HOME` may replace `~/.relmio` for testing or advanced use, but it must
|
|
260
|
+
be an absolute path whose final component is `.relmio`.
|
|
261
|
+
|
|
262
|
+
Controls:
|
|
263
|
+
|
|
264
|
+
- Managed directories use mode `0700`; generated files use owner-only modes.
|
|
265
|
+
- The Platform API key is seeded over stdin by a transient, network-disabled
|
|
266
|
+
helper into a private labeled named volume and is never written to a host
|
|
267
|
+
file or Compose environment value.
|
|
268
|
+
- Existing unmanaged directories are never overwritten.
|
|
269
|
+
- Symlinks in a managed path are rejected.
|
|
270
|
+
- A schema-2 JSON marker identifies the target, configured port, validated
|
|
271
|
+
Docker host, 32-hex-character install ID, and collision-resistant Compose
|
|
272
|
+
project name; it contains no secrets.
|
|
273
|
+
- Docker is invoked with argument arrays and `shell: false`.
|
|
274
|
+
- The command allowlist is scoped to the selected Relmio Compose project and
|
|
275
|
+
service.
|
|
276
|
+
- No command targets n8n or `/docker/n8n-openai-oauth`.
|
|
277
|
+
- Local port availability is checked before a new install or a port change.
|
|
278
|
+
- Upstream credentials are cleared from request objects after installation
|
|
279
|
+
completes or fails.
|
|
280
|
+
- Native Windows is rejected before filesystem or Docker writes because the
|
|
281
|
+
required POSIX owner-only modes cannot be enforced there.
|
|
282
|
+
|
|
283
|
+
## Container hardening
|
|
284
|
+
|
|
285
|
+
Both long-running endpoint services:
|
|
286
|
+
|
|
287
|
+
- run as a non-root user;
|
|
288
|
+
- set `no-new-privileges`;
|
|
289
|
+
- drop all Linux capabilities;
|
|
290
|
+
- use a read-only root filesystem;
|
|
291
|
+
- use bounded tmpfs, PID, memory, and CPU resources;
|
|
292
|
+
- have no Docker socket or host filesystem mount;
|
|
293
|
+
- publish one explicit loopback port only;
|
|
294
|
+
- use pinned application dependencies.
|
|
295
|
+
|
|
296
|
+
The OpenAI install also invokes a one-shot credential seed helper. It has no
|
|
297
|
+
network or published port, disables logging, uses the same read-only image,
|
|
298
|
+
sets `no-new-privileges`, and has tight CPU, memory, and PID limits. It runs as
|
|
299
|
+
root only long enough to replace the volume entry atomically and retains only
|
|
300
|
+
the `CHOWN` capability needed to make that entry readable by the non-root
|
|
301
|
+
gateway; it is removed immediately after seeding.
|
|
302
|
+
|
|
303
|
+
## Threat model
|
|
304
|
+
|
|
305
|
+
### Assets
|
|
306
|
+
|
|
307
|
+
- OpenAI Platform API key
|
|
308
|
+
- Codex/ChatGPT refresh and access credentials in the Codex volume
|
|
309
|
+
- generated local capability tokens
|
|
310
|
+
- user prompts, outputs, and Codex thread history
|
|
311
|
+
- local applications that trust the endpoint
|
|
312
|
+
|
|
313
|
+
### Trust boundaries
|
|
314
|
+
|
|
315
|
+
- browser wizard to loopback wizard server
|
|
316
|
+
- wizard process to local filesystem
|
|
317
|
+
- wizard process to Docker Engine
|
|
318
|
+
- local client to published loopback endpoint
|
|
319
|
+
- gateway to `api.openai.com`
|
|
320
|
+
- Codex App Server to OpenAI's Codex services
|
|
321
|
+
|
|
322
|
+
### Principal threats and controls
|
|
323
|
+
|
|
324
|
+
| Threat | Required control |
|
|
325
|
+
|---|---|
|
|
326
|
+
| LAN/public exposure | literal `127.0.0.1` Compose binding plus template and runtime inspection tests |
|
|
327
|
+
| Local cross-site request | bearer capability, exact Origin allowlist, strict preflight, Host validation |
|
|
328
|
+
| Upstream key disclosure | separate local/upstream credentials, stdin-seeded private named volume, redacted errors, no body logging |
|
|
329
|
+
| ChatGPT token repurposing | official App Server only; no `/v1` adapter for Codex |
|
|
330
|
+
| Command injection | validated scalar values, spawn argument arrays, no shell |
|
|
331
|
+
| Managed-path takeover | refuse unmanaged roots and every symlinked component |
|
|
332
|
+
| Streaming resource exhaustion | header/body/concurrency/time bounds and backpressure |
|
|
333
|
+
| Docker privilege compromise | document Docker control as a privileged local boundary; mount no Docker socket into services |
|
|
334
|
+
| Secret recovery from UI | show capabilities once; never use browser storage; rotate on reinstall |
|
|
335
|
+
| Codex capability compromise | explicit credential-equivalent warning; trusted same-owner native clients only; private container volumes and no host mounts |
|
|
336
|
+
|
|
337
|
+
## Acceptance criteria
|
|
338
|
+
|
|
339
|
+
- The browser wizard visibly offers both local providers and the legacy VPS
|
|
340
|
+
path remains separate.
|
|
341
|
+
- Platform keys are accepted only by `openai-api`; ChatGPT auth is accepted only
|
|
342
|
+
by official App Server.
|
|
343
|
+
- Generated Compose files publish only literal loopback bindings.
|
|
344
|
+
- Every non-health gateway operation that can reach OpenAI and every App Server
|
|
345
|
+
WebSocket handshake is capability-authenticated; exact-origin CORS preflight
|
|
346
|
+
is a non-forwarding metadata exception.
|
|
347
|
+
- Gateway unit/integration tests cover auth, origins, Host validation,
|
|
348
|
+
streaming, cancellation, upstream errors, and secret redaction.
|
|
349
|
+
- Local installer tests prove confirmation, unmanaged-root refusal, symlink
|
|
350
|
+
refusal, port collision behavior, exact Docker arguments, file modes, and
|
|
351
|
+
absence of n8n commands.
|
|
352
|
+
- Server tests prove install serialization, lock release, and fresh-process
|
|
353
|
+
Codex login only after persisted installation attestation.
|
|
354
|
+
- Codex login tests use a fake stdio App Server process and cover initialization,
|
|
355
|
+
device-code response validation, completion, cancellation, malformed output,
|
|
356
|
+
and bounded output.
|
|
357
|
+
- Existing remote tests remain green.
|
|
358
|
+
- Opera GX runtime QA verifies keyboard flow, responsive layout, clean console,
|
|
359
|
+
no credential persistence, and correct mode-specific copy.
|
|
360
|
+
- Full `npm run check`, `npm audit --audit-level=high`, and
|
|
361
|
+
`npm pack --dry-run` succeed before handoff.
|
|
362
|
+
|
|
363
|
+
## Out of scope
|
|
364
|
+
|
|
365
|
+
- Public, LAN, hosted, reverse-proxied, or multi-user endpoints
|
|
366
|
+
- Translating Codex turns into `/v1/chat/completions` or `/v1/responses`
|
|
367
|
+
- Sharing, pooling, reselling, or redistributing any ChatGPT account or benefit
|
|
368
|
+
- TLS termination (loopback-only transport is the boundary for this release)
|
|
369
|
+
- Automatic migration or modification of the existing VPS/n8n deployment
|
|
370
|
+
- A production-support promise for experimental Codex WebSocket transport
|