use anyhow::Result;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
pub fn validate_identity_file_security(path: &Path, line_number: usize) -> Result<()> {
let path_str = path.to_string_lossy();
let sensitive_patterns = [
"/etc/passwd",
"/etc/shadow",
"/etc/group",
"/proc/",
"/sys/",
"/dev/",
"/boot/",
"/usr/bin/",
"/bin/",
"/sbin/",
"\\Windows\\",
"\\System32\\",
"\\Program Files\\",
];
for pattern in &sensitive_patterns {
if path_str.contains(pattern) {
anyhow::bail!(
"Security violation: Identity file path '{path_str}' at line {line_number} points to sensitive system location. \
Access to system files is not allowed for security reasons."
);
}
}
#[cfg(unix)]
if path.exists()
&& path.is_file()
&& let Ok(metadata) = std::fs::metadata(path)
{
let permissions = metadata.permissions();
let mode = permissions.mode();
if mode & 0o004 != 0 {
tracing::warn!(
"Security warning: Identity file '{}' at line {} is world-readable. \
Private SSH keys should not be readable by other users (chmod 600 recommended).",
path_str,
line_number
);
}
if mode & 0o040 != 0 {
tracing::warn!(
"Security warning: Identity file '{}' at line {} is group-readable. \
Private SSH keys should only be readable by the owner (chmod 600 recommended).",
path_str,
line_number
);
}
if mode & 0o002 != 0 {
anyhow::bail!(
"Security violation: Identity file '{path_str}' at line {line_number} is world-writable. \
This is extremely dangerous and must be fixed immediately."
);
}
}
Ok(())
}
pub fn validate_known_hosts_file_security(path: &Path, line_number: usize) -> Result<()> {
let path_str = path.to_string_lossy();
let sensitive_patterns = [
"/etc/passwd",
"/etc/shadow",
"/etc/group",
"/proc/",
"/sys/",
"/dev/",
"/boot/",
"/usr/bin/",
"/bin/",
"/sbin/",
"\\Windows\\",
"\\System32\\",
"\\Program Files\\",
];
for pattern in &sensitive_patterns {
if path_str.contains(pattern) {
anyhow::bail!(
"Security violation: Known hosts file path '{path_str}' at line {line_number} points to sensitive system location. \
Access to system files is not allowed for security reasons."
);
}
}
let path_lower = path_str.to_lowercase();
if !path_lower.contains("ssh")
&& !path_lower.contains("known")
&& !path_str.contains("/.")
&& !path_str.starts_with("/etc/ssh/")
&& !path_str.starts_with("/usr/")
&& !path_str.contains("/home/")
&& !path_str.contains("/Users/")
{
tracing::warn!(
"Security warning: Known hosts file '{}' at line {} is in an unusual location. \
Ensure this is intentional and the file is trustworthy.",
path_str,
line_number
);
}
Ok(())
}
pub fn validate_certificate_file_security(path: &Path, line_number: usize) -> Result<()> {
let path_str = path.to_string_lossy();
let forbidden_patterns = [
"/etc/passwd",
"/etc/shadow",
"/etc/group",
"/etc/sudoers",
"/etc/master.passwd", "/etc/security/",
"/proc/",
"/sys/",
"/dev/",
"/boot/",
"/usr/bin/",
"/bin/",
"/sbin/",
"\\Windows\\System32\\",
"\\Windows\\SysWOW64\\",
"\\SAM", ".bash_history",
".zsh_history",
".mysql_history",
".psql_history",
"id_rsa", "id_dsa",
"id_ecdsa",
"id_ed25519",
];
for pattern in &forbidden_patterns {
if path_str.contains(pattern) {
if pattern.starts_with("id_") {
if path_str.ends_with("-cert.pub")
|| path_str.ends_with("_cert.pub")
|| path_str.ends_with("-cert.pem")
{
continue; }
if path_str.ends_with(pattern) || path_str.ends_with(&format!("{pattern}.pub")) {
anyhow::bail!(
"Security violation: Certificate file path '{path_str}' at line {line_number} appears to be a private key or regular public key. \
SSH certificate files should end with '-cert.pub' or similar suffix. Use CertificateFile for certificates, not regular keys."
);
}
continue; }
anyhow::bail!(
"Security violation: Certificate file path '{path_str}' at line {line_number} points to forbidden system location. \
System files and sensitive locations cannot be used as SSH certificates."
);
}
}
if !path_str.ends_with(".pub")
&& !path_str.ends_with(".pem")
&& !path_str.ends_with(".crt")
&& !path_str.ends_with(".cert")
{
tracing::warn!(
"Security warning: Certificate file '{}' at line {} has an unusual extension. \
SSH certificates typically end with '.pub', '-cert.pub', '.pem', or '.crt'.",
path_str,
line_number
);
}
let path_lower = path_str.to_lowercase();
if !path_lower.contains("ssh")
&& !path_lower.contains("cert")
&& !path_str.contains("/.") && !path_str.starts_with("/etc/ssh/")
&& !path_str.starts_with("/usr/")
&& !path_str.contains("/home/")
&& !path_str.contains("/Users/") && !path_str.contains("/var/lib/")
{
tracing::warn!(
"Security warning: Certificate file '{}' at line {} is in an unusual location. \
Ensure this is intentional and the file is a valid SSH certificate.",
path_str,
line_number
);
}
Ok(())
}
pub fn validate_general_file_security(path: &Path, line_number: usize) -> Result<()> {
let path_str = path.to_string_lossy();
let forbidden_patterns = [
"/etc/passwd",
"/etc/shadow",
"/etc/group",
"/etc/sudoers",
"/proc/",
"/sys/",
"/dev/random",
"/dev/urandom",
"/boot/",
"/usr/bin/",
"/bin/",
"/sbin/",
"\\Windows\\System32\\",
"\\Windows\\SysWOW64\\",
];
for pattern in &forbidden_patterns {
if path_str.contains(pattern) {
anyhow::bail!(
"Security violation: File path '{path_str}' at line {line_number} points to forbidden system location. \
Access to this location is not allowed for security reasons."
);
}
}
Ok(())
}