Skip to content
Open
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
4 changes: 4 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 2024-05-22 - [SSH Private Key Race Condition]

Check failure on line 1 in .jules/sentinel.md

View workflow job for this annotation

GitHub Actions / Lint Documentation

First line in a file should be a top-level heading

.jules/sentinel.md:1 MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "## 2024-05-22 - [SSH Private K..."] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md041.md

Check failure on line 1 in .jules/sentinel.md

View workflow job for this annotation

GitHub Actions / Lint Documentation

Headings should be surrounded by blank lines

.jules/sentinel.md:1 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## 2024-05-22 - [SSH Private Key Race Condition]"] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md022.md
**Vulnerability:** Found a TOCTOU (Time-of-Check to Time-of-Use) race condition in `tools/setup-ssh-keys.sh`. The script created the private key file using output redirection (`>`) which uses the default umask (typically 022), resulting in the file being world-readable for a brief window before `chmod 600` was executed.

Check failure on line 2 in .jules/sentinel.md

View workflow job for this annotation

GitHub Actions / Lint Documentation

Line length

.jules/sentinel.md:2:81 MD013/line-length Line length [Expected: 80; Actual: 321] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md
**Learning:** Shell redirection and standard file creation tools respect the current `umask`. Relying on a subsequent `chmod` to secure sensitive files leaves a window of exposure.

Check failure on line 3 in .jules/sentinel.md

View workflow job for this annotation

GitHub Actions / Lint Documentation

Line length

.jules/sentinel.md:3:81 MD013/line-length Line length [Expected: 80; Actual: 180] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md
**Prevention:** Always set `umask 077` (or similar restrictive mask) *before* creating sensitive files in shell scripts to ensure they are born secure. Restore the original umask afterwards if necessary.

Check failure on line 4 in .jules/sentinel.md

View workflow job for this annotation

GitHub Actions / Lint Documentation

Line length

.jules/sentinel.md:4:81 MD013/line-length Line length [Expected: 80; Actual: 203] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md
Comment on lines +1 to +4
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix markdownlint failures (MD041/MD022/MD013) and US spelling.

Line 1 must be an H1 with blank lines, and Lines 2–4 exceed the 80‑char limit; the linter will block the PR. Also prefer β€œafterward” for US English.

βœ… Suggested fix
-## 2024-05-22 - [SSH Private Key Race Condition]
-**Vulnerability:** Found a TOCTOU (Time-of-Check to Time-of-Use) race condition in `tools/setup-ssh-keys.sh`. The script created the private key file using output redirection (`>`) which uses the default umask (typically 022), resulting in the file being world-readable for a brief window before `chmod 600` was executed.
-**Learning:** Shell redirection and standard file creation tools respect the current `umask`. Relying on a subsequent `chmod` to secure sensitive files leaves a window of exposure.
-**Prevention:** Always set `umask 077` (or similar restrictive mask) *before* creating sensitive files in shell scripts to ensure they are born secure. Restore the original umask afterwards if necessary.
+# 2024-05-22 - [SSH Private Key Race Condition]
+
+**Vulnerability:** Found a TOCTOU (Time-of-Check to Time-of-Use) race condition in
+`tools/setup-ssh-keys.sh`. The script created the private key file using output
+redirection (`>`), which uses the default umask (typically 022), resulting in the
+file being world-readable for a brief window before `chmod 600` was executed.
+
+**Learning:** Shell redirection and standard file creation tools respect the
+current `umask`. Relying on a subsequent `chmod` to secure sensitive files leaves
+a window of exposure.
+
+**Prevention:** Always set `umask 077` (or similar restrictive mask) *before*
+creating sensitive files in shell scripts to ensure they are born secure. Restore
+the original umask afterward if necessary.
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## 2024-05-22 - [SSH Private Key Race Condition]
**Vulnerability:** Found a TOCTOU (Time-of-Check to Time-of-Use) race condition in `tools/setup-ssh-keys.sh`. The script created the private key file using output redirection (`>`) which uses the default umask (typically 022), resulting in the file being world-readable for a brief window before `chmod 600` was executed.
**Learning:** Shell redirection and standard file creation tools respect the current `umask`. Relying on a subsequent `chmod` to secure sensitive files leaves a window of exposure.
**Prevention:** Always set `umask 077` (or similar restrictive mask) *before* creating sensitive files in shell scripts to ensure they are born secure. Restore the original umask afterwards if necessary.
# 2024-05-22 - [SSH Private Key Race Condition]
**Vulnerability:** Found a TOCTOU (Time-of-Check to Time-of-Use) race condition in
`tools/setup-ssh-keys.sh`. The script created the private key file using output
redirection (`>`), which uses the default umask (typically 022), resulting in the
file being world-readable for a brief window before `chmod 600` was executed.
**Learning:** Shell redirection and standard file creation tools respect the
current `umask`. Relying on a subsequent `chmod` to secure sensitive files leaves
a window of exposure.
**Prevention:** Always set `umask 077` (or similar restrictive mask) *before*
creating sensitive files in shell scripts to ensure they are born secure. Restore
the original umask afterward if necessary.
🧰 Tools
πŸͺ› GitHub Check: Lint Documentation

[failure] 4-4: Line length
.jules/sentinel.md:4:81 MD013/line-length Line length [Expected: 80; Actual: 203] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md


[failure] 3-3: Line length
.jules/sentinel.md:3:81 MD013/line-length Line length [Expected: 80; Actual: 180] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md


