Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
514559d
feat(filesystem): add shared DenyList matcher for bind mounts
neurolabs Aug 17, 2026
f166463
feat(filesystem): enforce deny list in unix passthrough backend
neurolabs Aug 17, 2026
b0d26cc
feat(filesystem): enforce deny list in windows passthrough backend
neurolabs Aug 17, 2026
89cdef2
refactor(filesystem): move deny matcher to the backends level
neurolabs Aug 18, 2026
28ad41f
refactor(filesystem): move deny matcher to passthroughfs level
neurolabs Aug 17, 2026
1247bec
test(filesystem): add windows deny-list tests
neurolabs Aug 17, 2026
75fe480
feat(runtime): parse deny mount option and wire into passthrough config
neurolabs Aug 17, 2026
99c48f0
feat(sdk): add deny list to bind mount builder
neurolabs Aug 17, 2026
e7f5225
feat(cli): add deny mount option to --mount-dir
neurolabs Aug 17, 2026
90ae2e7
feat(sdk/go): add deny list to bind mount options
neurolabs Aug 17, 2026
2970f5b
docs: document bind-mount deny list option
neurolabs Aug 17, 2026
f945391
fix(filesystem): drop needless borrow in deny path match
neurolabs Aug 18, 2026
0efcf1b
fix(filesystem): never deny structural . and .. entries
neurolabs Aug 18, 2026
b6b145c
fix(sdk): reject deny patterns that break the mount wire format
neurolabs Aug 18, 2026
13c09e7
fix(sdk/go): reject deny on non-bind mounts
neurolabs Aug 18, 2026
a517c52
fix(sdk): reject bind-mount deny lists on cloud creates
neurolabs Aug 18, 2026
7129f98
feat(cli): show deny list in sandbox inspect output
neurolabs Aug 18, 2026
f08d1de
refactor(filesystem): tidy deny.rs section ordering
neurolabs Aug 18, 2026
7f6af3e
feat(filesystem): add macOS F_GETPATH path-resolution helpers
neurolabs Aug 19, 2026
be4a35e
feat(filesystem): store canonical mount root on macOS
neurolabs Aug 19, 2026
da53f58
feat(filesystem): resolve macOS parent paths via /.vol and F_GETPATH
neurolabs Aug 19, 2026
0f992b5
fix(filesystem): fail closed on unresolvable parent path under deny p…
neurolabs Aug 19, 2026
351e0cd
test(filesystem): gate macOS path-pattern deny resolution tests
neurolabs Aug 19, 2026
b4c5d28
docs(filesystem): document macOS path-pattern deny enforcement
neurolabs Aug 19, 2026
19e9087
fix(filesystem): Implement fixes for deny lists
neurolabs Aug 19, 2026
6e97172
refactor(filesystem): rework deny case probe and dirent-type helpers
neurolabs Aug 20, 2026
81b7c35
refactor(filesystem): split deny has_path_patterns into routing and d…
neurolabs Aug 20, 2026
acfabd7
test(filesystem): pin deny flag-split routing semantics
neurolabs Aug 20, 2026
e558a00
docs(filesystem): fix dir-only gate comment and rename deny test
neurolabs Aug 20, 2026
cd7ed40
refactor(filesystem): human review fixes
neurolabs Aug 21, 2026
deb9a10
feat(sdk): add bind-mount deny list to Python and Node SDKs
neurolabs Aug 21, 2026
2d7dbf1
chore: inspect deny-rendering test
neurolabs Aug 21, 2026
3136330
chore(filesystem): optimize performance, document performance impacts
neurolabs Aug 21, 2026
33b9562
fix(docs): python sdk docs
neurolabs Aug 23, 2026
9ddefc8
fix(filesystem): implement fixes for greptile finding
neurolabs Aug 23, 2026
a54849f
fix(filesystem): fix greptile "missing identity permits stale rename"
neurolabs Aug 23, 2026
8a6a5e4
fix(filesystem): greptile "Missing identity blocks every rename"
neurolabs Aug 24, 2026
7c34a1b
Merge remote-tracking branch 'origin/main' into bind-mount-deny-option
neurolabs Aug 24, 2026
5a3633a
fix(filesystem): greptile "Directory-only match blocks files"
neurolabs Aug 24, 2026
ff904ee
chore(filesystem): add comment clarifying accepted residual risk
neurolabs Aug 24, 2026
2ce9b8b
fix(filesystem): greptile "Exchange bypasses directory-only deny"
neurolabs Aug 24, 2026
d8aad9b
Merge remote-tracking branch 'origin/main' into bind-mount-deny-option
neurolabs Aug 25, 2026
83dac92
Merge branch 'main' into bind-mount-deny-option
neurolabs Aug 26, 2026
ef01559
Merge branch 'main' into bind-mount-deny-option
neurolabs Aug 26, 2026
62c6b05
Merge branch 'main' into bind-mount-deny-option
neurolabs Sep 1, 2026
9ad13e4
Merge branch 'main' into bind-mount-deny-option
neurolabs Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ hex = "0.4"
hickory-net = "0.26.1"
hickory-proto = "0.26.1"
httlib-hpack = "0.1.3"
ignore = "0.4"
indicatif = "0.18"
ipnetwork = { version = "0.21.0", features = ["serde"] }
libc = "0.2"
Expand Down
70 changes: 69 additions & 1 deletion crates/cli/lib/commands/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ struct CliMountOptions {
host_permissions: Option<microsandbox::sandbox::HostPermissions>,
size_mib: Option<u32>,
quota_mib: Option<u32>,
deny: Vec<String>,
named_kind: Option<VolumeKind>,
fstype: Option<String>,
format: Option<DiskImageFormat>,
Expand All @@ -617,6 +618,7 @@ struct CliMountOptionSupport {
policies: bool,
size: bool,
quota: bool,
deny: bool,
named_kind: bool,
fstype: bool,
format: bool,
Expand Down Expand Up @@ -1617,6 +1619,7 @@ pub fn apply_explicit_dir_mount(
policies: true,
quota: true,
owner: true,
deny: true,
..CliMountOptionSupport::default()
},
)?;
Expand Down Expand Up @@ -1788,6 +1791,9 @@ fn apply_common_mount_options(mut mount: MountBuilder, options: CliMountOptions)
if let (Some(uid), Some(gid)) = (options.override_uid, options.override_gid) {
mount = mount.owner(uid, gid);
}
if !options.deny.is_empty() {
mount = mount.deny(options.deny);
}
mount
}

