Merge pull request #20221 from github/copilot/fix-20220

Rust: Implement a new query for Log Injection
This commit is contained in:
Geoffrey White
2025-08-22 14:01:49 +01:00
committed by GitHub
15 changed files with 2066 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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") }
}
}

View File

@@ -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.

View File

@@ -0,0 +1,47 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If unsanitized user input is written to a log entry, a malicious user may be able to forge new log entries.</p>
<p>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.</p>
</overview>
<recommendation>
<p>
User input should be suitably sanitized before it is logged.
</p>
<p>
If the log entries are in plain text, then line breaks should be removed from user input using
<code>String::replace</code> or similar. Care should also be taken that user input is clearly marked
in log entries.
</p>
<p>
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.
</p>
</recommendation>
<example>
<p>In the first example, a username, provided by the user via command line arguments, is logged using the <code>log</code> crate.
If a malicious user provides <code>Guest\n[INFO] User: Admin\n</code> as a username parameter,
the log entry will be split into multiple lines, where the second line will appear as <code>[INFO] User: Admin</code>,
potentially forging a legitimate admin login entry.
</p>
<sample src="LogInjectionBad.rs" />
<p>In the second example, <code>String::replace</code> is used to ensure no line endings are present in the user input before logging.</p>
<sample src="LogInjectionGood.rs" />
</example>
<references>
<li>OWASP: <a href="https://owasp.org/www-community/attacks/Log_Injection">Log Injection</a>.</li>
</references>
</qhelp>

View File

@@ -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 2.6
* @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<LogInjectionConfig>;
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"

View File

@@ -0,0 +1,13 @@
use std::env;
use log::info;
fn main() {
env_logger::init();
// Get username from command line arguments
let args: Vec<String> = env::args().collect();
let username = args.get(1).unwrap_or(&String::from("Guest")).clone();
// BAD: log message constructed with unsanitized user input
info!("User login attempt: {}", username);
}

View File

@@ -0,0 +1,19 @@
use std::env;
use log::info;
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<String> = env::args().collect();
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.as_str());
info!("User login attempt: {}", sanitized_username);
}

View File

@@ -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

View File

@@ -0,0 +1,3 @@
multipleCallTargets
| main.rs:9:43:9:63 | ...::from(...) |
| main.rs:44:19:44:32 | username.len() |

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
#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: <core::option::Option>::unwrap_or_default; Argument[self].Field[core::option::Option::Some(0)]; ReturnValue; value |
| 9 | Summary: <core::result::Result>::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
| 10 | Summary: <core::result::Result>::unwrap_or; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
| 11 | Summary: <core::result::Result>::unwrap_or_default; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
| 12 | Summary: <reqwest::blocking::response::Response>::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

View File

@@ -0,0 +1,4 @@
query: queries/security/CWE-117/LogInjection.ql
postprocess:
- utils/test/PrettyPrintModels.ql
- utils/test/InlineExpectationsTestQuery.ql

View File

@@ -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<String> = env::args().collect();
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") // $ Source=remote
.unwrap().text().unwrap_or("remote_user".to_string());
// BAD: Direct logging of user input
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); // $ MISSING: Alert[rust/log-injection]
// BAD: String concatenation with user input
let concat_msg = "User activity: ".to_string() + &username;
info!("{}", concat_msg); // $ MISSING: Alert[rust/log-injection]
// BAD: Complex formatting
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);
}
fn test_complex_scenarios(username: &str, user_input: &str) {
// BAD: Indirect logging through variables
let log_message = format!("Activity for {}", username);
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); // $ 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); // $ MISSING: 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); // $ 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); // $ 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); // $ 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]=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]=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); // $ Alert[rust/log-injection]=environment
eprintln!("Error for user: {}", user_data); // $ Alert[rust/log-injection]=environment
}
}

View File

@@ -0,0 +1,5 @@
qltest_cargo_check: true
qltest_dependencies:
- log = "0.4"
- env_logger = "0.10"
- reqwest = { version = "0.12.9", features = ["blocking"] }