@oneciel-ai/ciel-runtime 0.2.39 → 0.2.41
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 +15 -0
- package/ciel_runtime.py +1 -0
- package/ciel_runtime_support/codex_router_auth.py +88 -0
- package/ciel_runtime_support/router_access.py +18 -3
- package/ciel_runtime_support/runtime_constants.py +1 -1
- package/ciel_runtime_support/runtime_launch.py +3 -0
- package/ciel_runtime_support/terminal_input_frames.py +78 -0
- package/ciel_runtime_support/windows_conpty.py +13 -8
- package/docs/journal/2026/09/07/diagnostics/windows/clipboard-paste-markers.okf +18 -0
- package/docs/journal/2026/09/07/diagnostics/windows/remote-backend-log-access.okf +14 -0
- package/docs/journal/2026/09/07/diagnostics/windows/remote-console-capture.okf +36 -0
- package/docs/journal/2026/09/07/diagnostics/windows/web-backend-startup.okf +12 -0
- package/docs/journal/2026/09/07/release/stable/0.2.40.okf +19 -0
- package/docs/journal/2026/09/07/release/stable/0.2.41.okf +26 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ capability, followed by the complete commit ledger merged into each release.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.2.41 — 2026-09-07
|
|
9
|
+
|
|
10
|
+
- Automatically authenticate Codex model and Ciel MCP requests to LAN-bound
|
|
11
|
+
routers using a separate process-local header. Preserve native OpenAI
|
|
12
|
+
authorization and strip Ciel credentials before forwarding upstream.
|
|
13
|
+
- Reassemble fragmented Windows console VT input sequences before forwarding
|
|
14
|
+
them to ConPTY, preventing split paste boundaries from entering the Codex
|
|
15
|
+
draft as text. Preserve literal text and standalone Escape (100ms idle bound).
|
|
16
|
+
|
|
17
|
+
## 0.2.40 — 2026-09-07
|
|
18
|
+
|
|
19
|
+
- Authenticate internal router health checks when the web backend uses a
|
|
20
|
+
specific LAN address. Preserve the external listener and authentication
|
|
21
|
+
requirements instead of reporting an authenticated running server as down.
|
|
22
|
+
|
|
8
23
|
## 0.2.39 — 2026-09-07
|
|
9
24
|
|
|
10
25
|
- Fix native Codex startup failing with `invalid transport` when DuckDuckGo or
|
package/ciel_runtime.py
CHANGED
|
@@ -3024,6 +3024,7 @@ def router_health() -> dict[str, Any] | None:
|
|
|
3024
3024
|
try:
|
|
3025
3025
|
data = http_json(
|
|
3026
3026
|
f"{ROUTER_BASE}/health",
|
|
3027
|
+
headers=_ROUTER_ACCESS_POLICY.health_headers(ROUTER_BASE, load_config(), router_external_access_token),
|
|
3027
3028
|
timeout=router_health_timeout_seconds(),
|
|
3028
3029
|
)
|
|
3029
3030
|
return data if isinstance(data, dict) else None
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Attach router authentication without replacing native OpenAI credentials."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
from urllib.parse import urlparse
|
|
6
|
+
|
|
7
|
+
from .router_access import is_loopback_address
|
|
8
|
+
|
|
9
|
+
CLIENT_TOKEN_ENV = "CIEL_RUNTIME_ROUTER_CLIENT_TOKEN"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def authenticated_codex_command(cmd: list[str], env: dict[str, str], router_base: str) -> list[str]:
|
|
13
|
+
router = urlparse(router_base)
|
|
14
|
+
targets = []
|
|
15
|
+
builtin_base = None
|
|
16
|
+
overrides = {}
|
|
17
|
+
index = 1
|
|
18
|
+
while index < len(cmd):
|
|
19
|
+
argument = cmd[index]
|
|
20
|
+
index += 1
|
|
21
|
+
if argument == '--':
|
|
22
|
+
break
|
|
23
|
+
if argument in ('-c', '--config') and index < len(cmd):
|
|
24
|
+
setting = cmd[index]
|
|
25
|
+
index += 1
|
|
26
|
+
elif argument.startswith('--config='):
|
|
27
|
+
setting = argument.split('=', 1)[1]
|
|
28
|
+
else:
|
|
29
|
+
continue
|
|
30
|
+
key, separator, raw = setting.partition('=')
|
|
31
|
+
if separator:
|
|
32
|
+
key = key.strip()
|
|
33
|
+
# Do not attach credentials based on an overridden earlier URL.
|
|
34
|
+
overrides = {old: value for old, value in overrides.items()
|
|
35
|
+
if not old.startswith(key + '.')}
|
|
36
|
+
overrides[key] = raw
|
|
37
|
+
for key, raw in overrides.items():
|
|
38
|
+
if not raw:
|
|
39
|
+
continue
|
|
40
|
+
match = re.fullmatch(r"model_providers\.([\w-]+)\.base_url", key)
|
|
41
|
+
if match:
|
|
42
|
+
prefix = f"model_providers.{match.group(1)}"
|
|
43
|
+
elif key == "openai_base_url":
|
|
44
|
+
prefix = "model_providers.ciel-runtime-native-auth"
|
|
45
|
+
elif key == "mcp_servers.ciel-runtime-router.url":
|
|
46
|
+
prefix = "mcp_servers.ciel-runtime-router"
|
|
47
|
+
else:
|
|
48
|
+
continue
|
|
49
|
+
try:
|
|
50
|
+
url = urlparse(json.loads(raw))
|
|
51
|
+
except (ValueError, TypeError):
|
|
52
|
+
continue
|
|
53
|
+
# These are Ciel's generated local-router routes, not arbitrary
|
|
54
|
+
# upstream provider URLs. Only authorize this launch's exact origin.
|
|
55
|
+
if (not is_loopback_address(url.hostname) and url.scheme == router.scheme
|
|
56
|
+
and url.netloc == router.netloc
|
|
57
|
+
and url.path.rstrip('/') in ('/v1', '/backend-api/codex', '/ca/mcp')):
|
|
58
|
+
targets.append(prefix)
|
|
59
|
+
if key == 'openai_base_url':
|
|
60
|
+
builtin_base = raw
|
|
61
|
+
if not targets:
|
|
62
|
+
return cmd
|
|
63
|
+
token = str(env.get("CIEL_RUNTIME_ROUTER_EXTERNAL_TOKEN") or "").strip()
|
|
64
|
+
if not token:
|
|
65
|
+
state = env.get("CIEL_RUNTIME_STATE_DIR")
|
|
66
|
+
if state:
|
|
67
|
+
try:
|
|
68
|
+
token = (Path(state) / "router-external-token").read_text(encoding="utf-8").strip()
|
|
69
|
+
except OSError:
|
|
70
|
+
pass
|
|
71
|
+
if not token:
|
|
72
|
+
return cmd
|
|
73
|
+
env[CLIENT_TOKEN_ENV] = token
|
|
74
|
+
options = []
|
|
75
|
+
if builtin_base is not None:
|
|
76
|
+
# Modern Codex forbids overriding the built-in `openai` table.
|
|
77
|
+
# Use a per-launch provider retaining native OpenAI authentication.
|
|
78
|
+
provider = 'ciel-runtime-native-auth'
|
|
79
|
+
cmd = [f'model_provider="{provider}"' if item == 'model_provider="openai"' else item for item in cmd]
|
|
80
|
+
for setting in (f'model_provider="{provider}"',
|
|
81
|
+
f'model_providers.{provider}.name="Ciel Runtime Codex"',
|
|
82
|
+
f'model_providers.{provider}.base_url={builtin_base}',
|
|
83
|
+
f'model_providers.{provider}.wire_api="responses"',
|
|
84
|
+
f'model_providers.{provider}.requires_openai_auth=true'):
|
|
85
|
+
options.extend(['-c', setting])
|
|
86
|
+
for prefix in dict.fromkeys(targets):
|
|
87
|
+
options.extend(['-c', f'{prefix}.env_http_headers.x-ciel-runtime-token="{CLIENT_TOKEN_ENV}"'])
|
|
88
|
+
return [cmd[0], *options, *cmd[1:]]
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import secrets
|
|
9
9
|
import time
|
|
10
|
+
import urllib.parse
|
|
10
11
|
from collections.abc import Callable, Mapping, MutableMapping
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from pathlib import Path
|
|
@@ -63,6 +64,18 @@ class RouterAccessPolicy:
|
|
|
63
64
|
parse_env_bool: Callable[[str | None, bool | None], bool | None]
|
|
64
65
|
load_config: Callable[[], dict[str, Any]]
|
|
65
66
|
|
|
67
|
+
def health_headers(
|
|
68
|
+
self, base: str, config: dict[str, Any], token_provider: Callable[[], str],
|
|
69
|
+
) -> dict[str, str]:
|
|
70
|
+
# Specific LAN binds are also used as the internal client address;
|
|
71
|
+
# those requests must satisfy external authentication just like peers.
|
|
72
|
+
if is_loopback_address(urllib.parse.urlparse(base).hostname):
|
|
73
|
+
return {}
|
|
74
|
+
if not self.administrative_external_access_enabled(config):
|
|
75
|
+
return {}
|
|
76
|
+
token = token_provider()
|
|
77
|
+
return {"Authorization": f"Bearer {token}"} if token else {}
|
|
78
|
+
|
|
66
79
|
def remote_bridge_enabled(self, config: Mapping[str, Any]) -> bool:
|
|
67
80
|
override = str(
|
|
68
81
|
self.environ.get("CIEL_RUNTIME_REMOTE_BRIDGE") or ""
|
|
@@ -188,9 +201,11 @@ class RouterAccessPolicy:
|
|
|
188
201
|
and hmac.compare_digest(expected, bridge_expected)
|
|
189
202
|
):
|
|
190
203
|
return False
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
204
|
+
local_token = str(handler.headers.get("x-ciel-runtime-token") or "").strip()
|
|
205
|
+
return bool(expected and (
|
|
206
|
+
(supplied and hmac.compare_digest(expected, supplied))
|
|
207
|
+
or (local_token and hmac.compare_digest(expected, local_token))
|
|
208
|
+
))
|
|
194
209
|
return False
|
|
195
210
|
|
|
196
211
|
|
|
@@ -9,6 +9,7 @@ from dataclasses import dataclass
|
|
|
9
9
|
|
|
10
10
|
from ciel_runtime_support.claude_environment import CLAUDE_PROJECTED_ENV_KEYS
|
|
11
11
|
from ciel_runtime_support.managed_tool_injection import should_inject_tool, codex_native_web_tool_overrides
|
|
12
|
+
from ciel_runtime_support.codex_router_auth import authenticated_codex_command
|
|
12
13
|
from ciel_runtime_support.runtime_constants import (
|
|
13
14
|
CLAUDE_SERVER_SIDE_WEB_TOOLS,
|
|
14
15
|
CODEX_RUNTIME_API_KEY_ENV,
|
|
@@ -973,6 +974,7 @@ def run_codex(
|
|
|
973
974
|
if workspace_mcp is not None and workspace_mcp_launch is not None:
|
|
974
975
|
workspace_mcp.finish(workspace_mcp_launch)
|
|
975
976
|
raise
|
|
977
|
+
cmd = authenticated_codex_command(cmd, env, ROUTER_BASE)
|
|
976
978
|
_log_codex_command_for_diagnostics(cmd, env)
|
|
977
979
|
record_launch_state_for_cwd(
|
|
978
980
|
current_launch_cwd_key(),
|
|
@@ -1287,6 +1289,7 @@ def run_codex_app_server(
|
|
|
1287
1289
|
print(f"Codex App Server listen: {cmd[cmd.index('--listen') + 1]}", flush=True)
|
|
1288
1290
|
except Exception:
|
|
1289
1291
|
pass
|
|
1292
|
+
cmd = authenticated_codex_command(cmd, env, ROUTER_BASE)
|
|
1290
1293
|
_log_codex_app_server_command_for_diagnostics(cmd, env)
|
|
1291
1294
|
record_launch_state_for_cwd(
|
|
1292
1295
|
current_launch_cwd_key(),
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Reassemble short VT input sequences across parent-console reads."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import threading
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TerminalInputFrames:
|
|
9
|
+
"""Do not expose a partial CSI to a child's keyboard-event parser.
|
|
10
|
+
|
|
11
|
+
A bounded idle timer preserves a standalone Escape key. Nothing is
|
|
12
|
+
stripped or rewritten, including literal text resembling paste markers.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, write: Callable[[bytes], None], *, idle_seconds: float = 0.1):
|
|
16
|
+
self.write = write
|
|
17
|
+
self.idle_seconds = idle_seconds
|
|
18
|
+
self.pending = bytearray()
|
|
19
|
+
self.lock = threading.Lock()
|
|
20
|
+
self.timer: threading.Timer | None = None
|
|
21
|
+
self.generation = 0
|
|
22
|
+
|
|
23
|
+
def feed(self, data: bytes) -> None:
|
|
24
|
+
with self.lock:
|
|
25
|
+
self._cancel()
|
|
26
|
+
ready = bytearray()
|
|
27
|
+
for byte in data:
|
|
28
|
+
if not self.pending:
|
|
29
|
+
if byte == 0x1b:
|
|
30
|
+
self.pending.append(byte)
|
|
31
|
+
else:
|
|
32
|
+
ready.append(byte)
|
|
33
|
+
continue
|
|
34
|
+
self.pending.append(byte)
|
|
35
|
+
# CSI/SS3 sequences end with a final byte; other ESC keys
|
|
36
|
+
# (including Alt+key) are complete after the next byte.
|
|
37
|
+
complete = (
|
|
38
|
+
len(self.pending) == 2 and byte not in (ord('['), ord('O'))
|
|
39
|
+
) or (
|
|
40
|
+
len(self.pending) >= 3 and 0x40 <= byte <= 0x7e
|
|
41
|
+
) or len(self.pending) >= 64
|
|
42
|
+
if complete:
|
|
43
|
+
ready.extend(self.pending)
|
|
44
|
+
self.pending.clear()
|
|
45
|
+
if ready:
|
|
46
|
+
self.write(bytes(ready))
|
|
47
|
+
if self.pending:
|
|
48
|
+
generation = self.generation
|
|
49
|
+
self.timer = threading.Timer(self.idle_seconds, self._expire, args=(generation,))
|
|
50
|
+
self.timer.daemon = True
|
|
51
|
+
self.timer.start()
|
|
52
|
+
|
|
53
|
+
def _cancel(self) -> None:
|
|
54
|
+
self.generation += 1
|
|
55
|
+
if self.timer is not None:
|
|
56
|
+
self.timer.cancel()
|
|
57
|
+
self.timer = None
|
|
58
|
+
|
|
59
|
+
def _expire(self, generation: int) -> None:
|
|
60
|
+
with self.lock:
|
|
61
|
+
if generation != self.generation:
|
|
62
|
+
return
|
|
63
|
+
self.timer = None
|
|
64
|
+
self._flush()
|
|
65
|
+
|
|
66
|
+
def _flush(self) -> None:
|
|
67
|
+
if self.pending:
|
|
68
|
+
data = bytes(self.pending)
|
|
69
|
+
self.pending.clear()
|
|
70
|
+
try:
|
|
71
|
+
self.write(data)
|
|
72
|
+
except OSError:
|
|
73
|
+
pass # Child may have exited while Escape was pending.
|
|
74
|
+
|
|
75
|
+
def close(self) -> None:
|
|
76
|
+
with self.lock:
|
|
77
|
+
self._cancel()
|
|
78
|
+
self._flush()
|
|
@@ -13,6 +13,7 @@ from collections.abc import Mapping
|
|
|
13
13
|
from typing import Any, Callable
|
|
14
14
|
|
|
15
15
|
from .terminal_platform_io import TERMINAL_INPUT_MODE_RESET
|
|
16
|
+
from .terminal_input_frames import TerminalInputFrames
|
|
16
17
|
from .windows_command_line import command_line_for_create_process
|
|
17
18
|
|
|
18
19
|
|
|
@@ -980,15 +981,19 @@ class WindowsConPtySession:
|
|
|
980
981
|
self._parent_input_draft = draft
|
|
981
982
|
|
|
982
983
|
def _pump_input(self) -> None:
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
984
|
+
frames = TerminalInputFrames(self.write)
|
|
985
|
+
try:
|
|
986
|
+
while not self._stop.is_set():
|
|
987
|
+
try:
|
|
988
|
+
data = self._read_input_bytes()
|
|
989
|
+
if not data:
|
|
990
|
+
return
|
|
991
|
+
self._observe_parent_input(data)
|
|
992
|
+
frames.feed(data)
|
|
993
|
+
except OSError:
|
|
987
994
|
return
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
except OSError:
|
|
991
|
-
return
|
|
995
|
+
finally:
|
|
996
|
+
frames.close()
|
|
992
997
|
|
|
993
998
|
|
|
994
999
|
__all__ = ["WindowsConPtySession", "conpty_enabled"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
title: Remote Windows clipboard paste exposes delimiter-like text
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
status: investigated; remote cause not established
|
|
4
|
+
observation: User screenshot shows [20~ before a pasted URL and [201~ after it.
|
|
5
|
+
interpretation: Consistent with exposed bracketed-paste delimiters; not evidence of mouse reporting.
|
|
6
|
+
reference: https://github.com/microsoft/terminal/issues/12385
|
|
7
|
+
protocol: Bracketed paste wraps text in ESC[200~ and ESC[201~.
|
|
8
|
+
source_checks:
|
|
9
|
+
parent_input: windows_conpty._read_input_bytes uses ReadConsoleW then UTF-8 encoding.
|
|
10
|
+
pump: _pump_input observes draft state then writes input bytes without textual marker replacement.
|
|
11
|
+
transport: write loops until all bytes have been written to ConPTY.
|
|
12
|
+
verification:
|
|
13
|
+
command: python -m unittest discover -s tests -p test_windows_conpty.py -v
|
|
14
|
+
result: 33 tests passed in 0.361s on local Windows.
|
|
15
|
+
coverage: Includes real ConPTY bracketed-paste/large Korean payload byte integrity and submission tests.
|
|
16
|
+
limitation: Does not reproduce the remote user's clipboard, outer terminal, or Codex TUI input path.
|
|
17
|
+
next_evidence: Remote machine access or exact terminal/runtime versions and relevant transport startup logs.
|
|
18
|
+
changes: No runtime code changes or deployment; do not strip literal marker-like text from user messages.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
title: Attempt remote web backend log capture for Windows paste-marker issue
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
target: http://10.0.0.238:9683
|
|
4
|
+
authorization: User requested backend log capture and console/mouse diagnostics.
|
|
5
|
+
request: GET /health without credentials
|
|
6
|
+
response:
|
|
7
|
+
timestamp: Mon, 07 Sep 2026 18:02:22 GMT
|
|
8
|
+
status: HTTP/1.0 401 Unauthorized
|
|
9
|
+
challenge: Bearer realm="ciel-runtime"
|
|
10
|
+
message: ciel-runtime router external authentication is required.
|
|
11
|
+
conclusion: Backend reachable; administrative authentication required before logs or TUI diagnostics can be retrieved.
|
|
12
|
+
limitation: Remote runtime version, logs, and console state not retrieved; no assertion that paste issue is diagnosed.
|
|
13
|
+
next_requirement: User-provided administrative external access token for this remote backend.
|
|
14
|
+
changes: No remote writes, input injection, configuration changes, or authentication bypass attempted.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
title: Authenticated remote backend console investigation
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
target: http://10.0.0.238:9683
|
|
4
|
+
credentials: Omitted; user-provided token used only for authenticated requests.
|
|
5
|
+
captures:
|
|
6
|
+
health:
|
|
7
|
+
ok: true
|
|
8
|
+
version: 0.2.40
|
|
9
|
+
source_fingerprint: 3bc950932f5d1691
|
|
10
|
+
pid: 21824
|
|
11
|
+
workspace: c:\users\1347817
|
|
12
|
+
router_port: 9683
|
|
13
|
+
instance_id: 9683-a2f58d1342e0
|
|
14
|
+
active_client_count: 1
|
|
15
|
+
active_client_pids: [29116]
|
|
16
|
+
provider: codex
|
|
17
|
+
chat_health:
|
|
18
|
+
ok: true
|
|
19
|
+
base: http://10.0.0.238:9683
|
|
20
|
+
tui_status:
|
|
21
|
+
ok: true
|
|
22
|
+
enabled: true
|
|
23
|
+
active_count: 0
|
|
24
|
+
latest_event_id: 0
|
|
25
|
+
capture_scope: routed runtime traffic
|
|
26
|
+
excluded: hidden thinking, tool arguments, native traffic that bypasses this router, terminal pixels
|
|
27
|
+
tui_recent: '{"ok":true,"events":[],"last_id":0,"active":[]}'
|
|
28
|
+
events_recent: '{"ok":true,"events":[]}'
|
|
29
|
+
source_audit:
|
|
30
|
+
router_http: Authenticated GET dispatches TUI/events/config/web/chat/runtime APIs; no raw router.log download handler found.
|
|
31
|
+
tui: Observation API explicitly excludes terminal pixels and native bypass traffic.
|
|
32
|
+
conclusion:
|
|
33
|
+
confirmed: Remote 0.2.40 is running and reachable on LAN with authentication; one active client recorded.
|
|
34
|
+
not_confirmed: Clipboard delimiter corruption location, actual ConPTY/fallback selection, mouse state, live terminal input bytes.
|
|
35
|
+
blocker: Existing backend does not expose the raw log and console input/output evidence required for this native-terminal issue.
|
|
36
|
+
remote_changes: None; no input injection, model request, log-level change, or session restart.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
title: Router startup timeout after remote Windows web backend configuration
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
status: conditional configuration bug reproduced; remote root cause unconfirmed
|
|
4
|
+
evidence:
|
|
5
|
+
traceback: router_process_lifecycle.start_router_if_needed exhausted 30-second router_up polling.
|
|
6
|
+
remote_initial: http://10.0.0.238:9683/health returned 401 external authentication required.
|
|
7
|
+
remote_later: Connection timed out after 3015 milliseconds.
|
|
8
|
+
source: WebBackendSettings.client_host preserves specific LAN addresses; router_health sends no authentication; RouterAccessPolicy permits unauthenticated loopback only.
|
|
9
|
+
local_policy_reproduction: client_host=10.0.0.238; LAN health without token allowed=False; loopback health allowed=True.
|
|
10
|
+
limitations: No remote router.log or configured bind host available; startup process crash and bind errors remain possible.
|
|
11
|
+
changes: No runtime configuration or code modified.
|
|
12
|
+
required_evidence: Remote router.log or authenticated backend access after service recovery.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
title: Authenticate LAN-bound managed router health checks
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
version: 0.2.40
|
|
4
|
+
authorization: User explicitly requested fix and new-version main deployment.
|
|
5
|
+
evidence:
|
|
6
|
+
remote_log: Listener and client base both use 10.0.0.238:9683.
|
|
7
|
+
observed_http: Earlier external health request returned 401.
|
|
8
|
+
source: router_health omitted authentication; non-loopback requests require authentication.
|
|
9
|
+
change:
|
|
10
|
+
health: Read existing administrative token for non-loopback health checks when administrative external access is enabled.
|
|
11
|
+
preserved: Bind address, port, global/workspace configuration, external authentication rules, active sessions.
|
|
12
|
+
secrets: No token generation or token logging in the health probe.
|
|
13
|
+
verification:
|
|
14
|
+
real_http: Isolated HTTP server with production access policy: missing token fails, correct token makes router_up true, wrong token fails.
|
|
15
|
+
limitations: Test socket is loopback with simulated LAN peer policy; remote user's final restart not yet verified.
|
|
16
|
+
targeted: 2 tests passed in 0.513s.
|
|
17
|
+
runtime: 280 tests passed with 16 skips.
|
|
18
|
+
static: ruff and py_compile passed.
|
|
19
|
+
deployment: main push triggers full-test npm latest publishing; registry and downloaded artifact checked after publishing.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
title: Codex LAN client authentication and Windows paste framing
|
|
2
|
+
date: 2026-09-07
|
|
3
|
+
version: 0.2.41
|
|
4
|
+
authorization: User requested handling paste corruption and internal Codex router authentication failure; continuing requested main release workflow.
|
|
5
|
+
causes:
|
|
6
|
+
auth: Previous patch authenticated health only; native model Authorization cannot double as Ciel administrative token.
|
|
7
|
+
paste_reproduction: Real Windows Codex 0.153.4 rendered [200~1660377[201~ when ESC and remaining delimiter bytes were written 20ms apart.
|
|
8
|
+
changes:
|
|
9
|
+
auth: Model and builtin Ciel MCP receive x-ciel-runtime-token from process environment, loaded from this instance token repository; no user TOML edits.
|
|
10
|
+
scope: Exact router origin only; later URL overrides excluded; native OpenAI Authorization retained.
|
|
11
|
+
upstream: Existing x-ciel-runtime header exclusion verified; router credential not forwarded to OpenAI.
|
|
12
|
+
builtin: Reserved openai provider uses a per-launch custom provider retaining requires_openai_auth, verified with actual Codex config loader.
|
|
13
|
+
paste: Frame CSI/SS3 boundaries across parent reads without stripping or rewriting payload; single Escape flushed after 100ms idle.
|
|
14
|
+
verification:
|
|
15
|
+
actual_codex_paste: Replayed split input through corrected pump; 1660377 visible, neither delimiter visible; no prompt submitted.
|
|
16
|
+
actual_codex_http: Local test server received Ciel token on MCP and model requests; native Authorization also present on model requests. Server intentionally returns 400 without inference or forwarding.
|
|
17
|
+
auth_tests: 5 passed, including real CLI and overridden destination protection.
|
|
18
|
+
conpty: 33 passed.
|
|
19
|
+
framing: 3 passed, covering every split, Korean, literal marker text, standalone Escape, arrows and F9.
|
|
20
|
+
runtime_group: 285 passed with 12 skips before additional URL-override test.
|
|
21
|
+
router_debug: 25 passed.
|
|
22
|
+
static: Ruff and architecture budget passed.
|
|
23
|
+
limitations:
|
|
24
|
+
remote: Original mouse gesture on remote machine not directly replayed; reproduced matching corruption with fragmented native Codex input locally.
|
|
25
|
+
idle: Incomplete sequences exceeding 100ms idle are flushed unchanged to preserve standalone Escape.
|
|
26
|
+
deployment: Main workflow and published package verification follow commit.
|
package/package.json
CHANGED