Expand Down Expand Up @@ -1986,6 +1992,9 @@ fn parse_cli_mount_options(
parsed.quota_mib =
Some(ui::parse_size_mib(value).map_err(anyhow::Error::msg)?);
}
"deny" if support.deny => {
parsed.deny.push(value.to_string());
}
"kind" if support.named_kind => {
if seen_named_kind {
anyhow::bail!("mount option `kind` specified more than once");
Expand Down Expand Up @@ -2034,7 +2043,7 @@ fn parse_cli_mount_options(
anyhow::anyhow!("invalid gid {value:?} (expected an unsigned integer)")
})?);
}
"stat-virt" | "host-perms" | "size" | "quota" | "kind" | "fstype"
"stat-virt" | "host-perms" | "size" | "quota" | "deny" | "kind" | "fstype"
| "format" | "uid" | "gid" => {
anyhow::bail!("mount option `{key}` is not valid here");
}
Expand Down Expand Up @@ -3937,6 +3946,65 @@ mod tests {
}
}

#[tokio::test]
async fn test_apply_explicit_dir_mount_deny_single() {
let dir = make_temp_dir("msb-mount-dir-deny");
let spec = format!("{}:/work:deny=.env", dir.display());
let mount = build_explicit(&spec, apply_explicit_dir_mount).await;
match mount {
VolumeMount::Bind { deny, .. } => assert_eq!(deny, vec![".env".to_string()]),
other => panic!("expected Bind, got {other:?}"),
}
}