[failure] 2-2: Line length
.jules/sentinel.md:2:81 MD013/line-length Line length [Expected: 80; Actual: 321] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md


[failure] 1-1: First line in a file should be a top-level heading
.jules/sentinel.md:1 MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "## 2024-05-22 - [SSH Private K..."] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md041.md


[failure] 1-1: Headings should be surrounded by blank lines
.jules/sentinel.md:1 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## 2024-05-22 - [SSH Private Key Race Condition]"] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md022.md

πŸͺ› LanguageTool

[locale-violation] ~4-~4: In American English, β€˜afterward’ is the preferred variant. β€˜Afterwards’ is more commonly used in British English and other dialects.
Context: ...born secure. Restore the original umask afterwards if necessary.

(AFTERWARDS_US)

πŸ€– Prompt for AI Agents
In @.jules/sentinel.md around lines 1 - 4, Update .jules/sentinel.md to satisfy
markdownlint: make the top heading an H1 (prepend "# " to the first line) and
ensure there's a blank line after it, wrap the following lines (the SSH Private
Key Race Condition paragraph, Vulnerability, Learning, and Prevention lines) to
<=80 characters per line, and change "afterwards" to US spelling "afterward";
keep the same content and references to tools/setup-ssh-keys.sh, umask, and
chmod 600 so the context remains clear.

82 changes: 82 additions & 0 deletions tests/verify_ssh_permissions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash
set -e

# Setup mock environment
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$TEST_DIR/.." && pwd)"
MOCK_BIN="$TEST_DIR/bin"
TEMP_HOME="$TEST_DIR/temp_home"

# Clean up previous run
rm -rf "$TEMP_HOME"
rm -rf "$MOCK_BIN"
mkdir -p "$TEMP_HOME"
mkdir -p "$MOCK_BIN"

# Mock 'op' command
cat <<EOF > "$MOCK_BIN/op"
#!/bin/bash
if [[ "\$1" == "account" && "\$2" == "list" ]]; then
exit 0
elif [[ "\$1" == "item" && "\$2" == "get" ]]; then
exit 0
elif [[ "\$1" == "read" ]]; then
if [[ "\$2" == *"private_key"* ]]; then
echo "PRIVATE KEY CONTENT"
elif [[ "\$2" == *"public_key"* ]]; then
echo "PUBLIC KEY CONTENT"
fi
fi
EOF
chmod +x "$MOCK_BIN/op"

# Mock 'yq' command (optional, but good to have)
cat <<EOF > "$MOCK_BIN/yq"
#!/bin/bash
exit 1 # simulate not installed or failing, script handles it
EOF
chmod +x "$MOCK_BIN/yq"

# Add mock bin to PATH
export PATH="$MOCK_BIN:$PATH"
export HOME="$TEMP_HOME"
export XDG_CONFIG_HOME="$TEMP_HOME/.config"

# Run the script
echo "Running setup-ssh-keys.sh restore..."
"$REPO_ROOT/tools/setup-ssh-keys.sh" restore

# Verify files exist
PRIVATE_KEY="$TEMP_HOME/.ssh/id_ed25519"
PUBLIC_KEY="$TEMP_HOME/.ssh/id_ed25519.pub"

if [[ ! -f "$PRIVATE_KEY" ]]; then
echo "ERROR: Private key not found at $PRIVATE_KEY"
exit 1
fi

if [[ ! -f "$PUBLIC_KEY" ]]; then
echo "ERROR: Public key not found at $PUBLIC_KEY"
exit 1
fi

# Verify permissions
# Private key should be 600 (-rw-------)
PERMS=$(stat -c "%a" "$PRIVATE_KEY" 2>/dev/null || stat -f "%Lp" "$PRIVATE_KEY")
if [[ "$PERMS" != "600" ]]; then
echo "ERROR: Private key permissions are $PERMS, expected 600"
exit 1
fi

# Public key should be 644 (-rw-r--r--)
PERMS=$(stat -c "%a" "$PUBLIC_KEY" 2>/dev/null || stat -f "%Lp" "$PUBLIC_KEY")
if [[ "$PERMS" != "644" ]]; then
echo "ERROR: Public key permissions are $PERMS, expected 644"
exit 1
fi

echo "SUCCESS: SSH keys restored with correct permissions."

# Cleanup
rm -rf "$TEMP_HOME"
rm -rf "$MOCK_BIN"
18 changes: 16 additions & 2 deletions tools/setup-ssh-keys.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,28 @@ cmd_restore() {

say "Restoring SSH key from 1Password..."

# Create SSH directory
mkdir -p "$SSH_DIR"
# Create SSH directory safely
if [[ ! -d "$SSH_DIR" ]]; then
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
fi
# Ensure directory permissions are restrictive
chmod 700 "$SSH_DIR"

# Set umask to ensure new files are only readable by owner
local old_umask
old_umask=$(umask)
umask 077

# Read private key from 1Password and save locally
# umask 077 ensures file is created with 600 permissions
op read "op://$VAULT/$KEY_NAME/private_key" > "$PRIVATE_KEY_FILE"
# Redundant chmod just to be sure, but file was created securely
chmod 600 "$PRIVATE_KEY_FILE"

# Restore umask for public key (we want 644)
umask "$old_umask"

# Read public key from 1Password and save locally
op read "op://$VAULT/$KEY_NAME/public_key" > "$PUBLIC_KEY_FILE"
chmod 644 "$PUBLIC_KEY_FILE"
Expand Down
Loading