upd-cli 0.1.4__tar.gz → 0.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {upd_cli-0.1.4 → upd_cli-0.1.6}/CHANGELOG.md +28 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/Cargo.lock +1 -1
- {upd_cli-0.1.4 → upd_cli-0.1.6}/Cargo.toml +1 -1
- {upd_cli-0.1.4 → upd_cli-0.1.6}/PKG-INFO +1 -1
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/audit/mod.rs +221 -11
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/cli.rs +31 -0
- upd_cli-0.1.6/src/http.rs +516 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/lib.rs +1 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/main.rs +39 -6
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/crates_io.rs +17 -15
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/github_releases.rs +9 -7
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/go_proxy.rs +12 -10
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/mod.rs +2 -2
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/npm.rs +12 -9
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/nuget.rs +9 -7
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/pypi.rs +13 -11
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/rubygems.rs +9 -7
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/terraform.rs +18 -14
- {upd_cli-0.1.4 → upd_cli-0.1.6}/.mise.toml +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/.pre-commit-config.yaml +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/.pre-commit-hooks.yaml +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/.rumdl.toml +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/LICENSE +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/Makefile +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/README.md +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/assets/logo-wide.svg +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/assets/logo.svg +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/pyproject.toml +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/python/upd_cli/__init__.py +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/python/upd_cli/__main__.py +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/python/upd_cli/py.typed +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/rust-toolchain.toml +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/align.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/audit/cache.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/audit/cvss.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/cache.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/config.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/cooldown.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/interactive.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/lockfile.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/output.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/mock.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/registry/utils.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/cargo_toml.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/csproj.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/gemfile.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/github_actions.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/go_mod.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/mise.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/mod.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/npm_range.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/package_json.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/pre_commit.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/pyproject.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/requirements.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/updater/terraform.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/version/compare.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/version/mod.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/version/pep440.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/version/semver_util.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/src/version/tag.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/audit_offline.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/audit_sarif.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/audit_severity.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/bump_filter.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/cooldown_e2e.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/discovery_no_ignore.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/exit_codes.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/fix_audit.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/format_json.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/help_text.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/interactive_tty.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/invalid_positional.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/no_args_scope.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/output_streams.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/tests/package_filter.rs +0 -0
- {upd_cli-0.1.4 → upd_cli-0.1.6}/vership.toml +0 -0
|
@@ -14,6 +14,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## [0.1.6](https://github.com/rvben/upd/compare/v0.1.5...v0.1.6) - 2026-04-29
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **http**: wire TLS init into networked subcommand entry points ([04f7b0d](https://github.com/rvben/upd/commit/04f7b0d6362f32ff2a6212386f310b481bfd7d56))
|
|
24
|
+
- **cli**: add --insecure global flag ([df1943f](https://github.com/rvben/upd/commit/df1943ff53369b0cc0b32401cbbedb1095f07230))
|
|
25
|
+
- **http**: attach TLS hint to send errors via wrap_send_err ([9b1fd2c](https://github.com/rvben/upd/commit/9b1fd2cef7e307a6d3328074a82353a772a5b44c))
|
|
26
|
+
- **http**: apply TLS options to ClientBuilder ([7d8f616](https://github.com/rvben/upd/commit/7d8f6161f5f7be31e829f89ba9aaeb332532229f))
|
|
27
|
+
- **http**: implement init() over pure helpers ([79556c6](https://github.com/rvben/upd/commit/79556c6d5a34cf5eda97e645b4d62c62ad706809))
|
|
28
|
+
- **http**: detect TLS trust failures in error chains ([e63be5d](https://github.com/rvben/upd/commit/e63be5d62cfa82f2a73f3e78e294c65868c53ba7))
|
|
29
|
+
- **http**: parse PEM CA bundle with multi-cert support ([07e3983](https://github.com/rvben/upd/commit/07e3983a9477dad042ea0f5b10ee8efa9382b384))
|
|
30
|
+
- **http**: resolve CA bundle path from env vars ([2bd42ec](https://github.com/rvben/upd/commit/2bd42ec7018094e1daf399af8fb2e7e288e9e206))
|
|
31
|
+
- **http**: scaffold TLS options module ([3f797cc](https://github.com/rvben/upd/commit/3f797cc658298b9b952e566b49d4998a4decfc64))
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **http**: propagate --insecure global flag to self-update ([88b397e](https://github.com/rvben/upd/commit/88b397ee4a169eab94b56247050e8b1814c8ccf3))
|
|
36
|
+
- **http**: defer TLS init past offline and no-op early returns ([41cfb40](https://github.com/rvben/upd/commit/41cfb40f2f72e6090fc0f083c13371c3a4269347))
|
|
37
|
+
- **http**: skip CA bundle resolution when --insecure is set ([8d73e27](https://github.com/rvben/upd/commit/8d73e27aadaa5ac488bbbf7f556476cf9bfaa0e6))
|
|
38
|
+
|
|
39
|
+
## [0.1.5](https://github.com/rvben/upd/compare/v0.1.4...v0.1.5) - 2026-04-28
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **audit**: include package names and HTTP body in OSV error diagnostics ([87b9aa8](https://github.com/rvben/upd/commit/87b9aa8d27545abf449461c3c3d096a661fa377f))
|
|
44
|
+
|
|
17
45
|
## [0.1.4](https://github.com/rvben/upd/compare/v0.1.3...v0.1.4) - 2026-04-28
|
|
18
46
|
|
|
19
47
|
### Added
|
|
@@ -20,6 +20,35 @@ const BATCH_SIZE: usize = 1000;
|
|
|
20
20
|
/// Maximum concurrent requests for fetching vulnerability details
|
|
21
21
|
const MAX_CONCURRENT_REQUESTS: usize = 20;
|
|
22
22
|
|
|
23
|
+
/// Cap for how many bytes of a non-success OSV response body we surface in
|
|
24
|
+
/// an error message. A proxy or CDN can return arbitrarily large HTML error
|
|
25
|
+
/// pages on the failure path, so we bound the read at the network layer
|
|
26
|
+
/// (via `Response::chunk`) rather than buffering the whole body and then
|
|
27
|
+
/// truncating. 1 KiB comfortably fits typical OSV/proxy diagnostics.
|
|
28
|
+
const ERROR_BODY_LIMIT: usize = 1024;
|
|
29
|
+
|
|
30
|
+
/// Read up to `max` bytes from `response`'s body and return them as a
|
|
31
|
+
/// lossy UTF-8 string. Stops early at the first chunk that fills the cap
|
|
32
|
+
/// so an oversized or slow body cannot stall the call up to the client
|
|
33
|
+
/// timeout or balloon memory beyond `max` bytes.
|
|
34
|
+
async fn read_bounded_body(response: &mut reqwest::Response, max: usize) -> String {
|
|
35
|
+
let mut buf: Vec<u8> = Vec::with_capacity(max);
|
|
36
|
+
while buf.len() < max {
|
|
37
|
+
match response.chunk().await {
|
|
38
|
+
Ok(Some(chunk)) => {
|
|
39
|
+
let remaining = max - buf.len();
|
|
40
|
+
let take = chunk.len().min(remaining);
|
|
41
|
+
buf.extend_from_slice(&chunk[..take]);
|
|
42
|
+
if take < chunk.len() {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
Ok(None) | Err(_) => break,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
String::from_utf8_lossy(&buf).into_owned()
|
|
50
|
+
}
|
|
51
|
+
|
|
23
52
|
/// Ecosystem names for OSV API
|
|
24
53
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
25
54
|
pub enum Ecosystem {
|
|
@@ -174,10 +203,11 @@ impl OsvClient {
|
|
|
174
203
|
/// Create a client pointing at a custom base URL (used by tests).
|
|
175
204
|
pub fn with_base_url(base_url: String) -> Self {
|
|
176
205
|
Self {
|
|
177
|
-
client:
|
|
178
|
-
.timeout(Duration::from_secs(30))
|
|
179
|
-
|
|
180
|
-
|
|
206
|
+
client: crate::http::apply(
|
|
207
|
+
Client::builder().timeout(Duration::from_secs(30))
|
|
208
|
+
)
|
|
209
|
+
.build()
|
|
210
|
+
.expect("Failed to create HTTP client. This usually indicates a TLS/SSL configuration issue on your system."),
|
|
181
211
|
base_url,
|
|
182
212
|
}
|
|
183
213
|
}
|
|
@@ -273,7 +303,12 @@ impl OsvClient {
|
|
|
273
303
|
}
|
|
274
304
|
}
|
|
275
305
|
Err(e) => {
|
|
276
|
-
|
|
306
|
+
let pkg_names: Vec<&str> = chunk.iter().map(|p| p.name.as_str()).collect();
|
|
307
|
+
result.errors.push(format!(
|
|
308
|
+
"Batch query failed for [{}]: {}",
|
|
309
|
+
pkg_names.join(", "),
|
|
310
|
+
e
|
|
311
|
+
));
|
|
277
312
|
}
|
|
278
313
|
}
|
|
279
314
|
}
|
|
@@ -299,15 +334,23 @@ impl OsvClient {
|
|
|
299
334
|
|
|
300
335
|
let request = OsvBatchRequest { queries };
|
|
301
336
|
|
|
302
|
-
let
|
|
337
|
+
let url = format!("{}/querybatch", self.base_url);
|
|
338
|
+
let mut response = self
|
|
303
339
|
.client
|
|
304
|
-
.post(
|
|
340
|
+
.post(&url)
|
|
305
341
|
.json(&request)
|
|
306
342
|
.send()
|
|
307
|
-
.await
|
|
343
|
+
.await
|
|
344
|
+
.map_err(|e| crate::http::wrap_send_err(e, &url))?;
|
|
308
345
|
|
|
309
346
|
if !response.status().is_success() {
|
|
310
|
-
|
|
347
|
+
let status = response.status();
|
|
348
|
+
let snippet = read_bounded_body(&mut response, ERROR_BODY_LIMIT).await;
|
|
349
|
+
if snippet.is_empty() {
|
|
350
|
+
anyhow::bail!("OSV API error: HTTP {}", status);
|
|
351
|
+
} else {
|
|
352
|
+
anyhow::bail!("OSV API error: HTTP {}: {}", status, snippet);
|
|
353
|
+
}
|
|
311
354
|
}
|
|
312
355
|
|
|
313
356
|
let batch_response: OsvBatchResponse = response.json().await?;
|
|
@@ -357,11 +400,13 @@ impl OsvClient {
|
|
|
357
400
|
|
|
358
401
|
/// Fetch vulnerability details by ID
|
|
359
402
|
async fn fetch_vuln_by_id(&self, id: &str) -> Result<Vulnerability> {
|
|
403
|
+
let url = format!("{}/vulns/{}", self.base_url, id);
|
|
360
404
|
let response = self
|
|
361
405
|
.client
|
|
362
|
-
.get(
|
|
406
|
+
.get(&url)
|
|
363
407
|
.send()
|
|
364
|
-
.await
|
|
408
|
+
.await
|
|
409
|
+
.map_err(|e| crate::http::wrap_send_err(e, &url))?;
|
|
365
410
|
|
|
366
411
|
if !response.status().is_success() {
|
|
367
412
|
anyhow::bail!(
|
|
@@ -1003,4 +1048,169 @@ mod tests {
|
|
|
1003
1048
|
assert_eq!(result.safe_count, 1);
|
|
1004
1049
|
// wiremock will assert that no requests were received when the server drops.
|
|
1005
1050
|
}
|
|
1051
|
+
|
|
1052
|
+
// ─── batch failure error message ─────────────────────────────────────────
|
|
1053
|
+
|
|
1054
|
+
#[tokio::test]
|
|
1055
|
+
async fn batch_failure_error_includes_package_names_status_and_body() {
|
|
1056
|
+
use wiremock::matchers::{method, path};
|
|
1057
|
+
use wiremock::{Mock, MockServer, ResponseTemplate};
|
|
1058
|
+
|
|
1059
|
+
let server = MockServer::start().await;
|
|
1060
|
+
|
|
1061
|
+
// OSV returns a non-success response with a body. Both should surface
|
|
1062
|
+
// in the AuditResult error so operators can diagnose the failure.
|
|
1063
|
+
Mock::given(method("POST"))
|
|
1064
|
+
.and(path("/querybatch"))
|
|
1065
|
+
.respond_with(
|
|
1066
|
+
ResponseTemplate::new(503).set_body_string("upstream temporarily unavailable"),
|
|
1067
|
+
)
|
|
1068
|
+
.mount(&server)
|
|
1069
|
+
.await;
|
|
1070
|
+
|
|
1071
|
+
let client = OsvClient::with_base_url(server.uri());
|
|
1072
|
+
let pkgs = vec![
|
|
1073
|
+
Package {
|
|
1074
|
+
name: "requests".into(),
|
|
1075
|
+
version: "2.0.0".into(),
|
|
1076
|
+
ecosystem: Ecosystem::PyPI,
|
|
1077
|
+
},
|
|
1078
|
+
Package {
|
|
1079
|
+
name: "boto3".into(),
|
|
1080
|
+
version: "1.0.0".into(),
|
|
1081
|
+
ecosystem: Ecosystem::PyPI,
|
|
1082
|
+
},
|
|
1083
|
+
];
|
|
1084
|
+
|
|
1085
|
+
let result = client.check_packages(&pkgs).await.unwrap();
|
|
1086
|
+
|
|
1087
|
+
assert!(result.vulnerable.is_empty());
|
|
1088
|
+
assert_eq!(
|
|
1089
|
+
result.errors.len(),
|
|
1090
|
+
1,
|
|
1091
|
+
"one error for the failing batch, got {:?}",
|
|
1092
|
+
result.errors
|
|
1093
|
+
);
|
|
1094
|
+
let err = &result.errors[0];
|
|
1095
|
+
assert!(
|
|
1096
|
+
err.contains("requests") && err.contains("boto3"),
|
|
1097
|
+
"error should list the failing batch's package names: {err}"
|
|
1098
|
+
);
|
|
1099
|
+
assert!(
|
|
1100
|
+
err.contains("503"),
|
|
1101
|
+
"error should include the HTTP status: {err}"
|
|
1102
|
+
);
|
|
1103
|
+
assert!(
|
|
1104
|
+
err.contains("upstream temporarily unavailable"),
|
|
1105
|
+
"error should include the response body: {err}"
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
#[tokio::test]
|
|
1110
|
+
async fn batch_failure_stops_reading_at_cap_when_body_stalls() {
|
|
1111
|
+
// Stand up a TCP server that lies about Content-Length: it claims
|
|
1112
|
+
// 1 MB but only delivers 2 KiB, then stalls forever. The bounded
|
|
1113
|
+
// read in query_batch must fill its 1 KiB cap from the head of
|
|
1114
|
+
// the body and return immediately. An unbounded read (e.g.
|
|
1115
|
+
// `response.text().await`) would block until either the rest of
|
|
1116
|
+
// the body arrives or the 30 s client timeout fires.
|
|
1117
|
+
use std::time::{Duration, Instant};
|
|
1118
|
+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
1119
|
+
use tokio::net::TcpListener;
|
|
1120
|
+
|
|
1121
|
+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
1122
|
+
let addr = listener.local_addr().unwrap();
|
|
1123
|
+
|
|
1124
|
+
let server = tokio::spawn(async move {
|
|
1125
|
+
let (mut stream, _) = listener.accept().await.unwrap();
|
|
1126
|
+
|
|
1127
|
+
// Drain the request until we see the empty header line.
|
|
1128
|
+
let mut buf = [0u8; 4096];
|
|
1129
|
+
loop {
|
|
1130
|
+
let n = match stream.read(&mut buf).await {
|
|
1131
|
+
Ok(n) if n > 0 => n,
|
|
1132
|
+
_ => return,
|
|
1133
|
+
};
|
|
1134
|
+
if buf[..n].windows(4).any(|w| w == b"\r\n\r\n") {
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Headers promise 1 MB; we send only 2 KiB then stall.
|
|
1140
|
+
let head = b"HTTP/1.1 503 Service Unavailable\r\n\
|
|
1141
|
+
Content-Type: text/plain\r\n\
|
|
1142
|
+
Content-Length: 1000000\r\n\
|
|
1143
|
+
Connection: close\r\n\r\n";
|
|
1144
|
+
let _ = stream.write_all(head).await;
|
|
1145
|
+
let _ = stream.write_all(&vec![b'x'; 2048]).await;
|
|
1146
|
+
let _ = stream.flush().await;
|
|
1147
|
+
|
|
1148
|
+
// Park the task so the connection stays open without further
|
|
1149
|
+
// bytes. The test will abort the handle after the client
|
|
1150
|
+
// returns.
|
|
1151
|
+
std::future::pending::<()>().await;
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
let client = OsvClient::with_base_url(format!("http://{}", addr));
|
|
1155
|
+
let started = Instant::now();
|
|
1156
|
+
let outcome = tokio::time::timeout(
|
|
1157
|
+
Duration::from_secs(10),
|
|
1158
|
+
client.check_packages(&[Package {
|
|
1159
|
+
name: "pkg".into(),
|
|
1160
|
+
version: "1.0.0".into(),
|
|
1161
|
+
ecosystem: Ecosystem::PyPI,
|
|
1162
|
+
}]),
|
|
1163
|
+
)
|
|
1164
|
+
.await;
|
|
1165
|
+
let elapsed = started.elapsed();
|
|
1166
|
+
|
|
1167
|
+
server.abort();
|
|
1168
|
+
|
|
1169
|
+
let result = outcome
|
|
1170
|
+
.expect(
|
|
1171
|
+
"OSV call must return promptly when the body stalls; \
|
|
1172
|
+
if it didn't, the body is being read unbounded",
|
|
1173
|
+
)
|
|
1174
|
+
.unwrap();
|
|
1175
|
+
|
|
1176
|
+
assert_eq!(result.errors.len(), 1, "{:?}", result.errors);
|
|
1177
|
+
let err = &result.errors[0];
|
|
1178
|
+
assert!(err.contains("503"), "should include status: {err}");
|
|
1179
|
+
// 5 s leaves a >100x margin over the expected ~1 RTT return
|
|
1180
|
+
// time, while staying well under the 30 s client timeout that
|
|
1181
|
+
// the buggy implementation would hit.
|
|
1182
|
+
assert!(
|
|
1183
|
+
elapsed < Duration::from_secs(5),
|
|
1184
|
+
"bounded read should return promptly; took {:?}",
|
|
1185
|
+
elapsed
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
#[tokio::test]
|
|
1190
|
+
async fn batch_failure_error_includes_package_names_on_network_error() {
|
|
1191
|
+
// Point at a closed port so reqwest fails to connect. Package names
|
|
1192
|
+
// should still be embedded in the resulting error message.
|
|
1193
|
+
let client = OsvClient::with_base_url("http://127.0.0.1:1/".to_string());
|
|
1194
|
+
let pkgs = vec![
|
|
1195
|
+
Package {
|
|
1196
|
+
name: "lonely-pkg".into(),
|
|
1197
|
+
version: "1.0.0".into(),
|
|
1198
|
+
ecosystem: Ecosystem::PyPI,
|
|
1199
|
+
},
|
|
1200
|
+
Package {
|
|
1201
|
+
name: "another-pkg".into(),
|
|
1202
|
+
version: "2.0.0".into(),
|
|
1203
|
+
ecosystem: Ecosystem::PyPI,
|
|
1204
|
+
},
|
|
1205
|
+
];
|
|
1206
|
+
|
|
1207
|
+
let result = client.check_packages(&pkgs).await.unwrap();
|
|
1208
|
+
|
|
1209
|
+
assert_eq!(result.errors.len(), 1, "{:?}", result.errors);
|
|
1210
|
+
let err = &result.errors[0];
|
|
1211
|
+
assert!(
|
|
1212
|
+
err.contains("lonely-pkg") && err.contains("another-pkg"),
|
|
1213
|
+
"network-level errors should still name the failing packages: {err}"
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1006
1216
|
}
|
|
@@ -192,6 +192,16 @@ pub struct Cli {
|
|
|
192
192
|
/// Explicit file paths are always processed regardless of this flag.
|
|
193
193
|
#[arg(long = "no-ignore", global = true)]
|
|
194
194
|
pub no_ignore: bool,
|
|
195
|
+
|
|
196
|
+
/// Disable TLS certificate verification.
|
|
197
|
+
///
|
|
198
|
+
/// Skips verification of server certificates for all HTTPS requests this run.
|
|
199
|
+
/// Hostname verification is also disabled on a best-effort basis (may not be
|
|
200
|
+
/// honored under every TLS backend). Use only as a last resort in environments
|
|
201
|
+
/// where the system trust store cannot be configured. Prefer setting
|
|
202
|
+
/// REQUESTS_CA_BUNDLE or SSL_CERT_FILE to your corporate CA bundle.
|
|
203
|
+
#[arg(long, global = true)]
|
|
204
|
+
pub insecure: bool,
|
|
195
205
|
}
|
|
196
206
|
|
|
197
207
|
#[derive(Subcommand)]
|
|
@@ -297,6 +307,7 @@ mod tests {
|
|
|
297
307
|
assert!(cli.paths.is_empty());
|
|
298
308
|
assert!(cli.command.is_none());
|
|
299
309
|
assert!(cli.min_age.is_none());
|
|
310
|
+
assert!(!cli.insecure);
|
|
300
311
|
}
|
|
301
312
|
|
|
302
313
|
#[test]
|
|
@@ -874,4 +885,24 @@ mod tests {
|
|
|
874
885
|
assert!(cli.no_ignore);
|
|
875
886
|
assert!(matches!(cli.command, Some(Command::Align { .. })));
|
|
876
887
|
}
|
|
888
|
+
|
|
889
|
+
#[test]
|
|
890
|
+
fn test_cli_parses_insecure() {
|
|
891
|
+
let cli = Cli::try_parse_from(["upd", "--insecure"]).unwrap();
|
|
892
|
+
assert!(cli.insecure);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
#[test]
|
|
896
|
+
fn test_cli_insecure_default_false() {
|
|
897
|
+
let cli = Cli::try_parse_from(["upd"]).unwrap();
|
|
898
|
+
assert!(!cli.insecure);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
#[test]
|
|
902
|
+
fn test_cli_insecure_is_global_across_subcommands() {
|
|
903
|
+
for sub in ["audit", "update", "align"] {
|
|
904
|
+
let cli = Cli::try_parse_from(["upd", sub, "--insecure"]).unwrap();
|
|
905
|
+
assert!(cli.insecure, "--insecure must be accepted under `{sub}`");
|
|
906
|
+
}
|
|
907
|
+
}
|
|
877
908
|
}
|