Skip to content
Draft
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
39 changes: 30 additions & 9 deletions scripts/recovery/autonuke.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import sys
import json
import subprocess
import shlex
import time
from pathlib import Path
from typing import Dict, List, Tuple, Optional
Expand Down Expand Up @@ -113,17 +114,37 @@ def confirm_action(self, message: str, danger_level: str = "LOW") -> bool:
response = input(f"{Colors.CYAN}Continue? [y/N]: {Colors.END}").strip().lower()
return response in ['y', 'yes']

def run_command(self, cmd: str, shell: bool = True) -> Tuple[int, str, str]:
"""Run a command and return exit code, stdout, stderr"""
def run_command(self, cmd: str, shell: bool = False) -> Tuple[int, str, str]:
"""Run a command and return exit code, stdout, stderr

Args:
cmd: Command string (will be split into list if shell=False)
shell: If True, runs command in shell (use only for trusted input)

Note: For security, shell=False is default. Command is split using shlex.split()
which properly handles quoted arguments and special characters.
"""
self.log(f"Executing: {cmd}")
try:
result = subprocess.run(
cmd,
shell=shell,
capture_output=True,
text=True,
cwd=self.project_root
)
if shell:
# Only use shell mode for trusted, hardcoded commands
result = subprocess.run(
cmd,
shell=True,
capture_output=True,
text=True,
cwd=self.project_root
)
else:
# Safer mode: split command using shlex for proper quoting
cmd_list = shlex.split(cmd)
result = subprocess.run(
cmd_list,
shell=False,
capture_output=True,
text=True,
cwd=self.project_root
)
return result.returncode, result.stdout, result.stderr
except Exception as e:
self.log(f"Command failed: {e}", "ERROR")
Expand Down
1 change: 1 addition & 0 deletions scripts/recovery/hardware-recovery.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ if [ -f "$FIRMWARE_IMAGE" ]; then
[ -n "$VERBOSE" ] && ARGS="$ARGS -v"
[ -n "$VERIFY_ONLY" ] && ARGS="$ARGS --verify-only"

# shellcheck disable=SC2086
sudo python3 scripts/hardware_firmware_recovery.py $ARGS --output hardware_recovery_results.json
else
echo "ERROR: Clean firmware image not found at $FIRMWARE_IMAGE"
Expand Down
26 changes: 26 additions & 0 deletions scripts/recovery/nuclear-wipe.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ if [ "$EUID" -ne 0 ]; then
exit 1
fi

# Validate device path format for security
validate_device_path() {
local device="$1"
if [[ ! "$device" =~ ^/dev/(sd[a-z]{1,4}|nvme[0-9]+n[0-9]+|vd[a-z]{1,2}|mmcblk[0-9]+)$ ]]; then
echo "❌ Invalid device path format: $device"
echo " Expected format: /dev/sdX, /dev/nvmeXnY, /dev/vdX, or /dev/mmcblkX"
return 1
fi
return 0
}

# Check if nwipe is installed
if ! command -v nwipe &> /dev/null; then
echo "⚠️ nwipe not found - attempting to install..."
Expand Down Expand Up @@ -94,6 +105,11 @@ case "$choice" in
exit 1
fi

# Validate device path format for security
if ! validate_device_path "$device"; then
exit 1
fi

echo "⚠️ FINAL CONFIRMATION"
echo " Device: $device"
echo " Method: Quick wipe (zeros)"
Expand All @@ -115,6 +131,11 @@ case "$choice" in
exit 1
fi

# Validate device path format for security
if ! validate_device_path "$device"; then
exit 1
fi

echo "⚠️ FINAL CONFIRMATION"
echo " Device: $device"
echo " Method: DoD Short (3 passes)"
Expand All @@ -137,6 +158,11 @@ case "$choice" in
exit 1
fi

# Validate device path format for security
if ! validate_device_path "$device"; then
exit 1
fi

echo "⚠️ FINAL CONFIRMATION"
echo " Device: $device"
echo " Method: PRNG Stream (cryptographically secure)"
Expand Down
13 changes: 7 additions & 6 deletions scripts/secure-boot/enable-secureboot-kexec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ fi

