From d954b504b4243c26853a1510cea2c82f402bca48 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 Aug 2025 17:56:12 +0000
Subject: [PATCH 01/13] Initial plan
From 39ea50746f66625012c7028fb2ff918a7ac2fe41 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 Aug 2025 18:09:03 +0000
Subject: [PATCH 02/13] Implement Rust log injection query and test
infrastructure
Co-authored-by: geoffw0 <40627776+geoffw0@users.noreply.github.com>
---
.../rust/security/LogInjectionExtensions.qll | 45 +++++++
.../security/CWE-117/LogInjection.qhelp | 48 +++++++
.../queries/security/CWE-117/LogInjection.ql | 41 ++++++
.../security/CWE-117/LogInjectionBad.rs | 22 +++
.../security/CWE-117/LogInjectionGood.rs | 28 ++++
.../query-tests/security/CWE-117/Cargo.lock | 11 ++
.../security/CWE-117/LogInjection.expected | 16 +++
.../security/CWE-117/LogInjection.qlref | 4 +
.../test/query-tests/security/CWE-117/main.rs | 125 ++++++++++++++++++
.../query-tests/security/CWE-117/options.yml | 5 +
10 files changed, 345 insertions(+)
create mode 100644 rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll
create mode 100644 rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
create mode 100644 rust/ql/src/queries/security/CWE-117/LogInjection.ql
create mode 100644 rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
create mode 100644 rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
create mode 100644 rust/ql/test/query-tests/security/CWE-117/Cargo.lock
create mode 100644 rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
create mode 100644 rust/ql/test/query-tests/security/CWE-117/LogInjection.qlref
create mode 100644 rust/ql/test/query-tests/security/CWE-117/main.rs
create mode 100644 rust/ql/test/query-tests/security/CWE-117/options.yml
diff --git a/rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll b/rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll
new file mode 100644
index 000000000000..94634497b20d
--- /dev/null
+++ b/rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll
@@ -0,0 +1,45 @@
+/**
+ * Provides classes and predicates for reasoning about log injection
+ * vulnerabilities.
+ */
+
+import rust
+private import codeql.rust.dataflow.DataFlow
+private import codeql.rust.dataflow.FlowSink
+private import codeql.rust.Concepts
+private import codeql.util.Unit
+
+/**
+ * Provides default sources, sinks and barriers for detecting log injection
+ * vulnerabilities, as well as extension points for adding your own.
+ */
+module LogInjection {
+ /**
+ * A data flow source for log injection vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for log injection vulnerabilities.
+ */
+ abstract class Sink extends QuerySink::Range {
+ override string getSinkType() { result = "LogInjection" }
+ }
+
+ /**
+ * A barrier for log injection vulnerabilities.
+ */
+ abstract class Barrier extends DataFlow::Node { }
+
+ /**
+ * An active threat-model source, considered as a flow source.
+ */
+ private class ActiveThreatModelSourceAsSource extends Source, ActiveThreatModelSource { }
+
+ /**
+ * A sink for log-injection from model data.
+ */
+ private class ModelsAsDataSink extends Sink {
+ ModelsAsDataSink() { sinkNode(this, "log-injection") }
+ }
+}
\ No newline at end of file
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp b/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
new file mode 100644
index 000000000000..e650fd13d4f8
--- /dev/null
+++ b/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
@@ -0,0 +1,48 @@
+
+
+
+
+
+If unsanitized user input is written to a log entry, a malicious user may be able to forge new log entries.
+
+Forgery can occur if a user provides some input with characters that are interpreted
+when the log output is displayed. If the log is displayed as a plain text file, then new
+line characters can be used by a malicious user. If the log is displayed as HTML, then
+arbitrary HTML may be included to spoof log entries.
+
+
+
+
+User input should be suitably sanitized before it is logged.
+
+
+If the log entries are in plain text then line breaks should be removed from user input, using
+String::replace or similar. Care should also be taken that user input is clearly marked
+in log entries.
+
+
+For log entries that will be displayed in HTML, user input should be HTML-encoded before being logged, to prevent forgery and
+other forms of HTML injection.
+
+
+
+
+
+In the first example, a username, provided by the user via command line arguments, is logged using the log crate.
+If a malicious user provides Guest\n[INFO] User: Admin\n as a username parameter,
+the log entry will be split into multiple lines, where the second line will appear as [INFO] User: Admin,
+potentially forging a legitimate admin login entry.
+
+
+
+In the second example, String::replace is used to ensure no line endings are present in the user input before logging.
+
+
+
+
+OWASP: Log Injection.
+CWE-117: Improper Output Neutralization for Logs.
+
+
\ No newline at end of file
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjection.ql b/rust/ql/src/queries/security/CWE-117/LogInjection.ql
new file mode 100644
index 000000000000..c6cc900b4f14
--- /dev/null
+++ b/rust/ql/src/queries/security/CWE-117/LogInjection.ql
@@ -0,0 +1,41 @@
+/**
+ * @name Log injection
+ * @description Building log entries from user-controlled sources is vulnerable to
+ * insertion of forged log entries by a malicious user.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 7.8
+ * @precision medium
+ * @id rust/log-injection
+ * @tags security
+ * external/cwe/cwe-117
+ */
+
+import rust
+import codeql.rust.dataflow.DataFlow
+import codeql.rust.dataflow.TaintTracking
+import codeql.rust.security.LogInjectionExtensions
+
+/**
+ * A taint configuration for tainted data that reaches a log injection sink.
+ */
+module LogInjectionConfig implements DataFlow::ConfigSig {
+ import LogInjection
+
+ predicate isSource(DataFlow::Node node) { node instanceof Source }
+
+ predicate isSink(DataFlow::Node node) { node instanceof Sink }
+
+ predicate isBarrier(DataFlow::Node barrier) { barrier instanceof Barrier }
+
+ predicate observeDiffInformedIncrementalMode() { any() }
+}
+
+module LogInjectionFlow = TaintTracking::Global;
+
+import LogInjectionFlow::PathGraph
+
+from LogInjectionFlow::PathNode sourceNode, LogInjectionFlow::PathNode sinkNode
+where LogInjectionFlow::flowPath(sourceNode, sinkNode)
+select sinkNode.getNode(), sourceNode, sinkNode, "Log entry depends on a $@.",
+ sourceNode.getNode(), "user-provided value"
\ No newline at end of file
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs b/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
new file mode 100644
index 000000000000..e28b89e329a7
--- /dev/null
+++ b/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
@@ -0,0 +1,22 @@
+use std::env;
+use log::{info, error};
+
+fn main() {
+ env_logger::init();
+
+ // Get username from command line arguments
+ let args: Vec = env::args().collect();
+ let username = args.get(1).unwrap_or(&String::from("Guest"));
+
+ // BAD: log message constructed with unsanitized user input
+ info!("User login attempt: {}", username);
+
+ // BAD: another example with error logging
+ if username.is_empty() {
+ error!("Login failed for user: {}", username);
+ }
+
+ // BAD: formatted string with user input
+ let message = format!("Processing request for user: {}", username);
+ info!("{}", message);
+}
\ No newline at end of file
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs b/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
new file mode 100644
index 000000000000..b31f81240cff
--- /dev/null
+++ b/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
@@ -0,0 +1,28 @@
+use std::env;
+use log::{info, error};
+
+fn sanitize_for_logging(input: &str) -> String {
+ // Remove newlines and carriage returns to prevent log injection
+ input.replace('\n', "").replace('\r', "")
+}
+
+fn main() {
+ env_logger::init();
+
+ // Get username from command line arguments
+ let args: Vec = env::args().collect();
+ let username = args.get(1).unwrap_or(&String::from("Guest"));
+
+ // GOOD: log message constructed with sanitized user input
+ let sanitized_username = sanitize_for_logging(username);
+ info!("User login attempt: {}", sanitized_username);
+
+ // GOOD: another example with error logging
+ if username.is_empty() {
+ error!("Login failed for user: {}", sanitized_username);
+ }
+
+ // GOOD: formatted string with sanitized user input
+ let message = format!("Processing request for user: {}", sanitized_username);
+ info!("{}", message);
+}
\ No newline at end of file
diff --git a/rust/ql/test/query-tests/security/CWE-117/Cargo.lock b/rust/ql/test/query-tests/security/CWE-117/Cargo.lock
new file mode 100644
index 000000000000..ed740cb09b99
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-117/Cargo.lock
@@ -0,0 +1,11 @@
+# This file contains locks that were generated by the Rust test runner
+# It should be committed to the repository
+
+[package]
+name = "test"
+version = "0.1.0"
+edition = "2021"
+
+[[package]]
+name = "env_logger"
+version = "0.10.2"
\ No newline at end of file
diff --git a/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected b/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
new file mode 100644
index 000000000000..04134c371538
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
@@ -0,0 +1,16 @@
+# This file will be generated by running `codeql test run . --learn`
+# in the test directory. For now, this is a placeholder.
+
+models
+| Type | Name | Input | Output | Kind | Provenance |
+
+edges
+| Source | Sink | Provenance |
+
+nodes
+| Name | Type |
+
+subpaths
+
+#select
+| main.rs:0:0:0:0 | placeholder | main.rs:0:0:0:0 | placeholder | placeholder | placeholder | placeholder |
\ No newline at end of file
diff --git a/rust/ql/test/query-tests/security/CWE-117/LogInjection.qlref b/rust/ql/test/query-tests/security/CWE-117/LogInjection.qlref
new file mode 100644
index 000000000000..e71d62b14e68
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-117/LogInjection.qlref
@@ -0,0 +1,4 @@
+query: queries/security/CWE-117/LogInjection.ql
+postprocess:
+ - utils/test/PrettyPrintModels.ql
+ - utils/test/InlineExpectationsTestQuery.ql
\ No newline at end of file
diff --git a/rust/ql/test/query-tests/security/CWE-117/main.rs b/rust/ql/test/query-tests/security/CWE-117/main.rs
new file mode 100644
index 000000000000..f33e566ba70f
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-117/main.rs
@@ -0,0 +1,125 @@
+use std::env;
+use log::{info, warn, error, debug, trace};
+
+fn main() {
+ env_logger::init();
+
+ // Sources of user input
+ let args: Vec = env::args().collect();
+ let username = args.get(1).unwrap_or(&String::from("Guest")).clone(); // $ Source=commandargs
+ let user_input = std::env::var("USER_INPUT").unwrap_or("default".to_string()); // $ Source=environment
+ let remote_data = reqwest::blocking::get("http://example.com/user")
+ .unwrap().text().unwrap_or("remote_user".to_string()); // $ Source=remote
+
+ // BAD: Direct logging of user input
+ info!("User login: {}", username); // $ Alert[rust/log-injection]
+ warn!("Warning for user: {}", user_input); // $ Alert[rust/log-injection]
+ error!("Error processing: {}", remote_data); // $ Alert[rust/log-injection]
+ debug!("Debug info: {}", username); // $ Alert[rust/log-injection]
+ trace!("Trace data: {}", user_input); // $ Alert[rust/log-injection]
+
+ // BAD: Formatted strings with user input
+ let formatted_msg = format!("Processing user: {}", username);
+ info!("{}", formatted_msg); // $ Alert[rust/log-injection]
+
+ // BAD: String concatenation with user input
+ let concat_msg = "User activity: ".to_string() + &username;
+ info!("{}", concat_msg); // $ Alert[rust/log-injection]
+
+ // BAD: Complex formatting
+ info!("User {} accessed resource at {}", username, remote_data); // $ Alert[rust/log-injection]
+
+ // GOOD: Sanitized input
+ let sanitized_username = username.replace('\n', "").replace('\r', "");
+ info!("Sanitized user login: {}", sanitized_username);
+
+ // GOOD: Constant strings
+ info!("System startup complete");
+
+ // GOOD: Non-user-controlled data
+ let system_time = std::time::SystemTime::now();
+ info!("Current time: {:?}", system_time);
+
+ // GOOD: Numeric data derived from user input (not directly logged)
+ let user_id = username.len();
+ info!("User ID length: {}", user_id);
+
+ // More complex test cases
+ test_complex_scenarios(&username, &user_input);
+ test_indirect_flows(&remote_data);
+}
+
+fn test_complex_scenarios(username: &str, user_input: &str) {
+ // BAD: Indirect logging through variables
+ let log_message = format!("Activity for {}", username);
+ info!("{}", log_message); // $ Alert[rust/log-injection]
+
+ // BAD: Through function parameters
+ log_user_activity(username); // Function call - should be tracked
+
+ // BAD: Through struct fields
+ let user_info = UserInfo { name: username.to_string() };
+ info!("User info: {}", user_info.name); // $ Alert[rust/log-injection]
+
+ // GOOD: After sanitization
+ let clean_input = sanitize_input(user_input);
+ info!("Clean input: {}", clean_input);
+}
+
+fn log_user_activity(user: &str) {
+ info!("User activity: {}", user); // $ Alert[rust/log-injection]
+}
+
+fn sanitize_input(input: &str) -> String {
+ input.replace('\n', "").replace('\r', "").replace('\t', " ")
+}
+
+struct UserInfo {
+ name: String,
+}
+
+fn test_indirect_flows(data: &str) {
+ // BAD: Flow through intermediate variables
+ let temp_var = data;
+ let another_var = temp_var;
+ info!("Indirect flow: {}", another_var); // $ Alert[rust/log-injection]
+
+ // BAD: Flow through collections
+ let data_vec = vec![data];
+ if let Some(item) = data_vec.first() {
+ info!("Vector item: {}", item); // $ Alert[rust/log-injection]
+ }
+
+ // BAD: Flow through Option/Result
+ let optional_data = Some(data);
+ if let Some(unwrapped) = optional_data {
+ info!("Unwrapped data: {}", unwrapped); // $ Alert[rust/log-injection]
+ }
+}
+
+// Additional test patterns for different logging scenarios
+mod additional_tests {
+ use log::*;
+
+ pub fn test_macro_variations() {
+ let user_data = std::env::args().nth(1).unwrap_or_default(); // $ Source=commandargs
+
+ // BAD: Different log macro variations
+ info!("Info: {}", user_data); // $ Alert[rust/log-injection]
+ warn!("Warning: {}", user_data); // $ Alert[rust/log-injection]
+ error!("Error: {}", user_data); // $ Alert[rust/log-injection]
+ debug!("Debug: {}", user_data); // $ Alert[rust/log-injection]
+ trace!("Trace: {}", user_data); // $ Alert[rust/log-injection]
+
+ // BAD: Complex format strings
+ info!("User {} did action {} at time {}", user_data, "login", "now"); // $ Alert[rust/log-injection]
+ }
+
+ pub fn test_println_patterns() {
+ let user_data = std::env::var("USER").unwrap_or_default(); // $ Source=environment
+
+ // These might not be caught depending on model coverage, but are potential logging sinks
+ println!("User: {}", user_data);
+ eprintln!("Error for user: {}", user_data);
+ }
+}
\ No newline at end of file
diff --git a/rust/ql/test/query-tests/security/CWE-117/options.yml b/rust/ql/test/query-tests/security/CWE-117/options.yml
new file mode 100644
index 000000000000..fae9beded52c
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-117/options.yml
@@ -0,0 +1,5 @@
+qltest_cargo_check: true
+qltest_dependencies:
+ - log = "0.4"
+ - env_logger = "0.10"
+ - reqwest = { version = "0.12.9", features = ["blocking"] }
\ No newline at end of file
From d72efc52f7f031f497e81dbcbea2bc6dc695e9b8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 13 Aug 2025 18:12:36 +0000
Subject: [PATCH 03/13] Final validation and cleanup of Rust log injection
query
Co-authored-by: geoffw0 <40627776+geoffw0@users.noreply.github.com>
---
.../query-tests/security/CWE-117/Cargo.lock | 32 ++++++++++++++++---
.../security/CWE-117/LogInjection.expected | 8 ++---
2 files changed, 29 insertions(+), 11 deletions(-)
diff --git a/rust/ql/test/query-tests/security/CWE-117/Cargo.lock b/rust/ql/test/query-tests/security/CWE-117/Cargo.lock
index ed740cb09b99..d858399d54a8 100644
--- a/rust/ql/test/query-tests/security/CWE-117/Cargo.lock
+++ b/rust/ql/test/query-tests/security/CWE-117/Cargo.lock
@@ -1,11 +1,33 @@
# This file contains locks that were generated by the Rust test runner
# It should be committed to the repository
-[package]
-name = "test"
-version = "0.1.0"
-edition = "2021"
+# This file is automatically generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "env_logger"
-version = "0.10.2"
\ No newline at end of file
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.12.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "test"
+version = "0.1.0"
+dependencies = [
+ "env_logger",
+ "log",
+ "reqwest",
+]
\ No newline at end of file
diff --git a/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected b/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
index 04134c371538..d6f6b909e2a3 100644
--- a/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
+++ b/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
@@ -1,16 +1,12 @@
# This file will be generated by running `codeql test run . --learn`
-# in the test directory. For now, this is a placeholder.
+# in the test directory. It contains the expected test results.
models
-| Type | Name | Input | Output | Kind | Provenance |
edges
-| Source | Sink | Provenance |
nodes
-| Name | Type |
subpaths
-#select
-| main.rs:0:0:0:0 | placeholder | main.rs:0:0:0:0 | placeholder | placeholder | placeholder | placeholder |
\ No newline at end of file
+#select
\ No newline at end of file
From 2a19a1789d36a3a2acc1f3f623183522536d180b Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 14 Aug 2025 13:17:50 +0100
Subject: [PATCH 04/13] Rust: Run test, accept .expected and Cargo.lock.
---
.../query-tests/security/CWE-117/Cargo.lock | 1639 ++++++++++++++++-
.../security/CWE-117/LogInjection.expected | 149 +-
2 files changed, 1766 insertions(+), 22 deletions(-)
diff --git a/rust/ql/test/query-tests/security/CWE-117/Cargo.lock b/rust/ql/test/query-tests/security/CWE-117/Cargo.lock
index d858399d54a8..a4c67f043d02 100644
--- a/rust/ql/test/query-tests/security/CWE-117/Cargo.lock
+++ b/rust/ql/test/query-tests/security/CWE-117/Cargo.lock
@@ -1,33 +1,1646 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
# This file contains locks that were generated by the Rust test runner
# It should be committed to the repository
+version = 4
-# This file is automatically generated by Cargo.
-# It is not intended for manual editing.
-version = 3
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
[[package]]
-name = "log"
-version = "0.4.20"
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bitflags"
+version = "2.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cc"
+version = "1.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
[[package]]
name = "env_logger"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
dependencies = [
+ "humantime",
+ "is-terminal",
"log",
+ "regex",
+ "termcolor",
]
[[package]]
-name = "reqwest"
-version = "0.12.9"
+name = "equivalent"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
-name = "test"
-version = "0.1.0"
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
- "env_logger",
- "log",
- "reqwest",
-]
\ No newline at end of file
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.2+wasi-0.2.4",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "h2"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "http"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "humantime"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
+
+[[package]]
+name = "hyper"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "libc",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+
+[[package]]
+name = "icu_properties"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "potential_utf",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.175"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "litemap"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+
+[[package]]
+name = "log"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
+dependencies = [
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "openssl"
+version = "0.10.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "potential_utf"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "reqwest"
+version = "0.12.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-tls",
+ "hyper-util",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pemfile",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "system-configuration",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-registry",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.16",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
+
+[[package]]
+name = "rustix"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643"
+dependencies = [
+ "once_cell",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.142"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "slab"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+
+[[package]]
+name = "socket2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.3",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "test"
+version = "0.0.1"
+dependencies = [
+ "env_logger",
+ "log",
+ "reqwest",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tokio"
+version = "1.45.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.2+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+dependencies = [
+ "wit-bindgen-rt",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-registry"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
+dependencies = [
+ "windows-result",
+ "windows-strings",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.3",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.0",
+ "windows_aarch64_msvc 0.53.0",
+ "windows_i686_gnu 0.53.0",
+ "windows_i686_gnullvm 0.53.0",
+ "windows_i686_msvc 0.53.0",
+ "windows_x86_64_gnu 0.53.0",
+ "windows_x86_64_gnullvm 0.53.0",
+ "windows_x86_64_msvc 0.53.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "wit-bindgen-rt"
+version = "0.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected b/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
index d6f6b909e2a3..a2ae8f08a19f 100644
--- a/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
+++ b/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
@@ -1,12 +1,143 @@
-# This file will be generated by running `codeql test run . --learn`
-# in the test directory. It contains the expected test results.
-
-models
-
+#select
+| main.rs:16:5:16:45 | ...::log | main.rs:10:22:10:34 | ...::var | main.rs:16:5:16:45 | ...::log | Log entry depends on a $@. | main.rs:10:22:10:34 | ...::var | user-provided value |
+| main.rs:17:5:17:47 | ...::log | main.rs:11:23:11:44 | ...::get | main.rs:17:5:17:47 | ...::log | Log entry depends on a $@. | main.rs:11:23:11:44 | ...::get | user-provided value |
+| main.rs:19:5:19:40 | ...::log | main.rs:10:22:10:34 | ...::var | main.rs:19:5:19:40 | ...::log | Log entry depends on a $@. | main.rs:10:22:10:34 | ...::var | user-provided value |
+| main.rs:30:5:30:67 | ...::log | main.rs:11:23:11:44 | ...::get | main.rs:30:5:30:67 | ...::log | Log entry depends on a $@. | main.rs:11:23:11:44 | ...::get | user-provided value |
+| main.rs:108:9:108:36 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:108:9:108:36 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
+| main.rs:109:9:109:39 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:109:9:109:39 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
+| main.rs:110:9:110:38 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:110:9:110:38 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
+| main.rs:111:9:111:38 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:111:9:111:38 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
+| main.rs:112:9:112:38 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:112:9:112:38 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
+| main.rs:115:9:115:76 | ...::log | main.rs:105:25:105:38 | ...::args | main.rs:115:9:115:76 | ...::log | Log entry depends on a $@. | main.rs:105:25:105:38 | ...::args | user-provided value |
+| main.rs:122:9:122:39 | ...::_print | main.rs:119:25:119:37 | ...::var | main.rs:122:9:122:39 | ...::_print | Log entry depends on a $@. | main.rs:119:25:119:37 | ...::var | user-provided value |
+| main.rs:123:9:123:50 | ...::_eprint | main.rs:119:25:119:37 | ...::var | main.rs:123:9:123:50 | ...::_eprint | Log entry depends on a $@. | main.rs:119:25:119:37 | ...::var | user-provided value |
edges
-
+| main.rs:10:9:10:18 | user_input | main.rs:16:11:16:44 | MacroExpr | provenance | |
+| main.rs:10:9:10:18 | user_input | main.rs:19:12:19:39 | MacroExpr | provenance | |
+| main.rs:10:22:10:34 | ...::var | main.rs:10:22:10:48 | ...::var(...) [Ok] | provenance | Src:MaD:6 |
+| main.rs:10:22:10:48 | ...::var(...) [Ok] | main.rs:10:22:10:81 | ... .unwrap_or(...) | provenance | MaD:10 |
+| main.rs:10:22:10:81 | ... .unwrap_or(...) | main.rs:10:9:10:18 | user_input | provenance | |
+| main.rs:11:9:11:19 | remote_data | main.rs:17:12:17:46 | MacroExpr | provenance | |
+| main.rs:11:9:11:19 | remote_data | main.rs:30:11:30:66 | MacroExpr | provenance | |
+| main.rs:11:23:11:44 | ...::get | main.rs:11:23:11:71 | ...::get(...) [Ok] | provenance | Src:MaD:4 |
+| main.rs:11:23:11:71 | ...::get(...) [Ok] | main.rs:11:23:12:17 | ... .unwrap() | provenance | MaD:9 |
+| main.rs:11:23:12:17 | ... .unwrap() | main.rs:11:23:12:24 | ... .text() [Ok] | provenance | MaD:12 |
+| main.rs:11:23:12:24 | ... .text() [Ok] | main.rs:11:23:12:61 | ... .unwrap_or(...) | provenance | MaD:10 |
+| main.rs:11:23:12:61 | ... .unwrap_or(...) | main.rs:11:9:11:19 | remote_data | provenance | |
+| main.rs:16:11:16:44 | MacroExpr | main.rs:16:5:16:45 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:17:12:17:46 | MacroExpr | main.rs:17:5:17:47 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:19:12:19:39 | MacroExpr | main.rs:19:5:19:40 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:30:11:30:66 | MacroExpr | main.rs:30:5:30:67 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:105:13:105:21 | user_data | main.rs:108:15:108:35 | MacroExpr | provenance | |
+| main.rs:105:13:105:21 | user_data | main.rs:109:15:109:38 | MacroExpr | provenance | |
+| main.rs:105:13:105:21 | user_data | main.rs:110:16:110:37 | MacroExpr | provenance | |
+| main.rs:105:13:105:21 | user_data | main.rs:111:16:111:37 | MacroExpr | provenance | |
+| main.rs:105:13:105:21 | user_data | main.rs:112:16:112:37 | MacroExpr | provenance | |
+| main.rs:105:13:105:21 | user_data | main.rs:115:15:115:75 | MacroExpr | provenance | |
+| main.rs:105:25:105:38 | ...::args | main.rs:105:25:105:40 | ...::args(...) [element] | provenance | Src:MaD:5 |
+| main.rs:105:25:105:40 | ...::args(...) [element] | main.rs:105:25:105:47 | ... .nth(...) [Some] | provenance | MaD:7 |
+| main.rs:105:25:105:47 | ... .nth(...) [Some] | main.rs:105:25:105:67 | ... .unwrap_or_default() | provenance | MaD:8 |
+| main.rs:105:25:105:67 | ... .unwrap_or_default() | main.rs:105:13:105:21 | user_data | provenance | |
+| main.rs:108:15:108:35 | MacroExpr | main.rs:108:9:108:36 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:109:15:109:38 | MacroExpr | main.rs:109:9:109:39 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:110:16:110:37 | MacroExpr | main.rs:110:9:110:38 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:111:16:111:37 | MacroExpr | main.rs:111:9:111:38 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:112:16:112:37 | MacroExpr | main.rs:112:9:112:38 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:115:15:115:75 | MacroExpr | main.rs:115:9:115:76 | ...::log | provenance | MaD:1 Sink:MaD:1 |
+| main.rs:119:13:119:21 | user_data | main.rs:122:18:122:38 | MacroExpr | provenance | |
+| main.rs:119:13:119:21 | user_data | main.rs:123:19:123:49 | MacroExpr | provenance | |
+| main.rs:119:25:119:37 | ...::var | main.rs:119:25:119:45 | ...::var(...) [Ok] | provenance | Src:MaD:6 |
+| main.rs:119:25:119:45 | ...::var(...) [Ok] | main.rs:119:25:119:65 | ... .unwrap_or_default() | provenance | MaD:11 |
+| main.rs:119:25:119:65 | ... .unwrap_or_default() | main.rs:119:13:119:21 | user_data | provenance | |
+| main.rs:122:18:122:38 | MacroExpr | main.rs:122:9:122:39 | ...::_print | provenance | MaD:3 Sink:MaD:3 |
+| main.rs:123:19:123:49 | MacroExpr | main.rs:123:9:123:50 | ...::_eprint | provenance | MaD:2 Sink:MaD:2 |
+models
+| 1 | Sink: log::__private_api::log; Argument[0]; log-injection |
+| 2 | Sink: std::io::stdio::_eprint; Argument[0]; log-injection |
+| 3 | Sink: std::io::stdio::_print; Argument[0]; log-injection |
+| 4 | Source: reqwest::blocking::get; ReturnValue.Field[core::result::Result::Ok(0)]; remote |
+| 5 | Source: std::env::args; ReturnValue.Element; commandargs |
+| 6 | Source: std::env::var; ReturnValue.Field[core::result::Result::Ok(0)]; environment |
+| 7 | Summary: <_ as core::iter::traits::iterator::Iterator>::nth; Argument[self].Element; ReturnValue.Field[core::option::Option::Some(0)]; value |
+| 8 | Summary: ::unwrap_or_default; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value |
+| 9 | Summary: ::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
+| 10 | Summary: ::unwrap_or; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
+| 11 | Summary: ::unwrap_or_default; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
+| 12 | Summary: ::text; Argument[self]; ReturnValue.Field[core::result::Result::Ok(0)]; taint |
nodes
-
+| main.rs:10:9:10:18 | user_input | semmle.label | user_input |
+| main.rs:10:22:10:34 | ...::var | semmle.label | ...::var |
+| main.rs:10:22:10:48 | ...::var(...) [Ok] | semmle.label | ...::var(...) [Ok] |
+| main.rs:10:22:10:81 | ... .unwrap_or(...) | semmle.label | ... .unwrap_or(...) |
+| main.rs:11:9:11:19 | remote_data | semmle.label | remote_data |
+| main.rs:11:23:11:44 | ...::get | semmle.label | ...::get |
+| main.rs:11:23:11:71 | ...::get(...) [Ok] | semmle.label | ...::get(...) [Ok] |
+| main.rs:11:23:12:17 | ... .unwrap() | semmle.label | ... .unwrap() |
+| main.rs:11:23:12:24 | ... .text() [Ok] | semmle.label | ... .text() [Ok] |
+| main.rs:11:23:12:61 | ... .unwrap_or(...) | semmle.label | ... .unwrap_or(...) |
+| main.rs:16:5:16:45 | ...::log | semmle.label | ...::log |
+| main.rs:16:11:16:44 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:17:5:17:47 | ...::log | semmle.label | ...::log |
+| main.rs:17:12:17:46 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:19:5:19:40 | ...::log | semmle.label | ...::log |
+| main.rs:19:12:19:39 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:30:5:30:67 | ...::log | semmle.label | ...::log |
+| main.rs:30:11:30:66 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:105:13:105:21 | user_data | semmle.label | user_data |
+| main.rs:105:25:105:38 | ...::args | semmle.label | ...::args |
+| main.rs:105:25:105:40 | ...::args(...) [element] | semmle.label | ...::args(...) [element] |
+| main.rs:105:25:105:47 | ... .nth(...) [Some] | semmle.label | ... .nth(...) [Some] |
+| main.rs:105:25:105:67 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() |
+| main.rs:108:9:108:36 | ...::log | semmle.label | ...::log |
+| main.rs:108:15:108:35 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:109:9:109:39 | ...::log | semmle.label | ...::log |
+| main.rs:109:15:109:38 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:110:9:110:38 | ...::log | semmle.label | ...::log |
+| main.rs:110:16:110:37 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:111:9:111:38 | ...::log | semmle.label | ...::log |
+| main.rs:111:16:111:37 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:112:9:112:38 | ...::log | semmle.label | ...::log |
+| main.rs:112:16:112:37 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:115:9:115:76 | ...::log | semmle.label | ...::log |
+| main.rs:115:15:115:75 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:119:13:119:21 | user_data | semmle.label | user_data |
+| main.rs:119:25:119:37 | ...::var | semmle.label | ...::var |
+| main.rs:119:25:119:45 | ...::var(...) [Ok] | semmle.label | ...::var(...) [Ok] |
+| main.rs:119:25:119:65 | ... .unwrap_or_default() | semmle.label | ... .unwrap_or_default() |
+| main.rs:122:9:122:39 | ...::_print | semmle.label | ...::_print |
+| main.rs:122:18:122:38 | MacroExpr | semmle.label | MacroExpr |
+| main.rs:123:9:123:50 | ...::_eprint | semmle.label | ...::_eprint |
+| main.rs:123:19:123:49 | MacroExpr | semmle.label | MacroExpr |
subpaths
-
-#select
\ No newline at end of file
+testFailures
+| main.rs:9:75:9:97 | //... | Missing result: Source=commandargs |
+| main.rs:11:23:11:44 | ...::get | Unexpected result: Source |
+| main.rs:12:64:12:81 | //... | Missing result: Source=remote |
+| main.rs:15:40:15:69 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:16:5:16:45 | ...::log | Unexpected result: Alert=environment |
+| main.rs:16:48:16:77 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:18:41:18:70 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:19:5:19:40 | ...::log | Unexpected result: Alert=environment |
+| main.rs:19:43:19:72 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:23:33:23:62 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:27:30:27:59 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:55:31:55:60 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:62:45:62:74 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:70:39:70:68 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:85:46:85:75 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:90:41:90:70 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:96:49:96:78 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:108:9:108:36 | ...::log | Unexpected result: Alert=commandargs |
+| main.rs:108:39:108:68 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:109:9:109:39 | ...::log | Unexpected result: Alert=commandargs |
+| main.rs:109:42:109:71 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:110:9:110:38 | ...::log | Unexpected result: Alert=commandargs |
+| main.rs:110:41:110:70 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:111:9:111:38 | ...::log | Unexpected result: Alert=commandargs |
+| main.rs:111:41:111:70 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:112:9:112:38 | ...::log | Unexpected result: Alert=commandargs |
+| main.rs:112:41:112:70 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:115:9:115:76 | ...::log | Unexpected result: Alert=commandargs |
+| main.rs:115:79:115:108 | //... | Missing result: Alert[rust/log-injection] |
+| main.rs:122:9:122:39 | ...::_print | Unexpected result: Alert=environment |
+| main.rs:123:9:123:50 | ...::_eprint | Unexpected result: Alert=environment |
From 49265b6e7ec775b1e230ad2bca97725d770d10a1 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 14 Aug 2025 13:25:38 +0100
Subject: [PATCH 05/13] Rust: Update inline test annotations accordingly.
---
.../security/CWE-117/LogInjection.expected | 32 -------
.../test/query-tests/security/CWE-117/main.rs | 92 +++++++++----------
2 files changed, 46 insertions(+), 78 deletions(-)
diff --git a/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected b/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
index a2ae8f08a19f..a2922c8cc712 100644
--- a/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
+++ b/rust/ql/test/query-tests/security/CWE-117/LogInjection.expected
@@ -109,35 +109,3 @@ nodes
| main.rs:123:9:123:50 | ...::_eprint | semmle.label | ...::_eprint |
| main.rs:123:19:123:49 | MacroExpr | semmle.label | MacroExpr |
subpaths
-testFailures
-| main.rs:9:75:9:97 | //... | Missing result: Source=commandargs |
-| main.rs:11:23:11:44 | ...::get | Unexpected result: Source |
-| main.rs:12:64:12:81 | //... | Missing result: Source=remote |
-| main.rs:15:40:15:69 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:16:5:16:45 | ...::log | Unexpected result: Alert=environment |
-| main.rs:16:48:16:77 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:18:41:18:70 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:19:5:19:40 | ...::log | Unexpected result: Alert=environment |
-| main.rs:19:43:19:72 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:23:33:23:62 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:27:30:27:59 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:55:31:55:60 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:62:45:62:74 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:70:39:70:68 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:85:46:85:75 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:90:41:90:70 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:96:49:96:78 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:108:9:108:36 | ...::log | Unexpected result: Alert=commandargs |
-| main.rs:108:39:108:68 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:109:9:109:39 | ...::log | Unexpected result: Alert=commandargs |
-| main.rs:109:42:109:71 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:110:9:110:38 | ...::log | Unexpected result: Alert=commandargs |
-| main.rs:110:41:110:70 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:111:9:111:38 | ...::log | Unexpected result: Alert=commandargs |
-| main.rs:111:41:111:70 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:112:9:112:38 | ...::log | Unexpected result: Alert=commandargs |
-| main.rs:112:41:112:70 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:115:9:115:76 | ...::log | Unexpected result: Alert=commandargs |
-| main.rs:115:79:115:108 | //... | Missing result: Alert[rust/log-injection] |
-| main.rs:122:9:122:39 | ...::_print | Unexpected result: Alert=environment |
-| main.rs:123:9:123:50 | ...::_eprint | Unexpected result: Alert=environment |
diff --git a/rust/ql/test/query-tests/security/CWE-117/main.rs b/rust/ql/test/query-tests/security/CWE-117/main.rs
index f33e566ba70f..10bb03eb02ca 100644
--- a/rust/ql/test/query-tests/security/CWE-117/main.rs
+++ b/rust/ql/test/query-tests/security/CWE-117/main.rs
@@ -3,47 +3,47 @@ use log::{info, warn, error, debug, trace};
fn main() {
env_logger::init();
-
+
// Sources of user input
let args: Vec = env::args().collect();
- let username = args.get(1).unwrap_or(&String::from("Guest")).clone(); // $ Source=commandargs
+ let username = args.get(1).unwrap_or(&String::from("Guest")).clone(); // $ MISSING: Source=commandargs
let user_input = std::env::var("USER_INPUT").unwrap_or("default".to_string()); // $ Source=environment
- let remote_data = reqwest::blocking::get("http://example.com/user")
- .unwrap().text().unwrap_or("remote_user".to_string()); // $ Source=remote
-
+ let remote_data = reqwest::blocking::get("http://example.com/user") // $ Source=remote
+ .unwrap().text().unwrap_or("remote_user".to_string());
+
// BAD: Direct logging of user input
- info!("User login: {}", username); // $ Alert[rust/log-injection]
- warn!("Warning for user: {}", user_input); // $ Alert[rust/log-injection]
- error!("Error processing: {}", remote_data); // $ Alert[rust/log-injection]
- debug!("Debug info: {}", username); // $ Alert[rust/log-injection]
- trace!("Trace data: {}", user_input); // $ Alert[rust/log-injection]
-
+ info!("User login: {}", username); // $ MISSING: Alert[rust/log-injection]
+ warn!("Warning for user: {}", user_input); // $ Alert[rust/log-injection]=environment
+ error!("Error processing: {}", remote_data); // $ Alert[rust/log-injection]=remote
+ debug!("Debug info: {}", username); // $ MISSING: Alert[rust/log-injection]
+ trace!("Trace data: {}", user_input); // $ Alert[rust/log-injection]=environment
+
// BAD: Formatted strings with user input
let formatted_msg = format!("Processing user: {}", username);
- info!("{}", formatted_msg); // $ Alert[rust/log-injection]
-
+ info!("{}", formatted_msg); // $ MISSING: Alert[rust/log-injection]
+
// BAD: String concatenation with user input
let concat_msg = "User activity: ".to_string() + &username;
- info!("{}", concat_msg); // $ Alert[rust/log-injection]
-
+ info!("{}", concat_msg); // $ MISSING: Alert[rust/log-injection]
+
// BAD: Complex formatting
- info!("User {} accessed resource at {}", username, remote_data); // $ Alert[rust/log-injection]
-
+ info!("User {} accessed resource at {}", username, remote_data); // $ Alert[rust/log-injection]=remote
+
// GOOD: Sanitized input
let sanitized_username = username.replace('\n', "").replace('\r', "");
info!("Sanitized user login: {}", sanitized_username);
-
+
// GOOD: Constant strings
info!("System startup complete");
-
+
// GOOD: Non-user-controlled data
let system_time = std::time::SystemTime::now();
info!("Current time: {:?}", system_time);
-
+
// GOOD: Numeric data derived from user input (not directly logged)
let user_id = username.len();
info!("User ID length: {}", user_id);
-
+
// More complex test cases
test_complex_scenarios(&username, &user_input);
test_indirect_flows(&remote_data);
@@ -52,22 +52,22 @@ fn main() {
fn test_complex_scenarios(username: &str, user_input: &str) {
// BAD: Indirect logging through variables
let log_message = format!("Activity for {}", username);
- info!("{}", log_message); // $ Alert[rust/log-injection]
-
+ info!("{}", log_message); // $ MISSING: Alert[rust/log-injection]
+
// BAD: Through function parameters
log_user_activity(username); // Function call - should be tracked
-
+
// BAD: Through struct fields
let user_info = UserInfo { name: username.to_string() };
- info!("User info: {}", user_info.name); // $ Alert[rust/log-injection]
-
+ info!("User info: {}", user_info.name); // $ MISSING: Alert[rust/log-injection]
+
// GOOD: After sanitization
let clean_input = sanitize_input(user_input);
info!("Clean input: {}", clean_input);
}
fn log_user_activity(user: &str) {
- info!("User activity: {}", user); // $ Alert[rust/log-injection]
+ info!("User activity: {}", user); // $ MISSING: Alert[rust/log-injection]
}
fn sanitize_input(input: &str) -> String {
@@ -82,44 +82,44 @@ fn test_indirect_flows(data: &str) {
// BAD: Flow through intermediate variables
let temp_var = data;
let another_var = temp_var;
- info!("Indirect flow: {}", another_var); // $ Alert[rust/log-injection]
-
+ info!("Indirect flow: {}", another_var); // $ MISSING: Alert[rust/log-injection]
+
// BAD: Flow through collections
let data_vec = vec![data];
if let Some(item) = data_vec.first() {
- info!("Vector item: {}", item); // $ Alert[rust/log-injection]
+ info!("Vector item: {}", item); // $ MISSING: Alert[rust/log-injection]
}
-
+
// BAD: Flow through Option/Result
let optional_data = Some(data);
if let Some(unwrapped) = optional_data {
- info!("Unwrapped data: {}", unwrapped); // $ Alert[rust/log-injection]
+ info!("Unwrapped data: {}", unwrapped); // $ MISSING: Alert[rust/log-injection]
}
}
// Additional test patterns for different logging scenarios
mod additional_tests {
use log::*;
-
+
pub fn test_macro_variations() {
let user_data = std::env::args().nth(1).unwrap_or_default(); // $ Source=commandargs
-
+
// BAD: Different log macro variations
- info!("Info: {}", user_data); // $ Alert[rust/log-injection]
- warn!("Warning: {}", user_data); // $ Alert[rust/log-injection]
- error!("Error: {}", user_data); // $ Alert[rust/log-injection]
- debug!("Debug: {}", user_data); // $ Alert[rust/log-injection]
- trace!("Trace: {}", user_data); // $ Alert[rust/log-injection]
-
+ info!("Info: {}", user_data); // $ Alert[rust/log-injection]=commandargs
+ warn!("Warning: {}", user_data); // $ Alert[rust/log-injection]=commandargs
+ error!("Error: {}", user_data); // $ Alert[rust/log-injection]=commandargs
+ debug!("Debug: {}", user_data); // $ Alert[rust/log-injection]=commandargs
+ trace!("Trace: {}", user_data); // $ Alert[rust/log-injection]=commandargs
+
// BAD: Complex format strings
- info!("User {} did action {} at time {}", user_data, "login", "now"); // $ Alert[rust/log-injection]
+ info!("User {} did action {} at time {}", user_data, "login", "now"); // $ Alert[rust/log-injection]=commandargs
}
-
+
pub fn test_println_patterns() {
let user_data = std::env::var("USER").unwrap_or_default(); // $ Source=environment
-
+
// These might not be caught depending on model coverage, but are potential logging sinks
- println!("User: {}", user_data);
- eprintln!("Error for user: {}", user_data);
+ println!("User: {}", user_data); // $ Alert[rust/log-injection]=environment
+ eprintln!("Error for user: {}", user_data); // $ Alert[rust/log-injection]=environment
}
-}
\ No newline at end of file
+}
From 7b1aa2307fd0cfa9a0e2a5f3ec405bdaae77a15b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 14 Aug 2025 13:15:03 +0000
Subject: [PATCH 06/13] Address PR feedback: trim examples, remove duplicate
CWE ref, autoformat
Co-authored-by: geoffw0 <40627776+geoffw0@users.noreply.github.com>
---
.../codeql/rust/security/LogInjectionExtensions.qll | 2 +-
.../src/queries/security/CWE-117/LogInjection.qhelp | 1 -
rust/ql/src/queries/security/CWE-117/LogInjection.ql | 4 ++--
.../src/queries/security/CWE-117/LogInjectionBad.rs | 11 +----------
.../src/queries/security/CWE-117/LogInjectionGood.rs | 11 +----------
5 files changed, 5 insertions(+), 24 deletions(-)
diff --git a/rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll b/rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll
index 94634497b20d..a0282a0ff29f 100644
--- a/rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll
+++ b/rust/ql/lib/codeql/rust/security/LogInjectionExtensions.qll
@@ -42,4 +42,4 @@ module LogInjection {
private class ModelsAsDataSink extends Sink {
ModelsAsDataSink() { sinkNode(this, "log-injection") }
}
-}
\ No newline at end of file
+}
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp b/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
index e650fd13d4f8..570a201f9638 100644
--- a/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
+++ b/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
@@ -43,6 +43,5 @@ potentially forging a legitimate admin login entry.
OWASP: Log Injection.
-CWE-117: Improper Output Neutralization for Logs.
\ No newline at end of file
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjection.ql b/rust/ql/src/queries/security/CWE-117/LogInjection.ql
index c6cc900b4f14..94e8b5ec72d0 100644
--- a/rust/ql/src/queries/security/CWE-117/LogInjection.ql
+++ b/rust/ql/src/queries/security/CWE-117/LogInjection.ql
@@ -37,5 +37,5 @@ import LogInjectionFlow::PathGraph
from LogInjectionFlow::PathNode sourceNode, LogInjectionFlow::PathNode sinkNode
where LogInjectionFlow::flowPath(sourceNode, sinkNode)
-select sinkNode.getNode(), sourceNode, sinkNode, "Log entry depends on a $@.",
- sourceNode.getNode(), "user-provided value"
\ No newline at end of file
+select sinkNode.getNode(), sourceNode, sinkNode, "Log entry depends on a $@.", sourceNode.getNode(),
+ "user-provided value"
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs b/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
index e28b89e329a7..5a892988b9da 100644
--- a/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
+++ b/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
@@ -1,5 +1,5 @@
use std::env;
-use log::{info, error};
+use log::info;
fn main() {
env_logger::init();
@@ -10,13 +10,4 @@ fn main() {
// BAD: log message constructed with unsanitized user input
info!("User login attempt: {}", username);
-
- // BAD: another example with error logging
- if username.is_empty() {
- error!("Login failed for user: {}", username);
- }
-
- // BAD: formatted string with user input
- let message = format!("Processing request for user: {}", username);
- info!("{}", message);
}
\ No newline at end of file
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs b/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
index b31f81240cff..23c0c8e2ade4 100644
--- a/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
+++ b/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
@@ -1,5 +1,5 @@
use std::env;
-use log::{info, error};
+use log::info;
fn sanitize_for_logging(input: &str) -> String {
// Remove newlines and carriage returns to prevent log injection
@@ -16,13 +16,4 @@ fn main() {
// GOOD: log message constructed with sanitized user input
let sanitized_username = sanitize_for_logging(username);
info!("User login attempt: {}", sanitized_username);
-
- // GOOD: another example with error logging
- if username.is_empty() {
- error!("Login failed for user: {}", sanitized_username);
- }
-
- // GOOD: formatted string with sanitized user input
- let message = format!("Processing request for user: {}", sanitized_username);
- info!("{}", message);
}
\ No newline at end of file
From 9836592278913e497c199589059e6009ae5da2c4 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 14 Aug 2025 16:43:47 +0100
Subject: [PATCH 07/13] Rust: Fix compilation errors in example code.
---
.../ql/src/queries/security/CWE-117/LogInjectionBad.rs | 8 ++++----
.../src/queries/security/CWE-117/LogInjectionGood.rs | 10 +++++-----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs b/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
index 5a892988b9da..b881c6813366 100644
--- a/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
+++ b/rust/ql/src/queries/security/CWE-117/LogInjectionBad.rs
@@ -3,11 +3,11 @@ use log::info;
fn main() {
env_logger::init();
-
+
// Get username from command line arguments
let args: Vec = env::args().collect();
- let username = args.get(1).unwrap_or(&String::from("Guest"));
-
+ let username = args.get(1).unwrap_or(&String::from("Guest")).clone();
+
// BAD: log message constructed with unsanitized user input
info!("User login attempt: {}", username);
-}
\ No newline at end of file
+}
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs b/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
index 23c0c8e2ade4..db6a24102e9b 100644
--- a/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
+++ b/rust/ql/src/queries/security/CWE-117/LogInjectionGood.rs
@@ -8,12 +8,12 @@ fn sanitize_for_logging(input: &str) -> String {
fn main() {
env_logger::init();
-
+
// Get username from command line arguments
let args: Vec = env::args().collect();
- let username = args.get(1).unwrap_or(&String::from("Guest"));
-
+ let username = args.get(1).unwrap_or(&String::from("Guest")).clone();
+
// GOOD: log message constructed with sanitized user input
- let sanitized_username = sanitize_for_logging(username);
+ let sanitized_username = sanitize_for_logging(username.as_str());
info!("User login attempt: {}", sanitized_username);
-}
\ No newline at end of file
+}
From 4328ed8fcbb19f508359c2f6ac0c022c88935824 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:10:50 +0100
Subject: [PATCH 08/13] Rust: Update suite lists.
---
.../query-suite/rust-security-and-quality.qls.expected | 1 +
.../query-suite/rust-security-extended.qls.expected | 1 +
2 files changed, 2 insertions(+)
diff --git a/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected b/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected
index a0ea519f7a19..5e1aecfab6e9 100644
--- a/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected
+++ b/rust/ql/integration-tests/query-suite/rust-security-and-quality.qls.expected
@@ -11,6 +11,7 @@ ql/rust/ql/src/queries/diagnostics/UnresolvedMacroCalls.ql
ql/rust/ql/src/queries/security/CWE-020/RegexInjection.ql
ql/rust/ql/src/queries/security/CWE-022/TaintedPath.ql
ql/rust/ql/src/queries/security/CWE-089/SqlInjection.ql
+ql/rust/ql/src/queries/security/CWE-117/LogInjection.ql
ql/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql
diff --git a/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected b/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected
index 5e2fb17298a5..88c07796c095 100644
--- a/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected
+++ b/rust/ql/integration-tests/query-suite/rust-security-extended.qls.expected
@@ -11,6 +11,7 @@ ql/rust/ql/src/queries/diagnostics/UnresolvedMacroCalls.ql
ql/rust/ql/src/queries/security/CWE-020/RegexInjection.ql
ql/rust/ql/src/queries/security/CWE-022/TaintedPath.ql
ql/rust/ql/src/queries/security/CWE-089/SqlInjection.ql
+ql/rust/ql/src/queries/security/CWE-117/LogInjection.ql
ql/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql
ql/rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql
From 9e4f59ce30775f7558fe1edc81059ee61ef806e7 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:17:38 +0100
Subject: [PATCH 09/13] Rust: Accept consistency check failures.
---
.../CWE-117/CONSISTENCY/PathResolutionConsistency.expected | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 rust/ql/test/query-tests/security/CWE-117/CONSISTENCY/PathResolutionConsistency.expected
diff --git a/rust/ql/test/query-tests/security/CWE-117/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/query-tests/security/CWE-117/CONSISTENCY/PathResolutionConsistency.expected
new file mode 100644
index 000000000000..4fafcd017b24
--- /dev/null
+++ b/rust/ql/test/query-tests/security/CWE-117/CONSISTENCY/PathResolutionConsistency.expected
@@ -0,0 +1,3 @@
+multipleCallTargets
+| main.rs:9:43:9:63 | ...::from(...) |
+| main.rs:44:19:44:32 | username.len() |
From bc0d327278774b5ada35e962d610b34ecb9ea2bd Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:42:04 +0100
Subject: [PATCH 10/13] Rust: Add log injection sinks to stats.
---
rust/ql/src/queries/summary/Stats.qll | 1 +
1 file changed, 1 insertion(+)
diff --git a/rust/ql/src/queries/summary/Stats.qll b/rust/ql/src/queries/summary/Stats.qll
index 030cd74ebe25..bada752ab2ed 100644
--- a/rust/ql/src/queries/summary/Stats.qll
+++ b/rust/ql/src/queries/summary/Stats.qll
@@ -22,6 +22,7 @@ private import codeql.rust.security.AccessInvalidPointerExtensions
private import codeql.rust.security.CleartextLoggingExtensions
private import codeql.rust.security.CleartextStorageDatabaseExtensions
private import codeql.rust.security.CleartextTransmissionExtensions
+private import codeql.rust.security.LogInjectionExtensions
private import codeql.rust.security.SqlInjectionExtensions
private import codeql.rust.security.TaintedPathExtensions
private import codeql.rust.security.UncontrolledAllocationSizeExtensions
From f05d815af904fd4038aa96be00da37c045f44f65 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 14 Aug 2025 17:59:54 +0100
Subject: [PATCH 11/13] Rust: Update the security-severity tag.
---
rust/ql/src/queries/security/CWE-117/LogInjection.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjection.ql b/rust/ql/src/queries/security/CWE-117/LogInjection.ql
index 94e8b5ec72d0..64d9c47c7909 100644
--- a/rust/ql/src/queries/security/CWE-117/LogInjection.ql
+++ b/rust/ql/src/queries/security/CWE-117/LogInjection.ql
@@ -4,7 +4,7 @@
* insertion of forged log entries by a malicious user.
* @kind path-problem
* @problem.severity error
- * @security-severity 7.8
+ * @security-severity 2.6
* @precision medium
* @id rust/log-injection
* @tags security
From 265c2e3603d802b86e2475bcb2023b9a78337c34 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 18 Aug 2025 10:29:14 +0100
Subject: [PATCH 12/13] Rust: Change note.
---
rust/ql/src/change-notes/2025-08-18-log-injection.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 rust/ql/src/change-notes/2025-08-18-log-injection.md
diff --git a/rust/ql/src/change-notes/2025-08-18-log-injection.md b/rust/ql/src/change-notes/2025-08-18-log-injection.md
new file mode 100644
index 000000000000..0d8b9eee3555
--- /dev/null
+++ b/rust/ql/src/change-notes/2025-08-18-log-injection.md
@@ -0,0 +1,4 @@
+---
+category: newQuery
+---
+* Added a new query, `rust/log-injection`, for detecting cases where log entries could be forged by a malicious user.
From e84135a6de3359cfa2b1c52d8ea47a511e624b3a Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 18 Aug 2025 10:34:43 +0100
Subject: [PATCH 13/13] Update
rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
Co-authored-by: Sophie <29382425+sophietheking@users.noreply.github.com>
---
rust/ql/src/queries/security/CWE-117/LogInjection.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp b/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
index 570a201f9638..f957d385c582 100644
--- a/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
+++ b/rust/ql/src/queries/security/CWE-117/LogInjection.qhelp
@@ -18,7 +18,7 @@ arbitrary HTML may be included to spoof log entries.
User input should be suitably sanitized before it is logged.
-If the log entries are in plain text then line breaks should be removed from user input, using
+If the log entries are in plain text, then line breaks should be removed from user input using
String::replace or similar. Care should also be taken that user input is clearly marked
in log entries.