-
Notifications
You must be signed in to change notification settings - Fork 1
π‘οΈ Sentinel: [HIGH] Fix insecure backup permissions and command injection #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ## 2025-03-05 - File permissions and array argument injection in backup scripts | ||
|
Check failure on line 1 in .jules/sentinel.md
|
||
| **Vulnerability:** A backup script was creating a temporary directory with default permissions and a zip archive containing sensitive files with default `umask`. Furthermore, an array of exclude patterns with strings separated by spaces was being built and executed via command substitution, leading to word-splitting and the risk of command argument injection for patterns with spaces or shell metacharacters. | ||
|
Check failure on line 2 in .jules/sentinel.md
|
||
| **Learning:** Shell scripts generating sensitive archives (like zip backups) must enforce strict access controls on the parent directories (e.g., `chmod 700`) and the unencrypted archives themselves (e.g., via `umask 077` in the generation subshell). Additionally, injecting arguments constructed as space-separated strings into commands creates word-splitting issues and argument injection vulnerabilities, requiring `# shellcheck disable=SC2086` overrides. Arguments must always be built using proper Bash arrays and mapfile/readarray. | ||
|
Check failure on line 3 in .jules/sentinel.md
|
||
| **Prevention:** Explicitly set `chmod 700` on backup directories and use `umask 077` subshells when creating archives. Construct arguments dynamically with Bash arrays using `mapfile` to prevent word-splitting and command argument injection vulnerabilities without relying on Shellcheck overrides. | ||
|
Check failure on line 4 in .jules/sentinel.md
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -238,7 +238,9 @@ build_exclude_args() { | |||||||||||||||||||||||||||||||
| for pattern in "${EXCLUDE_PATTERNS[@]}"; do | ||||||||||||||||||||||||||||||||
| args+=("-x" "*/${pattern}/*" "-x" "*/${pattern}") | ||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||
| echo "${args[@]}" | ||||||||||||||||||||||||||||||||
| if [[ ${#args[@]} -gt 0 ]]; then | ||||||||||||||||||||||||||||||||
| printf '%s\n' "${args[@]}" | ||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # --- Git Sync --- | ||||||||||||||||||||||||||||||||
|
|
@@ -267,6 +269,7 @@ sync_git_repos() { | |||||||||||||||||||||||||||||||
| local repo_dir | ||||||||||||||||||||||||||||||||
| repo_dir=$(dirname "$git_dir") | ||||||||||||||||||||||||||||||||
| local repo_name | ||||||||||||||||||||||||||||||||
| # shellcheck disable=SC2034 | ||||||||||||||||||||||||||||||||
| repo_name=$(basename "$repo_dir") | ||||||||||||||||||||||||||||||||
| local relative_path="${repo_dir#$HOME/}" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -303,7 +306,8 @@ sync_git_repos() { | |||||||||||||||||||||||||||||||
| git -C "$repo_dir" add -A 2>/dev/null | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Commit with auto-generated message | ||||||||||||||||||||||||||||||||
| local commit_msg="chore: auto-backup commit $(date '+%Y-%m-%d %H:%M')" | ||||||||||||||||||||||||||||||||
| local commit_msg | ||||||||||||||||||||||||||||||||
| commit_msg="chore: auto-backup commit $(date '+%Y-%m-%d %H:%M')" | ||||||||||||||||||||||||||||||||
| if git -C "$repo_dir" commit -m "$commit_msg" 2>/dev/null; then | ||||||||||||||||||||||||||||||||
| echo -e " ${GREEN}β${NC} Committed changes" | ||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||
|
|
@@ -352,6 +356,7 @@ cmd_backup() { | |||||||||||||||||||||||||||||||
| if [[ "$DRY_RUN" != true ]]; then | ||||||||||||||||||||||||||||||||
| mkdir -p "$BACKUP_TEMP_DIR" | ||||||||||||||||||||||||||||||||
| mkdir -p "$LOG_DIR" | ||||||||||||||||||||||||||||||||
| chmod 700 "$BACKUP_TEMP_DIR" "$LOG_DIR" | ||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||
|
Comment on lines
356
to
360
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fail closed if secure directory permissions cannot be enforced. Line 359 applies π Proposed fail-closed handling for secure directory setup if [[ "$DRY_RUN" != true ]]; then
- mkdir -p "$BACKUP_TEMP_DIR"
- mkdir -p "$LOG_DIR"
- chmod 700 "$BACKUP_TEMP_DIR" "$LOG_DIR"
+ if ! (umask 077 && mkdir -p "$BACKUP_TEMP_DIR" "$LOG_DIR"); then
+ error "Failed to create secure backup/log directories"
+ return 1
+ fi
+ if ! chmod 700 "$BACKUP_TEMP_DIR" "$LOG_DIR"; then
+ error "Failed to enforce 0700 permissions on backup/log directories"
+ return 1
+ fi
elseπ Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| debug "Would create: $BACKUP_TEMP_DIR" | ||||||||||||||||||||||||||||||||
| debug "Would create: $LOG_DIR" | ||||||||||||||||||||||||||||||||
|
|
@@ -406,17 +411,18 @@ cmd_backup() { | |||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||
| local exclude_args | ||||||||||||||||||||||||||||||||
| exclude_args=$(build_exclude_args) | ||||||||||||||||||||||||||||||||
| local exclude_args=() | ||||||||||||||||||||||||||||||||
| if [[ ${#EXCLUDE_PATTERNS[@]} -gt 0 ]]; then | ||||||||||||||||||||||||||||||||
| mapfile -t exclude_args < <(build_exclude_args) | ||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||
|
Comment on lines
+414
to
+417
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π§© Analysis chainπ Script executed: #!/bin/bash
set -euo pipefail
echo "=== Shebang in script ==="
head -n1 tools/backup-projects.sh
echo -e "\n=== System /bin/bash version ==="
/bin/bash --version | head -n1
echo -e "\n=== Does /bin/bash support mapfile? ==="
if /bin/bash -c 'type mapfile >/dev/null 2>&1'; then
echo "mapfile supported"
else
echo "mapfile NOT supported"
fi
echo -e "\n=== Actual code at lines 414-417 ==="
sed -n '414,417p' tools/backup-projects.sh
echo -e "\n=== Current mapfile usage in script ==="
rg -nP '\bmapfile\b' tools/backup-projects.shRepository: kidchenko/dotfiles Length of output: 547 π Script executed: #!/bin/bash
set -euo pipefail
echo "=== All mapfile usages in tools/backup-projects.sh ==="
rg -n '\bmapfile\b' tools/backup-projects.sh
echo -e "\n=== Total mapfile occurrences ==="
rg -c '\bmapfile\b' tools/backup-projects.sh || echo "0"
echo -e "\n=== Check for Bash version requirements in README or docs ==="
find . -maxdepth 2 -type f \( -name "README*" -o -name "readme*" -o -name "*.md" \) | head -5 | while read f; do
if rg -l "bash.*version|version.*bash|mapfile" "$f" 2>/dev/null; then
echo "Found in: $f"
fi
done
echo -e "\n=== Check shebang compatibility across other scripts ==="
find tools -name "*.sh" -type f -exec head -n1 {} + | sort | uniq -c
echo -e "\n=== Check if script has version checks ==="
rg -n "bash.*version|version.*bash|BASH_VERSINFO" tools/backup-projects.sh || echo "No version checks found"Repository: kidchenko/dotfiles Length of output: 1128 π Script executed: #!/bin/bash
set -euo pipefail
echo "=== README or setup documentation ==="
if [ -f README.md ]; then
echo "=== README.md content (first 100 lines) ==="
head -100 README.md
else
echo "No README.md found"
fi
echo -e "\n=== Check for any documentation mentioning Bash ==="
find . -maxdepth 2 -name "*.md" -type f -exec grep -l -i "bash" {} \;
echo -e "\n=== Check bootstrap or installation scripts ==="
head -30 tools/bootstrap.sh 2>/dev/null || echo "bootstrap.sh not found"Repository: kidchenko/dotfiles Length of output: 4671
Line 416 uses β»οΈ Bash-3.2-compatible alternative local exclude_args=()
if [[ ${`#EXCLUDE_PATTERNS`[@]} -gt 0 ]]; then
- mapfile -t exclude_args < <(build_exclude_args)
+ while IFS= read -r arg; do
+ exclude_args+=("$arg")
+ done < <(build_exclude_args)
fiπ Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ( | ||||||||||||||||||||||||||||||||
| umask 077 | ||||||||||||||||||||||||||||||||
| cd "$HOME" || exit 1 | ||||||||||||||||||||||||||||||||
| if [[ "$VERBOSE" == true ]]; then | ||||||||||||||||||||||||||||||||
| # shellcheck disable=SC2086 | ||||||||||||||||||||||||||||||||
| zip -r "$archive_path" "${relative_paths[@]}" $exclude_args | ||||||||||||||||||||||||||||||||
| zip -r "$archive_path" "${relative_paths[@]}" "${exclude_args[@]}" | ||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||
| # shellcheck disable=SC2086 | ||||||||||||||||||||||||||||||||
| zip -r -q "$archive_path" "${relative_paths[@]}" $exclude_args | ||||||||||||||||||||||||||||||||
| zip -r -q "$archive_path" "${relative_paths[@]}" "${exclude_args[@]}" | ||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix markdownlint violations blocking docs checks.
Line 1 should be an H1 with a blank line below it, and Lines 2β4 need wrapping to satisfy MD013.
π Proposed markdownlint-compliant rewrite
π§° Tools
πͺ GitHub Check: Lint Documentation
[failure] 4-4: Line length
.jules/sentinel.md:4:81 MD013/line-length Line length [Expected: 80; Actual: 297] 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: 537] 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: 410] 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: "## 2025-03-05 - File permissio..."] 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: "## 2025-03-05 - File permissions and array argument injection in backup scripts"] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md022.md
π€ Prompt for AI Agents