Skip to content

Fix mount write permission denial surfacing#92

Open
josh-richardson wants to merge 6 commits into
masterfrom
agent/mount-write-permission-denied-surfacing
Open

Fix mount write permission denial surfacing#92
josh-richardson wants to merge 6 commits into
masterfrom
agent/mount-write-permission-denied-surfacing

Conversation

@josh-richardson

@josh-richardson josh-richardson commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Move mounted approval gating to NFS OPEN(WRITE) so shell redirects fail synchronously with permission denied before payload bytes are accepted.
  • Pin bloom-mount to bloom-directory/embednfs rev 6cf74877518ea0b5e7f9d3c1fa5ee0b461cd3eb3, which adds the fork-side OpenSupport hook.
  • Add Handler::prepare_write_open and route it through the VFS so handlers can stage approval artifacts before denying a write-open.
  • Gate wallet outbox confirm at open-time and add confirm.override as a path-encoded override sink, since open-time decisions cannot depend on write payload bytes.
  • Gate paid request confirms at open-time as well.
  • Strengthen the real mounted NFS regression so it verifies the full FS-only flow: root listing, shell redirect fails, and cat approval_challenge.json succeeds through the mounted filesystem.

Root Cause

NFS WRITE, COMMIT, and CLOSE are not reliable user-visible error channels for shell redirects. The client can cache writes, and shells do not consistently surface close-time errors for printf ... > file. Returning the approval failure from WRITE or CLOSE can still leave the process with exit 0. The only operation in the redirect path that is synchronous and universally checked is open(2), so approval-gated write sinks need to fail from NFS OPEN.

Fork Dependency

This PR uses our fork rather than vendoring NFS code into Bloom.

Behavior

For a mounted approval-gated sink with no active grant:

printf "y\n" > /bloom/.../confirm
# shell: Permission denied
# exit: non-zero

cat /bloom/.../approval_challenge.json
# readable through the mounted filesystem

For soft policy warning bypasses, mounted intent is now encoded in the path:

printf "y\n" > /bloom/wallets/<wallet>/chains/<chain>/outbox/pending/<id>/confirm.override

Validation

  • cargo test -p embednfs in /tmp/embednfs-open-gate
  • cargo check -p bloom-mount --features mount
  • BLOOM_MOUNT_TEST_REQUIRE_REAL=1 cargo test -p bloom-mount --features mount --test write_permission_denied -- --ignored --nocapture
  • cargo test -p bloom-mount --features mount --lib --config patch... before the fork rev was pinned
  • cargo test -p bloom-vfs hyperliquid --lib
  • cargo test -p bloom-vfs --lib
  • cargo test -p bloom-hyperliquid --lib
  • cargo test -p bloom-tx --lib
  • cargo check --workspace --exclude bloom-mount

Fixes #77.

Checklist

  • Tests added or updated for behavior changes
  • Agent documentation updated for confirm.override and open-time permission denial
  • Sealed Approval invariants respected: no signing outside grants; open-time denial only stages/reuses challenge artifacts
  • Fork dependency used instead of vendoring embednfs into Bloom

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Comment thread crates/bloom-mount/Cargo.toml Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0867449c8e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/bloom-mount/src/lib.rs Outdated
#[cfg(target_os = "linux")]
fn build_mount_opts(port: u16) -> String {
format!("actimeo=0,vers=4.1,proto=tcp,port={port},rsize=65536,wsize=65536,timeo=10")
format!("actimeo=0,vers=4.1,proto=tcp,sync,port={port},rsize=65536,wsize=65536,timeo=10")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid forcing sync NFS writes

Adding sync here makes ordinary mounted writes use the adapter's sync-stable WRITE path; Linux nfs(5) documents that sync causes data to be flushed to the server before the write syscall returns. The adapter only supports sync writes when the current buffer is contiguous and then rejects any later sync chunk as FsError::Unsupported (adapter.rs:1064-1079), so a write split by wsize (for example anything over 64 KiB with these options, or lower negotiated sizes) flushes the first chunk as a whole file and fails the second offset chunk. This regresses normal large writes through the mount; either keep writes unstable and rely on CLOSE/COMMIT, or make multi-RPC sync writes coalesce correctly before forcing sync.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e61fe824ae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

BloomHandle::Root => return Err(FsError::IsDirectory),
BloomHandle::Path { path, .. } => path.clone(),
};
self.flush_path(&path).await

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid flushing path buffers from unrelated closes

CLOSE is per open state, but this flushes the per-path buffer without knowing whether the closing state performed the writes. When the same file is open twice, a read-only or other fd can close after another writer has sent an offset-0 UNSTABLE prefix but before later chunks; flush_path removes and persists that prefix, so subsequent offset chunks land in a fresh non-contiguous buffer and are never committed as the intended whole file. Since write buffers are keyed only by VfsPath, close flushing needs to be tied to the write-owning/open state or left to COMMIT to avoid corrupting concurrent/chunked writes.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 67aa7a29c2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


let unsigned = self.build_unsigned_evm_tx(&staged, chain)?;
let signing_hash = Self::unsigned_signing_hash(&unsigned);
self.ensure_action_authorized(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve session-authorized confirms on mounted open

When a pending tx is covered by an active bounded policy session, the real confirm() path first calls session_store.authorize_and_debit and only falls back to sealed approval when no session covers it. This new open-time preflight goes straight to ensure_action_authorized, so on an NFS-mounted confirm path the OPEN can return permission denied before the WRITE reaches confirm() and uses the session. That breaks mounted autonomous confirms for transactions that should be authorized by an existing policy session.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[vfs]: Return correct error code on write permission denied to the NFS layer

2 participants