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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Fixed

- AUR review hardening from the adapter review follow-up: per-commit
`.SRCINFO` reads distinguish content failures (missing, oversized,
non-UTF-8, malformed — counted as skips) from git plumbing failures
(propagated fail-closed, so object corruption can no longer masquerade as
"no parseable .SRCINFO"); `Package.version` now stores the pinned commit's
canonical version instead of the user's spelling, so vercmp aliases like
`1.1.0-01` resolve to identical store keys and displayed identities; the
extracted `.SRCINFO`'s declared `pkgbase` is cross-checked against the
resolved package base at the review boundary; `list_versions` keeps the
adapter's vercmp ordering instead of re-sorting with semver prerelease
rules; new tests pin the deterministic equal-timestamp hash tiebreak, the
split-package pkgname → pkgbase path, directory-not-file refusals, and the
pkgbase mismatch refusal.
- The MCP `check_known_clean` AUR arm compares the requested version against
stored clean versions with libalpm vercmp equality instead of canonical
strings, so grammar-accepted spellings of an approved release (pkgrel-less
`12.4.2`, epoch-explicit `0:12.4.2-1`) now report `isClean: true` against a
stored `12.4.2-1` instead of a false negative; an unparseable AUR version is
now an `invalid params` error rather than a guaranteed not-clean.
- AUR review follow-up fixes: R05 no longer runs the semver leg on AUR
versions (two-component `1.0-1` spellings are normal, validated with
`AurVersionInfo` instead); split-package pkgnames fail closed with a
pointer to review the pkgbase explicitly instead of silently swapping
identity; `release_author` pins the clone URL and verifies the commit
before reading the author email; R13 spots spaceless pipes (`curl x|bash`)
and fetcher pipes into `python`/`perl`/`ruby`/`php`.
- Release workflow smoke gate invokes the shipped binary with
`--policy blueline.toml --output json --yes` and asserts on the presence of
the `integrity` field, matching the current CLI flags and the 0.3.0
Expand Down
59 changes: 58 additions & 1 deletion src/heuristic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,21 @@ pub fn evaluate_with_trust(

// R05: Large diff anomaly on patch or non-standard semver
if let Some(base_ver_str) = &delta.baseline_version {
if ecosystem == Ecosystem::PyPi {
if ecosystem == Ecosystem::Aur {
let base_ok = crate::version::AurVersionInfo::parse(base_ver_str).is_ok();
let target_ok = crate::version::AurVersionInfo::parse(&delta.target_version).is_ok();
if !(base_ok && target_ok) {
findings.push(Finding {
rule_id: "R05_NON_STANDARD_VERSION".into(),
severity: VerdictBand::Medium,
title: "Non-standard version format".into(),
description: format!(
"Baseline `{base_ver_str}` or target `{}` does not conform to the AUR version grammar.",
delta.target_version
),
});
}
} else if ecosystem == Ecosystem::PyPi {
if let (Ok(base_v), Ok(target_v)) = (
crate::version::Pep440Version::parse(base_ver_str),
crate::version::Pep440Version::parse(&delta.target_version),
Expand Down Expand Up @@ -3276,4 +3290,47 @@ mod tests {
);
assert_eq!(verdict.band, VerdictBand::Low);
}

#[test]
fn aur_two_component_versions_are_not_non_standard() {
let delta = Delta {
baseline_version: Some("1.0-1".into()),
target_version: "1.0-2".into(),
files_added: vec![],
files_removed: vec![],
files_modified: vec![],
total_lines_added: 1,
total_lines_deleted: 0,
new_executables: vec![],
new_binaries: vec![],
modified_binaries: vec![],
new_lifecycle_scripts: vec![],
modified_lifecycle_scripts: vec![],
new_dependencies: vec![],
modified_dependencies: vec![],
removed_dependencies: vec![],
binding_gyp_added: false,
};
let verdict = evaluate_with_trust(
"test-pkg",
Ecosystem::Aur,
"sha256:abc",
&delta,
false,
false,
false,
false,
&Policy::default(),
None,
None,
);
assert!(
!verdict
.findings
.iter()
.any(|f| f.rule_id == "R05_NON_STANDARD_VERSION"),
"ordinary AUR versions must not trip R05, got {:?}",
verdict.findings
);
}
}
19 changes: 15 additions & 4 deletions src/pkgbuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1822,10 +1822,13 @@ fn line_matches_pipe_to_shell(line: &str) -> bool {
if !fetcher {
return false;
}
let shell_hits = ["bash", "sh", "dash", "zsh", "fish"].iter().any(|shell| {
right
.split_whitespace()
.any(|word| word.trim_matches(';') == *shell)
let interpreters = [
"bash", "sh", "dash", "zsh", "fish", "python", "python3", "perl", "ruby", "php",
];
let shell_hits = interpreters.iter().any(|shell| {
right.split_whitespace().any(|word| {
word.trim_matches(['|', '&', ';', '"', '\'', '(', ')', ',', '`', '{', '}']) == *shell
})
});
fetcher && shell_hits
}
Expand Down Expand Up @@ -2558,6 +2561,14 @@ mod tests {
assert!(has_rule(&findings, "R13_PIPE_TO_SHELL"));
}

#[test]
fn r13_catches_spaceless_pipe_and_script_interpreters() {
let findings = findings_for("build() {\n curl https://x|bash\n}\n");
assert!(has_rule(&findings, "R13_PIPE_TO_SHELL"));
let findings = findings_for("build() {\n curl https://x | python3\n}\n");
assert!(has_rule(&findings, "R13_PIPE_TO_SHELL"));
}

#[test]
fn indexed_assignment_overrides_slot() {
let folded = parse_pkgbuild("sha256sums=(aaa bbb)\nsha256sums[1]='SKIP'\n").unwrap();
Expand Down
Loading
Loading