pdftract/crates/pdftract-core/tests/th06_checksum_test.rs
jedarden c7acac5d1f feat(pdftract-4li3d): implement security constraints for serve mode
- Add startup banner with NO AUTH warning
- Add --max-decompress-gb CLI flag (default 1 GB)
- Add hard cap for --max-upload-mb at 4096 MB (4 GiB)
- Add max_decompress_gb form field parsing
- Update CLI help text with security model documentation
- Add comprehensive security model docs to serve.rs rustdoc

This implements the security constraints required by the bead:
- No built-in authentication (deploy behind reverse proxy)
- No file-path parameters (multipart upload only)
- Hard caps to prevent integer overflow
- Visible security warnings at startup

Closes: pdftract-4li3d
2026-05-26 18:47:51 -04:00

135 lines
4.6 KiB
Rust

//! TH-06 supply-chain gate tests for build-time data file checksums.
//!
//! This test module verifies that the build.rs checksum verification works
//! correctly. It tests both the normal case (all checksums match) and the
//! tampering case (checksum mismatch aborts the build).
//!
//! Bead: pdftract-1xf4d (TH-06 supply-chain gate)
//! Plan: line 909 (Build-time data files checksum pin)
use std::fs;
use std::path::Path;
/// Helper to compute SHA-256 checksum of a file.
fn compute_sha256(path: &Path) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
let contents = fs::read(path).unwrap();
hasher.update(&contents);
format!("{:x}", hasher.finalize())
}
/// Test that tampering with a build-time data file aborts the build.
///
/// This test verifies the TH-06 supply-chain gate by:
/// 1. Backing up the original std14-metrics.json
/// 2. Tampering with it (writing a single byte change)
/// 3. Attempting to build pdftract-core (should fail with checksum error)
/// 4. Restoring the original file
#[test]
fn test_tampering_detection() {
// Skip this test in CI environments where we don't want to modify build files
if std::env::var("CI").is_ok() {
println!("Skipping tampering test in CI environment");
return;
}
let build_dir = Path::new("crates/pdftract-core/build");
let test_file = build_dir.join("std14-metrics.json");
let backup_file = build_dir.join("std14-metrics.json.backup");
// Skip if the test file doesn't exist
if !test_file.exists() {
println!("Skipping tampering test: {} not found", test_file.display());
return;
}
// Backup the original file
let original_contents = fs::read(&test_file).unwrap();
fs::write(&backup_file, &original_contents).unwrap();
// Tamper with the file (change a single byte)
let mut tampered_contents = original_contents.clone();
if !tampered_contents.is_empty() {
tampered_contents[0] = tampered_contents[0].wrapping_add(1);
}
fs::write(&test_file, &tampered_contents).unwrap();
// Verify the checksum actually changed
let original_checksum = compute_sha256(&backup_file);
let tampered_checksum = compute_sha256(&test_file);
assert_ne!(
original_checksum, tampered_checksum,
"Tampering should change the checksum"
);
// Attempt to build pdftract-core - should fail with checksum error
let output = std::process::Command::new("cargo")
.args(["build", "--package", "pdftract-core"])
.output()
.unwrap();
// Restore the original file immediately
fs::write(&test_file, &original_contents).unwrap();
fs::remove_file(&backup_file).unwrap();
// Verify the build failed due to checksum mismatch
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let combined_output = format!("{}{}", stderr, stdout);
// The build should fail (non-zero exit code)
assert!(
!output.status.success(),
"Build should fail when checksums don't match.\nstdout:\n{}\nstderr:\n{}",
stdout,
stderr
);
// The error message should mention checksum verification
assert!(
combined_output.contains("checksum")
|| combined_output.contains("Checksum")
|| combined_output.contains("CHECKSUMS"),
"Error message should mention checksum verification.\nOutput:\n{}",
combined_output
);
// Verify the file was restored correctly
let restored_checksum = compute_sha256(&test_file);
assert_eq!(
original_checksum, restored_checksum,
"File should be restored to original state"
);
}
/// Test that normal build succeeds when all checksums match.
///
/// This is a sanity check that the checksum verification doesn't
/// incorrectly fail when all files are intact.
#[test]
fn test_normal_build_checksums_pass() {
// This test just verifies that a clean build succeeds
// If checksums are wrong, the build will fail and this test will fail
let output = std::process::Command::new("cargo")
.args(["check", "--package", "pdftract-core"])
.output()
.unwrap();
let stderr = String::from_utf8_lossy(&output.stderr);
// The build should succeed
assert!(
output.status.success(),
"Normal build should succeed when checksums match.\nstderr:\n{}",
stderr
);
// Should not contain checksum error messages
assert!(
!stderr.contains("Checksum verification failed"),
"Normal build should not report checksum failures.\nstderr:\n{}",
stderr
);
}