cdx-manager 0.24.5 → 0.24.7
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/README.md +1 -1
- package/VERSION +1 -1
- package/changelogs/CHANGELOGS_0_24_6.md +16 -0
- package/changelogs/CHANGELOGS_0_24_7.md +15 -0
- package/checksums/release-archives.json +22 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/provider_runtime.py +15 -1
- package/src/workspace_sandbox.py +56 -5
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br clear="left"/>
|
|
6
6
|
|
|
7
|
-
[](LICENSE) ](LICENSE)  
|
|
8
8
|
|
|
9
9
|
**The right AI coding account, when you need it.** CDX Manager keeps your Codex and Claude accounts in separate profiles, shows which ones have quota, and helps you pick the next usable session from your terminal.
|
|
10
10
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.24.
|
|
1
|
+
0.24.7
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# CDX Manager 0.24.6
|
|
2
|
+
|
|
3
|
+
Release date: 2026-10-29
|
|
4
|
+
|
|
5
|
+
This patch gives isolated online workspace runs their own egress-only network. (#63)
|
|
6
|
+
|
|
7
|
+
## Workspace runs
|
|
8
|
+
|
|
9
|
+
- Use a dedicated pasta network namespace for online `--isolate-workspace` runs, including provider transport, with port relays and host gateway mapping disabled. (#63)
|
|
10
|
+
- Apply nftables output filtering to reject loopback, private, link-local, and tailnet destination ranges, and use public DNS resolvers. (#63)
|
|
11
|
+
- Fail closed when pasta (passt), nftables, or Python 3 is unavailable, without falling back to the host network. (#63)
|
|
12
|
+
|
|
13
|
+
## Maintenance
|
|
14
|
+
|
|
15
|
+
- Record tray companion checksums for v0.24.5. (`78ba34dd71a19ac392feda771737705a72c765e5`)
|
|
16
|
+
- Record release archive checksums for v0.24.5. (`f835adb7acc495133438c6e719fa452843109d58`)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# CDX Manager 0.24.7
|
|
2
|
+
|
|
3
|
+
Release date: 2026-10-29
|
|
4
|
+
|
|
5
|
+
This patch fixes command execution in isolated Codex workspace runs. (#65)
|
|
6
|
+
|
|
7
|
+
## Workspace runs
|
|
8
|
+
|
|
9
|
+
- Drop Codex's nested sandbox when `--isolate-workspace` is enabled, avoiding the user namespace mapping failure that prevented commands from running. The outer workspace sandbox remains the isolation boundary. (#65)
|
|
10
|
+
- Remove Codex's sandbox mode and network overrides inside the isolated workspace and pass `--dangerously-bypass-approvals-and-sandbox`; the outer sandbox controls network access and keeps review runs read-only through its workspace mount. (#65)
|
|
11
|
+
|
|
12
|
+
## Maintenance
|
|
13
|
+
|
|
14
|
+
- Record tray companion checksums for v0.24.6. (`87aba5d2cffdfb1a96e386abcdabe54df9ee4808`)
|
|
15
|
+
- Record release archive checksums for v0.24.6. (`6b92094143ea28bf0cc096aef88d1015565aba18`)
|
|
@@ -628,6 +628,28 @@
|
|
|
628
628
|
"x86_64-pc-windows-msvc": "1fd621d4ba7175c868c4e598850c5f5ccd914c91aaef3d79024376082e8fe81d",
|
|
629
629
|
"x86_64-unknown-linux-musl": "f4c3a6e7c2af75cd8214609971fe2cbef6f34e818796d70b661d9a9f7e9327af"
|
|
630
630
|
}
|
|
631
|
+
},
|
|
632
|
+
"v0.24.5": {
|
|
633
|
+
"github_tarball_sha256": "0e34b1c67f3357d1551a2cce1187efb84cfb737163e91b3ae4e448f9bd2fee08",
|
|
634
|
+
"github_zip_sha256": "0f1242f234392f003fe6ed61206d4b931d72887f875cd0a60b20199a2f5745a6",
|
|
635
|
+
"tray_assets": {
|
|
636
|
+
"aarch64-apple-darwin": "6847d3f6b66137157896ec901b47a3b48913f78390b3fff50d1d59c85151e748",
|
|
637
|
+
"aarch64-unknown-linux-musl": "6f167515cdc0914b1d04ceee0ea60e28de57d2321d4073912409a6d9266c6185",
|
|
638
|
+
"x86_64-apple-darwin": "16a9bfc33fe03b771bdb7ffd417ce222785062f342c1c599bdda2a3fc7200841",
|
|
639
|
+
"x86_64-pc-windows-msvc": "74124b2e40a58a58b55724ef453c4b543a83eefab9a5f92685b239c5d97e7f67",
|
|
640
|
+
"x86_64-unknown-linux-musl": "089e552a4dfae4080a2e76cedb6fe72fc8013a9cd08cf3c8b333f916198da57c"
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
"v0.24.6": {
|
|
644
|
+
"github_tarball_sha256": "4b61861ae26fc7904e751571b65d0783c16dcf35d3b485379434469e27511fb4",
|
|
645
|
+
"github_zip_sha256": "6c5cfa2139eb53a20a8eca8624437a06b18392b0df7774d9c7c64a74b8066f59",
|
|
646
|
+
"tray_assets": {
|
|
647
|
+
"aarch64-apple-darwin": "7200f658e3413b2614a7ffa5d03e1dac173fc1a653dc659d92f2bef40f2bf5d4",
|
|
648
|
+
"aarch64-unknown-linux-musl": "b892a9a3bf009e5069dc798ab910b20e77ac9d03b9b575a80cb19af1dc13533a",
|
|
649
|
+
"x86_64-apple-darwin": "d1e1b619feab8eae1e2a25e17942d8d690fc1e6184f294599a15858a1f0c146e",
|
|
650
|
+
"x86_64-pc-windows-msvc": "0d125d8e0f7d9880a2ed9e0a794964f3d69ef3353b90a1d594b17f9392a0a0af",
|
|
651
|
+
"x86_64-unknown-linux-musl": "d6c369ace10a6d373366cd12e4e6cd7f2fd278fbce2fd9a13c5bf51047847441"
|
|
652
|
+
}
|
|
631
653
|
}
|
|
632
654
|
}
|
|
633
655
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/provider_runtime.py
CHANGED
|
@@ -1212,7 +1212,10 @@ def _run_headless_provider_command(session, cwd=None, env_override=None, initial
|
|
|
1212
1212
|
)
|
|
1213
1213
|
launch = session.get("launch") or {}
|
|
1214
1214
|
if launch.get("network_access"):
|
|
1215
|
-
if session.get("provider") == PROVIDER_CODEX:
|
|
1215
|
+
if session.get("provider") == PROVIDER_CODEX and launch.get("isolate_workspace"):
|
|
1216
|
+
# The outer sandbox owns the network; Codex's own sandbox is dropped below.
|
|
1217
|
+
pass
|
|
1218
|
+
elif session.get("provider") == PROVIDER_CODEX:
|
|
1216
1219
|
# Keep the workspace sandbox; append overrides before the prompt separator.
|
|
1217
1220
|
index = spec["args"].index("--") if "--" in spec["args"] else len(spec["args"])
|
|
1218
1221
|
spec["args"][index:index] = ["-c", 'sandbox_workspace_write.network_access=true', '-c', 'sandbox_workspace_write.writable_roots=["/cache", "/home/agent"]']
|
|
@@ -1226,6 +1229,17 @@ def _run_headless_provider_command(session, cwd=None, env_override=None, initial
|
|
|
1226
1229
|
# The outer OS sandbox is the permission boundary for unattended Bash.
|
|
1227
1230
|
index = spec["args"].index("--") if "--" in spec["args"] else len(spec["args"])
|
|
1228
1231
|
spec["args"][index:index] = ["--permission-mode", "bypassPermissions"]
|
|
1232
|
+
elif session.get("provider") == PROVIDER_CODEX:
|
|
1233
|
+
# Codex's bubblewrap sandbox cannot nest inside ours (no uid map), so the
|
|
1234
|
+
# outer box is the boundary: drop `-s <mode>` and bypass the inner one.
|
|
1235
|
+
# A review run stays read-only through the outer mount.
|
|
1236
|
+
args = spec["args"]
|
|
1237
|
+
while "-s" in args:
|
|
1238
|
+
index = args.index("-s")
|
|
1239
|
+
del args[index:index + 2]
|
|
1240
|
+
if "--dangerously-bypass-approvals-and-sandbox" not in args:
|
|
1241
|
+
index = args.index("--") if "--" in args else len(args)
|
|
1242
|
+
args[index:index] = ["--dangerously-bypass-approvals-and-sandbox"]
|
|
1229
1243
|
try:
|
|
1230
1244
|
argv, child_env = sandbox_command(
|
|
1231
1245
|
[spec["command"], *spec["args"]], Path(cwd),
|
package/src/workspace_sandbox.py
CHANGED
|
@@ -11,6 +11,41 @@ from pathlib import Path
|
|
|
11
11
|
|
|
12
12
|
CACHE = Path.home() / ".cdx" / "shared-cache"
|
|
13
13
|
|
|
14
|
+
# Installed before any untrusted command, in the namespace owned by pasta.
|
|
15
|
+
NETWORK_RULES = """table inet mission_egress {
|
|
16
|
+
chain output {
|
|
17
|
+
type filter hook output priority 0; policy accept;
|
|
18
|
+
oif "lo" accept
|
|
19
|
+
ip daddr { 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8,
|
|
20
|
+
169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16 } reject
|
|
21
|
+
ip6 daddr { ::1, fc00::/7, fe80::/10 } reject
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
"""
|
|
25
|
+
PUBLIC_RESOLV_CONF = "nameserver 1.1.1.1\nnameserver 9.9.9.9\n"
|
|
26
|
+
|
|
27
|
+
# Keep the trusted setup outside the agent's filesystem/PID/user namespaces.
|
|
28
|
+
NETWORK_SETUP = """
|
|
29
|
+
import pathlib
|
|
30
|
+
import subprocess
|
|
31
|
+
import sys
|
|
32
|
+
import tempfile
|
|
33
|
+
try:
|
|
34
|
+
subprocess.run([sys.argv[1], "-f", "-"], input=RULES, text=True,
|
|
35
|
+
check=True, timeout=15)
|
|
36
|
+
with tempfile.TemporaryDirectory(prefix="mission-dns-") as directory:
|
|
37
|
+
resolver = pathlib.Path(directory) / "resolv.conf"
|
|
38
|
+
resolver.write_text(RESOLVER)
|
|
39
|
+
args = sys.argv[2:]
|
|
40
|
+
index = args.index("--")
|
|
41
|
+
args[index:index] = ["--ro-bind", str(resolver), "/etc/resolv.conf"]
|
|
42
|
+
status = subprocess.call(args)
|
|
43
|
+
except (OSError, subprocess.SubprocessError) as error:
|
|
44
|
+
print("Isolated mission network setup failed: " + str(error), file=sys.stderr)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
sys.exit(status)
|
|
47
|
+
"""
|
|
48
|
+
|
|
14
49
|
|
|
15
50
|
def clean_env(source: dict) -> dict:
|
|
16
51
|
allowed = ("PATH", "LANG", "LC_ALL", "TERM", "TZ")
|
|
@@ -36,10 +71,20 @@ def sandbox_command(command: list[str], tree: Path, *, network: bool, writable:
|
|
|
36
71
|
raise RuntimeError("Workspace must be a directory")
|
|
37
72
|
tree = tree.resolve()
|
|
38
73
|
CACHE.mkdir(parents=True, exist_ok=True)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if
|
|
42
|
-
|
|
74
|
+
# Provider transport uses the same restricted egress, even for read-only tools.
|
|
75
|
+
online = bool(network or provider)
|
|
76
|
+
if online:
|
|
77
|
+
pasta = shutil.which("pasta", path=source.get("PATH"))
|
|
78
|
+
nft = shutil.which("nft", path=os.defpath + ":/usr/sbin:/sbin")
|
|
79
|
+
python = shutil.which("python3", path=source.get("PATH"))
|
|
80
|
+
if not pasta or not nft or not python:
|
|
81
|
+
raise RuntimeError("Isolated mission networking requires pasta (passt), nftables and python3; no shared-network fallback")
|
|
82
|
+
args = [bwrap, "--die-with-parent", "--new-session"]
|
|
83
|
+
# pasta already created the network namespace. A new child user namespace and
|
|
84
|
+
# dropped capabilities prevent the agent from changing its ancestor's nft rules.
|
|
85
|
+
args += (["--unshare-user", "--unshare-ipc", "--unshare-pid", "--unshare-uts", "--unshare-cgroup"]
|
|
86
|
+
if online else ["--unshare-all"])
|
|
87
|
+
args += ["--cap-drop", "ALL"]
|
|
43
88
|
args += ["--proc", "/proc", "--dev", "/dev", "--tmpfs", "/tmp", "--dir", "/home/agent"]
|
|
44
89
|
# An allowlist avoids overlooking another secret file or a credential symlink.
|
|
45
90
|
paths = [Path(p) for p in ("/usr", "/bin", "/sbin", "/lib", "/lib64")]
|
|
@@ -51,7 +96,7 @@ def sandbox_command(command: list[str], tree: Path, *, network: bool, writable:
|
|
|
51
96
|
args += ["--ro-bind", str(path), str(path)]
|
|
52
97
|
for path in ("/etc/ssl", "/etc/resolv.conf", "/etc/hosts", "/etc/nsswitch.conf", "/etc/ld.so.cache",
|
|
53
98
|
"/etc/fonts", "/etc/passwd", "/etc/group", "/etc/alternatives"):
|
|
54
|
-
if Path(path).exists():
|
|
99
|
+
if Path(path).exists() and not (online and path == "/etc/resolv.conf"):
|
|
55
100
|
args += ["--ro-bind", path, path]
|
|
56
101
|
# Fail closed for a provider installed outside the explicitly permitted toolchains.
|
|
57
102
|
resolved = Path(binary).resolve()
|
|
@@ -91,4 +136,10 @@ def sandbox_command(command: list[str], tree: Path, *, network: bool, writable:
|
|
|
91
136
|
elif provider:
|
|
92
137
|
raise RuntimeError("Unsupported isolated provider: " + provider)
|
|
93
138
|
args += ["--chdir", str(tree), "--", binary, *command[1:]]
|
|
139
|
+
if online:
|
|
140
|
+
setup = "RULES = " + repr(NETWORK_RULES) + "\nRESOLVER = " + repr(PUBLIC_RESOLV_CONF) + "\n" + NETWORK_SETUP
|
|
141
|
+
# Disable both directions explicitly: automatic host-loopback splicing
|
|
142
|
+
# bypasses output filtering unless -T/-U are also disabled.
|
|
143
|
+
args = [pasta, "--config-net", "--no-map-gw", "-t", "none", "-u", "none",
|
|
144
|
+
"-T", "none", "-U", "none", "-q", "--", python, "-I", "-c", setup, nft, *args]
|
|
94
145
|
return args, env
|