cdx-manager 0.24.5 → 0.24.6
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/checksums/release-archives.json +11 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -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.6
|
|
@@ -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`)
|
|
@@ -628,6 +628,17 @@
|
|
|
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
|
+
}
|
|
631
642
|
}
|
|
632
643
|
}
|
|
633
644
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
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
|