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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ venv/
.cache/
tmp/
temp/

# Local recovery and delegated-work areas
/backups/
/worktrees/
/CODEX_STATE_AUDIT_*.md
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ This project follows a simple chronological changelog. It is a learning reposito
- Replaced the static web starter with a Rust-first staging project.
- Clarified that funding metadata is inactive until GitHub exposes active funding links.
- Hardened repository checks for locked Cargo validation, unsafe-code rejection, secret-pattern screening, pinned workflow actions, and explicitly redacted evidence artifacts.
- Kept local backup and delegated-work directories out of Git and the repository
doctor so preserved external checkouts cannot create false secret findings.
- Expanded sensitive-filename checks to cover environment variants, credential
manifests, private-key files, and key-container formats.
- Reconciled the roadmap with completed issue and pull request practice.
- Promoted the Rust-native repository doctor in the README with current checks and sample passing output.

Expand Down
37 changes: 35 additions & 2 deletions src/bin/check_repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const SKIPPED_DIRS: &[&str] = &[
".git",
".venv",
"__pycache__",
"backups",
"build",
"coverage",
"dist",
Expand All @@ -77,6 +78,7 @@ const SKIPPED_DIRS: &[&str] = &[
"target",
"tmp",
"temp",
"worktrees",
];

const BINARY_SUFFIXES: &[&str] = &["gif", "ico", "jpeg", "jpg", "pdf", "png", "webp"];
Expand All @@ -87,12 +89,16 @@ const BLOCKED_FILENAMES: &[&str] = &[
".npmrc",
".pypirc",
".netrc",
"credentials.json",
"id_dsa",
"id_ecdsa",
"id_ed25519",
"id_rsa",
"service-account.json",
];

const BLOCKED_SECRET_SUFFIXES: &[&str] = &[".jks", ".key", ".keystore", ".p12", ".pfx"];

const PRIVATE_KEY_PREFIX: &str = "-----BEGIN ";
const PRIVATE_KEY_WORDS: [&str; 2] = ["PRIVATE ", "KEY-----"];

Expand Down Expand Up @@ -178,7 +184,7 @@ fn check_sensitive_files(root: &Path, failures: &mut Vec<String>) {
let relative_path = relative_path(root, &file_path);

if let Some(file_name) = file_path.file_name().and_then(|name| name.to_str()) {
if BLOCKED_FILENAMES.contains(&file_name) {
if is_blocked_filename(file_name) {
failures.push(format!("{relative_path} is a blocked sensitive filename"));
}
}
Expand Down Expand Up @@ -348,6 +354,15 @@ fn should_skip_dir(root: &Path, path: &Path) -> bool {
.is_some_and(|first_part| SKIPPED_DIRS.contains(&first_part))
}

fn is_blocked_filename(file_name: &str) -> bool {
let lower_name = file_name.to_ascii_lowercase();
BLOCKED_FILENAMES.contains(&lower_name.as_str())
|| (lower_name.starts_with(".env.") && lower_name != ".env.example")
|| BLOCKED_SECRET_SUFFIXES
.iter()
.any(|suffix| lower_name.ends_with(suffix))
}

fn is_binary_file(path: &Path) -> bool {
path.extension()
.and_then(|extension| extension.to_str())
Expand Down Expand Up @@ -430,8 +445,9 @@ fn relative_path(root: &Path, path: &Path) -> String {
mod tests {
use super::{
contains_aws_access_key, contains_prefixed_token, has_generic_secret_assignment,
is_full_sha,
is_blocked_filename, is_full_sha, should_skip_dir,
};
use std::path::Path;

#[test]
fn detects_github_token_shape() {
Expand All @@ -457,4 +473,21 @@ mod tests {
assert!(is_full_sha("9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"));
assert!(!is_full_sha("v4"));
}

#[test]
fn blocks_environment_and_key_container_filenames() {
assert!(is_blocked_filename(".env.production"));
assert!(is_blocked_filename("service-account.json"));
assert!(is_blocked_filename("signing.P12"));
assert!(!is_blocked_filename(".env.example"));
assert!(!is_blocked_filename("public-certificate.pem"));
}

#[test]
fn skips_only_reserved_top_level_local_work_areas() {
let root = Path::new("/repo");
assert!(should_skip_dir(root, &root.join("worktrees/transmission")));
assert!(should_skip_dir(root, &root.join("backups/archive")));
assert!(!should_skip_dir(root, &root.join("docs/worktrees")));
}
}