diff --git a/crates/bashkit/src/builtins/curl.rs b/crates/bashkit/src/builtins/curl.rs index c16214c6..49261b41 100644 --- a/crates/bashkit/src/builtins/curl.rs +++ b/crates/bashkit/src/builtins/curl.rs @@ -13,7 +13,6 @@ use async_trait::async_trait; -#[cfg(feature = "http_client")] use super::resolve_path; use super::{Builtin, Context}; use crate::error::Result; @@ -171,6 +170,33 @@ impl Builtin for Curl { i += 1; } + // Resolve -d @- (stdin) and -d @file (VFS file) before sending + if let Some(ref d) = data + && let Some(path) = d.strip_prefix('@') + { + if path == "-" { + // Read from stdin + data = Some(ctx.stdin.unwrap_or("").to_string()); + } else { + // Read from VFS file + let resolved = resolve_path(ctx.cwd, path); + match ctx.fs.read_file(&resolved).await { + Ok(content) => { + data = Some(String::from_utf8_lossy(&content).into_owned()); + } + Err(_) => { + return Ok(ExecResult::err( + format!( + "curl: Failed reading data file {}: No such file or directory\n", + path + ), + 26, + )); + } + } + } + } + // Validate URL let url = match url { Some(u) => u, @@ -1015,7 +1041,7 @@ mod tests { use std::path::PathBuf; use std::sync::Arc; - use crate::fs::InMemoryFs; + use crate::fs::{FileSystem, InMemoryFs}; async fn run_curl(args: &[&str]) -> ExecResult { let fs = Arc::new(InMemoryFs::new()); @@ -1094,6 +1120,90 @@ mod tests { assert!(result.stderr.contains("network access not configured")); } + async fn run_curl_with_stdin_and_fs( + args: &[&str], + stdin: Option<&str>, + files: &[(&str, &[u8])], + ) -> ExecResult { + let fs = Arc::new(InMemoryFs::new()); + for (path, content) in files { + fs.write_file(std::path::Path::new(path), content) + .await + .unwrap(); + } + let mut variables = HashMap::new(); + let env = HashMap::new(); + let mut cwd = PathBuf::from("/"); + + let args: Vec = args.iter().map(|s| s.to_string()).collect(); + let ctx = Context { + args: &args, + env: &env, + variables: &mut variables, + cwd: &mut cwd, + fs, + stdin, + #[cfg(feature = "http_client")] + http_client: None, + #[cfg(feature = "git")] + git_client: None, + shell: None, + }; + + Curl.execute(ctx).await.unwrap() + } + + #[tokio::test] + async fn test_curl_data_at_stdin() { + // -d @- should read from stdin (network not configured, but data resolution + // happens before the network check) + let result = + run_curl_with_stdin_and_fs(&["-d", "@-", "https://example.com"], Some("hello"), &[]) + .await; + // Without network, we get the "network access not configured" error, + // but the important thing is that @- was resolved (not sent literally) + assert!(result.stderr.contains("network access not configured")); + } + + #[tokio::test] + async fn test_curl_data_at_file() { + let result = run_curl_with_stdin_and_fs( + &["-d", "@/data.json", "https://example.com"], + None, + &[("/data.json", b"{\"key\":\"value\"}")], + ) + .await; + assert!(result.stderr.contains("network access not configured")); + } + + #[tokio::test] + async fn test_curl_data_at_file_not_found() { + let result = + run_curl_with_stdin_and_fs(&["-d", "@/missing.json", "https://example.com"], None, &[]) + .await; + assert_ne!(result.exit_code, 0); + assert_eq!(result.exit_code, 26); + assert!(result.stderr.contains("Failed reading data file")); + } + + #[tokio::test] + async fn test_curl_data_at_stdin_none() { + // -d @- with no stdin should resolve to empty string + let result = + run_curl_with_stdin_and_fs(&["-d", "@-", "https://example.com"], None, &[]).await; + // Should proceed past data resolution (get network error, not a data error) + assert!(result.stderr.contains("network access not configured")); + } + + #[tokio::test] + async fn test_curl_data_literal_no_at() { + // Regular -d without @ prefix should pass through unchanged + let result = + run_curl_with_stdin_and_fs(&["-d", "plain-data", "https://example.com"], None, &[]) + .await; + assert!(result.stderr.contains("network access not configured")); + } + #[cfg(feature = "http_client")] mod network_tests { use super::*; diff --git a/specs/005-builtins.md b/specs/005-builtins.md index 8f5241d5..0c096461 100644 --- a/specs/005-builtins.md +++ b/specs/005-builtins.md @@ -161,7 +161,7 @@ persist in shell variables as usual. #### Network - `curl` - HTTP client (requires http_client feature + allowlist) - - Options: `-s/--silent`, `-o FILE`, `-X METHOD`, `-d DATA`, `-H HEADER`, `-I/--head`, `-f/--fail`, `-L/--location`, `-w FORMAT`, `--compressed`, `-u/--user`, `-A/--user-agent`, `-e/--referer`, `-v/--verbose`, `-m/--max-time`, `--connect-timeout` + - Options: `-s/--silent`, `-o FILE`, `-X METHOD`, `-d DATA` (supports `@-` for stdin, `@file` for VFS file), `-H HEADER`, `-I/--head`, `-f/--fail`, `-L/--location`, `-w FORMAT`, `--compressed`, `-u/--user`, `-A/--user-agent`, `-e/--referer`, `-v/--verbose`, `-m/--max-time`, `--connect-timeout` - Security: URL allowlist enforced, 10MB response limit, timeouts clamped to [1s, 10min], zip bomb protection via size-limited decompression - `wget` - Download files (requires http_client feature + allowlist) - Options: `-q/--quiet`, `-O FILE`, `--spider`, `--header`, `-U/--user-agent`, `--post-data`, `-t/--tries`, `-T/--timeout`, `--connect-timeout` diff --git a/supply-chain/config.toml b/supply-chain/config.toml index e5704575..d80a162e 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -430,10 +430,6 @@ criteria = "safe-to-deploy" version = "0.3.32" criteria = "safe-to-deploy" -[[exemptions.generic-array]] -version = "0.14.7" -criteria = "safe-to-deploy" - [[exemptions.get-size-derive2]] version = "0.7.4" criteria = "safe-to-deploy" @@ -531,31 +527,31 @@ version = "0.1.2" criteria = "safe-to-deploy" [[exemptions.icu_collections]] -version = "2.1.1" +version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.icu_locale_core]] -version = "2.1.1" +version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.icu_normalizer]] -version = "2.1.1" +version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.icu_normalizer_data]] -version = "2.1.1" +version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.icu_properties]] -version = "2.1.2" +version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.icu_properties_data]] -version = "2.1.2" +version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.icu_provider]] -version = "2.1.1" +version = "2.2.0" criteria = "safe-to-deploy" [[exemptions.id-arena]] @@ -590,10 +586,6 @@ criteria = "safe-to-deploy" version = "2.12.0" criteria = "safe-to-deploy" -[[exemptions.iri-string]] -version = "0.7.11" -criteria = "safe-to-deploy" - [[exemptions.is-macro]] version = "0.3.7" criteria = "safe-to-deploy" @@ -663,7 +655,7 @@ version = "0.1.34" criteria = "safe-to-deploy" [[exemptions.js-sys]] -version = "0.3.93" +version = "0.3.94" criteria = "safe-to-deploy" [[exemptions.leb128fmt]] @@ -671,7 +663,7 @@ version = "0.1.0" criteria = "safe-to-deploy" [[exemptions.libc]] -version = "0.2.183" +version = "0.2.184" criteria = "safe-to-deploy" [[exemptions.libloading]] @@ -687,7 +679,7 @@ version = "0.12.1" criteria = "safe-to-run" [[exemptions.litemap]] -version = "0.8.1" +version = "0.8.2" criteria = "safe-to-deploy" [[exemptions.lock_api]] @@ -842,10 +834,6 @@ criteria = "safe-to-deploy" version = "0.2.17" criteria = "safe-to-deploy" -[[exemptions.pin-utils]] -version = "0.1.0" -criteria = "safe-to-deploy" - [[exemptions.plotters]] version = "0.3.7" criteria = "safe-to-run" @@ -871,7 +859,7 @@ version = "1.1.3" criteria = "safe-to-deploy" [[exemptions.potential_utf]] -version = "0.1.4" +version = "0.1.5" criteria = "safe-to-deploy" [[exemptions.ppv-lite86]] @@ -1038,10 +1026,6 @@ criteria = "safe-to-deploy" version = "0.1.6" criteria = "safe-to-deploy" -[[exemptions.regex-lite]] -version = "0.1.9" -criteria = "safe-to-deploy" - [[exemptions.regex-syntax]] version = "0.8.10" criteria = "safe-to-deploy" @@ -1054,10 +1038,6 @@ criteria = "safe-to-deploy" version = "0.17.14" criteria = "safe-to-deploy" -[[exemptions.rustc-hash]] -version = "2.1.1" -criteria = "safe-to-deploy" - [[exemptions.rustc-hash]] version = "2.1.2" criteria = "safe-to-deploy" @@ -1307,7 +1287,7 @@ version = "2.0.18" criteria = "safe-to-deploy" [[exemptions.tinystr]] -version = "0.8.2" +version = "0.8.3" criteria = "safe-to-deploy" [[exemptions.tinytemplate]] @@ -1467,23 +1447,23 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" criteria = "safe-to-run" [[exemptions.wasm-bindgen]] -version = "0.2.116" +version = "0.2.117" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-futures]] -version = "0.4.66" +version = "0.4.67" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-macro]] -version = "0.2.116" +version = "0.2.117" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-macro-support]] -version = "0.2.116" +version = "0.2.117" criteria = "safe-to-deploy" [[exemptions.wasm-bindgen-shared]] -version = "0.2.116" +version = "0.2.117" criteria = "safe-to-deploy" [[exemptions.wasm-encoder]] @@ -1503,7 +1483,7 @@ version = "0.244.0" criteria = "safe-to-deploy" [[exemptions.web-sys]] -version = "0.3.93" +version = "0.3.94" criteria = "safe-to-deploy" [[exemptions.web-time]] @@ -1711,11 +1691,11 @@ version = "1.0.1" criteria = "safe-to-run" [[exemptions.yoke]] -version = "0.8.1" +version = "0.8.2" criteria = "safe-to-deploy" [[exemptions.yoke-derive]] -version = "0.8.1" +version = "0.8.2" criteria = "safe-to-deploy" [[exemptions.zerocopy]] @@ -1727,11 +1707,11 @@ version = "0.8.48" criteria = "safe-to-deploy" [[exemptions.zerofrom]] -version = "0.1.6" +version = "0.1.7" criteria = "safe-to-deploy" [[exemptions.zerofrom-derive]] -version = "0.1.6" +version = "0.1.7" criteria = "safe-to-deploy" [[exemptions.zeroize]] @@ -1739,15 +1719,15 @@ version = "1.8.2" criteria = "safe-to-deploy" [[exemptions.zerotrie]] -version = "0.2.3" +version = "0.2.4" criteria = "safe-to-deploy" [[exemptions.zerovec]] -version = "0.11.5" +version = "0.11.6" criteria = "safe-to-deploy" [[exemptions.zerovec-derive]] -version = "0.11.2" +version = "0.11.3" criteria = "safe-to-deploy" [[exemptions.zmij]]