# Check if Secure Boot is already enabled
check_secureboot_enabled() {
if [ -f /sys/firmware/efi/efivars/SecureBoot-* ]; then
local sb_file=$(ls /sys/firmware/efi/efivars/SecureBoot-* 2>/dev/null | head -1)
if [ -n "$sb_file" ]; then
local sb_status=$(od -An -t u1 -j 4 -N 1 "$sb_file" 2>/dev/null | tr -d ' ')
local sb_file
for sb_file in /sys/firmware/efi/efivars/SecureBoot-*; do
if [ -f "$sb_file" ]; then
local sb_status
sb_status=$(od -An -t u1 -j 4 -N 1 "$sb_file" 2>/dev/null | tr -d ' ')
[ "$sb_status" = "1" ] && return 0
fi
fi
done
return 1
}

Expand Down Expand Up @@ -220,7 +221,7 @@ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
fi

# Create a script that will run after first kexec
TEMP_SCRIPT="/tmp/phoenixboot_secureboot_enable_phase2.sh"
TEMP_SCRIPT=$(mktemp /tmp/phoenixboot_secureboot_enable_phase2.XXXXXX.sh)

cat > "$TEMP_SCRIPT" << 'EOF'
#!/bin/bash
Expand Down
2 changes: 1 addition & 1 deletion scripts/secure-boot/keys-centralize.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ DRY_RUN=${DRY_RUN:-0}
PRUNE=${PRUNE:-0}
if [ "${1:-}" = "--prune" ]; then PRUNE=1; fi

run() { if [ "$DRY_RUN" = 1 ]; then echo "DRY: $*"; else eval "$*"; fi }
run() { if [ "$DRY_RUN" = 1 ]; then echo "DRY: $*"; else "$@"; fi }

move_if_exists() {
local src="$1" dest_dir="$2"
Expand Down
2 changes: 1 addition & 1 deletion scripts/uefi-tools/uuefi-apply.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ info "☠ UUEFI apply (set BootNext for selected app)"

# Dry-run mode: UUEFI_DRYRUN=1
DRY=${UUEFI_DRYRUN:-}
run() { if [ -n "$DRY" ]; then echo "DRYRUN: $*"; else eval "$*"; fi }
run() { if [ -n "$DRY" ]; then echo "DRYRUN: $*"; else "$@"; fi }
need_sudo() {
if [ -n "$DRY" ]; then echo sudo -n "$@" || true; else sudo -n "$@"; fi
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/validation/detect_bootkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ def main():
time.sleep(10)

# Trigger recovery
os.system("sudo make reboot-to-vm")
subprocess.run(["sudo", "make", "reboot-to-vm"], check=False)

return 0

Expand Down
16 changes: 15 additions & 1 deletion web/hardware_database_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@
from flask import Flask, request, jsonify, render_template_string, send_file
import json
import os
import secrets
from datetime import datetime
from pathlib import Path
from typing import Dict, List
import sqlite3
import hashlib
import re

app = Flask(__name__)
app.config['SECRET_KEY'] = 'phoenix_guard_hardware_db'
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', secrets.token_hex(32))

# Database setup
DB_PATH = Path("hardware_profiles.db")
Expand Down Expand Up @@ -512,8 +514,20 @@ def search_hardware():
@app.route('/api/download/<hardware_id>')
def download_config(hardware_id):
"""Download universal BIOS config for hardware"""
# Validate hardware_id to prevent path traversal
if not re.match(r'^[a-zA-Z0-9_-]+$', hardware_id):
return "Invalid hardware ID format", 400

profile_file = UPLOADS_PATH / f"{hardware_id}.json"

# Resolve symlinks and verify path stays within UPLOADS_PATH
try:
profile_file = profile_file.resolve()
if not str(profile_file).startswith(str(UPLOADS_PATH.resolve())):
return "Invalid file path", 400
except (OSError, RuntimeError):
return "Invalid file path", 400

if not profile_file.exists():
return "Profile not found", 404

Expand Down