Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion crates/bashkit/docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ handling, including discarded output. Stdout cached text shares that budget
| jq control normalization amplification (TM-DOS-100) | Literal controls expand sixfold as `\u00XX` | Charge single-pass work and lease live bytes before allocation growth | MITIGATED |
| yq structured-data amplification (TM-DOS-101) | Deep/multi-document YAML or JSON, runaway filters, expanded output | Parser depth and 4096-document caps, aggregate budgets, shared jaq work/deadline/output limits, final render cap | MITIGATED |
| Archive decoder pre-allocation (TM-DOS-102) | Compressed output grows before memory checks | Validate and charge each decoder chunk before reserve/copy | MITIGATED |
| Post-allocation charging (TM-DOS-103) | Archive/compression buffers or tar file-list names grow before their live-byte check | Owning budget-aware string/vector/byte builders charge before reserve, roll back errors, and release on drop; tar file lists also lease raw reads and charge per-line work | MITIGATED |
| Post-allocation charging (TM-DOS-103) | Archive/compression buffers, tar file-list names, or standard-stream operands grow before their live-byte check | Owning budget-aware string/vector/byte builders charge before reserve, roll back errors, and release on drop; tar file lists also lease raw reads and charge per-line work. Standard-stream captures enforce stdout/stderr ceilings before write/append and combined collection, retain byte/text leases through redirects/hooks, and check work/cancellation/deadlines on every write. Wrapper-owned stdin/copy storage is also admitted | MITIGATED |
| Pipeline stderr aggregation (TM-DOS-104) | Many stderr-producing pipeline stages accumulate past the output cap | Stop appending at `max_stderr_bytes` and propagate the truncation flag to the result | MITIGATED |
| Zip creation pre-allocation (TM-DOS-106) | Recursive inputs and encoded output allocate before live-memory checks | Collect paths only, lease file sizes before VFS reads, and encode through a budget-aware byte buffer | MITIGATED |
| Analysis command-name validation (TM-DOS-107) | A huge comment before thousands of commands makes validation rescan the source per command | Index source character positions once, then binary-search each name | MITIGATED |
Expand Down
6 changes: 3 additions & 3 deletions crates/bashkit/src/fs/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ use super::vfs_join;
use fail::fail_point;

/// Bytes returned by one read of `/dev/zero` (TM-DOS-003: never unbounded).
const DEV_ZERO_READ_BYTES: usize = 1 << 20;
pub(crate) const DEV_ZERO_READ_BYTES: usize = 1 << 20;
pub(crate) const DEV_RANDOM_READ_BYTES: usize = 8192;

/// In-memory filesystem implementation.
///
Expand Down Expand Up @@ -534,8 +535,7 @@ impl InMemoryFs {
/// Returns exactly 8192 bytes to prevent unbounded reads while
/// supporting common patterns like `od -N8 -tx1 /dev/urandom`.
fn generate_random_bytes() -> Result<Vec<u8>> {
const SIZE: usize = 8192;
let mut buf = vec![0; SIZE];
let mut buf = vec![0; DEV_RANDOM_READ_BYTES];
// Important decision: use the OS CSPRNG backing getrandom, not
// non-CSPRNG hasher seeds, because scripts commonly use these paths
// for tokens, salts, nonces, and keys.
Expand Down
6 changes: 3 additions & 3 deletions crates/bashkit/src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ pub async fn verify_filesystem_requirements(fs: &dyn FileSystem) -> Result<()> {
Ok(())
}

/// Resolve every symlink in `path` (realpath), with an ELOOP hop cap.
/// Resolve every symlink to a POSIX VFS path on every host, with an ELOOP hop cap.
pub(crate) async fn canonicalize(fs: &dyn FileSystem, path: &Path) -> crate::Result<PathBuf> {
let mut rest: std::collections::VecDeque<String> = path
.components()
Expand All @@ -665,15 +665,15 @@ pub(crate) async fn canonicalize(fs: &dyn FileSystem, path: &Path) -> crate::Res
out.pop();
continue;
}
let candidate = out.join(&comp);
let candidate = vfs_join(&out, &comp);
match fs.lstat(&candidate).await {
Ok(m) if m.file_type.is_symlink() => {
hops += 1;
if hops > MAX_SYMLINK_HOPS {
return Err(std::io::Error::other("Too many levels of symbolic links").into());
}
let target = fs.read_link(&candidate).await?;
if target.is_absolute() {
if target.has_root() {
out = PathBuf::from("/");
}
let mut parts: Vec<String> = target
Expand Down
Loading
Loading