#![cfg(unix)]
use super::scan_version;
use crate::candidate_object::{ObjectId, parse_object_id};
use crate::candidate_store::CandidateStore;
use crate::candidate_version::{CandidateSource, CandidateVersion};
use crate::git_metadata::strip_git_line;
use crate::scanner_adapter::{CandidateScanner, RulesSource};
use crate::scanner_test_support::{needle, rules_file, run_isolated};
use crate::test_support::{REAL_GIT, git, repository};
use forbidden_strings::{CandidateScan, ScanFinding};
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::rc::Rc;
pub(super) fn loaded(rules: &Path) -> CandidateScanner {
let source: RulesSource = RulesSource {
path: rules.to_path_buf(),
explicit: true,
};
match CandidateScanner::load(&source, false) {
Ok(scanner) => return scanner,
Err(error) => panic!("planted rules must load: {error}"),
}
}
pub(super) fn store(repository: &Path) -> CandidateStore {
return CandidateStore::new(
Path::new(REAL_GIT),
&[OsString::from("-C"), repository.as_os_str().to_os_string()],
&[],
);
}
pub(super) fn findings_at(
version: &CandidateVersion,
scans: &[CandidateScan],
path: &[u8],
) -> Vec<ScanFinding> {
let index: usize = match version.candidate_at_path(path) {
Some(candidate) => candidate.identity.index,
None => panic!("no candidate at {:?}", String::from_utf8_lossy(path)),
};
for scan in scans {
if scan.identity == index {
return scan.findings.clone();
}
}
panic!(
"candidate at {:?} was not scanned",
String::from_utf8_lossy(path)
);
}
pub(super) fn was_scanned(
version: &CandidateVersion,
scans: &[CandidateScan],
path: &[u8],
) -> bool {
let index: usize = match version.candidate_at_path(path) {
Some(candidate) => candidate.identity.index,
None => panic!("no candidate at {:?}", String::from_utf8_lossy(path)),
};
for scan in scans {
if scan.identity == index {
return true;
}
}
return false;
}
fn staged_bytes_are_scanned_not_the_worktree_body(directory: &Path) {
let scanner: CandidateScanner = loaded(rules_file(directory).as_path());
let token: String = needle();
let leak: String = format!("safe line\n{token}\n");
let repo: PathBuf = repository(directory, "repo");
std::fs::write(repo.join("worktree-only.txt"), "clean\n").expect("clean file");
std::fs::write(repo.join("staged-only.txt"), leak.as_str()).expect("leaking file");
std::fs::write(repo.join("staged-then-removed.txt"), leak.as_str()).expect("leaking file");
std::fs::write(repo.join("clean.txt"), "clean\n").expect("clean file");
std::os::unix::fs::symlink(token.as_str(), repo.join("link"))
.expect("link whose target is the token");
git(repo.as_path(), &["add", "--all"]);
std::fs::write(repo.join("worktree-only.txt"), leak.as_str()).expect("worktree edit");
std::fs::write(repo.join("staged-only.txt"), "clean\n").expect("worktree edit");
std::fs::remove_file(repo.join("staged-then-removed.txt")).expect("worktree removal");
std::fs::write(repo.join("never-staged.txt"), leak.as_str()).expect("untracked file");
let mut subject: CandidateStore = store(repo.as_path());
let version: Rc<CandidateVersion> = subject
.version(&CandidateSource::StagedAgainstHead)
.expect("staged version");
let scans: Vec<CandidateScan> =
scan_version(&scanner, &mut subject, &version, None).expect("scan pass");
let reported: Vec<ScanFinding> = vec![ScanFinding::Content {
line: 2,
rule: String::from("0"),
}];
assert_eq!(scans.len(), 5);
assert_eq!(
findings_at(&version, &scans, b"worktree-only.txt"),
Vec::new()
);
assert_eq!(findings_at(&version, &scans, b"clean.txt"), Vec::new());
assert_eq!(findings_at(&version, &scans, b"staged-only.txt"), reported);
assert_eq!(
findings_at(&version, &scans, b"staged-then-removed.txt"),
reported
);
assert_eq!(
findings_at(&version, &scans, b"link"),
vec![ScanFinding::Content {
line: 1,
rule: String::from("0")
}]
);
assert!(version.candidate_at_path(b"never-staged.txt").is_none());
assert!(!format!("{scans:?}").contains(token.as_str()));
}
#[test]
fn staged_bytes_are_scanned_not_the_worktree() {
run_isolated(
"scanner_run::tests::staged_bytes_are_scanned_not_the_worktree",
"run-staged",
staged_bytes_are_scanned_not_the_worktree_body,
);
}
fn committed_bytes_are_scanned_not_the_worktree_body(directory: &Path) {
let scanner: CandidateScanner = loaded(rules_file(directory).as_path());
let token: String = needle();
let leak: String = format!("{token}\n");
let repo: PathBuf = repository(directory, "repo");
std::fs::write(repo.join("committed-leak.txt"), leak.as_str()).expect("leaking file");
std::fs::write(repo.join("committed-clean.txt"), "clean\n").expect("clean file");
std::fs::create_dir(repo.join(token.as_str())).expect("directory named by the token");
std::fs::write(repo.join(token.as_str()).join("inside.txt"), "clean\n").expect("file in it");
git(repo.as_path(), &["add", "--all"]);
git(repo.as_path(), &["commit", "--quiet", "--message=landed"]);
let landed: ObjectId = parse_object_id(strip_git_line(
git(repo.as_path(), &["rev-parse", "--verify", "HEAD"])
.stdout
.as_slice(),
))
.expect("commit name");
std::fs::write(repo.join("committed-leak.txt"), "clean\n").expect("worktree edit");
std::fs::write(repo.join("committed-clean.txt"), leak.as_str()).expect("worktree edit");
git(repo.as_path(), &["add", "--all"]);
let mut subject: CandidateStore = store(repo.as_path());
let version: Rc<CandidateVersion> = subject
.version(&CandidateSource::Committed(landed))
.expect("committed version");
let scans: Vec<CandidateScan> =
scan_version(&scanner, &mut subject, &version, None).expect("scan pass");
assert_eq!(
findings_at(&version, &scans, b"committed-leak.txt"),
vec![ScanFinding::Content {
line: 1,
rule: String::from("0")
}]
);
assert_eq!(
findings_at(&version, &scans, b"committed-clean.txt"),
Vec::new()
);
let inside: Vec<u8> = format!("{token}/inside.txt").into_bytes();
assert_eq!(
findings_at(&version, &scans, inside.as_slice()),
vec![ScanFinding::Name {
component: 1,
rule: String::from("0")
}]
);
assert!(!format!("{scans:?}").contains(token.as_str()));
}
#[test]
fn committed_bytes_are_scanned_not_the_worktree() {
run_isolated(
"scanner_run::tests::committed_bytes_are_scanned_not_the_worktree",
"run-committed",
committed_bytes_are_scanned_not_the_worktree_body,
);
}