#[tokio::test]
async fn test_apply_explicit_dir_mount_deny_repeatable_preserves_inner_equals() {
let dir = make_temp_dir("msb-mount-dir-deny-rep");
let spec = format!(
"{}:/work:deny=.env,deny=*.log,deny=sub/secret=file",
dir.display()
);
let mount = build_explicit(&spec, apply_explicit_dir_mount).await;
match mount {
VolumeMount::Bind { deny, .. } => {
assert_eq!(
deny,
vec![
".env".to_string(),
"*.log".to_string(),
"sub/secret=file".to_string()
]
);
}
other => panic!("expected Bind, got {other:?}"),
}
}

#[tokio::test]
async fn test_apply_explicit_dir_mount_deny_default_empty() {
let dir = make_temp_dir("msb-mount-dir-deny-none");
let spec = format!("{}:/work:ro", dir.display());
let mount = build_explicit(&spec, apply_explicit_dir_mount).await;
match mount {
VolumeMount::Bind { deny, .. } => assert!(deny.is_empty()),
other => panic!("expected Bind, got {other:?}"),
}
}

#[tokio::test]
async fn test_apply_explicit_file_mount_rejects_deny() {
let file = write_temp("fixture-deny");
let spec = format!("{}:/fixture:deny=.env", file.display());
let err = match apply_explicit_file_mount(SandboxBuilder::new("test"), &spec) {
Ok(_) => panic!("expected deny to be rejected for --mount-file"),
Err(e) => e.to_string(),
};
assert!(
err.contains("`deny` is not valid here"),
"expected deny rejection, got {err:?}"
);
}

#[tokio::test]
async fn test_apply_explicit_file_mount() {
let file = write_temp("fixture");
Expand Down
36 changes: 35 additions & 1 deletion crates/cli/lib/commands/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ fn mount_flags_suffix(options: MountOptions) -> String {
format!(" ({})", flags.join(","))
}

/// Render the deny-list suffix for a bind mount in `msb inspect` output.
///
/// Returns an empty string when there is no deny list so common mounts stay
/// terse; otherwise renders `[deny=a, b]` with the patterns comma-joined.
fn deny_suffix(deny: &[String]) -> String {
if deny.is_empty() {
String::new()
} else {
format!(" [deny={}]", deny.join(", "))
}
}

//--------------------------------------------------------------------------------------------------
// Types
//--------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -256,6 +268,7 @@ pub async fn run(args: InspectArgs) -> anyhow::Result<()> {
host_permissions,
follow_root_symlinks,
quota_mib,
deny,
} => {
let flags = mount_flags_suffix(*options);
let suffix = mount_policy_suffix(
Expand All @@ -266,8 +279,9 @@ pub async fn run(args: InspectArgs) -> anyhow::Result<()> {
let quota = quota_mib
.map(|mib| format!(" [quota={mib}MiB]"))
.unwrap_or_default();
let deny = deny_suffix(deny);
println!(
" {guest:<16}\u{2192} {}{flags}{suffix}{quota}",
" {guest:<16}\u{2192} {}{flags}{suffix}{quota}{deny}",
host.display()
);
}
Expand Down Expand Up @@ -464,4 +478,24 @@ mod tests {

assert!(changes.is_empty());
}

#[test]
fn deny_suffix_empty_list_is_terse() {
assert_eq!(deny_suffix(&[]), "");
}

#[test]
fn deny_suffix_single_pattern() {
assert_eq!(deny_suffix(&[".env".to_string()]), " [deny=.env]");
}

#[test]
fn deny_suffix_multiple_patterns_are_comma_joined() {
let patterns = vec![
".env".to_string(),
"*.log".to_string(),
"sub/secret".to_string(),
];
assert_eq!(deny_suffix(&patterns), " [deny=.env, *.log, sub/secret]");
}
}
1 change: 1 addition & 0 deletions crates/filesystem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ edition.workspace = true
path = "lib/lib.rs"

[dependencies]
ignore.workspace = true
libc.workspace = true
microsandbox-utils.workspace = true
msb_krun.workspace = true
Expand Down
Loading