Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::InstallIntent;
use crate::pypi_certificate_store_authority::matches_pip_certificate_store_abbreviation;
use crate::pypi_client_certificate_authority::matches_pip_client_certificate_option;
use crate::pypi_log_output_authority::matches_pip_log_option;
use crate::pypi_proxy_authority::{
is_attached_direct_pip_proxy_selector, is_direct_pip_proxy_value_selector,
};
Expand Down Expand Up @@ -123,6 +124,26 @@ pub(crate) fn normalize_reviewed_direct_pip_global_options(
continue;
}

if matches_pip_log_option(argument) {
if let Some((_, value)) = argument.split_once('=') {
if value.is_empty() {
return None;
}
reviewed_global_arguments.push(arguments[index].clone());
index += 1;
} else {
// A separate log path is consumed General Option grammar. Attach
// it only in the internal policy copy so it cannot masquerade as
// an artifact operand; the submitted argv/hash stay authoritative.
push_attached_normalized_value_argument(
arguments,
&mut reviewed_global_arguments,
&mut index,
)?;
}
continue;
}

if matches_pip_python_interpreter_option(argument) {
let value = if let Some((_, value)) = argument.split_once('=') {
if value.is_empty() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub(crate) fn requests_unapproved_pypi_log_output_authority(intent: &InstallInte
/// pip uses Python optparse, which accepts unambiguous long-option prefixes.
/// Keep this accepted-language set explicit so an ambiguous prefix such as
/// `--lo` is not reinterpreted by Wardnet as valid caller authority.
fn matches_pip_log_option(argument: &str) -> bool {
pub(crate) fn matches_pip_log_option(argument: &str) -> bool {
let option = argument.split_once('=').map_or(argument, |(name, _)| name);
matches!(
option,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use wardnet_agent_artifact_admission::{
AdmissionPolicy, ApprovedArtifact, ApprovedManifest, ArtifactCoordinate, DecisionKind,
InstallIntent, InstructionSource, InstructionSourceKind, admission_decision,
InstallIntent, InstructionSource, InstructionSourceKind, admission_decision, sha256_hex,
};

#[test]
Expand Down Expand Up @@ -50,6 +50,66 @@ fn approved_pip_install_cannot_gain_caller_selected_log_write_authority() {
}
}

#[test]
fn valid_global_pip_log_options_remain_causal_write_authority_evidence() {
for executable in ["pip", "pip3"] {
let (policy, control_intent) = approved_pip_install(executable);

for global_log_arguments in [
vec!["--log=/tmp/wardnet-pip.log"],
vec!["--log", "/tmp/wardnet-pip.log"],
vec!["--log-file=/tmp/wardnet-pip.log"],
vec!["--log-file", "/tmp/wardnet-pip.log"],
vec!["--local-log=/tmp/wardnet-pip.log"],
vec!["--local-log", "/tmp/wardnet-pip.log"],
vec!["--log-f", "/tmp/wardnet-pip.log"],
vec!["--loc=/tmp/wardnet-pip.log"],
] {
let mut intent = control_intent.clone();
let mut argv = Vec::with_capacity(intent.argv.len() + global_log_arguments.len());
argv.push(executable.to_string());
argv.extend(
global_log_arguments
.iter()
.map(|argument| (*argument).to_string()),
);
argv.extend(control_intent.argv.iter().skip(1).cloned());
intent.argv = argv;
let submitted_argv = intent.argv.clone();

let decision = admission_decision(&policy, &intent);
assert_eq!(
decision.decision,
DecisionKind::Block,
"parser-valid global {executable} {} must fail closed",
global_log_arguments.join(" ")
);
assert!(
decision
.reason_codes
.iter()
.any(|reason| reason.as_str() == "alternate_install_root"),
"parser-valid global {executable} {} must retain causal log-write evidence: {:?}",
global_log_arguments.join(" "),
decision.reason_codes
);
assert!(
!decision
.reason_codes
.iter()
.any(|reason| reason.as_str() == "artifact_not_approved"),
"the log path consumed by pip General Options must not masquerade as an artifact operand: {:?}",
decision.reason_codes
);
assert_eq!(
decision.command_sha256,
sha256_hex(submitted_argv.join("\u{1f}").as_bytes()),
"policy normalization must preserve the exact submitted argv audit identity"
);
}
}
}

fn approved_pip_install(executable: &str) -> (AdmissionPolicy, InstallIntent) {
let artifact = ArtifactCoordinate {
ecosystem: "pypi".to_string(),
Expand Down
Loading