Skip to content

Safe Upgrade System

Overview

The Safe Upgrade System provides integrity verification and user modification detection during security controls upgrades. It prevents accidental loss of user customizations by detecting changes and providing interactive upgrade decisions.

Problem Statement

Current Risk: When upgrading security controls, user customizations can be silently overwritten without warning.

Examples of User Customizations: - Modified pre-push hooks with project-specific checks - Customized gitleaks rules for false positive reduction - Adjusted pinactlite allowlists for trusted actions - Modified workflow templates for CI/CD requirements

Without Safe Upgrade (old behavior):

# User customizes pre-push hook
$ edit .git/hooks/pre-push  # Add project-specific checks

# Runs manual upgrade script
$ ./install-security-controls.sh --force  # bypasses safe upgrade

# โŒ Customizations lost - no warning!

With Safe Upgrade (automatic):

# User just runs installer again
$ ./install-security-controls.sh

๐Ÿ” Checking for user modifications...
โš ๏ธ  File modified: .git/hooks/pre-push

๐Ÿ“ Changes detected:
  + # Project-specific security check
  + check_internal_compliance()

What would you like to do with this file?
  1. Keep my version (skip upgrade for this file)
  2. Replace with new version (your changes will be lost)
  3. Backup my version and install new version

Choose option [1/2/3]: 3
โœ… Backed up to: .security-controls/backup/pre-push.20251021_211500.backup
โœ… Installed new version

Design Principles

1. Don't Make Me Think (DMMT)

  • Auto-detect modifications: System automatically identifies changed files
  • Show, don't tell: Display actual diffs, not vague warnings
  • Guide decisions: Clear options with consequences explained
  • Safe defaults: Always backup before replacement

2. Trust Through Transparency

  • Version-specific hashes: Known good state for each release
  • Diff display: See exact changes before deciding
  • Audit trail: All backups timestamped and preserved
  • Verification: Confirm integrity before and after upgrade

