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
Expand Up @@ -36,6 +36,13 @@ pub(crate) fn requests_unapproved_pypi_cache_directory_authority(intent: &Instal
}
}

/// Match only the canonical pip General Option spelling. Pre-command option
/// normalization intentionally does not inherit install-parser abbreviations
/// without separate upstream evidence for that parser phase.
pub(crate) fn matches_canonical_pip_cache_directory_option(argument: &str) -> bool {
argument.split_once('=').map_or(argument, |(name, _)| name) == "--cache-dir"
}

/// pip uses Python optparse, which accepts an unambiguous long-option prefix.
/// `--ca` is the shortest prefix of `--cache-dir` that does not collide with
/// another current `pip install` long option at the reviewed upstream commit.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::InstallIntent;
use crate::pypi_cache_directory_authority::matches_canonical_pip_cache_directory_option;
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;
Expand Down Expand Up @@ -124,6 +125,26 @@ pub(crate) fn normalize_reviewed_direct_pip_global_options(
continue;
}

if matches_canonical_pip_cache_directory_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 cache 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_log_option(argument) {
if let Some((_, value)) = argument.split_once('=') {
if value.is_empty() {
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 @@ -46,6 +46,60 @@ fn approved_pip_install_cannot_gain_caller_selected_cache_directory_authority()
}
}

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

for global_cache_arguments in [
vec!["--cache-dir=/tmp/wardnet-pip-cache"],
vec!["--cache-dir", "/tmp/wardnet-pip-cache"],
] {
let mut intent = control_intent.clone();
let mut argv = Vec::with_capacity(intent.argv.len() + global_cache_arguments.len());
argv.push(executable.to_string());
argv.extend(
global_cache_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_cache_arguments.join(" ")
);
assert!(
decision
.reason_codes
.iter()
.any(|reason| reason.as_str() == "alternate_install_root"),
"parser-valid global {executable} {} must retain causal cache-directory evidence: {:?}",
global_cache_arguments.join(" "),
decision.reason_codes
);
assert!(
!decision
.reason_codes
.iter()
.any(|reason| reason.as_str() == "artifact_not_approved"),
"the cache 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