From 7c063160a2fe4821d85afea428e7ac6a6ee222c8 Mon Sep 17 00:00:00 2001 From: Black Circle Sentinel Date: Sat, 7 Feb 2026 00:21:33 -0500 Subject: [PATCH 1/6] auto-claude: subtask-1-1 - Add check_ssh_keys() function to scan_permissions. Implements CHK-PRM-013 to check SSH private key file permissions. Scans ~/.ssh for id_* files and *.pem files, ensuring they have 600 permissions. Follows existing patterns from check_openclaw_json and check_wallet_files. Co-Authored-By: Claude Sonnet 4.5 --- scripts/scan_permissions.sh | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/scripts/scan_permissions.sh b/scripts/scan_permissions.sh index 168f4e1..d07bae7 100755 --- a/scripts/scan_permissions.sh +++ b/scripts/scan_permissions.sh @@ -646,6 +646,52 @@ check_cloud_sync() { fi } +# ─── CHK-PRM-013: SSH private key permissions ─────────────────────────────── + +check_ssh_keys() { + log_info "CHK-PRM-013: Checking SSH private key permissions" + + local ssh_dir="$HOME/.ssh" + local found=0 + + if [[ ! -d "$ssh_dir" ]]; then + add_finding "CHK-PRM-013" "info" \ + "No SSH directory found" \ + "No ~/.ssh directory exists on this system" "" "" "" + return + fi + + # Search for id_* files and *.pem files + while IFS= read -r -d '' f; do + found=1 + local perms + perms="$(get_perms "$f")" + [[ -z "$perms" ]] && continue + + if [[ "$perms" != "600" ]]; then + add_finding "CHK-PRM-013" "critical" \ + "SSH private key has insecure permissions" \ + "SSH private keys must be chmod 600 or SSH clients will refuse to use them. Current: $perms" \ + "$f mode $perms" \ + "Run: chmod 600 '$f'" \ + "chmod 600 '$f'" + else + add_finding "CHK-PRM-013" "ok" \ + "SSH private key permissions correct" \ + "SSH private key is properly restricted to owner read/write" \ + "$f mode $perms" "" "" + fi + done < <(find "$ssh_dir" -maxdepth 1 \( \ + -name 'id_*' -o -name '*.pem' \ + \) -type f ! -name '*.pub' -print0 2>/dev/null) + + if [[ "$found" -eq 0 ]]; then + add_finding "CHK-PRM-013" "info" \ + "No SSH private keys found" \ + "No SSH private key files detected in ~/.ssh directory" "" "" "" + fi +} + # ─── Run all checks ───────────────────────────────────────────────────────── main() { @@ -663,6 +709,7 @@ main() { check_credentials_dir check_log_secrets check_cloud_sync + check_ssh_keys log_info "Permissions scan complete: ${#FINDINGS[@]} finding(s)" From d678dbb858aeee448e7f65a9cd666222bbd45a13 Mon Sep 17 00:00:00 2001 From: Black Circle Sentinel Date: Sat, 7 Feb 2026 00:23:02 -0500 Subject: [PATCH 2/6] auto-claude: subtask-1-2 - Document CHK-PRM-013 in check-catalog.md --- references/check-catalog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/references/check-catalog.md b/references/check-catalog.md index 592a8aa..c47733f 100644 --- a/references/check-catalog.md +++ b/references/check-catalog.md @@ -246,6 +246,12 @@ severity, description, and remediation. - **Description:** Changes to permission rules are not logged. Malicious permission escalation cannot be detected after the fact. - **Remediation:** Enable audit logging for permission changes. +### CHK-PRM-013 -- SSH private key has overly permissive permissions +- **Severity:** Critical +- **Description:** SSH private key files (e.g., `~/.ssh/id_rsa`, `~/.ssh/id_ed25519`) have permissions more permissive than 600. SSH clients often refuse to use keys with incorrect permissions, and they can be read by other users on the system. +- **Remediation:** Set SSH key permissions to 600 (read/write owner only): `chmod 600 ~/.ssh/id_rsa` +- **Auto-fix:** `chmod 600 "$KEY_PATH"` + --- ## Cron (CHK-CRN) From f1c99f774864e54485071db40ff575dfdf0023da Mon Sep 17 00:00:00 2001 From: Black Circle Sentinel Date: Sat, 7 Feb 2026 00:26:30 -0500 Subject: [PATCH 3/6] auto-claude: subtask-1-3 - Test the new check with integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added test_prm_013() function to test_integration.sh - Test creates mock SSH keys with wrong permissions (644) - Verifies auto-fix command (chmod 600) works correctly - All 13 integration tests passing - Manual verification completed: critical → ok severity transition confirmed Co-Authored-By: Claude Sonnet 4.5 --- scripts/helpers/test_integration.sh | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/scripts/helpers/test_integration.sh b/scripts/helpers/test_integration.sh index ca88dcd..0d3bbfa 100755 --- a/scripts/helpers/test_integration.sh +++ b/scripts/helpers/test_integration.sh @@ -308,6 +308,47 @@ test_chmod_600() { [[ "$perms" == "600" ]] } +# --------------------------------------------------------------------------- +# Test: CHK-PRM-013 auto-fix (SSH private key permissions) +# --------------------------------------------------------------------------- +test_prm_013() { + # Create mock SSH directory + local ssh_dir="${TEST_DIR}/.ssh" + mkdir -p "$ssh_dir" + + # Create test SSH private keys with wrong permissions + local test_key="${ssh_dir}/id_test_rsa" + local test_pem="${ssh_dir}/test.pem" + + echo "-----BEGIN RSA PRIVATE KEY-----" > "$test_key" + echo "fake key content" >> "$test_key" + echo "-----END RSA PRIVATE KEY-----" >> "$test_key" + + echo "-----BEGIN PRIVATE KEY-----" > "$test_pem" + echo "fake pem content" >> "$test_pem" + echo "-----END PRIVATE KEY-----" >> "$test_pem" + + # Set insecure permissions + chmod 644 "$test_key" + chmod 644 "$test_pem" + + # Run the auto-fix command + chmod 600 "$test_key" + chmod 600 "$test_pem" + + # Verify permissions were fixed + local perms_key perms_pem + if [[ "$(uname -s)" == "Darwin" ]]; then + perms_key=$(stat -f "%Lp" "$test_key") + perms_pem=$(stat -f "%Lp" "$test_pem") + else + perms_key=$(stat -c "%a" "$test_key") + perms_pem=$(stat -c "%a" "$test_pem") + fi + + [[ "$perms_key" == "600" ]] && [[ "$perms_pem" == "600" ]] +} + # --------------------------------------------------------------------------- # Main test execution # --------------------------------------------------------------------------- @@ -336,6 +377,7 @@ run_test "CHK-CFG-010: Enable sensitive data redaction" "test_cfg_010" run_test "CHK-CFG-011: Enable browser restrictions" "test_cfg_011" run_test "CHK-CFG-012: Disable network discovery" "test_cfg_012" run_test "File permissions: chmod 600" "test_chmod_600" +run_test "CHK-PRM-013: Fix SSH key permissions" "test_prm_013" echo "─────────────────────────────────────────────────────────────────────────────" echo "" From 00132343bcc6ed04078a0fceeeb5c155d38b135e Mon Sep 17 00:00:00 2001 From: Black Circle Sentinel Date: Sun, 8 Feb 2026 21:44:59 -0500 Subject: [PATCH 4/6] =?UTF-8?q?fix:=20address=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20remove=20quotes=20from=20auto=5Ffix=20chmod,=20refa?= =?UTF-8?q?ctor=20test=20to=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- scripts/helpers/test_integration.sh | 56 +++++++++++++++-------------- scripts/scan_permissions.sh | 4 +-- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/scripts/helpers/test_integration.sh b/scripts/helpers/test_integration.sh index 0d3bbfa..f297a5a 100755 --- a/scripts/helpers/test_integration.sh +++ b/scripts/helpers/test_integration.sh @@ -316,37 +316,41 @@ test_prm_013() { local ssh_dir="${TEST_DIR}/.ssh" mkdir -p "$ssh_dir" - # Create test SSH private keys with wrong permissions + # Create test SSH private keys local test_key="${ssh_dir}/id_test_rsa" local test_pem="${ssh_dir}/test.pem" - echo "-----BEGIN RSA PRIVATE KEY-----" > "$test_key" - echo "fake key content" >> "$test_key" - echo "-----END RSA PRIVATE KEY-----" >> "$test_key" - - echo "-----BEGIN PRIVATE KEY-----" > "$test_pem" - echo "fake pem content" >> "$test_pem" - echo "-----END PRIVATE KEY-----" >> "$test_pem" - - # Set insecure permissions - chmod 644 "$test_key" - chmod 644 "$test_pem" - - # Run the auto-fix command - chmod 600 "$test_key" - chmod 600 "$test_pem" + cat > "$test_key" <<'EOF' +-----BEGIN RSA PRIVATE KEY----- +fake key content +-----END RSA PRIVATE KEY----- +EOF - # Verify permissions were fixed - local perms_key perms_pem - if [[ "$(uname -s)" == "Darwin" ]]; then - perms_key=$(stat -f "%Lp" "$test_key") - perms_pem=$(stat -f "%Lp" "$test_pem") - else - perms_key=$(stat -c "%a" "$test_key") - perms_pem=$(stat -c "%a" "$test_pem") - fi + cat > "$test_pem" <<'EOF' +-----BEGIN PRIVATE KEY----- +fake pem content +-----END PRIVATE KEY----- +EOF - [[ "$perms_key" == "600" ]] && [[ "$perms_pem" == "600" ]] + for key_file in "$test_key" "$test_pem"; do + # Set insecure permissions + chmod 644 "$key_file" + + # Run the auto-fix command + chmod 600 "$key_file" + + # Verify permissions were fixed + local perms + if [[ "$(uname -s)" == "Darwin" ]]; then + perms=$(stat -f "%Lp" "$key_file") + else + perms=$(stat -c "%a" "$key_file") + fi + + if [[ "$perms" != "600" ]]; then + return 1 + fi + done } # --------------------------------------------------------------------------- diff --git a/scripts/scan_permissions.sh b/scripts/scan_permissions.sh index d07bae7..e4d5bbe 100755 --- a/scripts/scan_permissions.sh +++ b/scripts/scan_permissions.sh @@ -673,8 +673,8 @@ check_ssh_keys() { "SSH private key has insecure permissions" \ "SSH private keys must be chmod 600 or SSH clients will refuse to use them. Current: $perms" \ "$f mode $perms" \ - "Run: chmod 600 '$f'" \ - "chmod 600 '$f'" + "Run: chmod 600 $f" \ + "chmod 600 $f" else add_finding "CHK-PRM-013" "ok" \ "SSH private key permissions correct" \ From 2403c6b99c60006086a49431404ced2a52944fef Mon Sep 17 00:00:00 2001 From: Black Circle Sentinel Date: Sun, 8 Feb 2026 23:08:45 -0500 Subject: [PATCH 5/6] fix: validate SSH key filename before emitting auto_fix command Instead of quoting $f in the auto_fix string (which would break the safe_exec whitelist pattern [a-zA-Z0-9/._-]+), validate that the filename contains only safe characters before emitting an auto_fix command. Files with unsafe characters (spaces, quotes, metacharacters) still get the critical finding but require manual remediation. Standard SSH key names (id_rsa, id_ed25519, *.pem) always pass this check. Co-Authored-By: Claude Opus 4.6 --- scripts/scan_permissions.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/scan_permissions.sh b/scripts/scan_permissions.sh index e4d5bbe..db6dcb2 100755 --- a/scripts/scan_permissions.sh +++ b/scripts/scan_permissions.sh @@ -669,12 +669,22 @@ check_ssh_keys() { [[ -z "$perms" ]] && continue if [[ "$perms" != "600" ]]; then + # Validate filename contains only safe characters before using in auto_fix. + # The safe_exec whitelist pattern for chmod requires paths matching + # [a-zA-Z0-9/._-]+, so we must not emit auto_fix for filenames with + # characters outside that set (e.g., spaces, quotes, shell metacharacters). + # Standard SSH key names (id_rsa, id_ed25519, *.pem) always pass this check. + local auto_fix_cmd="" remediation_msg="Run: chmod 600 \"$f\" (manual)" + if [[ "$f" =~ ^[a-zA-Z0-9/._-]+$ ]]; then + auto_fix_cmd="chmod 600 $f" + remediation_msg="Run: chmod 600 $f" + fi add_finding "CHK-PRM-013" "critical" \ "SSH private key has insecure permissions" \ "SSH private keys must be chmod 600 or SSH clients will refuse to use them. Current: $perms" \ "$f mode $perms" \ - "Run: chmod 600 $f" \ - "chmod 600 $f" + "$remediation_msg" \ + "$auto_fix_cmd" else add_finding "CHK-PRM-013" "ok" \ "SSH private key permissions correct" \ From 4915d714e256644d5d532567bd671b9dfc434c21 Mon Sep 17 00:00:00 2001 From: Black Circle Sentinel Date: Tue, 10 Feb 2026 09:41:00 -0500 Subject: [PATCH 6/6] =?UTF-8?q?fix:=20address=20PR=20#18=20review=20?= =?UTF-8?q?=E2=80=94=20harden=20remediation=20msg,=20use=20case-insensitiv?= =?UTF-8?q?e=20find?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace double-quoted filename in fallback remediation message with static placeholder "[file]" to prevent command substitution when the filename fails the safety regex (security-medium). - Switch find -name to -iname for key file matching and .pub exclusion so that ID_RSA, id_rsa.PEM, etc. are all detected (medium). - Add NOTE comment to test_prm_013 acknowledging the test covers auto-fix only, not scanner detection logic (suggestion). Co-Authored-By: Claude Opus 4.6 --- scripts/helpers/test_integration.sh | 4 ++++ scripts/scan_permissions.sh | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/helpers/test_integration.sh b/scripts/helpers/test_integration.sh index f297a5a..e423f5f 100755 --- a/scripts/helpers/test_integration.sh +++ b/scripts/helpers/test_integration.sh @@ -351,6 +351,10 @@ EOF return 1 fi done + # NOTE: This test verifies the chmod auto-fix command works correctly, but + # does not exercise the scanner's find/detect logic itself. This is + # consistent with other integration tests in this file which focus on + # auto-fix command validation rather than scanner detection coverage. } # --------------------------------------------------------------------------- diff --git a/scripts/scan_permissions.sh b/scripts/scan_permissions.sh index db6dcb2..94df089 100755 --- a/scripts/scan_permissions.sh +++ b/scripts/scan_permissions.sh @@ -674,7 +674,7 @@ check_ssh_keys() { # [a-zA-Z0-9/._-]+, so we must not emit auto_fix for filenames with # characters outside that set (e.g., spaces, quotes, shell metacharacters). # Standard SSH key names (id_rsa, id_ed25519, *.pem) always pass this check. - local auto_fix_cmd="" remediation_msg="Run: chmod 600 \"$f\" (manual)" + local auto_fix_cmd="" remediation_msg="Manual fix required: chmod 600 [file]" if [[ "$f" =~ ^[a-zA-Z0-9/._-]+$ ]]; then auto_fix_cmd="chmod 600 $f" remediation_msg="Run: chmod 600 $f" @@ -692,8 +692,8 @@ check_ssh_keys() { "$f mode $perms" "" "" fi done < <(find "$ssh_dir" -maxdepth 1 \( \ - -name 'id_*' -o -name '*.pem' \ - \) -type f ! -name '*.pub' -print0 2>/dev/null) + -iname 'id_*' -o -iname '*.pem' \ + \) -type f ! -iname '*.pub' -print0 2>/dev/null) if [[ "$found" -eq 0 ]]; then add_finding "CHK-PRM-013" "info" \