feat(minimald): support sftp to session filesystem - #267
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThis PR implements SFTP subsystem support in minimald by adding session name uniqueness enforcement, a CreateSession RPC, complete SFTP file operations with workspace-scoped path safety, and SSH subsystem integration across session storage, RPC/manager layers, SFTP handler, tests, and dependency updates. ChangesSFTP Subsystem with Session Management
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
crates/minimald/src/test_harness.rs (1)
151-151: ⚡ Quick winUse the SFTP subsystem name constant instead of a hardcoded string.
The subsystem name is hardcoded as
"sftp"here, but should usecrate::sftp::SUBSYSTEM_NAMEfor consistency with the subsystem dispatch logic inconnection.rs(line 370). This ensures the test and production code stay in sync if the subsystem name ever changes.♻️ Suggested fix
- channel.request_subsystem(true, "sftp").await.unwrap(); + channel.request_subsystem(true, crate::sftp::SUBSYSTEM_NAME).await.unwrap();🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/minimald/src/test_harness.rs` at line 151, The test uses a hardcoded subsystem string in channel.request_subsystem(true, "sftp").await.unwrap(); — replace the literal "sftp" with the subsystem name constant crate::sftp::SUBSYSTEM_NAME to keep tests consistent with the production dispatch logic; update the call to use crate::sftp::SUBSYSTEM_NAME wherever channel.request_subsystem is invoked in this test (referencing the channel.request_subsystem function and the crate::sftp::SUBSYSTEM_NAME constant).crates/minimald/src/sftp.rs (1)
278-312: 🏗️ Heavy lift
READDIR_BATCHdoesn't actually cap memory here.
opendirreads the entire directory intoVec<File>before the firstreaddirreply, so a large directory can still block the request and consume unbounded memory despite the batching constant. Keeping atokio::fs::ReadDirinOpenHandleand pulling one batch perreaddirwould make the limit real.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/minimald/src/sftp.rs` around lines 278 - 312, opendir currently eagerly reads the whole directory into a Vec<File>, bypassing READDIR_BATCH; change OpenHandle::Dir to store a tokio::fs::ReadDir (and any queued leading entries like "." and optional "..") instead of Vec<File>, have opendir create and mint an OpenHandle::Dir containing the ReadDir and the precomputed "."/".." entries, and update readdir to consume up to READDIR_BATCH entries by repeatedly calling ReadDir::next_entry().await to produce File values on demand, draining queued leading entries first and returning SftpError::Eof only when both the queue and the ReadDir are exhausted; update mint_handle/dir_mut usage to match the new OpenHandle::Dir shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/minimald/src/sftp.rs`:
- Around line 120-136: The resolve method currently uses candidate.absolutize()
which only performs lexical normalization and thus allows escaping via in-tree
symlinks; replace that with a real filesystem canonicalization
(std::fs::canonicalize) so symlinks are resolved and the comparison is performed
against the canonicalized workspace path (which you should canonicalize once or
ensure canonicalized before comparing). Update resolve to canonicalize the
candidate path and the workspace (or use a pre-canonicalized self.workspace),
map canonicalize errors to SftpError::Io (same as current mapping), and then
check starts_with against the canonical workspace, returning
SftpError::PathTraversal if it escapes.
- Around line 247-262: The server is allocating buf of size len directly from
the client in async fn read which lets a malicious client request enormous
allocations; introduce a sane maximum (e.g. const MAX_READ_BYTES) and clamp the
requested length before allocation (let cap = std::cmp::min(len as usize,
MAX_READ_BYTES)), allocate buf with cap, perform the read into that buffer,
truncate to the number of bytes read, and return Data { id, data: buf };
preserve the existing EOF handling (return Err(SftpError::Eof) when n == 0).
In `@crates/sessions/src/lib.rs`:
- Around line 22-23: The Record struct currently uses #[serde(default =
"Uuid::nil")] pub id: Uuid which causes missing legacy ids to become Uuid::nil()
and break ID-based lookup; change the deserialization strategy to treat the id
as optional (e.g., #[serde(default)] pub id: Option<Uuid>) and, in the
load/migration path that reads persisted records (the code that
constructs/returns Record instances used by List and GetRecord), detect missing
id and backfill it with a real id (Uuid::new_v4())—store or persist the updated
Record if appropriate so subsequent lookups work; update call sites that read
record.id to handle Option (or normalize to non-Option after backfill) to
restore correct ID-based lookup behavior.
In `@crates/sessions/src/store.rs`:
- Around line 110-114: DiskLoader::new currently assumes the on-disk index
deserializes to the new Index struct (with short_to_uuid and name_to_uuid) which
breaks upgrades from the old shape (a bare BTreeMap<String,Uuid>); change
DiskLoader::new to first attempt deserializing the file as Index, and if that
fails, attempt deserializing the old BTreeMap<String,Uuid> shape, then build a
new Index by setting short_to_uuid from that map and reconstructing name_to_uuid
by scanning each session's record.json (using each session's stored name) in the
sessions directory (or vice versa), inserting entries into name_to_uuid while
handling duplicates consistently with current rules; after reconstruction,
overwrite the on-disk index with the new Index format so future loads succeed
and proceed normally.
---
Nitpick comments:
In `@crates/minimald/src/sftp.rs`:
- Around line 278-312: opendir currently eagerly reads the whole directory into
a Vec<File>, bypassing READDIR_BATCH; change OpenHandle::Dir to store a
tokio::fs::ReadDir (and any queued leading entries like "." and optional "..")
instead of Vec<File>, have opendir create and mint an OpenHandle::Dir containing
the ReadDir and the precomputed "."/".." entries, and update readdir to consume
up to READDIR_BATCH entries by repeatedly calling ReadDir::next_entry().await to
produce File values on demand, draining queued leading entries first and
returning SftpError::Eof only when both the queue and the ReadDir are exhausted;
update mint_handle/dir_mut usage to match the new OpenHandle::Dir shape.
In `@crates/minimald/src/test_harness.rs`:
- Line 151: The test uses a hardcoded subsystem string in
channel.request_subsystem(true, "sftp").await.unwrap(); — replace the literal
"sftp" with the subsystem name constant crate::sftp::SUBSYSTEM_NAME to keep
tests consistent with the production dispatch logic; update the call to use
crate::sftp::SUBSYSTEM_NAME wherever channel.request_subsystem is invoked in
this test (referencing the channel.request_subsystem function and the
crate::sftp::SUBSYSTEM_NAME constant).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7a739d92-3c7d-4a58-a6be-822b6bd84dec
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
Cargo.tomlcrates/minimald/Cargo.tomlcrates/minimald/src/connection.rscrates/minimald/src/lib.rscrates/minimald/src/rpc.rscrates/minimald/src/sessions.rscrates/minimald/src/sftp.rscrates/minimald/src/test_harness.rscrates/sessions/src/lib.rscrates/sessions/src/store.rs
Relies on earlier PR, review that first to get a better diffbase: #265
The
sftphandler implements enough that you canscpin/out of the session:Mounting via sshfs and stuff should also work, just havent tested.
Summary by CodeRabbit
New Features
Bug Fixes / Improvements