Skip to content

feat(tool-sandbox): support Git fsmonitor socket via unix_socket_bind - #1780

Open
kipz wants to merge 2 commits into
nolabs-ai:mainfrom
kipz:kipz/fsmonitor-socket-grant
Open

feat(tool-sandbox): support Git fsmonitor socket via unix_socket_bind#1780
kipz wants to merge 2 commits into
nolabs-ai:mainfrom
kipz:kipz/fsmonitor-socket-grant

Conversation

@kipz

@kipz kipz commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Linked Issue

Closes #1779

Summary

Adds a unix_socket_bind command-sandbox field for connect(2)/bind(2) grants on named AF_UNIX sockets, plus the @git:fsmonitor-socket token so a command running with core.fsmonitor=true can reach the per-worktree fsmonitor daemon's IPC socket without a broader filesystem grant.

Test Plan

  • make ci (fmt, clippy -D warnings, full test suite)
  • nono why --profile <profile> --command git --path <repo>/.git/fsmonitor--daemon.ipc --op readwrite --workdir <repo> against a real git worktree, confirming the token resolves to the exact daemon socket path and the grant is allowed

Checklist

  • An issue exists and is linked above
  • All commits are signed-off, using DCO
  • All new code follows the project's coding standards (CLAUDE.md) and is covered by tests
  • Public-facing changes are paired with documentation updates
  • If this PR introduces a major feature, capability, or security-relevant change, a corresponding NEP has been opened or accepted and is linked

Agent Compliance Check (Required for AI/Automated PRs)

  • I am not prohibited from contributing under this policy
  • An issue already exists
  • I disclosed that I am an agent in the issue discussion
  • I described my intent and approach in the issue discussion
  • I reviewed repository coding and security rules for the affected area
  • I provided required attribution for reused or adapted code
  • I did not use forbidden patterns such as unwrap/expect
  • I used NonoError where required
  • I validated and canonicalized all relevant paths
  • This PR matches the approved or disclosed issue scope

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

PR Review Summary

Size

Metric Value
Lines added +1133
Lines removed -19
Total changed 1152
Classification Large (> 300 lines)

Affected crates

  • crates/nono-cli — CLI changes. Verify argument parsing, flag documentation, and UX behaviour across supported platforms.

Blast radius — Broad

This PR touches: source code,documentation,configuration / policy files


Updated automatically on each push to this PR.

@kipz
kipz marked this pull request as ready for review September 3, 2026 16:09
@SequeI
SequeI marked this pull request as draft September 4, 2026 10:24
@SequeI
SequeI marked this pull request as ready for review September 4, 2026 10:24

@nogent-nolabs-ai nogent-nolabs-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nogent code review

1 high-severity security bypass, 1 bug, and 1 schema design finding.

Findings (not tied to a changed line):

  • 🏗️ [LOW · design] crates/nono-cli/data/nono-profile.schema.json:802 — The new unix_socket_bind command-sandbox field was added to CommandSandboxConfig and documented, but is missing from the command policies schema definition in crates/nono-cli/data/nono-profile.schema.json. Update the schema to allow unix_socket_bind under individual command sandbox policies.

Automated code + security review. CI already covers clippy, rustfmt, tests, cargo-audit and commit-lint.

use super::dynamic_providers::expand_dynamic_tokens;
// Must canonicalize cwd to match dynamic-token providers, or a symlinked
// cwd escapes the write non-escalation downgrade.
let canonical_cwd = cwd

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

🔒 [HIGH · security] The write non-escalation downgrade logic in add_policy_unix_sockets (on both Linux and macOS) checks normalized.starts_with(&canonical_cwd). While canonical_cwd is canonicalized, normalized (returned by lexically_normalize) is not. Under a symlinked CWD (such as /tmp on macOS, which is a symlink to /private/tmp), a literal relative path like my.sock resolves to /tmp/my.sock, so normalized.starts_with(&canonical_cwd) evaluates to false. This bypasses the downgrade check, granting ReadWrite access to the parent directory and escalating write privileges outside the agent's authorized write root. Update the condition to check both: (normalized.starts_with(cwd) || normalized.starts_with(&canonical_cwd)).

if raw.is_empty() {
return None;
}
let backlink = PathBuf::from(raw).canonicalize().ok()?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

🐛 [MEDIUM · bug] In verify_worktree_backlink, PathBuf::from(raw).canonicalize() is called where raw represents the gitdir backlink path. Since Git worktrees often use relative paths for backlinks, calling .canonicalize() on a relative PathBuf without prefixing it with the parent directory resolves it against the process's working directory rather than the worktree metadata root. This breaks verification for valid repositories using relative backlinks. Join raw to canonical_candidate when relative before calling .canonicalize().

kipz added 2 commits September 4, 2026 12:19
Adds a unix_socket_bind command sandbox field for connect(2)/bind(2)
grants on named AF_UNIX sockets, plus the @git:fsmonitor-socket token
so a command running with core.fsmonitor=true can reach the per-
worktree fsmonitor daemon's IPC socket without a broader filesystem
grant. The token is resolved by a pure filesystem walk (no git
process spawn), matching how the other git path tokens avoid trusting
repo-local config.

Signed-off-by: James Carnegie <me@kipz.org>
…unix_socket_bind

The write non-escalation downgrade for unix_socket_bind compared a
canonicalized cwd against a merely-normalized candidate path, so a
literal relative socket entry under a symlinked cwd (e.g. macOS /tmp)
bypassed the downgrade and got ReadWrite instead of Read. The
worktree-backlink verifier canonicalized a relative gitdir backlink
against the process cwd instead of the worktree metadata root,
breaking verification for repos with relative backlinks. Also add the
missing unix_socket_bind field to the command-policy schema.

Signed-off-by: James Carnegie <me@kipz.org>
@kipz
kipz force-pushed the kipz/fsmonitor-socket-grant branch from 70acd69 to c781cb3 Compare September 4, 2026 11:23
@kipz

kipz commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the nogent findings (c781cb3):

  1. HIGH — symlinked-cwd bypass in add_policy_unix_sockets. normalized was only lexically cleaned, never canonicalized, so a literal relative socket path under a symlinked cwd (e.g. macOS /tmp) never matched canonical_cwd and skipped the write non-escalation downgrade. Now checks normalized.starts_with(cwd) || normalized.starts_with(&canonical_cwd). Added a regression test with a literal relative entry (the existing symlink test only covered the @git: dynamic-token path, which is already canonicalized upstream).

  2. MEDIUM — relative backlink in verify_worktree_backlink. The private gitdir's gitdir backlink was canonicalized against the process cwd instead of the worktree metadata root, breaking verification for a valid relative backlink. Now joins it onto canonical_candidate first when relative. Added a regression test.

  3. LOW — schema gap. Added unix_socket_bind to CommandSandboxConfig in nono-profile.schema.json (the field already existed on the struct and in the docs, just missing from the schema — also updated the schema-shape test's field list).

Rebased onto latest main, make ci clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Git fsmonitor daemon socket cannot be granted in a sandbox

1 participant