feat: spill long bash output to worker temp storage

This commit is contained in:
2026-08-31 16:52:27 +09:00
parent 10264b4019
commit 62eaefb1fa
22 changed files with 664 additions and 78 deletions
+2
View File
@@ -177,6 +177,8 @@ mod tests {
fn logical_paths_reject_absolute_parent_and_backslash_forms() {
assert!(FsPath::new("src/lib.rs").is_ok());
assert!(FsPath::new("/tmp/file").is_err());
assert!(FsPath::new_scoped("/tmp/file").is_ok());
assert!(FsPath::new_scoped("/tmp/../secret").is_err());
assert!(FsPath::new("../file").is_err());
assert!(FsPath::new("src\\lib.rs").is_err());
}
+22 -2
View File
@@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize};
use crate::FsError;
/// Logical path relative to the bound Workdir root.
/// Scope-checked filesystem path. Relative paths resolve below the bound
/// Workdir root; absolute paths require an explicit matching scope rule.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(transparent)]
pub struct FsPath(String);
@@ -16,11 +17,30 @@ impl<'de> Deserialize<'de> for FsPath {
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(&value).map_err(serde::de::Error::custom)
Self::new_scoped(&value).map_err(serde::de::Error::custom)
}
}
impl FsPath {
/// Construct a path for a scope-checked operation that may target an
/// explicitly granted absolute path outside the provider root.
pub fn new_scoped(value: impl Into<String>) -> Result<Self, FsError> {
let value = value.into();
if !Path::new(&value).is_absolute() {
return Self::new(value);
}
if value.contains('\\') {
return Err(FsError::InvalidPath(value));
}
if Path::new(&value)
.components()
.any(|component| component == Component::ParentDir)
{
return Err(FsError::InvalidPath(value));
}
Ok(Self(value))
}
pub fn root() -> Self {
Self(String::new())
}