Created
March 16, 2025 10:07
-
-
Save monperrus/35b280ea6c345d0bd4c7101a66105558 to your computer and use it in GitHub Desktop.
a python script to remove secrets and keys in bash history file
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # cleans bash history | |
| # generated with Claude on March 2025 | |
| # does not work out of the box | |
| import os | |
| import re | |
| import shutil | |
| import argparse | |
| from datetime import datetime | |
| def create_backup(file_path): | |
| """Create a backup of the original file.""" | |
| backup_path = f"{file_path}.backup.{datetime.now().strftime('%Y%m%d%H%M%S')}" | |
| shutil.copy2(file_path, backup_path) | |
| print(f"Backup created at: {backup_path}") | |
| return backup_path | |
| def get_patterns(): | |
| """Return regex patterns for common secrets and keys.""" | |
| patterns = [ | |
| # API keys, tokens, and secrets | |
| r'api[_-]?key[=:"\s][^\'"\s]+', | |
| r'secret[_-]?key[=:"\s][^\'"\s]+', | |
| r'access[_-]?key[=:"\s][^\'"\s]+', | |
| r'access[_-]?token[=:"\s][^\'"\s]+', | |
| r'auth[_-]?token[=:"\s][^\'"\s]+', | |
| r'password[=:"\s][^\'"\s]+', | |
| r'passwd[=:"\s][^\'"\s]+', | |
| r'pwd[=:"\s][^\'"\s]+', | |
| # AWS specific | |
| r'aws[_-]?access[_-]?key[_-]?id[=:"\s][^\'"\s]+', | |
| r'aws[_-]?secret[_-]?access[_-]?key[=:"\s][^\'"\s]+', | |
| # Database connection strings | |
| r'jdbc:[^\s]+', | |
| r'mongodb:[^\s]+', | |
| r'postgres:[^\s]+', | |
| r'mysql:[^\s]+', | |
| # Private keys and certificates | |
| r'-----BEGIN\s+PRIVATE\s+KEY-----', | |
| r'-----BEGIN\s+RSA\s+PRIVATE\s+KEY-----', | |
| r'-----BEGIN\s+CERTIFICATE-----', | |
| # Common commands that might contain secrets | |
| r'curl\s+.*(-u|--user)\s+[^\s]+:[^\s]+', | |
| # r'ssh\s+-i\s+[^\s]+', | |
| # r'scp\s+-i\s+[^\s]+', | |
| # OAuth tokens | |
| r'oauth[_-]?token[=:"\s][^\'"\s]+', | |
| # JWT tokens (simplified pattern) | |
| r'eyJ+\.+\.+', | |
| ] | |
| return patterns | |
| def clean_history_file(file_path, patterns, dry_run=False): | |
| """Remove lines containing secrets from the history file.""" | |
| with open(file_path, 'r', encoding='utf-8', errors='replace') as f: | |
| lines = f.readlines() | |
| original_count = len(lines) | |
| filtered_lines = [] | |
| removed_lines = [] | |
| for line in lines: | |
| should_remove = False | |
| for pattern in patterns: | |
| if re.search(pattern,line): | |
| should_remove = True | |
| # removed_count += 1 | |
| removed_lines.append(line) | |
| break | |
| if not should_remove: | |
| filtered_lines.append(line) | |
| if not dry_run: | |
| with open(file_path, 'w', encoding='utf-8') as f: | |
| f.writelines(filtered_lines) | |
| else: | |
| for i in removed_lines: | |
| print(i, end="") | |
| print(f"Original lines: {original_count}") | |
| print(f"Lines removed: {len(removed_lines)}") | |
| print(f"Remaining lines: {len(filtered_lines)}") | |
| return len(removed_lines) | |
| def main(): | |
| parser = argparse.ArgumentParser(description='Clean secrets and keys from bash history file') | |
| parser.add_argument('--file', default=os.path.expanduser('~/.bash_history'), | |
| help='Path to the history file (default: ~/.bash_history)') | |
| parser.add_argument('--dry-run', action='store_true', | |
| help='Only show what would be removed without making changes') | |
| parser.add_argument('--no-backup', action='store_true', | |
| help='Skip creating a backup of the original file') | |
| args = parser.parse_args() | |
| if not os.path.exists(args.file): | |
| print(f"Error: History file not found at {args.file}") | |
| return | |
| if not args.no_backup and not args.dry_run: | |
| backup_path = create_backup(args.file) | |
| print(f"Original file backed up to {backup_path}") | |
| patterns = get_patterns() | |
| print(f"{'Analyzing' if args.dry_run else 'Cleaning'} history file: {args.file}") | |
| if args.dry_run: | |
| print("DRY RUN: No changes will be made") | |
| removed = clean_history_file(args.file, patterns, args.dry_run) | |
| if removed > 0: | |
| if args.dry_run: | |
| print(f"Would remove {removed} lines containing potential secrets") | |
| else: | |
| print(f"Successfully removed {removed} lines containing potential secrets") | |
| else: | |
| print("No secrets found in the history file") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment