Repository navigation
ltic-main: sync to upstream v0.0.81 (0.0.81-ltic.1) - #1
Closed
smolyn-ubc wants to merge 36 commits into
Closed
smolyn-ubc wants to merge 36 commits into
smolyn-ubc wants to merge 36 commits into
Conversation
When `network.allowAllDomains: true`, filterNetworkRequest short-circuits to allow after the deniedDomains check runs, so explicit denies still take effect. Intended for environments where filesystem restrictions are the primary boundary and enumerating every reachable domain is impractical (e.g. opening web docs lookups in an otherwise locked-down sandbox). The flag is optional and additive: existing configs are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Read restrictions today have only two layers — denyRead and allowRead — with allowRead unconditionally winning. That makes it impossible to broadly allow a directory (e.g. ~/src for cross-project work) while still keeping credential-style files (.env, *.pem, ~/.aws/credentials) denied within it. The write side already has the symmetric capability via denyWrite, so reads were the odd one out. New optional `denyReadAlways: string[]` in FilesystemConfigSchema. On macOS it emits Seatbelt (deny file-read*) rules AFTER the allowRead loop so they take precedence (last-match-wins). On Linux it emits bubblewrap binds (--tmpfs for dirs, --ro-bind /dev/null for files) after the allowRead re-bind loop for the same effect. denyReadAlways paths are also passed to generateMoveBlockingRules so mv/rename cannot bypass the deny by relocating the file. Glob handling reuses the existing helpers — patterns work the same as denyRead. Patterns with a leading "/" match globally (e.g. "/**/.env*"); without it they are CWD-relative, matching existing denyRead behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # test/config-validation.test.ts
Make `srt --version` a reliable fork identifier. Upstream read the version from `process.env.npm_package_version`, which is unset for a globally-installed binary, so `--version` always fell back to a hardcoded "1.0.0" — identical on upstream and this fork. Read the version from package.json at runtime instead, and stamp this branch with a distinctive `-ltic.1` suffix so users can confirm they are on the patched fork (allowAllDomains + denyReadAlways). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When `network.allowAllDomains: true`, filterNetworkRequest short-circuits to allow after the deniedDomains check runs, so explicit denies still take effect. Intended for environments where filesystem restrictions are the primary boundary and enumerating every reachable domain is impractical (e.g. opening web docs lookups in an otherwise locked-down sandbox). The flag is optional and additive: existing configs are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Read restrictions today have only two layers — denyRead and allowRead — with allowRead unconditionally winning. That makes it impossible to broadly allow a directory (e.g. ~/src for cross-project work) while still keeping credential-style files (.env, *.pem, ~/.aws/credentials) denied within it. The write side already has the symmetric capability via denyWrite, so reads were the odd one out. New optional `denyReadAlways: string[]` in FilesystemConfigSchema. On macOS it emits Seatbelt (deny file-read*) rules AFTER the allowRead loop so they take precedence (last-match-wins). On Linux it emits bubblewrap binds (--tmpfs for dirs, --ro-bind /dev/null for files) after the allowRead re-bind loop for the same effect. denyReadAlways paths are also passed to generateMoveBlockingRules so mv/rename cannot bypass the deny by relocating the file. Glob handling reuses the existing helpers — patterns work the same as denyRead. Patterns with a leading "/" match globally (e.g. "/**/.env*"); without it they are CWD-relative, matching existing denyRead behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve version to 0.0.66-ltic.1 and keep the denyReadAlways config-validation test alongside upstream's new suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keep both fork tests (allowAllDomains + denyReadAlways) in config-validation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve version to 0.0.70-ltic.1 in package.json and package-lock.json (upstream's lockfile was still stamped 0.0.67). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Enable the seatbelt/seccomp log monitor in CLI mode (previously only proxy denials reached the store) and stream every violation line to ~/.local/state/srt/violations-<pid>.log, advertised to the child via SRT_VIOLATIONS_FILE. A Claude Code PostToolUse hook inside the sandbox tails the file and injects new lines as model context, so denials read as policy (with deniedDomainReasons) instead of mysterious 403/EPERM failures. The directory must be sandbox-readable but not writable so sandboxed code cannot forge lines into the agent's context. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zod strips unknown config keys silently, so a mistyped fork key (allowAllDomainz) or a config/build mismatch validated cleanly while the intended grant or restriction was silently lost — the exact footgun that makes stock srt turn an allowAllDomains config into deny-all with no feedback. The settings loader now walks the parsed JSON against the schema and names every unrecognized key in a stderr warning at launch. Tests pin both directions: all keys the ccx configs use (including fork keys allowAllDomains and denyReadAlways) are recognized, and unknown keys are reported with full dot-paths.
…ains The injectHosts ⊆ allowedDomains reachability check predates allowAllDomains: with the flag on, every host is reachable, but the validator still demanded allowlist entries the flag makes meaningless — the documented masking recipe (empty allowlist + injectHosts toward GitHub) failed to load without padding allowedDomains. Skip the subset check when allowAllDomains is set. Conversely, add the missing guard for the trap that combination creates: a masked credential with no injectHosts defaults to allowedDomains, which under allowAllDomains is typically empty — masked but injected nowhere, a silent auth failure. That spelling is now a validation error asking for explicit injectHosts. The excludeDomains contradiction checks and the explicit-empty-injectHosts rejection are unchanged.
The macOS GIT_SSH_COMMAND used BSD nc for its ProxyCommand, and nc only
speaks the SOCKS5 no-auth method. When SRT owns the proxy it always sets
a per-session auth token, so every sandboxed git-over-ssh attempt died
at the SOCKS handshake ("authentication method negotiation failed") —
the code comment acknowledged this and punted users to git-over-https.
Linux never had the problem because it emits
socat - PROXY:localhost:%h:%p,proxyport=<port>,proxyauth=<user>:<token>
— an HTTP CONNECT tunnel that can present Proxy-Authorization. The mux
serves HTTP CONNECT on the same port on macOS, and the terminate proxy
already sniffs for a ClientHello before MITM-ing, so non-TLS SSH bytes
pass through opaquely (pinned by connect-non-tls.test.ts).
macOS now emits the identical socat spelling whenever socat is found on
PATH (whichSync, per generateProxyEnvVars call — the wrapper already
probes PATH per wrap for the shell) and httpProxyPort is set, keeping
the nc spelling as the fallback for hosts without socat. socat stays an
optional macOS dependency, unlike Linux where it is mandatory.
Tests pin all four macOS shapes: socat+auth, socat+per-command
attribution username (userRaw, not percent-encoded — the Basic header
takes it verbatim), socat without auth, and the nc fallback. Also fixes
stale "Linux-only" wording in http-proxy.ts / connect-non-tls.test.ts
and the SOCKS5 misnomer in the linux-sandbox-utils docblock.
…f credential globs
denyReadAlways globs are deliberately broad ("/**/.env*", "/**/id_*"),
which also catches files that are meant to be read: .env.example and
friends are templates with no secrets, and id_*.pub are public keys —
whose unreadability breaks ssh's IdentitiesOnly agent flow (ssh needs
the .pub to know which agent key to offer). Plain globs cannot express
"everything except this suffix".
New optional filesystem.denyReadAlwaysExcept: paths re-allowed for
reading even when they match denyReadAlways. Highest-priority read
rule: denyAlwaysExcept > denyAlways > allowWithinDeny > denyOnly.
- macOS: exception rules are emitted immediately after the denyAlways
denies; Seatbelt is last-match-wins, so the allow fires when both
match. No move-blocking changes needed: creation/rename inside
write-allowed dirs is already re-allowed per-path by the
writeAllowPaths pass, and that is the only place writes happen.
- Linux: the platform layer only sees concrete paths, so exceptions
apply as set-subtraction in sandbox-manager (both getFsReadConfig and
the per-exec path), comparing normalized paths.
Also fixes two pre-existing gaps in the denyReadAlways feature:
- The maskedFileBinds readConfig reconstruction on macOS dropped
denyAlways, silently erasing every credential glob whenever
credentials.files was configured.
- hasReadRestrictions (macOS and Linux) ignored denyAlways, so a config
whose only read restriction was denyReadAlways ran unsandboxed.
Tests: new test/sandbox/deny-read-always.test.ts pins profile ordering,
the only-denyAlways wrapping fix, the mask-path regression, sandbox-exec
integration (.env denied / .env.example readable, including a file
created in-sandbox), and Linux set-subtraction; config-validation
covers the new schema key.
…ognized-keys test
…k names Upstream's resolved-address guard treats an allow-listed IP literal or localhost name as an explicit choice and skips its checks. The flag's short-circuit made every such destination count as allow-listed, so 169.254.169.254, 127.0.0.1 and localhost were reachable. The flag now covers hostnames only; those destinations still need an allowedDomains entry.
# Conflicts: # package-lock.json # package.json # src/cli.ts # src/sandbox/linux-sandbox-utils.ts # src/sandbox/macos-sandbox-utils.ts # src/sandbox/sandbox-manager.ts # src/sandbox/sandbox-schemas.ts # src/utils/config-loader.ts
…in-allow Upstream v0.0.77 makes a denyRead glob beat an enclosing allowRead on macOS (d106511) and Linux, and a narrower allowRead carves exceptions back out of a covering deny glob. That covers what denyReadAlways and denyReadAlwaysExcept were added for. The merge already took upstream's rewritten rule generators; this drops the leftover schema fields and tests. Leftover keys in a config are now reported as unrecognized.
Conflicts resolved: - src/cli.ts: keep the violations file patch (startViolationsFile, initialize(cfg, undefined, true), SRT_VIOLATIONS_FILE); take upstream's static package.json import for --version (anthropics#630) and the interrupt handling added around initialize(). - src/sandbox/sandbox-utils.ts, test/sandbox/proxy-env-vars.test.ts: take upstream. The fork's macOS socat GIT_SSH_COMMAND (0759421, PR anthropics#452) is retired: upstream anthropics#681 authenticates to the proxy with stock /usr/bin/nc via SRT_SSH_PROXY_COMMAND, so socat is no longer needed on macOS. - package.json, package-lock.json: version 0.0.81-ltic.1.
…suffix config-loader.ts walked SandboxRuntimeConfigSchema with the root 'zod' types while upstream anthropics#590 moved sandbox-config.ts to 'zod/v3', which tsc rejected as excessively deep. The instanceof checks also need the same module instance at runtime. The srt-proxy --version test required a bare X.Y.Z; the fork ships X.Y.Z-ltic.N, so allow a prerelease suffix.
0.77_to_0.81_NEW_FEATURES.md: fork patch outcomes (socat GIT_SSH_COMMAND retired for upstream anthropics#681), macOS-relevant behaviour changes, options to consider (requireHostMatch, SSH denies), library-level additions, Linux and Windows summaries. HANDOFF_sandbox-runtime-config_v0.0.81.md: self-contained brief for updating ltic-ai-guidelines/sandbox-runtime-config — verified outputs, file-by-file edits (README, DETAILS, justfile, configs) and an acceptance checklist.
Collaborator
Author
|
Not needed: the guidelines repo installs straight from the ltic-main branch, and the fork's main stays a mirror of upstream. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ltic-mainis now upstreammainat v0.0.81 + 4 commits (98a4016) plus the fork's patches. Version0.0.81-ltic.1(srt --version). Sync path: upstream →gs/allow-all-domains→ltic-main, as usual.Fork deltas over upstream (what this diff contains)
network.allowAllDomains(src/sandbox/sandbox-config.ts,src/sandbox/sandbox-manager.ts): allow any hostname afterdeniedDomainsare checked. IP literals and loopback names still need anallowedDomainsentry. Credential-masking validation is allowAllDomains-aware. Upstream PR feat(network): add allowAllDomains flag to bypass allowlist anthropics/sandbox-runtime#283, still open.src/cli.ts): enables the log monitor and streams each violation to~/.local/state/srt/violations-<pid>.log, advertised to the child asSRT_VIOLATIONS_FILE, so a PostToolUse hook can surface denial reasons to the agent. Logs older than a week are pruned.src/utils/config-loader.ts): walks the parsed JSON against the zod schema and warns at startup about keys the schema would otherwise silently drop.0.0.81-ltic.1inpackage.json/package-lock.json;test/proxy-cli.test.tsaccepts a prerelease suffix.0.62_to_0.70_NEW_FEATURES.md,0.70_to_0.77_NEW_FEATURES.md,0.77_to_0.81_NEW_FEATURES.md,HANDOFF_sandbox-runtime-config_v0.0.81.md.What changed in this sync (0.0.77-ltic.1 → 0.0.81-ltic.1)
GIT_SSH_COMMANDpatch. Upstream macOS: git over SSH authenticates to the sandbox's proxy anthropics/sandbox-runtime#681 does authenticated git-over-SSH on macOS with stock/usr/bin/nc. socat is no longer a dependency. The fork now has three deltas, not four.createRequireversion read. Upstream cli: read the version through a static import, so a compiled binary starts anthropics/sandbox-runtime#630 uses a static JSON import.import { z } from 'zod/v3'in the unrecognized-key walker; tsc rejected the old import and theinstanceofchecks would have missed at runtime.allowAllDomainsand the violations file merged with no conflicts; the violations setup was re-seated after upstream's new interrupt handling aroundinitialize().ltic-ai-guidelines/sandbox-runtime-config.Impact on the ccx configs
None required: both
~/.srt-claude-*.jsonload with no "unrecognized settings key" warning. New prerequisite: Node ≥ 22.12. Optional hardening (network.requireHostMatch) and other considerations are in0.77_to_0.81_NEW_FEATURES.md, section 4.Verification
npm install && npm run build && npm run typecheckclean.npm test: 3400 pass, 1179 skipped (Linux/Windows only), 28 fail. Every failure also fails on pristine upstream/main on the same Mac (Bun 1.4.4 vs CI's 1.4.2 for the request-target tests; macOS 26's tcsh/csh for the shell tests). None touch fork code._ccx_runwrapper under both configs:srt --version, HTTPS egress 200, IMDS 403,gist.github.com403 with the configured reason in-band,gh api user,git ls-remoteover SSH with socat offPATH, and the violations file carrying the same deny lines.Details in
0.77_to_0.81_NEW_FEATURES.mdandHANDOFF_sandbox-runtime-config_v0.0.81.md.Follow-up (not in this PR)
ltic-ai-guidelines/sandbox-runtime-configper the handoff doc: drop socat, add the Node floor, add the 0.81 notes.