3. Fail Secure

  • Block unknown: If hash unknown, ask user (don't assume)
  • Preserve on doubt: When uncertain, keep user version
  • Reversible operations: All replacements backed up
  • Verification gates: Verify new installation after upgrade

Architecture

Version-Specific Hash Registry

Each release includes SHA256 hashes for all managed files:

# Hash registry structure
declare -A VERSION_HASHES

# Version 0.6.10 known good hashes
VERSION_HASHES["0.6.10|.security-controls/bin/pinactlite"]="8869c009..."
VERSION_HASHES["0.6.10|.security-controls/bin/gitleakslite"]="a1b2c3d4..."
VERSION_HASHES["0.6.10|.git/hooks/pre-push"]="e5f6g7h8..."

# Version 0.6.9 known good hashes
VERSION_HASHES["0.6.9|.security-controls/bin/pinactlite"]="9c580e3a..."

How It Works: 1. Detect installed version from .security-controls/.version 2. Look up expected hash for each file at that version 3. Calculate actual hash of installed file 4. Compare: expected_hash == actual_hash - Match โ†’ File intact (safe to replace) - Mismatch โ†’ File modified (ask user) - Unknown โ†’ No hash for this version (ask user)

File Integrity States

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     File Integrity Check                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                            โ”‚
                            โ”œโ”€โ–บ [Intact] โœ…
                            โ”‚   Expected hash matches actual
                            โ”‚   โ†’ Safe to replace
                            โ”‚
                            โ”œโ”€โ–บ [Modified] โš ๏ธ
                            โ”‚   Hash mismatch detected
                            โ”‚   โ†’ Show diff, ask user
                            โ”‚
                            โ”œโ”€โ–บ [Missing] โŒ
                            โ”‚   File expected but not found
                            โ”‚   โ†’ Install new version
                            โ”‚
                            โ””โ”€โ–บ [Unknown] โ“
                                No hash record for this version
                                โ†’ Ask user, proceed with caution

Interactive Upgrade Workflow

1. Version Detection
   โ”œโ”€ Read .security-controls/.version
   โ””โ”€ Extract current version number

2. Integrity Verification
   โ”œโ”€ For each managed file:
   โ”‚  โ”œโ”€ Get expected hash for current version
   โ”‚  โ”œโ”€ Calculate actual file hash
   โ”‚  โ””โ”€ Compare and classify state
   โ””โ”€ Generate integrity report

3. Modification Handling
   โ”œโ”€ If modifications detected:
   โ”‚  โ”œโ”€ Display summary of modified files
   โ”‚  โ”œโ”€ Offer options:
   โ”‚  โ”‚  โ”œโ”€ [1] Review each file (show diffs)
   โ”‚  โ”‚  โ”œโ”€ [2] Backup all and proceed
   โ”‚  โ”‚  โ””โ”€ [3] Cancel upgrade
   โ”‚  โ””โ”€ Execute user choice
   โ””โ”€ If all intact: proceed to upgrade

4. Per-File Interactive Review
   โ”œโ”€ For each modified file:
   โ”‚  โ”œโ”€ Show diff (current vs new)
   โ”‚  โ”œโ”€ Ask: Keep / Replace / Backup+Replace
   โ”‚  โ””โ”€ Record decision
   โ””โ”€ Apply all decisions atomically

5. Upgrade Execution
   โ”œโ”€ Backup modified files (if selected)
   โ”œโ”€ Download new installer
   โ”œโ”€ Run installation with recorded decisions
   โ””โ”€ Verify new installation integrity

6. Post-Upgrade Verification
   โ”œโ”€ Check all files match new version hashes
   โ”œโ”€ Generate upgrade report
   โ””โ”€ Notify user of backup locations

Usage

Automatic Upgrade Detection (DMMT)

Don't Make Me Think: The installer automatically detects existing installations and triggers safe upgrade.

# User just runs installer - system detects it's an upgrade
$ ./install-security-controls.sh

๐Ÿ”„ Existing installation detected (v0.6.9)
   Upgrading to v0.6.10 with modification detection...

๐Ÿ” Checking for user modifications...
โš ๏ธ  File modified: .git/hooks/pre-push

# Interactive workflow begins automatically...

How It Works: 1. Installer checks for .security-controls-version file 2. If version โ‰  installer version โ†’ auto-run safe-upgrade 3. Safe upgrade handles integrity checks and user decisions 4. No manual script execution needed

Manual Safe Upgrade

You can also run safe-upgrade directly (default behavior):

# Safe upgrade with user confirmation (DEFAULT)
$ ./scripts/safe-upgrade.sh

# Same as above - safe upgrade is default when no args

Check Installation Integrity

Verify current installation without upgrading:

$ ./scripts/safe-upgrade.sh --check

๐Ÿ” Verifying installation integrity for version 0.6.10

โœ… .security-controls/bin/pinactlite - intact
โœ… .security-controls/bin/gitleakslite - intact
โš ๏ธ  .git/hooks/pre-push - MODIFIED
โœ… .git/hooks/pre-commit - intact

๐Ÿ“Š Integrity Verification Summary:
   Total files checked:  4
   Intact:               3
   Modified:             1
   Missing:              0
   Unknown/Not tracked:  0

โš ๏ธ Modified files detected:
   โ€ข .git/hooks/pre-push

Interactive Upgrade Workflow

When safe-upgrade runs (automatically or manually), it provides interactive decisions:

$ ./scripts/safe-upgrade.sh  # or just run installer

๐Ÿ”„ Starting safe upgrade process...

Current installation: version 0.6.10

๐Ÿ” Checking for user modifications...

โœ… .security-controls/bin/pinactlite - intact
โš ๏ธ  .git/hooks/pre-push - MODIFIED

โš ๏ธ  Modified files detected in current installation
These files differ from the original version 0.6.10 installation.
They may contain your customizations or local changes.

Options:
  1. View diffs and decide per file (recommended)
  2. Backup all and proceed with upgrade
  3. Cancel upgrade

Choose option [1/2/3]: 1

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
File modified: .git/hooks/pre-push

๐Ÿ“ Changes detected:
  @@ -150,6 +150,12 @@
   # Run security checks
   run_security_checks

  +# Project-specific compliance check
  +if [[ -f ".compliance/check.sh" ]]; then
  +  .compliance/check.sh || exit 1
  +fi
  +
   # Push if all checks pass
   exit 0
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

What would you like to do with this file?
  1. Keep my version (skip upgrade for this file)
  2. Replace with new version (your changes will be lost)
  3. Backup my version and install new version

Choose option [1/2/3]: 3

โœ… Backed up to: .security-controls/backup/pre-push.20251021_211500.backup
Will install new version of .git/hooks/pre-push

๐Ÿ“ฅ Downloading new installer version...
๐Ÿš€ Running upgrade...
โœ… Upgrade completed successfully

๐Ÿ“‹ Upgrade Summary:
   Files replaced:  1
   Files kept:      0
   Backups created: 1

๐Ÿ’ก To restore your customizations:
   $ diff .git/hooks/pre-push .security-controls/backup/pre-push.20251021_211500.backup
   $ # Cherry-pick desired changes

Skip all confirmations (dangerous):

$ ./scripts/safe-upgrade.sh --force

โš ๏ธ  Force mode: skipping safety confirmations
๐Ÿ”„ Starting safe upgrade process...
โš ๏ธ  Force mode: proceeding without confirmation
๐Ÿ“ฅ Downloading new installer version...
๐Ÿš€ Running upgrade...
โœ… Upgrade completed successfully

Implementation in Installer

Automatic Upgrade Detection

The installer automatically detects existing installations and triggers safe upgrade:

# In install-security-controls.sh - execute_upgrade_commands()

# AUTO-DETECT EXISTING INSTALLATION (DMMT: Don't Make Me Think)
if [[ -f $VERSION_FILE ]]; then
  installed_version=$(get_installed_version)

  # If we detect a different version, auto-run safe-upgrade
  if [[ $installed_version != "$SCRIPT_VERSION" && $installed_version != "unknown" ]]; then
    print_status $BLUE "๐Ÿ”„ Existing installation detected (v$installed_version)"
    print_status $BLUE "   Upgrading to v$SCRIPT_VERSION with modification detection..."

    # Auto-run safe-upgrade (default behavior is safe upgrade)
    if [[ -x ./scripts/safe-upgrade.sh ]]; then
      exec ./scripts/safe-upgrade.sh
    else
      # Fallback: warn user about risks
      print_status $YELLOW "โš ๏ธ  Safe upgrade script not found"
      read -rp "Continue with standard upgrade? [y/N]: " confirm
      [[ ! $confirm =~ ^[Yy]$ ]] && exit 0
    fi
  fi
fi

Key Features: - โœ… Zero user action: Just run installer again - โœ… Version comparison: Automatically detects upgrades - โœ… Safe fallback: Warns if safe-upgrade unavailable - โœ… User confirmation: Asks before risky operations

Version Tracking

During installation, version info is recorded:

# Write version file during installation
write_version_info() {
  cat >"$VERSION_FILE" <<EOF
# Security Controls Installation Information
version="$SCRIPT_VERSION"
install_date="$(date)"
installer_version="$SCRIPT_VERSION"
project_type="$(detect_project_type)"
EOF
}

Hash Registry Generation

Automated Release Process

Hash registries are automatically generated during releases via GitHub Actions:

# .github/workflows/release.yml

- name: Generate hash registry for safe-upgrade
  run: |
    # Extract version from tag (remove 'v' prefix)
    VERSION="${{ github.ref_name }}"
    VERSION="${VERSION#v}"

    echo "๐Ÿ” Generating hash registry for version $VERSION"

    # Generate hash registry in all formats
    ./scripts/generate-release-hashes.sh "$VERSION" --format bash
    ./scripts/generate-release-hashes.sh "$VERSION" --format json
    ./scripts/generate-release-hashes.sh "$VERSION" --format yaml

- name: Create Release
  uses: softprops/action-gh-release@v2
  with:
    files: |
      install-security-controls.sh
      checksums.txt
      release-hashes-*.txt   # Bash format (for embedding)
      release-hashes-*.json  # JSON format (auto-download)
      release-hashes-*.yaml  # YAML format (documentation)

Workflow: 1. Developer creates git tag (e.g., v0.6.11) 2. GitHub Actions workflow triggered automatically 3. Hash registry generated in 3 formats 4. All formats uploaded to GitHub release 5. Safe-upgrade auto-downloads JSON registry

Zero Manual Steps (DMMT compliance): - โœ… No manual hash generation - โœ… No manual file uploads - โœ… No manual registry updates - โœ… Consistent hashes for every release

Manual Hash Generation (Debugging)

For local testing or debugging:

# Generate hash registry for version 0.6.11
$ ./scripts/generate-release-hashes.sh 0.6.11

๐Ÿ” Generating release hash registry for version 0.6.11
Format: bash
Output: release-hashes-0.6.11.txt

Calculating file hashes...
โœ… .security-controls/bin/pinactlite
โœ… .security-controls/bin/gitleakslite
โœ… install-security-controls.sh

โœ… Hash registry generated: release-hashes-0.6.11.txt

Security Considerations

Hash Verification Security

Threat Model: - Attacker modifies files: Detected by hash mismatch - Attacker modifies hash registry: Requires repository access + signed commit - Attacker intercepts upgrade: Checksums verified during download - Attacker downgrades: Version number increases monotonically

Defense in Depth: 1. Signed Commits: All releases signed with Sigstore/gitsign 2. Checksum Verification: Installer checksums verified before execution 3. Version Monotonicity: Downgrade detection prevents rollback attacks 4. Backup Integrity: Backups also hashed and verified

Privacy Considerations

No Telemetry: Safe upgrade system operates entirely locally. No data sent to external services.

Backup Contents: Backups may contain sensitive customizations. Stored in .security-controls/backup/ (gitignored).

Implemented Enhancements

โœ… Automatic Hash Registry Downloads

Status: Implemented in v0.7.0

Download version-specific hash registries from GitHub releases:

$ ./scripts/safe-upgrade.sh --download-hashes 0.7.0

๐Ÿ“ฅ Downloading hash registry for version 0.7.0...
โœ… Downloaded hash registry from releases
โœ… Importing hashes from JSON registry...
โœ… Imported 5 file hashes from registry

# Auto-fallback to embedded hashes if download fails
โš ๏ธ Could not download hash registry from GitHub releases
โ„น๏ธ Falling back to embedded hash registry

Implementation: download_hash_registry() function - Fetches from: https://github.com/h4x0r/1-click-github-sec/releases/download/v{VERSION}/release-hashes-{VERSION}.json - Supports JSON and YAML formats - Automatic fallback to embedded hashes - Uses jq for JSON parsing (optional dependency)

โœ… Rollback Capability

Status: Implemented in v0.7.0

Restore from previous backups interactively:

$ ./scripts/safe-upgrade.sh --rollback

๐Ÿ”„ Rollback Wizard

๐Ÿ“ฆ Available backups:

  1. Backup from 2025-10-21 21:15:00 (3 files)
  2. Backup from 2025-10-15 14:30:00 (3 files)

Choose backup to restore [1-2] or 'q' to quit: 1

Selected backup timestamp: 20251021_211500

Files to restore:
  โ€ข pinactlite
  โ€ข gitleakslite
  โ€ข pre-push

Restore these files? [y/N]: y

Restoring backup...
โœ… Restored .security-controls/bin/pinactlite
โœ… Restored .security-controls/bin/gitleakslite
โœ… Restored .git/hooks/pre-push

โœ… Rollback completed

๐Ÿ’ก Tip: Run --check to verify restored installation

Implementation: rollback_to_backup() function - Lists timestamped backups from .security-controls/backup/ - Groups files by backup timestamp - Interactive selection with confirmation - Restores all files from selected backup - Verification suggested after rollback

โœ… Merge Tool Integration

Status: Implemented in v0.7.0

Use visual merge tools to resolve conflicts:

$ MERGE_TOOL=meld ./scripts/safe-upgrade.sh --upgrade

โš ๏ธ  File modified: .git/hooks/pre-push

๐Ÿ“ Changes detected:
  [diff display]

What would you like to do?
  1. Keep my version (skip upgrade for this file)
  2. Replace with new version (your changes will be lost)
  3. Backup my version and install new version
  4. Use merge tool to combine changes interactively

Choose option [1/2/3/4]: 4

Launching meld for 3-way merge...

Instructions:
  โ€ข LEFT: Your current version
  โ€ข RIGHT: New version from upgrade
  โ€ข Save merged result and exit merge tool to continue

[meld opens with 3-pane view]

โœ… Merge completed
โœ… Backed up to: .security-controls/backup/pre-push.20251021_211500.backup
โœ… Installed merged version

Implementation: merge_with_tool() and handle_modified_file_with_merge() functions - Auto-detects: meld, kdiff3, vimdiff (in preference order) - Environment variable: MERGE_TOOL for manual selection - 3-way merge support (base, current, new) - Automatic backup before applying merge - Merge result validation

Supported Merge Tools: | Tool | Type | Auto-Merge | Visual | Installation | |------|------|-----------|--------|--------------| | meld | GUI | Partial | โœ… | brew install meld | | kdiff3 | GUI | Yes | โœ… | brew install kdiff3 | | vimdiff | TUI | No | ๐ŸŸก | Built-in with vim |

โœ… Automated Hash Registry Generation

Status: Implemented in v0.7.0

Generate version-specific hash registries for releases:

$ ./scripts/generate-release-hashes.sh 0.7.0 --format json

โœ… Valid version format: 0.7.0
๐Ÿ” Generating release hash registry for version 0.7.0
Format: json
Output: release-hashes-0.7.0.json

Calculating file hashes...
โœ… .security-controls/bin/pinactlite
โœ… .security-controls/bin/gitleakslite
โœ… install-security-controls.sh
โœ… uninstall-security-controls.sh
โœ… yubikey-gitsign-toggle.sh

โœ… Generated JSON format with 5 files

Signing manifest with GPG...
โœ… Created signature: release-hashes-0.7.0.json.asc

โœ… Hash registry generated: release-hashes-0.7.0.json

๐Ÿ“‹ Integration Instructions:
   1. Include in GitHub release:
      $ gh release upload v0.7.0 release-hashes-0.7.0.json

   2. Safe upgrade will auto-download from:
      https://github.com/h4x0r/1-click-github-sec/releases/download/v0.7.0/release-hashes-0.7.0.json

๐Ÿ’ก Next Steps:
   โ€ข Commit hash registry to repository
   โ€ข Tag release: git tag -s v0.7.0 -m 'Release v0.7.0'
   โ€ข Push release: git push origin v0.7.0
   โ€ข Create GitHub release with hash registry attached

Implementation: scripts/generate-release-hashes.sh - Multiple formats: bash (for embedding), json (API), yaml (docs) - Semantic version validation - GPG signature generation (optional) - SHA256 hash calculation for all managed files - Integration instructions - Release workflow guidance

Output Formats:

// JSON format (release-hashes-0.7.0.json)
{
  "version": "0.7.0",
  "generated": "2025-10-22T03:15:00Z",
  "repository": "https://github.com/h4x0r/1-click-github-sec",
  "hashes": {
    ".security-controls/bin/pinactlite": "1fa109bc...",
    ".security-controls/bin/gitleakslite": "a1b2c3d4...",
    ...
  }
}
# YAML format (release-hashes-0.7.0.yaml)
version: 0.7.0
generated: 2025-10-22T03:15:00Z
repository: https://github.com/h4x0r/1-click-github-sec
hashes:
  .security-controls/bin/pinactlite: 1fa109bc...
  .security-controls/bin/gitleakslite: a1b2c3d4...
# Bash format (release-hashes-0.7.0.txt)
# Ready for copy/paste into safe-upgrade.sh
VERSION_HASHES["0.7.0|.security-controls/bin/pinactlite"]="1fa109bc..."
VERSION_HASHES["0.7.0|.security-controls/bin/gitleakslite"]="a1b2c3d4..."

Future Enhancements (Proposed)

Differential Hash Updates

Only download hash diff between versions instead of full registry:

# Proposed: Fetch only new/changed hashes
download_hash_diff() {
  local from_version="$1"
  local to_version="$2"

  # Download differential update
  curl -sSL "https://.../v${to_version}/hash-diff-${from_version}-to-${to_version}.json"
}

Smart Conflict Resolution

Use AI/ML to suggest conflict resolutions:

# Proposed: AI-assisted merge suggestions
analyze_conflict() {
  # Analyze both versions
  # Suggest safe merge strategy
  # Highlight potential issues
}

License

Same as main project: Apache License 2.0


Last Updated: 2025-10-22 Version: 1.0.0 Author: Albert Hui albert@securityronin.com