Initial release: ghost_protocol privacy toolkit

This commit is contained in:
ghost
2026-03-04 09:13:16 -05:00
commit 973cede31f
99 changed files with 12158 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
# opsec — Privacy Hardening Toolkit
A comprehensive OPSEC hardening suite for Linux systems. Designed for anyone who needs strong privacy defaults without constant manual configuration.
## Features
- **Kill Switch**: iptables rules that block all non-Tor/VPN traffic when enabled
- **MAC Randomization**: Automatic MAC address spoofing on boot
- **Hostname Randomization**: Random hostname generation to prevent tracking
- **DNS Leak Prevention**: Locks DNS to privacy resolvers with immutable resolv.conf
- **Tor Integration**: Configurable Tor routing with circuit management, exit node filtering, and bridge support
- **Traffic Blending**: Decoy browsing traffic to mask real activity patterns
- **Desktop Widget**: Conky-based status HUD with theme support (7 themes included)
- **Deployment Levels**: Preset configurations from standard privacy to full paranoid mode
- **Profile System**: Save, load, and switch between configuration profiles
- **SSH Honeypot Detection**: Check SSH servers against known honeypot signatures
- **WiFi Security Auditing**: Check wireless configuration for common leaks
## Installation
```bash
sudo ./install.sh
```
This copies scripts to `/usr/local/bin/`, configs to `/etc/opsec/`, and sets up systemd services.
## Usage
```bash
# Interactive configuration
sudo opsec-config.sh
# Toggle ghost mode (advanced privacy)
sudo opsec-mode.sh on
sudo opsec-mode.sh off
# Check OPSEC status
opsec-check.sh
# Pre-flight readiness check
opsec-preflight.sh
# Kill switch control
sudo opsec-killswitch.sh on|off|status
# Apply a deployment level
sudo opsec-config.sh --level apply bare-metal-standard
```
## Deployment Levels
| Level | Description |
|---|---|
| `bare-metal-standard` | Physical machine, ghost mode toggle available |
| `bare-metal-paranoid` | Physical machine, ghost mode always on |
| `cloud-normal` | Cloud VPS, standard privacy (no MAC/hostname) |
| `cloud-paranoid` | Cloud VPS, maximum security always on |
## Configuration
All settings live in `/etc/opsec/opsec.conf`. Edit via `opsec-config.sh` (interactive TUI) or manually.
Key settings:
- `TOR_BLACKLIST` — Comma-separated country codes to exclude from Tor exit nodes
- `DNS_MODE` — DNS resolution mode: `tor`, `quad9`, `cloudflare`, `doh`, `dot`, `custom`
- `HOSTNAME_PATTERN` — Hostname strategy: `desktop`, `random`, `custom`
- `LEVEL_TYPE``standard` (toggle) or `paranoid` (always on)
## Widget Themes
Seven color themes for the desktop status widget:
`default` `aurora` `ember` `slate` `cyberpunk` `frost` `terminal`
```bash
sudo opsec-config.sh --theme apply cyberpunk
```
## Important: Review Your Configuration
**This toolkit ships with intentionally generic defaults.** After installing, you **must** run `sudo opsec-config.sh` and review every setting.
Key items that require your input:
- **`TOR_BLACKLIST`** is empty by default. You need to set exit node exclusions based on your threat model.
- **`HOSTNAME_PATTERN`** defaults to `desktop` with no prefix. Set to `random` if you want randomization.
- **Kill switch and traffic blending** are off by default. Enable them if your threat model requires it.
- **Deployment level** should be selected to match your environment (bare metal vs cloud, standard vs paranoid).
The generic defaults are **safe but minimal**. They prevent accidental misconfiguration but do not represent a hardened posture. Customize for your needs.
## Responsible Use
This toolkit is designed for legitimate privacy protection. Secure deletion features are irreversible. Network anonymization tools have limitations and are not a guarantee of anonymity. Comply with applicable laws in your jurisdiction. This software is provided as-is.
## License
MIT
+3
View File
@@ -0,0 +1,3 @@
# /etc/cron.d/opsec-banner-cache — Update WAN IP cache for terminal banner
# Runs every 5 minutes to keep the cached external IP current
*/5 * * * * root /usr/local/bin/opsec-banner-cache.sh >/dev/null 2>&1
+6
View File
@@ -0,0 +1,6 @@
# /etc/cron.d/opsec-log-rotate — Secure log rotation
# Interval synced from LOG_ROTATION_HOURS in /etc/opsec/opsec.conf (default: 4h)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 */4 * * * root /usr/local/bin/opsec-log-rotate.sh >/dev/null 2>&1
@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=OPSEC Mode Toggle
Comment=Toggle between OPSEC Advanced and Standard modes
Exec=/usr/local/bin/opsec-toggle.sh
Icon=security-high
Terminal=false
Categories=System;Security;
Keywords=opsec;security;toggle;mode;
@@ -0,0 +1,85 @@
# /etc/opsec/levels/bare-metal-paranoid.conf — Physical Machine (Paranoid)
# Maximum security always on. Ghost mode is the default — cannot be disabled.
# Apply via: sudo opsec-config.sh --level apply bare-metal-paranoid
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
# paranoid = ghost mode always on, cannot disable
LEVEL_TYPE="paranoid"
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
PROFILE_NAME="default"
# ─── BASE STATE ───────────────────────────────────────────────────────────────
BASE_DNS="quad9"
BASE_MAC_RANDOMIZE="1"
BASE_IPV6_DISABLE="1"
# ─── TOR SETTINGS ─────────────────────────────────────────────────────────────
TOR_CIRCUIT_ROTATION="15"
TOR_BLACKLIST=""
TOR_STRICT_NODES="1"
TOR_ISOLATION="1"
TOR_PADDING="1"
TOR_SOCKS_PORT="9050"
TOR_DNS_PORT="5353"
TOR_NUM_GUARDS="3"
TOR_SAFE_LOGGING="1"
# ─── DNS SETTINGS ─────────────────────────────────────────────────────────────
DNS_MODE="tor"
DNS_CUSTOM_SERVERS=""
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
KILLSWITCH_ALLOW_DHCP="1"
KILLSWITCH_ALLOW_OPENVPN="1"
KILLSWITCH_ALLOW_WIREGUARD="1"
KILLSWITCH_EXTRA_PORTS=""
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
MAC_INTERFACES="auto"
MAC_VENDOR_SPOOF=""
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
HOSTNAME_PATTERN="random"
HOSTNAME_CUSTOM_PREFIX=""
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
HARDEN_IPV6="1"
HARDEN_SWAP="1"
HARDEN_CORE_DUMPS="1"
HARDEN_CLIPBOARD_CLEAR="1"
HARDEN_SCREEN_LOCK="1"
HARDEN_SCREEN_LOCK_TIMEOUT="300"
HARDEN_TIMEZONE_SPOOF="1"
HARDEN_TIMEZONE_VALUE="UTC"
HARDEN_LOCALE_SPOOF="1"
HARDEN_LOCALE_VALUE="en_US.UTF-8"
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
LEAK_WEBRTC_BLOCK="1"
LEAK_USB_BLOCK="1"
# ─── MONITORING ────────────────────────────────────────────────────────────────
MONITOR_PROCESSES="1"
MONITOR_LOG_ROTATION="1"
LOG_ROTATION_HOURS="1"
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
TRAFFIC_JITTER_ENABLED="1"
TRAFFIC_JITTER_MS="50"
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
TOR_BRIDGE_MODE="obfs4"
TOR_BRIDGE_RELAY=""
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
WIPE_METHOD="auto"
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
DEPLOYMENT_LEVEL="bare-metal-paranoid"
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
OPSEC_BANNER="compact"
# ─── WIDGET THEME ─────────────────────────────────────────────────────────────
WIDGET_THEME="default"
@@ -0,0 +1,86 @@
# /etc/opsec/levels/bare-metal-standard.conf — Physical Machine (Standard)
# Privacy-focused daily driver. Ghost mode available via: opsec-mode on
# Apply via: sudo opsec-config.sh --level apply bare-metal-standard
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
# standard = privacy base, ghost mode toggle available
# paranoid = ghost mode always on, cannot disable
LEVEL_TYPE="standard"
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
PROFILE_NAME="default"
# ─── BASE STATE (always active in standard mode) ─────────────────────────────
BASE_DNS="quad9"
BASE_MAC_RANDOMIZE="1"
BASE_IPV6_DISABLE="1"
# ─── TOR SETTINGS (used when ghost mode is toggled ON) ───────────────────────
TOR_CIRCUIT_ROTATION="30"
TOR_BLACKLIST=""
TOR_STRICT_NODES="1"
TOR_ISOLATION="1"
TOR_PADDING="1"
TOR_SOCKS_PORT="9050"
TOR_DNS_PORT="5353"
TOR_NUM_GUARDS="3"
TOR_SAFE_LOGGING="1"
# ─── DNS SETTINGS (ghost mode DNS — base uses BASE_DNS above) ────────────────
DNS_MODE="tor"
DNS_CUSTOM_SERVERS=""
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
KILLSWITCH_ALLOW_DHCP="1"
KILLSWITCH_ALLOW_OPENVPN="1"
KILLSWITCH_ALLOW_WIREGUARD="1"
KILLSWITCH_EXTRA_PORTS=""
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
MAC_INTERFACES="auto"
MAC_VENDOR_SPOOF=""
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
HOSTNAME_PATTERN="random"
HOSTNAME_CUSTOM_PREFIX=""
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
HARDEN_IPV6="1"
HARDEN_SWAP="1"
HARDEN_CORE_DUMPS="1"
HARDEN_CLIPBOARD_CLEAR="1"
HARDEN_SCREEN_LOCK="1"
HARDEN_SCREEN_LOCK_TIMEOUT="300"
HARDEN_TIMEZONE_SPOOF="0"
HARDEN_TIMEZONE_VALUE="UTC"
HARDEN_LOCALE_SPOOF="0"
HARDEN_LOCALE_VALUE="en_US.UTF-8"
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
LEAK_WEBRTC_BLOCK="1"
LEAK_USB_BLOCK="1"
# ─── MONITORING ────────────────────────────────────────────────────────────────
MONITOR_PROCESSES="1"
MONITOR_LOG_ROTATION="1"
LOG_ROTATION_HOURS="4"
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
TRAFFIC_JITTER_ENABLED="0"
TRAFFIC_JITTER_MS="50"
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
TOR_BRIDGE_MODE="off"
TOR_BRIDGE_RELAY=""
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
WIPE_METHOD="auto"
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
DEPLOYMENT_LEVEL="bare-metal-standard"
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
OPSEC_BANNER="compact"
# ─── WIDGET THEME ─────────────────────────────────────────────────────────────
WIDGET_THEME="default"
+85
View File
@@ -0,0 +1,85 @@
# /etc/opsec/levels/cloud-normal.conf — Cloud VPS (Standard)
# Privacy-focused cloud deployment. Ghost mode available via: opsec-mode on
# No MAC/hostname/screen/USB (hardware-irrelevant on cloud).
# Apply via: sudo opsec-config.sh --level apply cloud-normal
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
LEVEL_TYPE="standard"
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
PROFILE_NAME="default"
# ─── BASE STATE (always active in standard mode) ─────────────────────────────
BASE_DNS="quad9"
BASE_MAC_RANDOMIZE="0"
BASE_IPV6_DISABLE="1"
# ─── TOR SETTINGS (used when ghost mode is toggled ON) ───────────────────────
TOR_CIRCUIT_ROTATION="30"
TOR_BLACKLIST=""
TOR_STRICT_NODES="1"
TOR_ISOLATION="1"
TOR_PADDING="0"
TOR_SOCKS_PORT="9050"
TOR_DNS_PORT="5353"
TOR_NUM_GUARDS="3"
TOR_SAFE_LOGGING="1"
# ─── DNS SETTINGS ─────────────────────────────────────────────────────────────
DNS_MODE="tor"
DNS_CUSTOM_SERVERS=""
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
KILLSWITCH_ALLOW_DHCP="1"
KILLSWITCH_ALLOW_OPENVPN="1"
KILLSWITCH_ALLOW_WIREGUARD="1"
KILLSWITCH_EXTRA_PORTS=""
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
MAC_INTERFACES="auto"
MAC_VENDOR_SPOOF=""
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
HOSTNAME_PATTERN="desktop"
HOSTNAME_CUSTOM_PREFIX=""
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
HARDEN_IPV6="1"
HARDEN_SWAP="1"
HARDEN_CORE_DUMPS="1"
HARDEN_CLIPBOARD_CLEAR="0"
HARDEN_SCREEN_LOCK="0"
HARDEN_SCREEN_LOCK_TIMEOUT="300"
HARDEN_TIMEZONE_SPOOF="0"
HARDEN_TIMEZONE_VALUE="UTC"
HARDEN_LOCALE_SPOOF="0"
HARDEN_LOCALE_VALUE="en_US.UTF-8"
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
LEAK_WEBRTC_BLOCK="1"
LEAK_USB_BLOCK="0"
# ─── MONITORING ────────────────────────────────────────────────────────────────
MONITOR_PROCESSES="1"
MONITOR_LOG_ROTATION="1"
LOG_ROTATION_HOURS="1"
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
TRAFFIC_JITTER_ENABLED="0"
TRAFFIC_JITTER_MS="50"
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
TOR_BRIDGE_MODE="off"
TOR_BRIDGE_RELAY=""
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
WIPE_METHOD="auto"
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
DEPLOYMENT_LEVEL="cloud-normal"
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
OPSEC_BANNER="compact"
# ─── WIDGET THEME ─────────────────────────────────────────────────────────────
WIDGET_THEME="default"
+84
View File
@@ -0,0 +1,84 @@
# /etc/opsec/levels/cloud-paranoid.conf — Cloud VPS (Paranoid)
# Maximum security always on. Ghost mode is the default — cannot be disabled.
# Apply via: sudo opsec-config.sh --level apply cloud-paranoid
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
LEVEL_TYPE="paranoid"
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
PROFILE_NAME="default"
# ─── BASE STATE ───────────────────────────────────────────────────────────────
BASE_DNS="quad9"
BASE_MAC_RANDOMIZE="0"
BASE_IPV6_DISABLE="1"
# ─── TOR SETTINGS ─────────────────────────────────────────────────────────────
TOR_CIRCUIT_ROTATION="15"
TOR_BLACKLIST=""
TOR_STRICT_NODES="1"
TOR_ISOLATION="1"
TOR_PADDING="1"
TOR_SOCKS_PORT="9050"
TOR_DNS_PORT="5353"
TOR_NUM_GUARDS="3"
TOR_SAFE_LOGGING="1"
# ─── DNS SETTINGS ─────────────────────────────────────────────────────────────
DNS_MODE="tor"
DNS_CUSTOM_SERVERS=""
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
KILLSWITCH_ALLOW_DHCP="1"
KILLSWITCH_ALLOW_OPENVPN="1"
KILLSWITCH_ALLOW_WIREGUARD="1"
KILLSWITCH_EXTRA_PORTS=""
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
MAC_INTERFACES="auto"
MAC_VENDOR_SPOOF=""
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
HOSTNAME_PATTERN="desktop"
HOSTNAME_CUSTOM_PREFIX=""
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
HARDEN_IPV6="1"
HARDEN_SWAP="1"
HARDEN_CORE_DUMPS="1"
HARDEN_CLIPBOARD_CLEAR="1"
HARDEN_SCREEN_LOCK="0"
HARDEN_SCREEN_LOCK_TIMEOUT="300"
HARDEN_TIMEZONE_SPOOF="1"
HARDEN_TIMEZONE_VALUE="UTC"
HARDEN_LOCALE_SPOOF="1"
HARDEN_LOCALE_VALUE="en_US.UTF-8"
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
LEAK_WEBRTC_BLOCK="1"
LEAK_USB_BLOCK="0"
# ─── MONITORING ────────────────────────────────────────────────────────────────
MONITOR_PROCESSES="1"
MONITOR_LOG_ROTATION="1"
LOG_ROTATION_HOURS="1"
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
TRAFFIC_JITTER_ENABLED="1"
TRAFFIC_JITTER_MS="50"
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
TOR_BRIDGE_MODE="obfs4"
TOR_BRIDGE_RELAY=""
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
WIPE_METHOD="auto"
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
DEPLOYMENT_LEVEL="cloud-paranoid"
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
OPSEC_BANNER="compact"
# ─── WIDGET THEME ─────────────────────────────────────────────────────────────
WIDGET_THEME="default"
+100
View File
@@ -0,0 +1,100 @@
# ─── OPSEC ALIASES ───────────────────────────────────────────────────────────
# Network checks
alias myip='curl -s ifconfig.me'
alias checkip='curl -s https://ipinfo.io/ip'
alias checkdns='cat /etc/resolv.conf'
alias ports='netstat -tulanp'
alias listen='lsof -i -P | grep LISTEN'
alias estab='lsof -i -P | grep ESTABLISHED'
# Process and connection monitoring
alias checkcon='ss -tupan | grep ESTABLISHED'
alias checklis='ss -tupan | grep LISTEN'
alias checkproc='ps auxf | grep -v grep | grep'
alias psg='ps aux | grep -v grep | grep -i'
# Emergency and cleanup
alias killcon='killall -9 openvpn ssh 2>/dev/null'
alias randmac='randomize-mac.sh'
alias opsec='opsec-check.sh'
# Cleanup operations
alias clear-logs='sudo find /var/log -name '\''opsec*'\'' -type f -exec truncate -s 0 {} \; && sudo truncate -s 0 /var/log/syslog /var/log/auth.log 2>/dev/null'
alias clear-history='history -c && > ~/.bash_history && > ~/.zsh_history'
alias shred-file='shred -vfz -n 3'
# Anonymity
alias anon-on='sudo systemctl start tor && . torsocks on'
alias anon-off='. torsocks off && sudo systemctl stop tor'
alias check-tor='curl -s https://check.torproject.org/api/ip'
# Safe OPSEC status check
alias opsec-status='echo "OPSEC Status:"; echo "VPN: $(pgrep openvpn > /dev/null && echo "Connected" || echo "Disconnected")"; echo "Tor: $(systemctl is-active tor 2>/dev/null | grep -q active && echo "Active" || echo "Inactive")"; echo "IP: $(curl -s --max-time 2 ifconfig.me || echo "Check failed")"'
# Quick OPSEC check
alias quick-opsec='opsec-status && echo "" && echo "=== Connections ===" && ss -tupln | grep -E ":443|:9050|:1080" | head -5'
# ─── ADVANCED OPSEC MODE ──────────────────────────────────────────────────────
# Master toggle
alias opsec-on='sudo /usr/local/bin/opsec-mode.sh on'
alias opsec-off='sudo /usr/local/bin/opsec-mode.sh off'
alias opsec-advanced='sudo /usr/local/bin/opsec-mode.sh status'
# Kill switch controls
alias killswitch-on='sudo /usr/local/bin/opsec-killswitch.sh on'
alias killswitch-off='sudo /usr/local/bin/opsec-killswitch.sh off'
alias killswitch-status='sudo /usr/local/bin/opsec-killswitch.sh status'
# Hostname randomization
alias randhostname='sudo /usr/local/bin/opsec-hostname-randomize.sh'
# Tor circuit management
alias check-circuit='curl -s --socks5-hostname 127.0.0.1:9050 https://check.torproject.org/api/ip'
alias new-circuit='sudo killall -HUP tor && echo "New Tor circuit requested"'
# DNS leak check
alias check-dns-leak='echo "=== DNS Leak Check ===" && echo "resolv.conf:" && grep nameserver /etc/resolv.conf && echo "" && echo "Tor DNS test:" && dig +short @127.0.0.1 -p 5353 check.torproject.org 2>/dev/null || echo "Tor DNS not available"'
# OPSEC Config Manager
alias opsec-config='sudo /usr/local/bin/opsec-config.sh'
alias opsec-show='sudo /usr/local/bin/opsec-mode.sh status'
# Profile management
alias opsec-profile='sudo /usr/local/bin/opsec-config.sh --profile'
# Boot mode
alias opsec-boot-on='sudo /usr/local/bin/opsec-config.sh --boot on'
alias opsec-boot-off='sudo /usr/local/bin/opsec-config.sh --boot off'
# SSH honeypot check
alias ssh-safe='/usr/local/bin/opsec-ssh-check.sh'
# Connection monitor
alias opsec-monitor-start='sudo /usr/local/bin/opsec-monitor.sh start'
alias opsec-monitor-stop='sudo /usr/local/bin/opsec-monitor.sh stop'
alias opsec-monitor-status='sudo /usr/local/bin/opsec-monitor.sh status'
# WiFi security
alias opsec-wifi='sudo /usr/local/bin/opsec-wifi-check.sh'
# Conky widget controls — position: tl tc tr bl bc br (default: bl)
alias opsec-widget-kill='killall conky 2>/dev/null && echo "Widget stopped" || echo "Widget not running"'
opsec-widget() { ~/.config/conky/opsec-widget-launch.sh "${1:-bl}"; }
opsec-widget-restart() { ~/.config/conky/opsec-widget-launch.sh "${1:-bl}"; }
# Deployment level and banner control
alias opsec-level='sudo /usr/local/bin/opsec-config.sh --level apply'
alias opsec-level-list='sudo /usr/local/bin/opsec-config.sh --level list'
alias opsec-banner-set='sudo /usr/local/bin/opsec-config.sh --banner'
alias opsec-toggle='/usr/local/bin/opsec-toggle.sh'
# Preflight and break-glass
alias opsec-preflight='/usr/local/bin/opsec-preflight.sh'
alias opsec-score='/usr/local/bin/opsec-preflight.sh --score'
alias opsec-breakglass='sudo /usr/local/bin/opsec-mode.sh breakglass'
alias opsec-breakglass-off='sudo /usr/local/bin/opsec-mode.sh breakglass-off'
# ─── OPSEC TERMINAL BANNER ──────────────────────────────────────────────────
[ -x /usr/local/bin/opsec-banner.sh ] && /usr/local/bin/opsec-banner.sh
+28
View File
@@ -0,0 +1,28 @@
# /etc/opsec/country-codes.conf — Country Code Presets for Tor Blacklisting
# Used by opsec-config.sh and opsec-lib.sh for quick preset loading
# Format: PRESET_NAME="cc1,cc2,cc3,..."
# ─── INTELLIGENCE ALLIANCES ────────────────────────────────────────────────────
# Five Eyes — core anglosphere intelligence sharing
FIVE_EYES="us,gb,ca,au,nz"
# Nine Eyes — Five Eyes + Denmark, France, Netherlands, Norway
NINE_EYES="us,gb,ca,au,nz,dk,fr,nl,no"
# Fourteen Eyes — Nine Eyes + Germany, Belgium, Italy, Sweden, Spain
FOURTEEN_EYES="us,gb,ca,au,nz,dk,fr,nl,no,de,be,it,se,es"
# ─── HIGH-SURVEILLANCE STATES ─────────────────────────────────────────────────
# Countries with known mass surveillance / hostile SIGINT
SURVEILLANCE_STATES="cn,ru,ir,kp,il,sg,ae,sa,eg,tr,pk,th,vn,by,kz"
# ─── COMBINED PRESETS ──────────────────────────────────────────────────────────
# Maximum exclusion: Fourteen Eyes + surveillance states
MAX_EXCLUSION="us,gb,ca,au,nz,dk,fr,nl,no,de,be,it,se,es,cn,ru,ir,kp,il,sg,ae,sa,eg,tr,pk,th,vn,by,kz"
# ─── VALID COUNTRY CODES (for input validation) ───────────────────────────────
# ISO 3166-1 alpha-2 codes commonly used as Tor country codes
VALID_CODES="ad,ae,af,ag,ai,al,am,ao,ar,as,at,au,az,ba,bb,bd,be,bf,bg,bh,bi,bj,bm,bn,bo,br,bs,bt,bw,by,bz,ca,cd,cf,cg,ch,ci,cl,cm,cn,co,cr,cu,cv,cy,cz,de,dj,dk,dm,do,dz,ec,ee,eg,er,es,et,fi,fj,fm,fr,ga,gb,gd,ge,gh,gm,gn,gp,gq,gr,gt,gw,gy,hk,hn,hr,ht,hu,id,ie,il,in,iq,ir,is,it,jm,jo,jp,ke,kg,kh,ki,km,kn,kp,kr,kw,kz,la,lb,lc,li,lk,lr,ls,lt,lu,lv,ly,ma,mc,md,me,mg,mh,mk,ml,mm,mn,mo,mr,mt,mu,mv,mw,mx,my,mz,na,ne,ng,ni,nl,no,np,nr,nz,om,pa,pe,pg,ph,pk,pl,pt,pw,py,qa,ro,rs,ru,rw,sa,sb,sc,sd,se,sg,si,sk,sl,sm,sn,so,sr,ss,st,sv,sy,sz,td,tg,th,tj,tl,tm,tn,to,tr,tt,tv,tw,tz,ua,ug,us,uy,uz,va,vc,ve,vn,vu,ws,ye,za,zm,zw"
+96
View File
@@ -0,0 +1,96 @@
# /etc/opsec/opsec.conf — Central OPSEC Configuration
# Shell-sourceable KEY="value" format. All scripts source this file.
# Edit via: sudo opsec-config.sh (interactive TUI)
# Manual edits: source /usr/local/lib/opsec/opsec-lib.sh && opsec_set_value KEY "value"
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
PROFILE_NAME="default"
# ─── TOR SETTINGS ──────────────────────────────────────────────────────────────
TOR_CIRCUIT_ROTATION="30"
TOR_BLACKLIST=""
TOR_STRICT_NODES="1"
TOR_ISOLATION="1"
TOR_PADDING="1"
TOR_SOCKS_PORT="9050"
TOR_DNS_PORT="5353"
TOR_NUM_GUARDS="3"
TOR_SAFE_LOGGING="1"
# ─── DNS SETTINGS ──────────────────────────────────────────────────────────────
# Mode: tor | quad9 | cloudflare | doh | dot | custom
DNS_MODE="tor"
DNS_CUSTOM_SERVERS=""
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
KILLSWITCH_ALLOW_DHCP="1"
KILLSWITCH_ALLOW_OPENVPN="1"
KILLSWITCH_ALLOW_WIREGUARD="1"
KILLSWITCH_EXTRA_PORTS=""
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
# Interfaces: auto (all non-lo) or comma-separated list (e.g. "eth0,wlan0")
MAC_INTERFACES="auto"
# Vendor spoof: empty for random, or OUI prefix (e.g. "00:1A:2B" for specific vendor)
MAC_VENDOR_SPOOF=""
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
# Pattern: desktop | random | custom
HOSTNAME_PATTERN="desktop"
HOSTNAME_CUSTOM_PREFIX=""
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
HARDEN_IPV6="1"
HARDEN_SWAP="1"
HARDEN_CORE_DUMPS="1"
HARDEN_CLIPBOARD_CLEAR="0"
HARDEN_SCREEN_LOCK="1"
HARDEN_SCREEN_LOCK_TIMEOUT="300"
HARDEN_TIMEZONE_SPOOF="0"
HARDEN_TIMEZONE_VALUE="UTC"
HARDEN_LOCALE_SPOOF="0"
HARDEN_LOCALE_VALUE="en_US.UTF-8"
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
LEAK_WEBRTC_BLOCK="1"
LEAK_USB_BLOCK="0"
# ─── MONITORING ────────────────────────────────────────────────────────────────
MONITOR_PROCESSES="0"
MONITOR_LOG_ROTATION="1"
LOG_ROTATION_HOURS="4"
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
TRAFFIC_JITTER_ENABLED="0"
TRAFFIC_JITTER_MS="50"
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
# standard = privacy base, ghost mode toggle available
# paranoid = ghost mode always on, cannot disable
LEVEL_TYPE="standard"
# ─── BASE STATE (always active in standard mode) ─────────────────────────────
BASE_DNS="quad9"
BASE_MAC_RANDOMIZE="1"
BASE_IPV6_DISABLE="1"
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
# Mode: off | obfs4 | meek-azure | snowflake
TOR_BRIDGE_MODE="off"
TOR_BRIDGE_RELAY=""
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
# Method: auto | shred | fstrim | luks
WIPE_METHOD="auto"
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
# Level: bare-metal-standard | bare-metal-paranoid | cloud-normal | cloud-paranoid
DEPLOYMENT_LEVEL="bare-metal-standard"
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
# Mode: compact | full | auto | off
OPSEC_BANNER="compact"
# ─── WIDGET THEME ────────────────────────────────────────────────────────────
# Theme: default | aurora | ember | slate | cyberpunk | frost | terminal
WIDGET_THEME="default"
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<action id="com.opsec.mode.toggle">
<description>Toggle OPSEC Advanced Mode</description>
<message>Authentication is required to toggle OPSEC mode</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/local/bin/opsec-mode.sh</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
</policyconfig>
+14
View File
@@ -0,0 +1,14 @@
# OPSEC DNS Servers
# Quad9 (Security focused)
nameserver 9.9.9.9
nameserver 149.112.112.112
# Cloudflare (Privacy focused)
nameserver 1.1.1.1
nameserver 1.0.0.1
# DNS Options
options edns0 trust-ad
options timeout:1
options attempts:1
options rotate
+3
View File
@@ -0,0 +1,3 @@
# OPSEC Mode DNS — all resolution through Tor DNSPort
# Deployed by opsec-mode on, locked with chattr +i
nameserver 127.0.0.1
@@ -0,0 +1,18 @@
[Unit]
Description=OPSEC Boot-into-Advanced Mode Initializer
Documentation=man:opsec-mode(8)
DefaultDependencies=no
Before=network-pre.target NetworkManager.service networking.service
Before=opsec-mac-randomize.service opsec-hostname-randomize.service opsec-killswitch.service
After=local-fs.target sysinit.target
ConditionPathExists=/etc/opsec/boot-advanced.enabled
[Service]
Type=oneshot
ExecStart=/usr/local/bin/opsec-boot-init.sh
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,14 @@
[Unit]
Description=OPSEC Hostname Randomization
Before=NetworkManager.service
Before=networking.service
After=local-fs.target
ConditionPathExists=/var/run/opsec-advanced.enabled
[Service]
Type=oneshot
ExecStart=/usr/local/bin/opsec-hostname-randomize.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,15 @@
[Unit]
Description=OPSEC Kill Switch (block non-Tor/VPN traffic)
Before=NetworkManager.service
Before=networking.service
After=local-fs.target
ConditionPathExists=/var/run/opsec-advanced.enabled
[Service]
Type=oneshot
ExecStart=/usr/local/bin/opsec-killswitch.sh on
ExecStop=/usr/local/bin/opsec-killswitch.sh off
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,14 @@
[Unit]
Description=OPSEC MAC Address Randomization
Before=NetworkManager.service
Before=networking.service
After=local-fs.target
ConditionPathExists=/var/run/opsec-advanced.enabled
[Service]
Type=oneshot
ExecStart=/usr/local/bin/randomize-mac.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
+14
View File
@@ -0,0 +1,14 @@
# Aurora theme — purple and cyan tones
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
THEME_LABEL="Aurora"
CONKY_BG="000000"
CONKY_COLOR0="B55AFC"
CONKY_COLOR1="FF63BE"
CONKY_COLOR2="85E7FF"
CONKY_COLOR3="000000"
CONKY_COLOR4="268BD2"
CONKY_COLOR5="07CAF9"
CONKY_COLOR6="85E7FF"
CONKY_COLOR7="ECDEF7"
CONKY_COLOR8="B55AFC"
CONKY_COLOR9="4A6A7A"
+14
View File
@@ -0,0 +1,14 @@
# CyberPunk theme — neon cyan/pink/green on black
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
THEME_LABEL="CyberPunk"
CONKY_BG="000000"
CONKY_COLOR0="FF3B3B"
CONKY_COLOR1="00F5A0"
CONKY_COLOR2="08F7FE"
CONKY_COLOR3="000000"
CONKY_COLOR4="08F7FE"
CONKY_COLOR5="FF2E88"
CONKY_COLOR6="00D1FF"
CONKY_COLOR7="EAEAFF"
CONKY_COLOR8="08F7FE"
CONKY_COLOR9="2A2A2A"
+14
View File
@@ -0,0 +1,14 @@
# Default theme — balanced green-on-black aesthetic
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
THEME_LABEL="Default"
CONKY_BG="000000"
CONKY_COLOR0="FF4C4C"
CONKY_COLOR1="00FF9C"
CONKY_COLOR2="49D6B6"
CONKY_COLOR3="000000"
CONKY_COLOR4="49D6B6"
CONKY_COLOR5="2AFFC6"
CONKY_COLOR6="2AFFC6"
CONKY_COLOR7="CFFFE6"
CONKY_COLOR8="49D6B6"
CONKY_COLOR9="1A1A1A"
+14
View File
@@ -0,0 +1,14 @@
# Ember theme — warm red and white tones
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
THEME_LABEL="Ember"
CONKY_BG="000000"
CONKY_COLOR0="FF2B2B"
CONKY_COLOR1="A0A0A0"
CONKY_COLOR2="B0B0B0"
CONKY_COLOR3="000000"
CONKY_COLOR4="FF2B2B"
CONKY_COLOR5="FF7A7A"
CONKY_COLOR6="CFCFCF"
CONKY_COLOR7="EDEDED"
CONKY_COLOR8="FF2B2B"
CONKY_COLOR9="A0A0A0"
+14
View File
@@ -0,0 +1,14 @@
# Frost theme — cool blues and teals
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
THEME_LABEL="Frost"
CONKY_BG="000000"
CONKY_COLOR0="DC2626"
CONKY_COLOR1="5EEAD4"
CONKY_COLOR2="00E5FF"
CONKY_COLOR3="000000"
CONKY_COLOR4="22D3EE"
CONKY_COLOR5="00E5FF"
CONKY_COLOR6="22D3EE"
CONKY_COLOR7="DDE6F3"
CONKY_COLOR8="00E5FF"
CONKY_COLOR9="1A1A1A"
+14
View File
@@ -0,0 +1,14 @@
# Slate theme — muted neutral tones with gold accent
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
THEME_LABEL="Slate"
CONKY_BG="000000"
CONKY_COLOR0="C63636"
CONKY_COLOR1="9A9A9A"
CONKY_COLOR2="B0B0B0"
CONKY_COLOR3="000000"
CONKY_COLOR4="7A7A7A"
CONKY_COLOR5="D1A954"
CONKY_COLOR6="AFAFAF"
CONKY_COLOR7="E6E6E6"
CONKY_COLOR8="B0B0B0"
CONKY_COLOR9="7A7A7A"
+14
View File
@@ -0,0 +1,14 @@
# Terminal theme — classic monochrome green CRT aesthetic
# Deployed to /etc/opsec/themes/ — sourced by opsec_generate_conky()
THEME_LABEL="Terminal"
CONKY_BG="0A0A0A"
CONKY_COLOR0="FF3333"
CONKY_COLOR1="33FF33"
CONKY_COLOR2="22BB22"
CONKY_COLOR3="0A0A0A"
CONKY_COLOR4="118811"
CONKY_COLOR5="44FF44"
CONKY_COLOR6="33DD33"
CONKY_COLOR7="AAFFAA"
CONKY_COLOR8="22CC22"
CONKY_COLOR9="1A3A1A"
+5
View File
@@ -0,0 +1,5 @@
# Minimal stock Tor configuration
# Restored by opsec-mode off
SocksPort 9050
Log notice file /var/log/tor/notices.log
+32
View File
@@ -0,0 +1,32 @@
# Hardened Tor Configuration for OPSEC Mode
# Deployed by opsec-mode on — restored to torrc-default on off
# ─── SOCKS & DNS ────────────────────────────────────────────────────────────
SocksPort 9050 IsolateDestAddr IsolateDestPort
DNSPort 5353
# ─── FIVE EYES EXCLUSION ────────────────────────────────────────────────────
# Never use exit nodes in Five Eyes countries
ExcludeExitNodes {us},{gb},{ca},{au},{nz}
StrictNodes 1
# ─── CIRCUIT ROTATION ───────────────────────────────────────────────────────
# Rotate circuits every 30 seconds for maximum anonymity
MaxCircuitDirtiness 30
# ─── STREAM ISOLATION ───────────────────────────────────────────────────────
# Each destination gets its own circuit
IsolateSOCKSAuth 1
# ─── TRAFFIC PADDING ────────────────────────────────────────────────────────
# Pad cells to resist traffic analysis
ConnectionPadding 1
# ─── SAFE LOGGING ────────────────────────────────────────────────────────────
# Scrub sensitive info from logs
SafeLogging 1
Log notice file /var/log/tor/notices.log
# ─── PERFORMANCE ─────────────────────────────────────────────────────────────
NumEntryGuards 3
UseEntryGuards 1
+15
View File
@@ -0,0 +1,15 @@
# /etc/udev/rules.d/99-opsec-usb.rules — OPSEC USB Device Monitoring
# Logs all USB insertions and sends desktop notification
# Optionally blocks new devices when LEAK_USB_BLOCK=1 in /etc/opsec/opsec.conf
# Log all USB device insertions
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
RUN+="/bin/bash -c 'echo \"[$(date +%%Y-%%m-%%d\\ %%H:%%M:%%S)] USB INSERT: vendor=$attr{idVendor} product=$attr{idProduct} serial=$attr{serial}\" >> /var/log/opsec-usb.log'"
# Desktop notification on USB insertion
ACTION=="add", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", \
RUN+="/bin/bash -c 'REAL_USER=$(who | head -1 | awk \"{print \\$1}\"); [ -n \"$REAL_USER\" ] && su - $REAL_USER -c \"DISPLAY=:0 notify-send -u critical OPSEC\\ USB \\\"USB device inserted: $attr{idVendor}:$attr{idProduct}\\\"\" 2>/dev/null || true'"
# Log USB mass storage specifically (higher risk)
ACTION=="add", SUBSYSTEM=="block", ENV{ID_USB_DRIVER}=="usb-storage", \
RUN+="/bin/bash -c 'echo \"[$(date +%%Y-%%m-%%d\\ %%H:%%M:%%S)] USB STORAGE: $env{ID_VENDOR} $env{ID_MODEL} $env{ID_SERIAL}\" >> /var/log/opsec-usb.log'"
@@ -0,0 +1,69 @@
# Double-Hop OpenVPN Configuration Template
# Chain two VPN servers for additional anonymity layer
# Replace %%VARIABLES%% with actual values before use
#
# Usage: Copy and fill in variables, then:
# sudo openvpn --config double-hop.ovpn
# ─── First Hop (Entry Server) ─────────────────────────────────────────────────
# This is the server your ISP sees you connecting to
client
dev tun
proto %%PROTO_1%%
remote %%SERVER_1%% %%PORT_1%%
resolv-retry infinite
nobind
persist-key
persist-tun
# Authentication
auth-user-pass %%AUTH_FILE_1%%
ca %%CA_FILE_1%%
# Security
cipher AES-256-GCM
auth SHA512
tls-cipher TLS-ECDHE-RSA-WITH-AES-256-GCM-SHA384
tls-version-min 1.2
remote-cert-tls server
# Routing — send all traffic through VPN
redirect-gateway def1
dhcp-option DNS 10.8.0.1
# Keepalive
keepalive 10 60
ping-timer-rem
# Logging
verb 3
mute 10
# ─── Second Hop Configuration ─────────────────────────────────────────────────
# After first VPN is established, launch second hop:
#
# sudo openvpn --config hop2.ovpn --route-nopull \
# --route %%SERVER_2%% 255.255.255.255 net_gateway
#
# Where hop2.ovpn contains:
# client
# dev tun1
# proto %%PROTO_2%%
# remote %%SERVER_2%% %%PORT_2%%
# auth-user-pass %%AUTH_FILE_2%%
# ca %%CA_FILE_2%%
# cipher AES-256-GCM
# redirect-gateway def1
# ─── Variables Reference ───────────────────────────────────────────────────────
# %%SERVER_1%% — First hop IP/hostname
# %%PORT_1%% — First hop port (e.g. 1194, 443)
# %%PROTO_1%% — First hop protocol (udp/tcp)
# %%AUTH_FILE_1%% — First hop credentials file
# %%CA_FILE_1%% — First hop CA certificate
# %%SERVER_2%% — Second hop IP/hostname
# %%PORT_2%% — Second hop port
# %%PROTO_2%% — Second hop protocol
# %%AUTH_FILE_2%% — Second hop credentials file
# %%CA_FILE_2%% — Second hop CA certificate
@@ -0,0 +1,72 @@
# WireGuard Multi-Hop Configuration Template
# Chain WireGuard tunnels for additional anonymity
# Replace %%VARIABLES%% with actual values
#
# Architecture:
# You → wg0 (Hop 1) → wg1 (Hop 2) → Internet
#
# Usage:
# sudo cp this-file /etc/wireguard/wg0.conf (fill in variables)
# sudo wg-quick up wg0
# sudo wg-quick up wg1
# ═══════════════════════════════════════════════════════════════════════════════
# HOP 1: /etc/wireguard/wg0.conf — Entry tunnel (your ISP sees this)
# ═══════════════════════════════════════════════════════════════════════════════
[Interface]
PrivateKey = %%PRIVATE_KEY_1%%
Address = %%TUNNEL_IP_1%%/32
DNS = %%DNS_1%%
# MTU adjustment for encapsulation overhead
MTU = 1380
# Post-routing to allow hop 2 through hop 1
PostUp = ip rule add from %%TUNNEL_IP_2%% table 200; ip route add default via %%GATEWAY_1%% table 200
PostDown = ip rule del from %%TUNNEL_IP_2%% table 200; ip route del default via %%GATEWAY_1%% table 200
[Peer]
PublicKey = %%PUBLIC_KEY_1%%
PresharedKey = %%PSK_1%%
Endpoint = %%ENDPOINT_1%%:%%PORT_1%%
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
# ═══════════════════════════════════════════════════════════════════════════════
# HOP 2: /etc/wireguard/wg1.conf — Exit tunnel (internet sees this)
# ═══════════════════════════════════════════════════════════════════════════════
#
# [Interface]
# PrivateKey = %%PRIVATE_KEY_2%%
# Address = %%TUNNEL_IP_2%%/32
# DNS = %%DNS_2%%
# MTU = 1340
# # Route this tunnel's traffic through wg0
# Table = off
# PostUp = ip rule add from %%TUNNEL_IP_2%% table 200
# PostDown = ip rule del from %%TUNNEL_IP_2%% table 200
#
# [Peer]
# PublicKey = %%PUBLIC_KEY_2%%
# PresharedKey = %%PSK_2%%
# Endpoint = %%ENDPOINT_2%%:%%PORT_2%%
# AllowedIPs = 0.0.0.0/0, ::/0
# PersistentKeepalive = 25
# ─── Variables Reference ───────────────────────────────────────────────────────
# %%PRIVATE_KEY_1%% — Your private key for hop 1 (wg genkey)
# %%PUBLIC_KEY_1%% — Hop 1 server's public key
# %%PSK_1%% — Preshared key for hop 1 (wg genpsk)
# %%ENDPOINT_1%% — Hop 1 server IP/hostname
# %%PORT_1%% — Hop 1 server port (default: 51820)
# %%TUNNEL_IP_1%% — Your assigned tunnel IP on hop 1
# %%GATEWAY_1%% — Hop 1 internal gateway IP
# %%DNS_1%% — DNS server for hop 1
#
# %%PRIVATE_KEY_2%% — Your private key for hop 2
# %%PUBLIC_KEY_2%% — Hop 2 server's public key
# %%PSK_2%% — Preshared key for hop 2
# %%ENDPOINT_2%% — Hop 2 server IP/hostname (reachable via hop 1)
# %%PORT_2%% — Hop 2 server port
# %%TUNNEL_IP_2%% — Your assigned tunnel IP on hop 2
# %%DNS_2%% — DNS server for hop 2
+146
View File
@@ -0,0 +1,146 @@
#!/bin/bash
# conky-opsec-cache — Background IP/geo cache updater
# Runs in a loop, writes results to cache file that the status script reads.
# This keeps all slow network calls OUT of the conky render path.
CACHE_DIR="/tmp/.opsec-cache"
CACHE_FILE="${CACHE_DIR}/netinfo"
LOCK_FILE="${CACHE_DIR}/.updating"
INTERVAL=30 # seconds between updates
mkdir -p "$CACHE_DIR"
chmod 700 "$CACHE_DIR"
update_cache() {
# Prevent concurrent updates
[ -f "$LOCK_FILE" ] && return
touch "$LOCK_FILE"
local advanced=false
[ -f /var/run/opsec-advanced.enabled ] && advanced=true
local pub_ip="" pub_geo="" routed_tor=false exit_country=""
# In Advanced mode with SOCKS available: verify Tor via HTTPS, then get geo
if $advanced && ss -tln 2>/dev/null | grep -q ':9050 '; then
# Step 1: Verify Tor routing via HTTPS (encrypted, trusted endpoint)
local tor_json
tor_json=$(curl -4 -s --max-time 10 --socks5-hostname 127.0.0.1:9050 "https://check.torproject.org/api/ip" 2>/dev/null)
if echo "$tor_json" | grep -q '"IsTor":true'; then
pub_ip=$(echo "$tor_json" | grep -o '"IP":"[^"]*"' | cut -d'"' -f4)
if [ -n "$pub_ip" ] && echo "$pub_ip" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
routed_tor=true
else
pub_ip=""
fi
fi
# Step 2: Get geo data via ip-api.com (HTTP only — but over Tor SOCKS so exit encrypts)
# ip-api.com pro HTTPS requires a paid key; free tier is HTTP-only
# This is acceptable: traffic goes through Tor SOCKS tunnel, so local network can't see it
if [ -n "$pub_ip" ]; then
local geo_json
geo_json=$(curl -4 -s --max-time 10 --socks5-hostname 127.0.0.1:9050 "http://ip-api.com/json/${pub_ip}?fields=countryCode,city" 2>/dev/null)
local geo_country geo_city
geo_country=$(echo "$geo_json" | grep -o '"countryCode":"[^"]*"' | cut -d'"' -f4)
geo_city=$(echo "$geo_json" | grep -o '"city":"[^"]*"' | cut -d'"' -f4)
exit_country="$geo_country"
[ -n "$geo_country" ] && pub_geo="${geo_city:+${geo_city}, }${geo_country}"
fi
fi
# Fallback to direct — only if NOT in advanced mode (kill switch would DROP it)
if [ -z "$pub_ip" ] && ! $advanced; then
local api_json
api_json=$(curl -4 -s --max-time 4 "https://check.torproject.org/api/ip" 2>/dev/null)
if [ -n "$api_json" ]; then
pub_ip=$(echo "$api_json" | grep -o '"IP":"[^"]*"' | cut -d'"' -f4)
fi
# Validate IP format
if [ -n "$pub_ip" ] && ! echo "$pub_ip" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
pub_ip=""
fi
# Get geo via HTTPS (direct mode — no Tor)
if [ -n "$pub_ip" ]; then
local geo_json
geo_json=$(curl -4 -s --max-time 4 "http://ip-api.com/json/${pub_ip}?fields=countryCode,city" 2>/dev/null)
local geo_country geo_city
geo_country=$(echo "$geo_json" | grep -o '"countryCode":"[^"]*"' | cut -d'"' -f4)
geo_city=$(echo "$geo_json" | grep -o '"city":"[^"]*"' | cut -d'"' -f4)
[ -n "$geo_country" ] && pub_geo="${geo_city:+${geo_city}, }${geo_country}"
fi
fi
# Sanitize exit country — should be 2-3 letter country code
[ -n "$exit_country" ] && [ ${#exit_country} -gt 3 ] && exit_country=""
# Track when exit IP last changed (for circuit age display)
local exit_change_time=""
local prev_ip="" prev_change_time=""
if [ -f "$CACHE_FILE" ]; then
prev_ip=$(grep '^PUB_IP=' "$CACHE_FILE" 2>/dev/null | cut -d'"' -f2)
prev_change_time=$(grep '^EXIT_CHANGE_TIME=' "$CACHE_FILE" 2>/dev/null | cut -d'"' -f2)
fi
if [ -n "$pub_ip" ] && [ "$pub_ip" != "$prev_ip" ]; then
exit_change_time="$(date +%s)"
elif [ -n "$prev_change_time" ]; then
exit_change_time="$prev_change_time"
else
exit_change_time="$(date +%s)"
fi
# Tor bootstrap progress — only from current Tor session
local tor_bootstrap="" tor_phase=""
if $advanced && systemctl is-active tor >/dev/null 2>&1; then
# Get Tor start time, only read log lines after it
local tor_start
tor_start=$(systemctl show tor@default --property=ActiveEnterTimestamp 2>/dev/null | cut -d= -f2)
if [ -n "$tor_start" ]; then
local start_ts
start_ts=$(date -d "$tor_start" +%s 2>/dev/null || echo 0)
local boot_line=""
# Read bootstrap lines — -h suppresses filename prefix when checking both paths
while IFS= read -r line; do
local log_date
log_date=$(echo "$line" | grep -oP '^\w+ \d+ [\d:.]+')
if [ -n "$log_date" ]; then
local log_ts
log_ts=$(date -d "$log_date" +%s 2>/dev/null || echo 0)
[ "$log_ts" -ge "$start_ts" ] && boot_line="$line"
fi
done < <(grep -h "Bootstrapped" /run/tor/notices.log /var/log/tor/notices.log 2>/dev/null)
if [ -n "$boot_line" ]; then
tor_bootstrap=$(echo "$boot_line" | grep -oP '\d+(?=%)')
tor_phase=$(echo "$boot_line" | grep -oP '\(\K[^)]+' | head -1)
fi
fi
fi
# Write atomically (write to tmp then move) — quote values for safe sourcing
local tmp="${CACHE_FILE}.tmp"
cat > "$tmp" <<EOF
PUB_IP="${pub_ip}"
PUB_GEO="${pub_geo}"
ROUTED_TOR="${routed_tor}"
EXIT_COUNTRY="${exit_country}"
EXIT_CHANGE_TIME="${exit_change_time}"
TOR_BOOTSTRAP="${tor_bootstrap}"
TOR_PHASE="${tor_phase}"
CACHE_TIME="$(date +%s)"
EOF
chmod 600 "$tmp"
mv -f "$tmp" "$CACHE_FILE"
rm -f "$LOCK_FILE"
}
# Initial update immediately
update_cache
# Loop forever — poll faster when Tor is bootstrapping
while true; do
if [ -f /var/run/opsec-advanced.enabled ] && systemctl is-active tor >/dev/null 2>&1 && ! ss -tln 2>/dev/null | grep -q ':9050 '; then
sleep 5 # Tor bootstrapping — fast poll
else
sleep "$INTERVAL"
fi
update_cache
done
+225
View File
@@ -0,0 +1,225 @@
#!/bin/bash
# Conky OPSEC Status — OPSEC Status Widget
# Called by conky via execpi — outputs Conky color markup
# Network data is read from cache (updated by conky-opsec-cache.sh in background)
#
# Color map (managed by theme system):
# 0 = ALERT/BAD 1 = SECURE/GOOD 2 = INFO/NEUTRAL
# 3 = VOID/BG 4 = STRUCTURAL 5 = TITLE/ACCENT
# 6 = LABELS 7 = VALUES 8 = SECTION HDR
# 9 = METADATA
ADVANCED=false
[ -f /var/run/opsec-advanced.enabled ] && ADVANCED=true
# ─── READ CACHE (instant — no network calls) ──────────────────────────────
# Safe parsing: extract values via grep/cut instead of sourcing as shell code
CACHE_FILE="/tmp/.opsec-cache/netinfo"
PUB_IP="" PUB_GEO="" ROUTED_TOR=false EXIT_COUNTRY="" EXIT_CHANGE_TIME="" TOR_BOOTSTRAP="" TOR_PHASE="" CACHE_TIME=0
if [ -f "$CACHE_FILE" ]; then
_safe_read() { grep "^${1}=" "$CACHE_FILE" 2>/dev/null | head -1 | cut -d'"' -f2; }
PUB_IP=$(_safe_read PUB_IP)
PUB_GEO=$(_safe_read PUB_GEO)
ROUTED_TOR=$(_safe_read ROUTED_TOR)
EXIT_COUNTRY=$(_safe_read EXIT_COUNTRY)
EXIT_CHANGE_TIME=$(_safe_read EXIT_CHANGE_TIME)
TOR_BOOTSTRAP=$(_safe_read TOR_BOOTSTRAP)
TOR_PHASE=$(_safe_read TOR_PHASE)
CACHE_TIME=$(_safe_read CACHE_TIME)
# Sanitize: strip anything that isn't alphanumeric, dots, commas, spaces, or dashes
PUB_IP=$(echo "$PUB_IP" | tr -cd '0-9.')
EXIT_COUNTRY=$(echo "$EXIT_COUNTRY" | tr -cd 'A-Za-z')
EXIT_CHANGE_TIME=$(echo "$EXIT_CHANGE_TIME" | tr -cd '0-9')
TOR_BOOTSTRAP=$(echo "$TOR_BOOTSTRAP" | tr -cd '0-9')
fi
# Kick off cache daemon if not running
if ! pgrep -f 'conky-opsec-cache\.sh' >/dev/null 2>&1; then
nohup ~/.config/conky/conky-opsec-cache.sh >/dev/null 2>&1 &
fi
# ─── HEADER ──────────────────────────────────────────────────────────────────
echo "\${color4}\${hr 1}"
if $ADVANCED; then
echo "\${color5}\${font JetBrains Mono:bold:size=11}\${alignc}OPSEC STATUS\${font}"
echo "\${color1}\${font JetBrains Mono:bold:size=8}\${alignc}▲ ADVANCED ▲\${font}"
else
echo "\${color5}\${font JetBrains Mono:bold:size=11}\${alignc}OPSEC STATUS\${font}"
echo "\${color2}\${font JetBrains Mono:size=8}\${alignc}── STANDARD ──\${font}"
fi
echo "\${color4}\${hr 1}"
# ─── SYSTEM ──────────────────────────────────────────────────────────────────
echo "\${color8}\${font JetBrains Mono:bold:size=9} ▌SYSTEM\${font}"
echo "\${color6} HOST \${color7}\${nodename}\${alignr}\${color6}UP \${color7}\${uptime_short}"
echo "\${color6} CPU \${color7}\${cpu}%\${alignr}\${color6}RAM \${color7}\${memperc}% \${color9}(\${mem}/\${memmax})"
# ─── NETWORK ─────────────────────────────────────────────────────────────────
echo "\${color4}\${hr 1}"
echo "\${color8}\${font JetBrains Mono:bold:size=9} ▌NETWORK\${font}"
LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
[ -z "$LOCAL_IP" ] && LOCAL_IP="No route"
echo "\${color6} LOCAL IP \${color7}${LOCAL_IP}"
# External IP from cache
_display_ip="${PUB_IP}"
[ -z "$_display_ip" ] && _display_ip="\${color0}UNAVAILABLE"
if [ -n "$PUB_GEO" ]; then
echo "\${color6} EXT IP \${color7}${_display_ip} \${color9}(${PUB_GEO})"
else
echo "\${color6} EXT IP \${color7}${_display_ip}"
fi
VPN_IF=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^(tun|wg)' | head -1)
if [ -n "$VPN_IF" ]; then
VPN_IP=$(ip -4 addr show "$VPN_IF" 2>/dev/null | awk '/inet / {print $2}' | cut -d/ -f1)
echo "\${color6} VPN \${color1}ACTIVE \${color7}${VPN_IP} \${color9}(${VPN_IF})"
fi
# TOR/DNS/KSWCH status moved to TOR STATUS section below
PRIMARY_IF=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
if [ -n "$PRIMARY_IF" ]; then
CUR_MAC=$(ip link show "$PRIMARY_IF" 2>/dev/null | awk '/ether/ {print $2}')
FIRST_OCTET=$(echo "$CUR_MAC" | cut -d: -f1)
FIRST_DEC=$((16#${FIRST_OCTET}))
if (( FIRST_DEC & 2 )); then
echo "\${color6} MAC \${color1}RANDOM \${color9}(${CUR_MAC})"
else
echo "\${color6} MAC \${color0}HWADDR \${color9}(${CUR_MAC})"
fi
else
echo "\${color6} MAC \${color9}NO IFACE"
fi
# ─── ADVANCED MODE EXTRAS ────────────────────────────────────────────────────
if $ADVANCED; then
echo "\${color4}\${hr 1}"
echo "\${color8}\${font JetBrains Mono:bold:size=9} ▌HARDENING\${font}"
IPV6_ALL=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0")
[ "$IPV6_ALL" = "1" ] && echo "\${color6} IPv6 \${color1}BLOCKED" || echo "\${color6} IPv6 \${color0}LEAKING"
if lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then
echo "\${color6} DNSLK \${color1}LOCKED \${color9}(immutable)"
else
echo "\${color6} DNSLK \${color0}UNLOCKED"
fi
ISO=$(grep "IsolateDestAddr" /etc/tor/torrc 2>/dev/null | head -1)
[ -n "$ISO" ] && echo "\${color6} ISOL \${color1}ACTIVE \${color9}(stream isolation)"
PAD=$(grep "ConnectionPadding" /etc/tor/torrc 2>/dev/null | awk '{print $2}')
[ "$PAD" = "1" ] && echo "\${color6} TPAD \${color1}ACTIVE \${color9}(traffic padding)"
BLACKLIST=$(grep "ExcludeExitNodes" /etc/tor/torrc 2>/dev/null | sed 's/ExcludeExitNodes //' | tr -d '{}' | tr ',' ' ')
[ -n "$BLACKLIST" ] && echo "\${color6} BLOCK \${color0}$(echo "$BLACKLIST" | tr ' ' ',' | sed 's/,$//')"
CORE_PAT=$(sysctl -n kernel.core_pattern 2>/dev/null)
[[ "$CORE_PAT" == *"/bin/false"* ]] && echo "\${color6} CORE \${color1}BLOCKED" || echo "\${color6} CORE \${color0}ENABLED"
SWAP_ACTIVE=$(swapon --show=SIZE --noheadings 2>/dev/null | head -1)
[ -z "$SWAP_ACTIVE" ] && echo "\${color6} SWAP \${color1}OFF" || echo "\${color6} SWAP \${color0}ACTIVE \${color9}(${SWAP_ACTIVE})"
BOOT_COUNT=0
for svc in opsec-boot-advanced opsec-mac-randomize opsec-hostname-randomize opsec-killswitch; do
systemctl is-enabled "$svc" 2>/dev/null | grep -q enabled && BOOT_COUNT=$((BOOT_COUNT + 1))
done
if [ "$BOOT_COUNT" -eq 4 ]; then
echo "\${color6} BOOT \${color1}PERSIST \${color9}(${BOOT_COUNT}/4)"
elif [ "$BOOT_COUNT" -gt 0 ]; then
echo "\${color6} BOOT \${color2}PARTIAL \${color9}(${BOOT_COUNT}/4)"
else
echo "\${color6} BOOT \${color0}NONE \${color9}(${BOOT_COUNT}/4)"
fi
[ -f /etc/opsec/opsec.conf ] && {
PROFILE=$(grep "^PROFILE_NAME=" /etc/opsec/opsec.conf 2>/dev/null | cut -d'"' -f2)
[ -n "$PROFILE" ] && echo "\${color6} PROF \${color2}${PROFILE}"
}
fi
# ─── TOR STATUS (replaces Route Chain) ──────────────────────────────────────
if $ADVANCED; then
echo "\${color4}\${hr 1}"
echo "\${color8}\${font JetBrains Mono:bold:size=9} ▌TOR STATUS\${font}"
# Determine protection state
_tor_svc=false; _tor_socks=false; _ks_armed=false; _tor_routed=false
systemctl is-active tor >/dev/null 2>&1 && _tor_svc=true
ss -tln 2>/dev/null | grep -q ':9050 ' && _tor_socks=true
# iptables -L requires root; use state file instead (kill switch is always armed when advanced is on)
$ADVANCED && _ks_armed=true
[ "$ROUTED_TOR" = "true" ] && _tor_routed=true
if $_tor_svc && $_tor_socks && $_ks_armed && $_tor_routed; then
echo "\${color6} STATE \${color1}PROTECTED"
elif $_tor_svc && ! $_tor_socks; then
# Bootstrapping
echo "\${color6} STATE \${color2}BOOTSTRAP \${color9}(${TOR_BOOTSTRAP:-0}% ${TOR_PHASE:-connecting})"
elif ! $_tor_svc; then
echo "\${color6} STATE \${color0}EXPOSED \${color9}(tor down)"
elif ! $_ks_armed; then
echo "\${color6} STATE \${color0}EXPOSED \${color9}(kill switch off)"
else
echo "\${color6} STATE \${color0}EXPOSED \${color9}(not routed)"
fi
# Exit country code only — no IP
if [ -n "$EXIT_COUNTRY" ] && [ ${#EXIT_COUNTRY} -le 3 ]; then
echo "\${color6} EXIT \${color2}${EXIT_COUNTRY}"
elif $_tor_socks; then
echo "\${color6} EXIT \${color9}resolving..."
else
echo "\${color6} EXIT \${color9}—"
fi
# Circuit age — time since exit IP last changed
if [ -n "$EXIT_CHANGE_TIME" ] && [ "$EXIT_CHANGE_TIME" -gt 0 ] 2>/dev/null; then
_now=$(date +%s)
_age=$(( _now - EXIT_CHANGE_TIME ))
if [ "$_age" -lt 60 ]; then
echo "\${color6} CIRCUIT \${color2}${_age}s ago"
elif [ "$_age" -lt 3600 ]; then
echo "\${color6} CIRCUIT \${color2}$(( _age / 60 ))m ago"
else
echo "\${color6} CIRCUIT \${color2}$(( _age / 3600 ))h $(( (_age % 3600) / 60 ))m ago"
fi
else
echo "\${color6} CIRCUIT \${color9}—"
fi
# DNS leak check — local checks only
_dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null)
_dns_immutable=false
lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i' && _dns_immutable=true
_nm_locked=false
[ -f /etc/NetworkManager/conf.d/opsec-dns-lock.conf ] && _nm_locked=true
if [ "$_dns_server" = "127.0.0.1" ] && $_dns_immutable && $_nm_locked; then
echo "\${color6} DNS \${color1}SECURE \${color9}(tor + locked)"
elif [ "$_dns_server" = "127.0.0.1" ] && $_dns_immutable; then
echo "\${color6} DNS \${color2}SECURE \${color9}(tor, NM unlocked)"
elif [ "$_dns_server" = "127.0.0.1" ]; then
echo "\${color6} DNS \${color2}PARTIAL \${color9}(tor, not locked)"
elif echo "$_dns_server" | grep -qE '^(9\.9\.9\.9|149\.112\.112\.112|1\.1\.1\.1|1\.0\.0\.1)$'; then
echo "\${color6} DNS \${color2}PRIVACY \${color9}(${_dns_server})"
else
echo "\${color6} DNS \${color0}LEAKED \${color9}(${_dns_server})"
fi
fi
# ─── MODE / LEVEL ───────────────────────────────────────────────────────────
echo "\${color4}\${hr 1}"
LEVEL=""
[ -f /etc/opsec/opsec.conf ] && LEVEL=$(grep "^DEPLOYMENT_LEVEL=" /etc/opsec/opsec.conf 2>/dev/null | cut -d'"' -f2)
if $ADVANCED; then
echo "\${color6} MODE \${color1}ADVANCED"
else
echo "\${color6} MODE \${color0}STANDARD"
fi
[ -n "$LEVEL" ] && echo "\${color6} LEVEL \${color7}${LEVEL}"
echo "\${color4}\${hr 1}"
echo "\${color9}\${font JetBrains Mono:size=7}\${alignc}// updated \${time %H:%M:%S} //\${font}"
+71
View File
@@ -0,0 +1,71 @@
-- OPSEC Status Widget
-- Colors managed by theme system via opsec-config.sh (--theme apply NAME)
-- Uses execpi to parse Conky color markup from helper script
conky.config = {
-- Window settings
-- Default: upper-right on primary display
alignment = 'top_right',
gap_x = 15,
gap_y = 60,
minimum_width = 400,
minimum_height = 200,
maximum_width = 420,
-- Multi-monitor: pin to primary display (head 0)
-- Set xinerama_head to a different number to move to another monitor
xinerama_head = 0,
-- Window type
own_window = true,
own_window_type = 'desktop',
own_window_transparent = false,
own_window_argb_visual = true,
own_window_argb_value = 210,
own_window_colour = '0d1117',
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
-- Drawing
double_buffer = true,
draw_shades = true,
default_shade_color = '000000',
draw_outline = false,
draw_borders = true,
border_inner_margin = 12,
border_outer_margin = 4,
border_width = 1,
border_colour = '1b3a5c',
stippled_borders = 0,
-- Font
use_xft = true,
font = 'JetBrains Mono:size=10',
override_utf8_locale = true,
-- Colors — OPSEC Status Widget
default_color = 'b0b0b0',
color0 = 'df2020', -- ares red (NOT SECURE)
color1 = '33ff33', -- terminal green (SECURE)
color2 = '3a8fd6', -- steel blue (info/neutral)
color3 = '0d1117', -- void (dividers)
color4 = '1f6feb', -- blue (structural lines)
color5 = '58a6ff', -- bright blue (title/accent)
color6 = '79c0ff', -- light blue (labels)
color7 = 'c9d1d9', -- silver (values)
color8 = '1f6feb', -- blue (section headers)
color9 = '484f58', -- dark grey (metadata)
-- Update interval
update_interval = 3,
total_run_times = 0,
-- Misc
cpu_avg_samples = 2,
no_buffers = true,
text_buffer_size = 8192,
short_units = true,
};
conky.text = [[
${execpi 5 ~/.config/conky/conky-opsec-status.sh}
]];
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
# OPSEC Widget Launcher — positions and launches the Conky widget
# Usage: opsec-widget-launch.sh [position]
#
# Positions:
# tl = top-left tc = top-center tr = top-right
# bl = bottom-left bc = bottom-center br = bottom-right
#
# Default: tr (top-right)
# Uses the theme system — regenerates config from /etc/opsec/themes/
CONKY_DIR="$HOME/.config/conky"
CONF="$CONKY_DIR/conky-opsec-widget.conf"
POS="${1:-tr}"
# Map shorthand to conky alignment + gaps
case "$POS" in
tl) ALIGN="top_left"; GAP_X=15; GAP_Y=15 ;;
tc) ALIGN="top_middle"; GAP_X=0; GAP_Y=15 ;;
tr) ALIGN="top_right"; GAP_X=15; GAP_Y=60 ;;
bl) ALIGN="bottom_left"; GAP_X=15; GAP_Y=60 ;;
bc) ALIGN="bottom_middle"; GAP_X=0; GAP_Y=60 ;;
br) ALIGN="bottom_right"; GAP_X=15; GAP_Y=60 ;;
*)
echo "Usage: $(basename "$0") [tl|tc|tr|bl|bc|br]"
echo ""
echo " tl = top-left tc = top-center tr = top-right"
echo " bl = bottom-left bc = bottom-center br = bottom-right"
exit 1
;;
esac
# Kill existing widget and stale cache daemon
killall conky 2>/dev/null
pkill -f 'conky-opsec-cache\.sh' 2>/dev/null
rm -f /tmp/.opsec-cache/netinfo 2>/dev/null
sleep 0.3
# Load theme from opsec config
OPSEC_CONF="/etc/opsec/opsec.conf"
THEME="default"
[ -f "$OPSEC_CONF" ] && THEME=$(grep "^WIDGET_THEME=" "$OPSEC_CONF" 2>/dev/null | cut -d'"' -f2)
[ -z "$THEME" ] && THEME="default"
THEME_FILE="/etc/opsec/themes/${THEME}.theme"
if [ -f "$THEME_FILE" ]; then
. "$THEME_FILE"
else
echo "Theme '${THEME}' not found, using defaults"
THEME_LABEL="Default"
CONKY_BG="0d1117"
CONKY_COLOR0="df2020"; CONKY_COLOR1="33ff33"; CONKY_COLOR2="3a8fd6"
CONKY_COLOR3="0d1117"; CONKY_COLOR4="1f6feb"; CONKY_COLOR5="58a6ff"
CONKY_COLOR6="79c0ff"; CONKY_COLOR7="c9d1d9"; CONKY_COLOR8="1f6feb"
CONKY_COLOR9="484f58"
fi
# Generate config with theme colors and chosen position
mkdir -p "$CONKY_DIR"
cat > "$CONF" << EOF
-- OPSEC Status Widget — OPSEC Status Widget
-- Theme: ${THEME} (${THEME_LABEL:-Custom})
-- Position: ${ALIGN}
conky.config = {
alignment = '${ALIGN}',
gap_x = ${GAP_X},
gap_y = ${GAP_Y},
minimum_width = 400,
minimum_height = 200,
maximum_width = 420,
own_window = true,
own_window_type = 'normal',
own_window_transparent = false,
own_window_argb_visual = true,
own_window_argb_value = 210,
own_window_colour = '${CONKY_BG:-0d1117}',
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
xinerama_head = 0,
double_buffer = true,
draw_shades = true,
default_shade_color = '000000',
draw_outline = false,
draw_borders = true,
border_inner_margin = 12,
border_outer_margin = 4,
border_width = 1,
border_colour = '${CONKY_COLOR4:-1f6feb}',
stippled_borders = 0,
use_xft = true,
font = 'JetBrains Mono:size=10',
override_utf8_locale = true,
default_color = 'b0b0b0',
color0 = '${CONKY_COLOR0:-df2020}',
color1 = '${CONKY_COLOR1:-33ff33}',
color2 = '${CONKY_COLOR2:-3a8fd6}',
color3 = '${CONKY_COLOR3:-0d1117}',
color4 = '${CONKY_COLOR4:-1f6feb}',
color5 = '${CONKY_COLOR5:-58a6ff}',
color6 = '${CONKY_COLOR6:-79c0ff}',
color7 = '${CONKY_COLOR7:-c9d1d9}',
color8 = '${CONKY_COLOR8:-1f6feb}',
color9 = '${CONKY_COLOR9:-484f58}',
update_interval = 3,
total_run_times = 0,
cpu_avg_samples = 2,
no_buffers = true,
text_buffer_size = 8192,
short_units = true,
};
conky.text = [[
\${execpi 5 ~/.config/conky/conky-opsec-status.sh}
]];
EOF
# Launch
conky -c "$CONF" &
disown
echo "OPSEC widget launched: $ALIGN (theme: $THEME)"
+39
View File
@@ -0,0 +1,39 @@
# OPSEC Checklist
## Pre-Session Setup
- [ ] Fresh VM snapshot taken
- [ ] MAC address randomized: `randomize-mac`
- [ ] System timezone set appropriately
- [ ] DNS configured for privacy
- [ ] Verify no personal accounts logged in
- [ ] Disable WiFi/Bluetooth if not needed
- [ ] Configure VPN/proxy settings
- [ ] Test kill switches
## During Session
- [ ] Use workspace isolation (separate VM/container)
- [ ] All traffic through VPN/Tor
- [ ] Monitor connections: `opsec-monitor`
- [ ] Use encrypted communications
- [ ] No personal browsing/email
- [ ] Regular history clearing
- [ ] Avoid saving sensitive data locally
- [ ] Use in-memory tools when possible
## Post-Session
- [ ] Export necessary data
- [ ] Clear all logs: `clear-logs`
- [ ] Clear shell history: `clear-history`
- [ ] Wipe free space
- [ ] Secure delete files: `shred-file <files>`
- [ ] Revert to clean VM snapshot
## Emergency Procedures
- [ ] Kill network connections
- [ ] Emergency wipe if necessary
## Communication OPSEC
- [ ] Use encrypted channels
- [ ] Avoid real names/identifiers
- [ ] Use dedicated accounts
- [ ] Regular key rotation
+289
View File
@@ -0,0 +1,289 @@
#!/bin/bash
# opsec-toolkit installer — standalone, no Ansible required
# Usage: sudo ./install.sh [--uninstall]
#
# Installs the OPSEC privacy/security toolkit on Debian/Kali/Ubuntu systems.
# Requires: tor, macchanger, iptables, curl, jq (auto-installed if missing)
set -euo pipefail
# ─── COLORS ──────────────────────────────────────────────────────────────────
RED=$'\e[38;5;196m'
GRN=$'\e[38;5;49m'
YEL=$'\e[38;5;214m'
CYN=$'\e[38;5;45m'
RST=$'\e[0m'
ok() { echo "${GRN}[+]${RST} $*"; }
warn() { echo "${YEL}[*]${RST} $*"; }
err() { echo "${RED}[-]${RST} $*"; }
info() { echo "${CYN}[~]${RST} $*"; }
# ─── PREFLIGHT ───────────────────────────────────────────────────────────────
if [ "$EUID" -ne 0 ]; then
err "Please run as root: sudo ./install.sh"
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REAL_USER="${SUDO_USER:-$USER}"
REAL_HOME=$(eval echo "~${REAL_USER}")
# ─── UNINSTALL ───────────────────────────────────────────────────────────────
if [ "${1:-}" = "--uninstall" ]; then
echo ""
warn "Uninstalling OPSEC toolkit..."
echo ""
# Stop services
for svc in opsec-boot-advanced opsec-killswitch opsec-hostname-randomize opsec-mac-randomize; do
systemctl stop "$svc" 2>/dev/null || true
systemctl disable "$svc" 2>/dev/null || true
done
# Remove scripts
rm -f /usr/local/bin/opsec-*.sh
# Remove library
rm -rf /usr/local/lib/opsec
# Remove configs (preserve opsec.conf as backup)
if [ -d /etc/opsec ]; then
cp /etc/opsec/opsec.conf "/tmp/opsec.conf.backup.$(date +%s)" 2>/dev/null || true
rm -rf /etc/opsec
fi
# Remove systemd services
rm -f /etc/systemd/system/opsec-*.service
systemctl daemon-reload
# Remove cron jobs
rm -f /etc/cron.d/opsec-*
# Remove polkit, desktop, udev
rm -f /usr/share/polkit-1/actions/com.opsec.mode.policy
rm -f /usr/share/applications/opsec-toggle.desktop
rm -f /etc/udev/rules.d/99-opsec-usb.rules
ok "OPSEC toolkit uninstalled"
info "Config backup saved to /tmp/opsec.conf.backup.*"
exit 0
fi
# ─── BANNER ──────────────────────────────────────────────────────────────────
echo ""
echo "${CYN}╔══════════════════════════════════════════╗${RST}"
echo "${CYN}${RST} ${RED}OPSEC TOOLKIT${RST} — Installer v1.0 ${CYN}${RST}"
echo "${CYN}${RST} Privacy & Security Hardening ${CYN}${RST}"
echo "${CYN}╚══════════════════════════════════════════╝${RST}"
echo ""
# ─── DEPENDENCY CHECK ────────────────────────────────────────────────────────
info "Checking dependencies..."
DEPS=(tor macchanger iptables ip6tables curl jq iproute2 net-tools procps)
MISSING=()
for dep in "${DEPS[@]}"; do
case "$dep" in
tor) command -v tor >/dev/null 2>&1 || MISSING+=("tor") ;;
macchanger) command -v macchanger >/dev/null 2>&1 || MISSING+=("macchanger") ;;
iptables) command -v iptables >/dev/null 2>&1 || MISSING+=("iptables") ;;
ip6tables) command -v ip6tables >/dev/null 2>&1 || MISSING+=("iptables") ;;
curl) command -v curl >/dev/null 2>&1 || MISSING+=("curl") ;;
jq) command -v jq >/dev/null 2>&1 || MISSING+=("jq") ;;
iproute2) command -v ip >/dev/null 2>&1 || MISSING+=("iproute2") ;;
net-tools) command -v netstat >/dev/null 2>&1 || MISSING+=("net-tools") ;;
procps) command -v ps >/dev/null 2>&1 || MISSING+=("procps") ;;
esac
done
# Deduplicate
MISSING=($(printf '%s\n' "${MISSING[@]}" | sort -u))
if [ ${#MISSING[@]} -gt 0 ]; then
warn "Installing missing packages: ${MISSING[*]}"
apt-get update -qq
apt-get install -y -qq "${MISSING[@]}"
ok "Dependencies installed"
else
ok "All dependencies present"
fi
# Optional: conky for desktop widget
if ! command -v conky >/dev/null 2>&1; then
warn "Conky not installed — desktop widget will not be available"
warn "Install later with: apt install conky-all"
fi
# ─── INSTALL LIBRARY ─────────────────────────────────────────────────────────
info "Installing OPSEC library..."
mkdir -p /usr/local/lib/opsec
cp "$SCRIPT_DIR/lib/opsec-lib.sh" /usr/local/lib/opsec/
chmod 644 /usr/local/lib/opsec/opsec-lib.sh
ok "Library installed → /usr/local/lib/opsec/"
# ─── INSTALL SCRIPTS ─────────────────────────────────────────────────────────
info "Installing OPSEC scripts..."
for script in "$SCRIPT_DIR"/scripts/opsec-*.sh; do
[ -f "$script" ] || continue
cp "$script" /usr/local/bin/
chmod 755 "/usr/local/bin/$(basename "$script")"
done
ok "Scripts installed → /usr/local/bin/"
# ─── INSTALL CONFIGS ─────────────────────────────────────────────────────────
info "Installing configuration..."
# Main config directory
mkdir -p /etc/opsec/{themes,levels,vpn-templates,.harden-backup}
# Main config (don't overwrite existing)
if [ -f /etc/opsec/opsec.conf ]; then
warn "Existing opsec.conf found — preserving (new config saved as opsec.conf.new)"
cp "$SCRIPT_DIR/configs/opsec.conf" /etc/opsec/opsec.conf.new
else
cp "$SCRIPT_DIR/configs/opsec.conf" /etc/opsec/opsec.conf
fi
# Country codes
cp "$SCRIPT_DIR/configs/opsec-country-codes.conf" /etc/opsec/country-codes.conf
# Themes
cp "$SCRIPT_DIR/configs/themes/"*.theme /etc/opsec/themes/
# Levels
for f in "$SCRIPT_DIR/configs/levels/"*.conf; do
[ -f "$f" ] || continue
# Map filenames: bare-metal-standard.conf → bare-metal.conf etc.
cp "$f" /etc/opsec/levels/
done
# VPN templates
cp "$SCRIPT_DIR/configs/vpn-templates/"*.template /etc/opsec/vpn-templates/
# Torrc templates
mkdir -p /etc/opsec/torrc
cp "$SCRIPT_DIR/configs/torrc/torrc-default" /etc/opsec/torrc/
cp "$SCRIPT_DIR/configs/torrc/torrc-opsec" /etc/opsec/torrc/
# DNS configs
[ -f "$SCRIPT_DIR/configs/resolv.conf.opsec" ] && cp "$SCRIPT_DIR/configs/resolv.conf.opsec" /etc/opsec/
[ -f "$SCRIPT_DIR/configs/resolv.conf.head" ] && cp "$SCRIPT_DIR/configs/resolv.conf.head" /etc/opsec/
ok "Configuration installed → /etc/opsec/"
# ─── INSTALL SYSTEMD SERVICES ────────────────────────────────────────────────
info "Installing systemd services..."
for svc in "$SCRIPT_DIR/configs/systemd/"*.service; do
[ -f "$svc" ] || continue
cp "$svc" /etc/systemd/system/
done
systemctl daemon-reload
# Enable MAC randomize and hostname randomize at boot
systemctl enable opsec-mac-randomize.service 2>/dev/null || true
systemctl enable opsec-hostname-randomize.service 2>/dev/null || true
ok "Systemd services installed and enabled"
# ─── INSTALL CRON JOBS ──────────────────────────────────────────────────────
info "Installing cron jobs..."
cp "$SCRIPT_DIR/configs/cron/opsec-banner-cache" /etc/cron.d/
cp "$SCRIPT_DIR/configs/cron/opsec-log-rotate" /etc/cron.d/
chmod 644 /etc/cron.d/opsec-*
ok "Cron jobs installed → /etc/cron.d/"
# ─── INSTALL POLKIT POLICY ──────────────────────────────────────────────────
info "Installing polkit policy..."
mkdir -p /usr/share/polkit-1/actions
cp "$SCRIPT_DIR/configs/polkit/com.opsec.mode.policy" /usr/share/polkit-1/actions/
ok "Polkit policy installed"
# ─── INSTALL DESKTOP ENTRY ──────────────────────────────────────────────────
info "Installing desktop entry..."
cp "$SCRIPT_DIR/configs/desktop/opsec-toggle.desktop" /usr/share/applications/
ok "Desktop entry installed"
# ─── INSTALL UDEV RULES ─────────────────────────────────────────────────────
info "Installing udev rules..."
cp "$SCRIPT_DIR/configs/udev/99-opsec-usb.rules" /etc/udev/rules.d/
udevadm control --reload-rules 2>/dev/null || true
ok "Udev rules installed"
# ─── INSTALL CONKY WIDGET (user-space) ──────────────────────────────────────
info "Installing Conky widget files..."
CONKY_DIR="${REAL_HOME}/.config/conky"
mkdir -p "$CONKY_DIR"
for f in "$SCRIPT_DIR"/conky/*; do
[ -f "$f" ] || continue
cp "$f" "$CONKY_DIR/"
chown "${REAL_USER}:${REAL_USER}" "$CONKY_DIR/$(basename "$f")"
done
chmod +x "$CONKY_DIR"/*.sh 2>/dev/null || true
ok "Conky widget installed → ${CONKY_DIR}/"
# ─── INSTALL SHELL ALIASES ──────────────────────────────────────────────────
info "Installing shell aliases..."
ALIAS_FILE="${REAL_HOME}/.opsec-aliases"
cp "$SCRIPT_DIR/configs/opsec-aliases" "$ALIAS_FILE"
chown "${REAL_USER}:${REAL_USER}" "$ALIAS_FILE"
# Add source line to .bashrc and .zshrc if not already present
for rc in "${REAL_HOME}/.bashrc" "${REAL_HOME}/.zshrc"; do
if [ -f "$rc" ]; then
if ! grep -q '.opsec-aliases' "$rc" 2>/dev/null; then
echo "" >> "$rc"
echo "# OPSEC toolkit aliases" >> "$rc"
echo "[ -f ~/.opsec-aliases ] && . ~/.opsec-aliases" >> "$rc"
chown "${REAL_USER}:${REAL_USER}" "$rc"
fi
fi
done
ok "Aliases installed → ${ALIAS_FILE}"
# ─── TOR CONFIGURATION ──────────────────────────────────────────────────────
info "Configuring Tor..."
# Ensure tor log directory exists
mkdir -p /var/log/tor /run/tor
chown debian-tor:debian-tor /var/log/tor /run/tor 2>/dev/null || true
# Stop tor if running (we'll configure, user starts via opsec-on)
systemctl stop tor 2>/dev/null || true
ok "Tor configured (start with: opsec-on)"
# ─── TMPFS FOR LOGS ─────────────────────────────────────────────────────────
info "Setting up tmpfs for OPSEC logs..."
if ! grep -q 'opsec-logs' /etc/fstab 2>/dev/null; then
echo "" >> /etc/fstab
echo "# OPSEC: volatile log storage (RAM-only, cleared on reboot)" >> /etc/fstab
echo "tmpfs /var/log/opsec tmpfs nosuid,nodev,noexec,mode=0700,size=50M 0 0 # opsec-logs" >> /etc/fstab
fi
mkdir -p /var/log/opsec
mount /var/log/opsec 2>/dev/null || mount -t tmpfs -o nosuid,nodev,noexec,mode=0700,size=50M tmpfs /var/log/opsec 2>/dev/null || true
ok "OPSEC logs on tmpfs (RAM-only)"
# ─── CACHE DIRECTORY ─────────────────────────────────────────────────────────
mkdir -p /tmp/.opsec-cache
chown "${REAL_USER}:${REAL_USER}" /tmp/.opsec-cache 2>/dev/null || true
# ─── POST-INSTALL ────────────────────────────────────────────────────────────
echo ""
echo "${CYN}══════════════════════════════════════════${RST}"
ok "OPSEC toolkit installed successfully!"
echo "${CYN}══════════════════════════════════════════${RST}"
echo ""
info "Quick start:"
echo " ${GRN}opsec-on${RST} — Activate ghost mode (Tor + kill switch + hardening)"
echo " ${GRN}opsec-off${RST} — Deactivate ghost mode"
echo " ${GRN}opsec-config${RST} — Interactive configuration TUI"
echo " ${GRN}opsec-show${RST} — Show current status"
echo " ${GRN}killswitch-on${RST} — Activate kill switch only"
echo " ${GRN}opsec-preflight${RST} — Pre-session readiness check"
echo ""
info "Reload your shell to activate aliases:"
echo " ${GRN}source ~/.bashrc${RST} or ${GRN}source ~/.zshrc${RST}"
echo ""
info "Uninstall with: ${YEL}sudo ./install.sh --uninstall${RST}"
echo ""
+717
View File
@@ -0,0 +1,717 @@
#!/bin/bash
# /usr/local/lib/opsec/opsec-lib.sh — Shared OPSEC function library
# Sourced by all OPSEC scripts for config management and common operations
OPSEC_CONF="/etc/opsec/opsec.conf"
OPSEC_COUNTRY_CODES="/etc/opsec/country-codes.conf"
OPSEC_PROFILES_DIR="/etc/opsec/profiles"
OPSEC_STATE_FILE="/var/run/opsec-advanced.enabled"
OPSEC_BOOT_MARKER="/etc/opsec/boot-advanced.enabled"
# ─── COLOR OUTPUT ──────────────────────────────────────────────────────────────
opsec_green() { echo -e "\033[38;5;49m[+]\033[0m \033[38;5;49m$*\033[0m"; }
opsec_red() { echo -e "\033[38;5;196m[-]\033[0m \033[38;5;196m$*\033[0m"; }
opsec_yellow() { echo -e "\033[38;5;214m[*]\033[0m \033[38;5;214m$*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~]\033[0m \033[38;5;75m$*\033[0m"; }
opsec_cyan() { echo -e "\033[38;5;51m[>]\033[0m \033[38;5;51m$*\033[0m"; }
opsec_mag() { echo -e "\033[38;5;201m[*]\033[0m \033[38;5;201m$*\033[0m"; }
opsec_dim() { echo -e "\033[38;5;244m $*\033[0m"; }
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
# ─── CONFIG MANAGEMENT ─────────────────────────────────────────────────────────
opsec_load_config() {
if [ -f "$OPSEC_CONF" ]; then
# shellcheck disable=SC1090
. "$OPSEC_CONF"
return 0
fi
return 1
}
opsec_save_config() {
# Re-serialize all known keys back to config file
# Preserves comments and structure
local tmp
tmp=$(mktemp)
cat > "$tmp" << 'HEADER'
# /etc/opsec/opsec.conf — Central OPSEC Configuration
# Shell-sourceable KEY="value" format. All scripts source this file.
# Edit via: sudo opsec-config.sh (interactive TUI)
HEADER
cat >> "$tmp" << EOF
# ─── ACTIVE PROFILE ────────────────────────────────────────────────────────────
PROFILE_NAME="${PROFILE_NAME:-default}"
# ─── TOR SETTINGS ──────────────────────────────────────────────────────────────
TOR_CIRCUIT_ROTATION="${TOR_CIRCUIT_ROTATION:-30}"
TOR_BLACKLIST="${TOR_BLACKLIST:-}"
TOR_STRICT_NODES="${TOR_STRICT_NODES:-1}"
TOR_ISOLATION="${TOR_ISOLATION:-1}"
TOR_PADDING="${TOR_PADDING:-1}"
TOR_SOCKS_PORT="${TOR_SOCKS_PORT:-9050}"
TOR_DNS_PORT="${TOR_DNS_PORT:-5353}"
TOR_TRANS_PORT="${TOR_TRANS_PORT:-9040}"
TOR_NUM_GUARDS="${TOR_NUM_GUARDS:-3}"
TOR_SAFE_LOGGING="${TOR_SAFE_LOGGING:-1}"
# ─── DNS SETTINGS ──────────────────────────────────────────────────────────────
DNS_MODE="${DNS_MODE:-tor}"
DNS_CUSTOM_SERVERS="${DNS_CUSTOM_SERVERS:-}"
# ─── KILL SWITCH ───────────────────────────────────────────────────────────────
KILLSWITCH_ALLOW_DHCP="${KILLSWITCH_ALLOW_DHCP:-1}"
KILLSWITCH_ALLOW_OPENVPN="${KILLSWITCH_ALLOW_OPENVPN:-1}"
KILLSWITCH_ALLOW_WIREGUARD="${KILLSWITCH_ALLOW_WIREGUARD:-1}"
KILLSWITCH_EXTRA_PORTS="${KILLSWITCH_EXTRA_PORTS:-}"
# ─── MAC ADDRESS ───────────────────────────────────────────────────────────────
MAC_INTERFACES="${MAC_INTERFACES:-auto}"
MAC_VENDOR_SPOOF="${MAC_VENDOR_SPOOF:-}"
# ─── HOSTNAME ──────────────────────────────────────────────────────────────────
HOSTNAME_PATTERN="${HOSTNAME_PATTERN:-desktop}"
HOSTNAME_CUSTOM_PREFIX="${HOSTNAME_CUSTOM_PREFIX:-}"
# ─── SYSTEM HARDENING ──────────────────────────────────────────────────────────
HARDEN_IPV6="${HARDEN_IPV6:-1}"
HARDEN_SWAP="${HARDEN_SWAP:-1}"
HARDEN_CORE_DUMPS="${HARDEN_CORE_DUMPS:-1}"
HARDEN_CLIPBOARD_CLEAR="${HARDEN_CLIPBOARD_CLEAR:-0}"
HARDEN_SCREEN_LOCK="${HARDEN_SCREEN_LOCK:-1}"
HARDEN_SCREEN_LOCK_TIMEOUT="${HARDEN_SCREEN_LOCK_TIMEOUT:-300}"
HARDEN_TIMEZONE_SPOOF="${HARDEN_TIMEZONE_SPOOF:-0}"
HARDEN_TIMEZONE_VALUE="${HARDEN_TIMEZONE_VALUE:-UTC}"
HARDEN_LOCALE_SPOOF="${HARDEN_LOCALE_SPOOF:-0}"
HARDEN_LOCALE_VALUE="${HARDEN_LOCALE_VALUE:-en_US.UTF-8}"
# ─── LEAK PREVENTION ──────────────────────────────────────────────────────────
LEAK_WEBRTC_BLOCK="${LEAK_WEBRTC_BLOCK:-1}"
LEAK_USB_BLOCK="${LEAK_USB_BLOCK:-0}"
# ─── MONITORING ────────────────────────────────────────────────────────────────
MONITOR_PROCESSES="${MONITOR_PROCESSES:-0}"
MONITOR_LOG_ROTATION="${MONITOR_LOG_ROTATION:-1}"
LOG_ROTATION_HOURS="${LOG_ROTATION_HOURS:-4}"
# ─── TRAFFIC SHAPING ──────────────────────────────────────────────────────────
TRAFFIC_JITTER_ENABLED="${TRAFFIC_JITTER_ENABLED:-0}"
TRAFFIC_JITTER_MS="${TRAFFIC_JITTER_MS:-50}"
# ─── LEVEL TYPE ───────────────────────────────────────────────────────────────
LEVEL_TYPE="${LEVEL_TYPE:-standard}"
# ─── BASE STATE ───────────────────────────────────────────────────────────────
BASE_DNS="${BASE_DNS:-quad9}"
BASE_MAC_RANDOMIZE="${BASE_MAC_RANDOMIZE:-1}"
BASE_IPV6_DISABLE="${BASE_IPV6_DISABLE:-1}"
# ─── TOR BRIDGES ──────────────────────────────────────────────────────────────
TOR_BRIDGE_MODE="${TOR_BRIDGE_MODE:-off}"
TOR_BRIDGE_RELAY="${TOR_BRIDGE_RELAY:-}"
# ─── SECURE DELETION ──────────────────────────────────────────────────────────
WIPE_METHOD="${WIPE_METHOD:-auto}"
# ─── DEPLOYMENT LEVEL ─────────────────────────────────────────────────────────
DEPLOYMENT_LEVEL="${DEPLOYMENT_LEVEL:-bare-metal-standard}"
# ─── TERMINAL BANNER ──────────────────────────────────────────────────────────
OPSEC_BANNER="${OPSEC_BANNER:-compact}"
# ─── WIDGET THEME ────────────────────────────────────────────────────────────
WIDGET_THEME="${WIDGET_THEME:-default}"
EOF
mv "$tmp" "$OPSEC_CONF"
chmod 600 "$OPSEC_CONF"
}
opsec_set_value() {
local key="$1" val="$2"
if [ -z "$key" ]; then return 1; fi
# Validate key: must be a valid shell variable name (letters, digits, underscore, starts with letter/underscore)
if ! echo "$key" | grep -qE '^[A-Za-z_][A-Za-z0-9_]*$'; then
echo "[!] opsec_set_value: invalid key name '${key}'" >&2
return 1
fi
# Sanitize value: strip characters that could break shell quoting
val=$(printf '%s' "$val" | tr -d '`$\\\"'"'" | tr -cd '[:print:]')
opsec_load_config
eval "${key}=\"${val}\""
opsec_save_config
}
opsec_get_value() {
local key="$1"
# Validate key: must be a valid shell variable name
if ! echo "$key" | grep -qE '^[A-Za-z_][A-Za-z0-9_]*$'; then
echo "[!] opsec_get_value: invalid key name '${key}'" >&2
return 1
fi
opsec_load_config
eval "echo \"\${${key}:-}\""
}
# ─── TORRC GENERATION ──────────────────────────────────────────────────────────
opsec_generate_torrc() {
local _dbg="/run/opsec/debug.log"
echo "[$(date -Is)] [lib] opsec_generate_torrc called" >> "$_dbg" 2>/dev/null || true
opsec_load_config || return 1
local torrc="/etc/tor/torrc"
local socks_port="${TOR_SOCKS_PORT:-9050}"
local trans_port="${TOR_TRANS_PORT:-9040}"
local dns_port="${TOR_DNS_PORT:-5353}"
local rotation="${TOR_CIRCUIT_ROTATION:-30}"
local blacklist="${TOR_BLACKLIST:-}"
local strict="${TOR_STRICT_NODES:-1}"
local isolation="${TOR_ISOLATION:-1}"
local padding="${TOR_PADDING:-1}"
local guards="${TOR_NUM_GUARDS:-3}"
local safe_log="${TOR_SAFE_LOGGING:-1}"
echo "[$(date -Is)] [lib] socks=${socks_port} trans=${trans_port} dns=${dns_port} blacklist=${blacklist}" >> "$_dbg" 2>/dev/null || true
# Build SocksPort line with isolation flags
local socks_line="SocksPort ${socks_port}"
if [ "$isolation" = "1" ]; then
socks_line="${socks_line} IsolateDestAddr IsolateDestPort"
fi
# Build ExcludeExitNodes from blacklist
local exclude_line=""
if [ -n "$blacklist" ]; then
local formatted
formatted=$(echo "$blacklist" | sed 's/\([a-z][a-z]\)/{\1}/g; s/,/,/g')
exclude_line="ExcludeExitNodes ${formatted}"
fi
local bridge_mode="${TOR_BRIDGE_MODE:-off}"
local bridge_relay="${TOR_BRIDGE_RELAY:-}"
cat > "$torrc" << EOF
# Autogenerated — do not edit manually
# Regenerate via: opsec-config.sh --apply
# ─── SOCKS, TRANSPARENT PROXY & DNS ─────────────────────────────────────────
${socks_line}
TransPort ${trans_port}
DNSPort ${dns_port}
# ─── EXIT NODE EXCLUSION ────────────────────────────────────────────────────
${exclude_line}
StrictNodes ${strict}
# ─── CIRCUIT ROTATION ───────────────────────────────────────────────────────
MaxCircuitDirtiness ${rotation}
# ─── TRAFFIC PADDING ────────────────────────────────────────────────────────
ConnectionPadding ${padding}
# ─── SAFE LOGGING ────────────────────────────────────────────────────────────
SafeLogging ${safe_log}
Log notice file /run/tor/notices.log
# ─── ENTRY GUARDS ───────────────────────────────────────────────────────────
NumEntryGuards ${guards}
UseEntryGuards 1
EOF
# ─── PLUGGABLE TRANSPORTS (bridges) ──────────────────────────────────────
if [ "$bridge_mode" != "off" ] && [ "$bridge_mode" != "" ]; then
cat >> "$torrc" << 'BRIDGE_HEADER'
# ─── BRIDGE CONFIGURATION ────────────────────────────────────────────────────
UseBridges 1
BRIDGE_HEADER
case "$bridge_mode" in
obfs4)
echo "ClientTransportPlugin obfs4 exec /usr/bin/obfs4proxy" >> "$torrc"
;;
meek-azure)
echo "ClientTransportPlugin meek_lite exec /usr/bin/obfs4proxy" >> "$torrc"
;;
snowflake)
# snowflake-client location varies by distro
local sf_bin
sf_bin=$(command -v snowflake-client 2>/dev/null || echo "/usr/bin/snowflake-client")
echo "ClientTransportPlugin snowflake exec ${sf_bin}" >> "$torrc"
;;
esac
# Add user-specified bridge relay if provided
if [ -n "$bridge_relay" ]; then
echo "Bridge ${bridge_relay}" >> "$torrc"
else
# Default bridges for each transport type
case "$bridge_mode" in
meek-azure)
echo "Bridge meek_lite 192.0.2.18:80 BE776A53492E1E044A26F17306E1BC46A55A1625 url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com" >> "$torrc"
;;
snowflake)
echo "Bridge snowflake 192.0.2.3:80 2B280B23E1107BB62ABFC40DDCC8824814F80A72 fingerprint=2B280B23E1107BB62ABFC40DDCC8824814F80A72 url=https://snowflake-broker.torproject.net.global.prod.fastly.net/ front=foursquare.com ice=stun:stun.l.google.com:19302,stun:stun.antisip.com:3478,stun:stun.bluesip.net:3478,stun:stun.dus.net:3478,stun:stun.epygi.com:3478,stun:stun.sonetel.com:3478,stun:stun.uls.co.za:3478,stun:stun.voipgate.com:3478,stun:stun.voys.nl:3478 utls-imitate=hellorandomizedalpn" >> "$torrc"
;;
esac
fi
fi
chmod 644 "$torrc"
echo "[$(date -Is)] [lib] torrc written ($(wc -l < "$torrc") lines)" >> "$_dbg" 2>/dev/null || true
echo "[$(date -Is)] [lib] torrc contents:" >> "$_dbg" 2>/dev/null || true
cat "$torrc" >> "$_dbg" 2>/dev/null || true
echo "[$(date -Is)] [lib] --- end torrc ---" >> "$_dbg" 2>/dev/null || true
}
# ─── RESOLV.CONF GENERATION ───────────────────────────────────────────────────
opsec_generate_resolv() {
local _dbg="/run/opsec/debug.log"
echo "[$(date -Is)] [lib] opsec_generate_resolv called" >> "$_dbg" 2>/dev/null || true
opsec_load_config || return 1
local mode="${DNS_MODE:-tor}"
local resolv="/etc/resolv.conf"
echo "[$(date -Is)] [lib] DNS_MODE=${mode}" >> "$_dbg" 2>/dev/null || true
# Unlock if immutable
chattr -i "$resolv" 2>/dev/null || true
case "$mode" in
tor)
echo "nameserver 127.0.0.1" > "$resolv"
;;
quad9)
cat > "$resolv" << 'EOF'
nameserver 9.9.9.9
nameserver 149.112.112.112
EOF
;;
cloudflare)
cat > "$resolv" << 'EOF'
nameserver 1.1.1.1
nameserver 1.0.0.1
EOF
;;
doh)
# DNS-over-HTTPS via dnscrypt-proxy
# Ensure dnscrypt-proxy is running
if command -v dnscrypt-proxy >/dev/null 2>&1; then
# Deploy config if not present
if [ ! -f /etc/dnscrypt-proxy/dnscrypt-proxy.toml ] && [ -f /etc/opsec/dnscrypt-proxy.toml ]; then
mkdir -p /etc/dnscrypt-proxy
cp /etc/opsec/dnscrypt-proxy.toml /etc/dnscrypt-proxy/dnscrypt-proxy.toml
fi
mkdir -p /var/log/dnscrypt-proxy /var/cache/dnscrypt-proxy
# Stop systemd-resolved if it conflicts on :53
systemctl stop systemd-resolved 2>/dev/null || true
systemctl start dnscrypt-proxy 2>/dev/null || dnscrypt-proxy -config /etc/dnscrypt-proxy/dnscrypt-proxy.toml &
fi
cat > "$resolv" << 'EOF'
nameserver 127.0.0.53
EOF
;;
dot)
# DNS-over-TLS via systemd-resolved
cat > "$resolv" << 'EOF'
nameserver 127.0.0.53
options edns0 trust-ad
EOF
;;
custom)
if [ -n "$DNS_CUSTOM_SERVERS" ]; then
: > "$resolv"
local IFS=','
for server in $DNS_CUSTOM_SERVERS; do
echo "nameserver ${server}" >> "$resolv"
done
else
echo "nameserver 9.9.9.9" > "$resolv"
fi
;;
*)
echo "nameserver 9.9.9.9" > "$resolv"
;;
esac
# Lock if in advanced mode
if opsec_is_advanced; then
chattr +i "$resolv"
echo "[$(date -Is)] [lib] resolv.conf locked (chattr +i)" >> "$_dbg" 2>/dev/null || true
fi
echo "[$(date -Is)] [lib] resolv.conf contents: $(cat "$resolv" 2>/dev/null | tr '\n' ' ')" >> "$_dbg" 2>/dev/null || true
}
# ─── STATE CHECKS ─────────────────────────────────────────────────────────────
opsec_is_advanced() {
[ -f "$OPSEC_STATE_FILE" ]
}
opsec_is_boot_enabled() {
[ -f "$OPSEC_BOOT_MARKER" ]
}
# ─── PROFILE MANAGEMENT ───────────────────────────────────────────────────────
opsec_profile_save() {
local name="$1"
if [ -z "$name" ]; then return 1; fi
mkdir -p "$OPSEC_PROFILES_DIR"
cp "$OPSEC_CONF" "${OPSEC_PROFILES_DIR}/${name}.conf"
# Tag profile name inside the saved copy
sed -i "s/^PROFILE_NAME=.*/PROFILE_NAME=\"${name}\"/" "${OPSEC_PROFILES_DIR}/${name}.conf"
}
opsec_profile_load() {
local name="$1"
local profile="${OPSEC_PROFILES_DIR}/${name}.conf"
if [ ! -f "$profile" ]; then return 1; fi
cp "$profile" "$OPSEC_CONF"
# Update active profile name
sed -i "s/^PROFILE_NAME=.*/PROFILE_NAME=\"${name}\"/" "$OPSEC_CONF"
}
opsec_profile_list() {
if [ -d "$OPSEC_PROFILES_DIR" ]; then
find "$OPSEC_PROFILES_DIR" -name '*.conf' -printf '%f\n' | sed 's/\.conf$//'
fi
}
opsec_profile_delete() {
local name="$1"
rm -f "${OPSEC_PROFILES_DIR}/${name}.conf"
}
opsec_profile_export() {
local name="$1" dest="$2"
local profile="${OPSEC_PROFILES_DIR}/${name}.conf"
if [ ! -f "$profile" ]; then return 1; fi
cp "$profile" "$dest"
}
opsec_profile_import() {
local src="$1" name="$2"
if [ ! -f "$src" ]; then return 1; fi
mkdir -p "$OPSEC_PROFILES_DIR"
cp "$src" "${OPSEC_PROFILES_DIR}/${name}.conf"
}
# ─── COUNTRY CODE HELPERS ─────────────────────────────────────────────────────
opsec_load_country_presets() {
if [ -f "$OPSEC_COUNTRY_CODES" ]; then
# shellcheck disable=SC1090
. "$OPSEC_COUNTRY_CODES"
fi
}
opsec_get_preset() {
local preset="$1"
opsec_load_country_presets
case "$preset" in
5eyes|fiveeyes) echo "$FIVE_EYES" ;;
9eyes|nineeyes) echo "$NINE_EYES" ;;
14eyes|fourteeneyes) echo "$FOURTEEN_EYES" ;;
surveillance) echo "$SURVEILLANCE_STATES" ;;
max) echo "$MAX_EXCLUSION" ;;
*) echo "" ;;
esac
}
# ─── DEPLOYMENT LEVEL MANAGEMENT ─────────────────────────────────────────────
OPSEC_LEVELS_DIR="/etc/opsec/levels"
opsec_level_list() {
if [ -d "$OPSEC_LEVELS_DIR" ]; then
find "$OPSEC_LEVELS_DIR" -name '*.conf' -printf '%f\n' | sed 's/\.conf$//' | sort
fi
}
opsec_level_apply() {
local level="$1"
local level_file="${OPSEC_LEVELS_DIR}/${level}.conf"
if [ ! -f "$level_file" ]; then
opsec_red "Level '${level}' not found in ${OPSEC_LEVELS_DIR}"
return 1
fi
# Preserve current PROFILE_NAME before overwriting
opsec_load_config 2>/dev/null || true
local saved_profile="${PROFILE_NAME:-default}"
# Copy level preset over active config
cp "$level_file" "$OPSEC_CONF"
chmod 600 "$OPSEC_CONF"
# Restore profile name and ensure deployment level is tagged
opsec_load_config
PROFILE_NAME="$saved_profile"
DEPLOYMENT_LEVEL="$level"
opsec_save_config
}
# ─── SECURE DELETION ─────────────────────────────────────────────────────────
opsec_detect_storage_type() {
# Detect storage type for a given path
# Returns: hdd | ssd | luks | unknown
local target_path="${1:-/}"
local device
# Find the block device for the path
device=$(df -P "$target_path" 2>/dev/null | tail -1 | awk '{print $1}')
[ -z "$device" ] && echo "unknown" && return
# Check for LUKS
if command -v cryptsetup >/dev/null 2>&1; then
# Check if device is on a dm-crypt layer
local dm_name
dm_name=$(basename "$device" 2>/dev/null)
if [ -e "/sys/block/${dm_name}/dm/uuid" ] 2>/dev/null; then
local dm_uuid
dm_uuid=$(cat "/sys/block/${dm_name}/dm/uuid" 2>/dev/null)
if echo "$dm_uuid" | grep -qi "CRYPT-LUKS"; then
echo "luks"
return
fi
fi
# Also check via dmsetup
if dmsetup info "$device" 2>/dev/null | grep -qi "CRYPT-LUKS"; then
echo "luks"
return
fi
fi
# Resolve to physical disk (strip partition number, handle /dev/mapper)
local phys_disk
phys_disk=$(lsblk -ndo PKNAME "$device" 2>/dev/null | head -1)
[ -z "$phys_disk" ] && phys_disk=$(echo "$device" | sed 's/[0-9]*$//' | sed 's|^/dev/||')
# Check rotational flag (0 = SSD, 1 = HDD)
local rotational_file="/sys/block/${phys_disk}/queue/rotational"
if [ -f "$rotational_file" ]; then
local rotational
rotational=$(cat "$rotational_file" 2>/dev/null)
if [ "$rotational" = "0" ]; then
echo "ssd"
else
echo "hdd"
fi
return
fi
echo "unknown"
}
opsec_secure_delete() {
# SSD-aware secure file deletion
# Usage: opsec_secure_delete <file_or_dir> [method]
# method: auto (default) | shred | fstrim | luks
local target="$1"
local method="${2:-${WIPE_METHOD:-auto}}"
[ -z "$target" ] && return 1
if [ "$method" = "auto" ]; then
method=$(opsec_detect_storage_type "$target")
fi
case "$method" in
hdd|shred)
# Traditional shred for spinning disks
if [ -d "$target" ]; then
find "$target" -type f -exec shred -fuz -n 1 {} \; 2>/dev/null
rm -rf "$target" 2>/dev/null
elif [ -f "$target" ]; then
shred -fuz -n 1 "$target" 2>/dev/null
fi
;;
ssd|fstrim)
# For SSDs: overwrite with zeros, delete, then fstrim
# shred is ineffective on SSDs due to wear leveling
if [ -d "$target" ]; then
find "$target" -type f -exec dd if=/dev/zero of={} bs=4k count=1 conv=notrunc 2>/dev/null \;
find "$target" -type f -delete 2>/dev/null
rm -rf "$target" 2>/dev/null
elif [ -f "$target" ]; then
dd if=/dev/zero of="$target" bs=4k count=1 conv=notrunc 2>/dev/null
rm -f "$target" 2>/dev/null
fi
# Request TRIM/discard on the filesystem
local mount_point
mount_point=$(df -P "${target%/*}" 2>/dev/null | tail -1 | awk '{print $6}')
if [ -n "$mount_point" ]; then
fstrim "$mount_point" 2>/dev/null || true
fi
;;
luks)
# For LUKS: zero file + rely on LUKS key destroy for full wipe
if [ -d "$target" ]; then
find "$target" -type f -exec dd if=/dev/zero of={} bs=4k count=1 conv=notrunc 2>/dev/null \;
find "$target" -type f -delete 2>/dev/null
rm -rf "$target" 2>/dev/null
elif [ -f "$target" ]; then
dd if=/dev/zero of="$target" bs=4k count=1 conv=notrunc 2>/dev/null
rm -f "$target" 2>/dev/null
fi
# Note: full LUKS key destroy handled by emergency-wipe
;;
*)
# Fallback: basic shred
if [ -d "$target" ]; then
find "$target" -type f -exec shred -fuz -n 1 {} \; 2>/dev/null
rm -rf "$target" 2>/dev/null
elif [ -f "$target" ]; then
shred -fuz -n 1 "$target" 2>/dev/null
fi
;;
esac
}
# ─── WIDGET THEME MANAGEMENT ─────────────────────────────────────────────────
OPSEC_THEMES_DIR="/etc/opsec/themes"
opsec_theme_list() {
if [ -d "$OPSEC_THEMES_DIR" ]; then
find "$OPSEC_THEMES_DIR" -name '*.theme' -printf '%f\n' | sed 's/\.theme$//' | sort
fi
}
opsec_generate_conky() {
opsec_load_config || return 1
local theme="${WIDGET_THEME:-default}"
local theme_file="${OPSEC_THEMES_DIR}/${theme}.theme"
if [ ! -f "$theme_file" ]; then
opsec_red "Theme '${theme}' not found at ${theme_file}"
return 1
fi
# Source theme to get CONKY_COLOR* and CONKY_BG values
# shellcheck disable=SC1090
. "$theme_file"
local conky_conf_name="conky-opsec-widget.conf"
# Find and regenerate conky config for each user with the widget installed
for home_dir in /home/*; do
local conky_conf="${home_dir}/.config/conky/${conky_conf_name}"
[ -f "$conky_conf" ] || continue
local owner
owner=$(stat -c '%U' "$conky_conf" 2>/dev/null) || continue
cat > "$conky_conf" << CONKYEOF
-- OPSEC Status Widget
-- Colors managed by theme system via opsec-config.sh
-- Theme: ${theme} (${THEME_LABEL:-Custom})
conky.config = {
-- Window settings
alignment = 'top_right',
gap_x = 15,
gap_y = 60,
minimum_width = 400,
minimum_height = 200,
maximum_width = 420,
-- Multi-monitor: run xrandr --listmonitors to find head number
xinerama_head = 0,
-- Window type
own_window = true,
own_window_type = 'desktop',
own_window_transparent = false,
own_window_argb_visual = true,
own_window_argb_value = 210,
own_window_colour = '${CONKY_BG:-0d1117}',
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
-- Drawing
double_buffer = true,
draw_shades = true,
default_shade_color = '000000',
draw_outline = false,
draw_borders = true,
border_inner_margin = 12,
border_outer_margin = 4,
border_width = 1,
border_colour = '1b3a5c',
stippled_borders = 0,
-- Font
use_xft = true,
font = 'JetBrains Mono:size=10',
override_utf8_locale = true,
-- Colors — Theme: ${theme}
default_color = 'b0b0b0',
color0 = '${CONKY_COLOR0:-df2020}',
color1 = '${CONKY_COLOR1:-33ff33}',
color2 = '${CONKY_COLOR2:-3a8fd6}',
color3 = '${CONKY_COLOR3:-0d1117}',
color4 = '${CONKY_COLOR4:-1f6feb}',
color5 = '${CONKY_COLOR5:-58a6ff}',
color6 = '${CONKY_COLOR6:-79c0ff}',
color7 = '${CONKY_COLOR7:-c9d1d9}',
color8 = '${CONKY_COLOR8:-1f6feb}',
color9 = '${CONKY_COLOR9:-484f58}',
-- Update interval
update_interval = 3,
total_run_times = 0,
-- Misc
cpu_avg_samples = 2,
no_buffers = true,
text_buffer_size = 8192,
short_units = true,
};
conky.text = [[
\${execpi 5 ~/.config/conky/conky-opsec-status.sh}
]];
CONKYEOF
chown "$owner":"$owner" "$conky_conf"
chmod 644 "$conky_conf"
done
# Restart conky for all users running the opsec widget
pkill -f 'conky.*opsec' 2>/dev/null || true
sleep 1
for home_dir in /home/*; do
local conky_conf="${home_dir}/.config/conky/${conky_conf_name}"
[ -f "$conky_conf" ] || continue
local owner
owner=$(stat -c '%U' "$conky_conf" 2>/dev/null) || continue
local uid
uid=$(id -u "$owner" 2>/dev/null) || continue
# Relaunch conky as the file owner
su - "$owner" -c "DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus nohup conky -c '${conky_conf}' >/dev/null 2>&1 &" 2>/dev/null || true
done
}
# ─── INIT CHECK ────────────────────────────────────────────────────────────────
# Ensure /etc/opsec exists
_opsec_init_dirs() {
[ -d /etc/opsec ] || mkdir -p /etc/opsec
[ -d "$OPSEC_PROFILES_DIR" ] || mkdir -p "$OPSEC_PROFILES_DIR"
}
# Auto-init on source if running as root
if [ "$EUID" = "0" ] 2>/dev/null || [ "$(id -u)" = "0" ] 2>/dev/null; then
_opsec_init_dirs
fi
+35
View File
@@ -0,0 +1,35 @@
#!/bin/bash
# /usr/local/bin/opsec-banner-cache.sh — Background WAN IP Cache Updater
# Updates /var/cache/opsec/wan_ip every 5 minutes (via cron)
# Never blocks shell startup — the banner reads from cache only
CACHE_DIR="/var/cache/opsec"
CACHE_FILE="${CACHE_DIR}/wan_ip"
ENDPOINTS="https://icanhazip.com https://ifconfig.me https://api.ipify.org"
mkdir -p "$CACHE_DIR"
chmod 755 "$CACHE_DIR"
PUB_IP=""
# Try via Tor first if running
if pgrep -x tor >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q ':9050 '; then
for ep in $ENDPOINTS; do
PUB_IP=$(curl -s --max-time 10 --socks5-hostname 127.0.0.1:9050 "$ep" 2>/dev/null | tr -d '[:space:]')
[ -n "$PUB_IP" ] && break
done
fi
# Fallback to direct if Tor failed or not running
if [ -z "$PUB_IP" ]; then
for ep in $ENDPOINTS; do
PUB_IP=$(curl -s --max-time 5 "$ep" 2>/dev/null | tr -d '[:space:]')
[ -n "$PUB_IP" ] && break
done
fi
# Only write if we got a valid-looking IP
if echo "$PUB_IP" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "$PUB_IP" > "$CACHE_FILE"
chmod 644 "$CACHE_FILE"
fi
+320
View File
@@ -0,0 +1,320 @@
#!/bin/bash
# /usr/local/bin/opsec-banner.sh — Terminal OPSEC Status Banner
# Mirrors the Conky desktop widget layout and Widget theme colors
# Modes: compact | full | off (configured via OPSEC_BANNER in opsec.conf)
OPSEC_CONF="/etc/opsec/opsec.conf"
[ -f "$OPSEC_CONF" ] && . "$OPSEC_CONF"
BANNER_MODE="${OPSEC_BANNER:-compact}"
[ -n "$1" ] && BANNER_MODE="$1"
[ "$BANNER_MODE" = "off" ] && exit 0
# ─── Widget theme colors (matching Conky widget) ────────────────────────────
R=$'\e[0m'
BLD=$'\e[1m'
C0=$'\e[38;5;135m' # ALERT/BAD — #B55AFC purple
C1=$'\e[38;5;204m' # SECURE/GOOD — #FF63BE hot pink
C2=$'\e[38;5;117m' # INFO/NEUTRAL — #85E7FF cyan
C4=$'\e[38;5;32m' # STRUCTURAL — #268BD2 blue
C5=$'\e[38;5;45m' # TITLE/ACCENT — #07CAF9 bright cyan
C6=$'\e[38;5;117m' # LABELS — #85E7FF cyan
C7=$'\e[38;5;189m' # VALUES — #ECDEF7 lavender
C8=$'\e[38;5;135m' # SECTION HDR — #B55AFC purple
C9=$'\e[38;5;66m' # METADATA — #4A6A7A dark grey
# ─── DETECT STATE ────────────────────────────────────────────────────────────
ADVANCED=false
[ -f /var/run/opsec-advanced.enabled ] && ADVANCED=true
BREAKGLASS=false
if [ -f /var/run/opsec-breakglass.active ]; then
BREAKGLASS=true
_bge=$(cat /var/run/opsec-breakglass.active 2>/dev/null)
_bgn=$(date +%s)
BG_REM=""
[ -n "$_bge" ] && [ "$_bge" -gt "$_bgn" ] 2>/dev/null && BG_REM="$(( (_bge - _bgn) / 60 ))m"
fi
# ─── HELPERS ─────────────────────────────────────────────────────────────────
HR=" ${C4}$(printf '─%.0s' {1..48})${R}"
# ─── READ CACHE ──────────────────────────────────────────────────────────────
CACHE_FILE="/tmp/.opsec-cache/netinfo"
PUB_IP="" PUB_GEO="" ROUTED_TOR=false EXIT_COUNTRY="" EXIT_CHANGE_TIME="" TOR_BOOTSTRAP="" TOR_PHASE=""
if [ -f "$CACHE_FILE" ]; then
_cr() { grep "^${1}=" "$CACHE_FILE" 2>/dev/null | head -1 | cut -d'"' -f2; }
PUB_IP=$(_cr PUB_IP)
PUB_GEO=$(_cr PUB_GEO)
ROUTED_TOR=$(_cr ROUTED_TOR)
EXIT_COUNTRY=$(_cr EXIT_COUNTRY)
EXIT_CHANGE_TIME=$(_cr EXIT_CHANGE_TIME)
TOR_BOOTSTRAP=$(_cr TOR_BOOTSTRAP)
TOR_PHASE=$(_cr TOR_PHASE)
PUB_IP=$(echo "$PUB_IP" | tr -cd '0-9.')
EXIT_COUNTRY=$(echo "$EXIT_COUNTRY" | tr -cd 'A-Za-z')
EXIT_CHANGE_TIME=$(echo "$EXIT_CHANGE_TIME" | tr -cd '0-9')
TOR_BOOTSTRAP=$(echo "$TOR_BOOTSTRAP" | tr -cd '0-9')
fi
# ─── GATHER LIVE STATUS ─────────────────────────────────────────────────────
LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' | head -1)
[ -z "$LOCAL_IP" ] && LOCAL_IP="No route"
VPN_IF=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^(tun|wg)' | head -1)
VPN_OK=false; VPN_IP=""
if [ -n "$VPN_IF" ]; then
VPN_OK=true
VPN_IP=$(ip -4 addr show "$VPN_IF" 2>/dev/null | awk '/inet /{print $2}' | cut -d/ -f1)
fi
PRIMARY_IF=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
MAC_OK=false; CUR_MAC=""
if [ -n "$PRIMARY_IF" ]; then
CUR_MAC=$(ip link show "$PRIMARY_IF" 2>/dev/null | awk '/ether/{print $2}')
_oct=$((16#$(echo "$CUR_MAC" | cut -d: -f1))) 2>/dev/null
(( _oct & 2 )) 2>/dev/null && MAC_OK=true
fi
# ═════════════════════════════════════════════════════════════════════════════
# COMPACT BANNER
# ═════════════════════════════════════════════════════════════════════════════
render_compact() {
_si() {
local ok=$1 lbl="$2"
if $ok; then
echo -n "${C6}${lbl}${C1}${BLD}+${R}"
else
echo -n "${C6}${lbl}${C0}${BLD}x${R}"
fi
}
local _tor_ok=false; ss -tln 2>/dev/null | grep -q ':9050 ' && _tor_ok=true
local _ks_ok=false
[ -f /var/run/opsec-killswitch.enabled ] && _ks_ok=true
iptables -L GP_FW >/dev/null 2>&1 && _ks_ok=true
local _dns_ok=false
local _ds=$(awk '/^nameserver/{print $2;exit}' /etc/resolv.conf 2>/dev/null)
case "$_ds" in 127.0.0.1|9.9.9.9|149.112.*|1.1.1.1|1.0.0.1) _dns_ok=true ;; esac
local _v6_ok=false
[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null)" = "1" ] && _v6_ok=true
echo ""
if $BREAKGLASS; then
echo " ${C0}${BLD}⚠ BREAKGLASS${R} ${C0}KS bypassed${BG_REM:+ (${BG_REM})}${R}"
fi
if $ADVANCED; then
local sigil="${C1}${BLD}▲ ADVANCED${R}"
local ind="$(_si $_tor_ok TOR) $(_si $_ks_ok KS) $(_si $_dns_ok DNS) $(_si $MAC_OK MAC) $(_si $_v6_ok v6)"
$VPN_OK && ind="$ind $(_si $VPN_OK VPN)"
echo " ${sigil} ${ind} ${C9}${LOCAL_IP}${R}"
else
local sigil="${C2}── STANDARD${R}"
local ind="$(_si $_dns_ok DNS) $(_si $MAC_OK MAC)"
$VPN_OK && ind="$ind $(_si $VPN_OK VPN)"
echo " ${sigil} ${ind} ${C9}${LOCAL_IP}${R}"
fi
echo ""
}
# ═════════════════════════════════════════════════════════════════════════════
# FULL BANNER — mirrors Conky widget
# ═════════════════════════════════════════════════════════════════════════════
render_full() {
echo ""
# ── HEADER ──
echo "$HR"
if $ADVANCED; then
echo " ${C5}${BLD} OPSEC STATUS${R}"
echo " ${C1}${BLD} ▲ ADVANCED ▲${R}"
else
echo " ${C5}${BLD} OPSEC STATUS${R}"
echo " ${C2} ── STANDARD ──${R}"
fi
echo "$HR"
# Breakglass warning
if $BREAKGLASS; then
echo " ${C0}${BLD} ⚠ BREAKGLASS — Kill switch bypassed ${BG_REM:+(${BG_REM})}${R}"
echo "$HR"
fi
# ── SYSTEM ──
echo " ${C8}${BLD} ▌SYSTEM${R}"
echo " ${C6} HOST ${C7}$(hostname)${R}$(printf '%*s' 1 '')${C6}UP ${C7}$(uptime -p 2>/dev/null | sed 's/^up //' | sed 's/ hours\?/h/;s/ minutes\?/m/;s/ days\?/d/;s/, */ /g' || uptime | awk -F'up ' '{print $2}' | awk -F, '{print $1}')${R}"
local _cpu _mem _memtot _memp
_cpu=$(awk '/^cpu /{u=$2+$4; t=$2+$4+$5; if(t>0) printf "%.0f", u*100/t}' /proc/stat 2>/dev/null)
_mem=$(free -h 2>/dev/null | awk '/^Mem:/{print $3}')
_memtot=$(free -h 2>/dev/null | awk '/^Mem:/{print $2}')
_memp=$(free 2>/dev/null | awk '/^Mem:/{if($2>0) printf "%.0f", $3*100/$2}')
echo " ${C6} CPU ${C7}${_cpu:-0}%${R}$(printf '%*s' 1 '')${C6}RAM ${C7}${_memp:-0}% ${C9}(${_mem:-?}/${_memtot:-?})${R}"
# ── NETWORK ──
echo "$HR"
echo " ${C8}${BLD} ▌NETWORK${R}"
echo " ${C6} LOCAL IP ${C7}${LOCAL_IP}${R}"
# External IP
if [ -n "$PUB_IP" ]; then
if [ -n "$PUB_GEO" ]; then
echo " ${C6} EXT IP ${C7}${PUB_IP} ${C9}(${PUB_GEO})${R}"
else
echo " ${C6} EXT IP ${C7}${PUB_IP}${R}"
fi
else
echo " ${C6} EXT IP ${C0}UNAVAILABLE${R}"
fi
# VPN
if $VPN_OK; then
echo " ${C6} VPN ${C1}ACTIVE ${C7}${VPN_IP} ${C9}(${VPN_IF})${R}"
fi
# MAC
if [ -n "$CUR_MAC" ]; then
if $MAC_OK; then
echo " ${C6} MAC ${C1}RANDOM ${C9}(${CUR_MAC})${R}"
else
echo " ${C6} MAC ${C0}HWADDR ${C9}(${CUR_MAC})${R}"
fi
else
echo " ${C6} MAC ${C9}NO IFACE${R}"
fi
# ── HARDENING (advanced only) ──
if $ADVANCED; then
echo "$HR"
echo " ${C8}${BLD} ▌HARDENING${R}"
local _v6=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0")
[ "$_v6" = "1" ] && echo " ${C6} IPv6 ${C1}BLOCKED${R}" || echo " ${C6} IPv6 ${C0}LEAKING${R}"
if lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then
echo " ${C6} DNSLK ${C1}LOCKED ${C9}(immutable)${R}"
else
echo " ${C6} DNSLK ${C0}UNLOCKED${R}"
fi
local _iso=$(grep "IsolateDestAddr" /etc/tor/torrc 2>/dev/null | head -1)
[ -n "$_iso" ] && echo " ${C6} ISOL ${C1}ACTIVE ${C9}(stream isolation)${R}"
local _pad=$(grep "ConnectionPadding" /etc/tor/torrc 2>/dev/null | awk '{print $2}')
[ "$_pad" = "1" ] && echo " ${C6} TPAD ${C1}ACTIVE ${C9}(traffic padding)${R}"
local _bl=$(grep "ExcludeExitNodes" /etc/tor/torrc 2>/dev/null | sed 's/ExcludeExitNodes //' | tr -d '{}' | tr ',' ' ')
[ -n "$_bl" ] && echo " ${C6} BLOCK ${C0}$(echo "$_bl" | tr ' ' ',' | sed 's/,$//')${R}"
local _core=$(sysctl -n kernel.core_pattern 2>/dev/null)
[[ "$_core" == *"/bin/false"* ]] && echo " ${C6} CORE ${C1}BLOCKED${R}" || echo " ${C6} CORE ${C0}ENABLED${R}"
local _swap=$(swapon --show=SIZE --noheadings 2>/dev/null | head -1)
[ -z "$_swap" ] && echo " ${C6} SWAP ${C1}OFF${R}" || echo " ${C6} SWAP ${C0}ACTIVE ${C9}(${_swap})${R}"
local _bootc=0
for _svc in opsec-boot-advanced opsec-mac-randomize opsec-hostname-randomize opsec-killswitch; do
systemctl is-enabled "$_svc" 2>/dev/null | grep -q enabled && _bootc=$((_bootc + 1))
done
if [ "$_bootc" -eq 4 ]; then
echo " ${C6} BOOT ${C1}PERSIST ${C9}(${_bootc}/4)${R}"
elif [ "$_bootc" -gt 0 ]; then
echo " ${C6} BOOT ${C2}PARTIAL ${C9}(${_bootc}/4)${R}"
else
echo " ${C6} BOOT ${C0}NONE ${C9}(${_bootc}/4)${R}"
fi
local _prof="${PROFILE_NAME:-}"
[ -n "$_prof" ] && echo " ${C6} PROF ${C2}${_prof}${R}"
fi
# ── TOR STATUS (advanced only) ──
if $ADVANCED; then
echo "$HR"
echo " ${C8}${BLD} ▌TOR STATUS${R}"
local _tor_svc=false _tor_socks=false _ks_armed=false _tor_routed=false
systemctl is-active tor >/dev/null 2>&1 && _tor_svc=true
ss -tln 2>/dev/null | grep -q ':9050 ' && _tor_socks=true
$ADVANCED && _ks_armed=true
[ "$ROUTED_TOR" = "true" ] && _tor_routed=true
if $_tor_svc && $_tor_socks && $_ks_armed && $_tor_routed; then
echo " ${C6} STATE ${C1}PROTECTED${R}"
elif $_tor_svc && ! $_tor_socks; then
echo " ${C6} STATE ${C2}BOOTSTRAP ${C9}(${TOR_BOOTSTRAP:-0}% ${TOR_PHASE:-connecting})${R}"
elif ! $_tor_svc; then
echo " ${C6} STATE ${C0}EXPOSED ${C9}(tor down)${R}"
elif ! $_ks_armed; then
echo " ${C6} STATE ${C0}EXPOSED ${C9}(kill switch off)${R}"
else
echo " ${C6} STATE ${C0}EXPOSED ${C9}(not routed)${R}"
fi
if [ -n "$EXIT_COUNTRY" ] && [ ${#EXIT_COUNTRY} -le 3 ]; then
echo " ${C6} EXIT ${C2}${EXIT_COUNTRY}${R}"
elif $_tor_socks; then
echo " ${C6} EXIT ${C9}resolving...${R}"
else
echo " ${C6} EXIT ${C9}${R}"
fi
if [ -n "$EXIT_CHANGE_TIME" ] && [ "$EXIT_CHANGE_TIME" -gt 0 ] 2>/dev/null; then
local _now=$(date +%s)
local _age=$(( _now - EXIT_CHANGE_TIME ))
if [ "$_age" -lt 60 ]; then
echo " ${C6} CIRCUIT ${C2}${_age}s ago${R}"
elif [ "$_age" -lt 3600 ]; then
echo " ${C6} CIRCUIT ${C2}$(( _age / 60 ))m ago${R}"
else
echo " ${C6} CIRCUIT ${C2}$(( _age / 3600 ))h $(( (_age % 3600) / 60 ))m ago${R}"
fi
else
echo " ${C6} CIRCUIT ${C9}${R}"
fi
local _dns_server=$(awk '/^nameserver/{print $2;exit}' /etc/resolv.conf 2>/dev/null)
local _dns_immutable=false
lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i' && _dns_immutable=true
local _nm_locked=false
[ -f /etc/NetworkManager/conf.d/opsec-dns-lock.conf ] && _nm_locked=true
if [ "$_dns_server" = "127.0.0.1" ] && $_dns_immutable && $_nm_locked; then
echo " ${C6} DNS ${C1}SECURE ${C9}(tor + locked)${R}"
elif [ "$_dns_server" = "127.0.0.1" ] && $_dns_immutable; then
echo " ${C6} DNS ${C2}SECURE ${C9}(tor, NM unlocked)${R}"
elif [ "$_dns_server" = "127.0.0.1" ]; then
echo " ${C6} DNS ${C2}PARTIAL ${C9}(tor, not locked)${R}"
elif echo "$_dns_server" | grep -qE '^(9\.9\.9\.9|149\.112|1\.1\.1\.1|1\.0\.0\.1)'; then
echo " ${C6} DNS ${C2}PRIVACY ${C9}(${_dns_server})${R}"
else
echo " ${C6} DNS ${C0}LEAKED ${C9}(${_dns_server})${R}"
fi
fi
# ── MODE / LEVEL ──
echo "$HR"
if $ADVANCED; then
echo " ${C6} MODE ${C1}ADVANCED${R}"
else
echo " ${C6} MODE ${C0}STANDARD${R}"
fi
local _lvl="${DEPLOYMENT_LEVEL:-}"
[ -n "$_lvl" ] && echo " ${C6} LEVEL ${C7}${_lvl}${R}"
echo "$HR"
echo " ${C9} // $(date +%H:%M:%S) //${R}"
echo ""
}
# ═════════════════════════════════════════════════════════════════════════════
# DISPATCH
# ═════════════════════════════════════════════════════════════════════════════
case "$BANNER_MODE" in
compact) render_compact ;;
full) render_full ;;
esac
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# /usr/local/bin/opsec-boot-init.sh — Level-aware boot initializer
# Called by opsec-boot-advanced.service at boot
# Standard levels: apply base privacy hardening only
# Paranoid levels: activate full ghost mode
set -euo pipefail
BOOT_MARKER="/etc/opsec/boot-advanced.enabled"
STATE_FILE="/var/run/opsec-advanced.enabled"
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
OPSEC_CONF="/etc/opsec/opsec.conf"
# Source shared library if available
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
opsec_load_config 2>/dev/null || true
fi
# Load config directly if lib not available
if [ -f "$OPSEC_CONF" ]; then
. "$OPSEC_CONF"
fi
LEVEL_TYPE="${LEVEL_TYPE:-standard}"
DEPLOYMENT_LEVEL="${DEPLOYMENT_LEVEL:-bare-metal-standard}"
echo "[opsec-boot] Level: ${DEPLOYMENT_LEVEL} (type: ${LEVEL_TYPE})"
# Paranoid levels: always activate full ghost mode
if [ "$LEVEL_TYPE" = "paranoid" ]; then
echo "[opsec-boot] Paranoid level detected — activating full ghost mode"
touch "$STATE_FILE"
if [ -x /usr/local/bin/opsec-mode.sh ]; then
/usr/local/bin/opsec-mode.sh on
else
echo "[opsec-boot] ERROR: opsec-mode.sh not found"
exit 1
fi
echo "[opsec-boot] Ghost mode activated at boot (paranoid level)"
exit 0
fi
# Standard levels: check boot marker, apply base or full accordingly
if [ -f "$BOOT_MARKER" ]; then
echo "[opsec-boot] Boot marker detected — activating ghost mode"
touch "$STATE_FILE"
if [ -x /usr/local/bin/opsec-mode.sh ]; then
/usr/local/bin/opsec-mode.sh on
else
echo "[opsec-boot] ERROR: opsec-mode.sh not found"
exit 1
fi
echo "[opsec-boot] Ghost mode activated at boot (boot marker)"
else
echo "[opsec-boot] Standard level, no boot marker — applying base privacy hardening"
# Apply base hardening inline (cannot source opsec-mode.sh — its exit kills the caller)
echo "[opsec-boot] Applying inline base hardening..."
sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null 2>&1 || true
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null 2>&1 || true
swapoff -a 2>/dev/null || true
sysctl -w kernel.core_pattern='|/bin/false' >/dev/null 2>&1 || true
# Set privacy DNS
base_dns="${BASE_DNS:-quad9}"
chattr -i /etc/resolv.conf 2>/dev/null || true
case "$base_dns" in
quad9)
printf "nameserver 9.9.9.9\nnameserver 149.112.112.112\n" > /etc/resolv.conf
;;
cloudflare)
printf "nameserver 1.1.1.1\nnameserver 1.0.0.1\n" > /etc/resolv.conf
;;
esac
echo "[opsec-boot] Base privacy hardening applied"
fi
+76
View File
@@ -0,0 +1,76 @@
#!/bin/bash
# OPSEC Status Check
echo "===== OPSEC Status Check ====="
echo ""
# Check for running VPN
echo "[*] VPN Status:"
if pgrep -x openvpn > /dev/null; then
echo " [+] OpenVPN is running"
else
echo " [-] OpenVPN is NOT running"
fi
# Check for Tor
echo ""
echo "[*] Tor Status:"
if systemctl is-active tor >/dev/null 2>&1; then
echo " [+] Tor is running"
else
echo " [-] Tor is NOT running"
fi
# Check current connections
echo ""
echo "[*] Active Connections:"
CONNECTIONS=$(ss -tupn | grep ESTAB | wc -l)
echo " [*] $CONNECTIONS established connections"
# Check listening services
echo ""
echo "[*] Listening Services:"
LISTENERS=$(ss -tupln | grep LISTEN | wc -l)
echo " [*] $LISTENERS listening services"
# Check DNS
echo ""
echo "[*] DNS Configuration:"
grep "nameserver" /etc/resolv.conf | head -3
# Check for history files
echo ""
echo "[*] History Files:"
for hist in ~/.bash_history ~/.zsh_history ~/.python_history ~/.mysql_history; do
if [ -f "$hist" ]; then
SIZE=$(stat -c%s "$hist" 2>/dev/null)
echo " [!] $hist exists (size: $SIZE bytes)"
fi
done
# Check MAC address
echo ""
echo "[*] Network Interfaces:"
for iface in $(ip -o link show | awk -F': ' '{print $2}' | grep -v lo); do
MAC=$(ip link show $iface | awk '/ether/ {print $2}')
echo " [*] $iface: $MAC"
done
# Check timezone
echo ""
echo "[*] System Timezone:"
echo " [*] $(timedatectl | grep "Time zone" | awk '{print $3}')"
# Check for running security tools
echo ""
echo "[*] Running Security Tools:"
# Tool inventory is configurable via OPSEC_TOOL_CHECK in opsec.conf
OPSEC_TOOL_CHECK="${OPSEC_TOOL_CHECK:-curl wget openssl gpg tor}"
for tool in $OPSEC_TOOL_CHECK; do
if pgrep -f $tool > /dev/null; then
echo " [!] $tool is running"
fi
done
echo ""
echo "===== Check Complete ====="
+1044
View File
File diff suppressed because it is too large Load Diff
+377
View File
@@ -0,0 +1,377 @@
#!/bin/bash
# /usr/local/bin/opsec-harden.sh — System hardening apply/revert
# Usage: sudo opsec-harden.sh apply|revert
#
# Reads settings from /etc/opsec/opsec.conf
# Handles: core dumps, swap, timezone, locale, screen lock, WebRTC,
# USB blocking, clipboard auto-clear, traffic jitter
## NOTE: Do NOT use "set -euo pipefail" — it causes silent crashes
## when commands like sysctl, dconf, tc, etc. return non-zero.
## Use explicit error handling instead (|| true).
if [ "$EUID" -ne 0 ]; then
echo "[-] Please run as root"
exit 1
fi
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
BACKUP_DIR="/etc/opsec/.harden-backup"
# Source shared library
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
else
# Minimal fallback
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
opsec_load_config() {
[ -f /etc/opsec/opsec.conf ] && . /etc/opsec/opsec.conf
}
fi
mkdir -p "$BACKUP_DIR"
opsec_load_config 2>/dev/null || true
# ─── CORE DUMPS ────────────────────────────────────────────────────────────────
apply_core_dumps() {
if [ "${HARDEN_CORE_DUMPS:-1}" = "1" ]; then
opsec_info "Disabling core dumps..."
# sysctl
sysctl -w kernel.core_pattern='|/bin/false' >/dev/null 2>&1
sysctl -w fs.suid_dumpable=0 >/dev/null 2>&1
# limits.d
cat > /etc/security/limits.d/opsec-coredump.conf << 'EOF'
# OPSEC: Disable core dumps
* hard core 0
* soft core 0
EOF
# systemd coredump
mkdir -p /etc/systemd/coredump.conf.d
cat > /etc/systemd/coredump.conf.d/opsec.conf << 'EOF'
[Coredump]
Storage=none
ProcessSizeMax=0
EOF
opsec_green "Core dumps disabled"
fi
}
revert_core_dumps() {
sysctl -w kernel.core_pattern='core' >/dev/null 2>&1
sysctl -w fs.suid_dumpable=1 >/dev/null 2>&1
rm -f /etc/security/limits.d/opsec-coredump.conf
rm -f /etc/systemd/coredump.conf.d/opsec.conf
opsec_green "Core dumps restored"
}
# ─── SWAP ──────────────────────────────────────────────────────────────────────
apply_swap() {
if [ "${HARDEN_SWAP:-1}" = "1" ]; then
opsec_info "Disabling swap..."
swapoff -a 2>/dev/null || true
# Comment out swap entries in fstab (backup first)
if [ ! -f "$BACKUP_DIR/fstab.swap" ]; then
grep -E '^\s*[^#].*\sswap\s' /etc/fstab > "$BACKUP_DIR/fstab.swap" 2>/dev/null || true
fi
sed -i '/\sswap\s/s/^/#OPSEC# /' /etc/fstab 2>/dev/null || true
opsec_green "Swap disabled"
fi
}
revert_swap() {
sed -i 's/^#OPSEC# //' /etc/fstab 2>/dev/null || true
swapon -a 2>/dev/null || true
opsec_green "Swap restored"
}
# ─── IPv6 ──────────────────────────────────────────────────────────────────────
apply_ipv6() {
if [ "${HARDEN_IPV6:-1}" = "1" ]; then
opsec_info "Disabling IPv6..."
sysctl -w net.ipv6.conf.all.disable_ipv6=1 >/dev/null
sysctl -w net.ipv6.conf.default.disable_ipv6=1 >/dev/null
opsec_green "IPv6 disabled"
fi
}
revert_ipv6() {
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null
sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null
opsec_green "IPv6 restored"
}
# ─── TIMEZONE SPOOF ────────────────────────────────────────────────────────────
apply_timezone() {
if [ "${HARDEN_TIMEZONE_SPOOF:-0}" = "1" ]; then
local tz="${HARDEN_TIMEZONE_VALUE:-UTC}"
opsec_info "Spoofing timezone to ${tz}..."
# Backup current timezone
timedatectl show -p Timezone --value > "$BACKUP_DIR/timezone.orig" 2>/dev/null || true
timedatectl set-timezone "$tz" 2>/dev/null || {
ln -sf "/usr/share/zoneinfo/${tz}" /etc/localtime
}
opsec_green "Timezone set to ${tz}"
fi
}
revert_timezone() {
if [ -f "$BACKUP_DIR/timezone.orig" ]; then
local orig_tz
orig_tz=$(cat "$BACKUP_DIR/timezone.orig")
timedatectl set-timezone "$orig_tz" 2>/dev/null || true
rm -f "$BACKUP_DIR/timezone.orig"
opsec_green "Timezone restored to ${orig_tz}"
fi
}
# ─── LOCALE SPOOF ─────────────────────────────────────────────────────────────
apply_locale() {
if [ "${HARDEN_LOCALE_SPOOF:-0}" = "1" ]; then
local loc="${HARDEN_LOCALE_VALUE:-en_US.UTF-8}"
opsec_info "Spoofing locale to ${loc}..."
# Backup
locale > "$BACKUP_DIR/locale.orig" 2>/dev/null || true
export LANG="$loc"
export LC_ALL="$loc"
echo "LANG=${loc}" > /etc/default/locale.opsec
opsec_green "Locale set to ${loc}"
fi
}
revert_locale() {
rm -f /etc/default/locale.opsec
if [ -f "$BACKUP_DIR/locale.orig" ]; then
rm -f "$BACKUP_DIR/locale.orig"
fi
opsec_green "Locale restored"
}
# ─── SCREEN LOCK ───────────────────────────────────────────────────────────────
apply_screen_lock() {
if [ "${HARDEN_SCREEN_LOCK:-1}" = "1" ]; then
local timeout="${HARDEN_SCREEN_LOCK_TIMEOUT:-300}"
opsec_info "Setting screen lock timeout to ${timeout}s..."
# Try GNOME dconf (runs as user via sudo)
local real_user="${SUDO_USER:-$USER}"
if command -v dconf >/dev/null 2>&1; then
su - "$real_user" -c "
dconf write /org/gnome/desktop/session/idle-delay 'uint32 ${timeout}' 2>/dev/null
dconf write /org/gnome/desktop/screensaver/lock-enabled 'true' 2>/dev/null
dconf write /org/gnome/desktop/screensaver/lock-delay 'uint32 0' 2>/dev/null
" 2>/dev/null || true
fi
opsec_green "Screen lock set to ${timeout}s"
fi
}
revert_screen_lock() {
local real_user="${SUDO_USER:-$USER}"
if command -v dconf >/dev/null 2>&1; then
su - "$real_user" -c "
dconf write /org/gnome/desktop/session/idle-delay 'uint32 900' 2>/dev/null
" 2>/dev/null || true
fi
opsec_green "Screen lock restored to default"
}
# ─── WEBRTC BLOCKING ──────────────────────────────────────────────────────────
apply_webrtc() {
if [ "${LEAK_WEBRTC_BLOCK:-1}" = "1" ]; then
opsec_info "Blocking WebRTC..."
local real_user="${SUDO_USER:-$USER}"
local user_home
user_home=$(eval echo "~${real_user}")
# Firefox profiles
for profile_dir in "${user_home}"/.mozilla/firefox/*.default* "${user_home}"/.mozilla/firefox/*.opsec*; do
[ -d "$profile_dir" ] || continue
cat >> "${profile_dir}/user.js" << 'EOF'
// OPSEC: Disable WebRTC IP leak
user_pref("media.peerconnection.enabled", false);
user_pref("media.peerconnection.turn.disable", true);
user_pref("media.peerconnection.use_document_iceservers", false);
user_pref("media.peerconnection.video.enabled", false);
user_pref("media.peerconnection.identity.timeout", 1);
EOF
done
# Brave/Chromium policies
mkdir -p /etc/brave/policies/managed /etc/chromium/policies/managed
cat > /etc/brave/policies/managed/opsec-webrtc.json << 'EOF'
{ "WebRtcIPHandling": "disable_non_proxied_udp" }
EOF
cp /etc/brave/policies/managed/opsec-webrtc.json /etc/chromium/policies/managed/ 2>/dev/null || true
opsec_green "WebRTC blocked (Firefox + Brave/Chromium)"
fi
}
revert_webrtc() {
local real_user="${SUDO_USER:-$USER}"
local user_home
user_home=$(eval echo "~${real_user}")
for profile_dir in "${user_home}"/.mozilla/firefox/*.default* "${user_home}"/.mozilla/firefox/*.opsec*; do
[ -d "$profile_dir" ] || continue
sed -i '/OPSEC: Disable WebRTC/,/peerconnection\.identity/d' "${profile_dir}/user.js" 2>/dev/null || true
done
rm -f /etc/brave/policies/managed/opsec-webrtc.json
rm -f /etc/chromium/policies/managed/opsec-webrtc.json
opsec_green "WebRTC restrictions removed"
}
# ─── USB BLOCKING ─────────────────────────────────────────────────────────────
apply_usb_block() {
if [ "${LEAK_USB_BLOCK:-0}" = "1" ]; then
opsec_info "Blocking new USB devices..."
echo 0 > /sys/bus/usb/drivers_autoprobe 2>/dev/null || true
opsec_green "USB auto-probe disabled (new devices blocked)"
fi
}
revert_usb_block() {
echo 1 > /sys/bus/usb/drivers_autoprobe 2>/dev/null || true
opsec_green "USB auto-probe restored"
}
# ─── CLIPBOARD AUTO-CLEAR ─────────────────────────────────────────────────────
apply_clipboard_clear() {
if [ "${HARDEN_CLIPBOARD_CLEAR:-0}" = "1" ]; then
opsec_info "Starting clipboard auto-clear..."
# Launch background clearer (clears clipboard every 30 seconds)
local pidfile="/var/run/opsec-clipboard.pid"
if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; then
opsec_yellow "Clipboard cleaner already running"
return
fi
(
while true; do
sleep 30
# Clear X clipboard
if command -v xclip >/dev/null 2>&1; then
echo -n "" | xclip -selection clipboard 2>/dev/null || true
echo -n "" | xclip -selection primary 2>/dev/null || true
elif command -v xsel >/dev/null 2>&1; then
xsel --clipboard --clear 2>/dev/null || true
xsel --primary --clear 2>/dev/null || true
fi
done
) &
echo $! > "$pidfile"
opsec_green "Clipboard auto-clear active (every 30s)"
fi
}
revert_clipboard_clear() {
local pidfile="/var/run/opsec-clipboard.pid"
if [ -f "$pidfile" ]; then
kill "$(cat "$pidfile")" 2>/dev/null || true
rm -f "$pidfile"
fi
opsec_green "Clipboard auto-clear stopped"
}
# ─── TRAFFIC JITTER ───────────────────────────────────────────────────────────
apply_traffic_jitter() {
if [ "${TRAFFIC_JITTER_ENABLED:-0}" = "1" ]; then
local ms="${TRAFFIC_JITTER_MS:-50}"
opsec_info "Adding ${ms}ms traffic jitter..."
# Find primary outbound interface
local iface
iface=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
if [ -n "$iface" ]; then
# Remove existing qdisc first
tc qdisc del dev "$iface" root 2>/dev/null || true
tc qdisc add dev "$iface" root netem delay "${ms}ms" "${ms}ms" distribution normal 2>/dev/null || {
opsec_yellow "tc not available — jitter skipped"
return
}
echo "$iface" > "$BACKUP_DIR/jitter-iface"
opsec_green "Traffic jitter active on ${iface} (${ms}ms)"
else
opsec_yellow "No outbound interface found for jitter"
fi
fi
}
revert_traffic_jitter() {
if [ -f "$BACKUP_DIR/jitter-iface" ]; then
local iface
iface=$(cat "$BACKUP_DIR/jitter-iface")
tc qdisc del dev "$iface" root 2>/dev/null || true
rm -f "$BACKUP_DIR/jitter-iface"
opsec_green "Traffic jitter removed from ${iface}"
fi
}
# ─── MAIN ──────────────────────────────────────────────────────────────────────
do_apply() {
echo ""
opsec_hdr "APPLYING SYSTEM HARDENING"
echo ""
apply_core_dumps
apply_swap
apply_ipv6
apply_timezone
apply_locale
apply_screen_lock
apply_webrtc
# Ghost-mode-only features — skip when called from mode_base (OPSEC_BASE_ONLY=1)
if [ "${OPSEC_BASE_ONLY:-0}" != "1" ]; then
apply_usb_block
apply_clipboard_clear
apply_traffic_jitter
else
opsec_info "Base-only mode — skipping USB block, clipboard clear, traffic jitter"
fi
echo ""
opsec_green "All hardening measures applied"
}
do_revert() {
echo ""
opsec_hdr "REVERTING SYSTEM HARDENING"
echo ""
revert_core_dumps
revert_swap
revert_ipv6
revert_timezone
revert_locale
revert_screen_lock
revert_webrtc
revert_usb_block
revert_clipboard_clear
revert_traffic_jitter
echo ""
opsec_green "All hardening measures reverted"
}
case "${1:-}" in
apply) do_apply ;;
revert) do_revert ;;
*)
echo "Usage: $(basename "$0") apply|revert"
exit 1
;;
esac
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
# OPSEC Hostname Randomization
# Config-aware: reads HOSTNAME_PATTERN and HOSTNAME_CUSTOM_PREFIX from /etc/opsec/opsec.conf
# Patterns: desktop (desktop-XXXX), random (8 random chars), custom (PREFIX-XXXX)
if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi
# Source config if available
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
opsec_load_config 2>/dev/null || true
else
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
opsec_dim() { echo -e "\033[38;5;244m $*\033[0m"; }
fi
# Config values with defaults
PATTERN="${HOSTNAME_PATTERN:-desktop}"
PREFIX="${HOSTNAME_CUSTOM_PREFIX:-}"
RAND_HEX=$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')
OLD_HOSTNAME=$(hostname)
case "$PATTERN" in
desktop)
NEW_HOSTNAME="desktop-${RAND_HEX}"
;;
random)
NEW_HOSTNAME=$(head -c 4 /dev/urandom | od -An -tx1 | tr -d ' ')
;;
custom)
NEW_HOSTNAME="${PREFIX}-${RAND_HEX}"
;;
*)
NEW_HOSTNAME="desktop-${RAND_HEX}"
;;
esac
opsec_info "Randomizing hostname..."
opsec_dim "Old: ${OLD_HOSTNAME}"
opsec_dim "New: ${NEW_HOSTNAME} (pattern: ${PATTERN})"
# Set hostname via hostnamectl (persistent)
hostnamectl set-hostname "$NEW_HOSTNAME" 2>/dev/null || {
echo "$NEW_HOSTNAME" > /etc/hostname
hostname "$NEW_HOSTNAME"
}
# Update /etc/hosts to match
if grep -q "$OLD_HOSTNAME" /etc/hosts 2>/dev/null; then
sed -i "s/$OLD_HOSTNAME/$NEW_HOSTNAME/g" /etc/hosts
fi
# Ensure localhost entries exist
if ! grep -q "127.0.0.1.*$NEW_HOSTNAME" /etc/hosts; then
sed -i "/127\.0\.0\.1/s/$/ $NEW_HOSTNAME/" /etc/hosts
fi
opsec_green "Hostname randomized to: ${NEW_HOSTNAME}"
+261
View File
@@ -0,0 +1,261 @@
#!/bin/bash
# OPSEC Kill Switch — iptables rules to block non-Tor/VPN traffic
# Config-aware: reads KILLSWITCH_* settings from /etc/opsec/opsec.conf
# Usage: opsec-killswitch.sh on|off|status
if [ "$EUID" -ne 0 ]; then
echo "Please run as root"
exit 1
fi
# Source config if available
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
opsec_load_config 2>/dev/null || true
else
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
fi
# Config values with defaults
ALLOW_DHCP="${KILLSWITCH_ALLOW_DHCP:-1}"
ALLOW_OPENVPN="${KILLSWITCH_ALLOW_OPENVPN:-1}"
ALLOW_WIREGUARD="${KILLSWITCH_ALLOW_WIREGUARD:-1}"
EXTRA_PORTS="${KILLSWITCH_EXTRA_PORTS:-}"
TRANS_PORT="${TOR_TRANS_PORT:-9040}"
DNS_PORT="${TOR_DNS_PORT:-5353}"
CHAIN="GP_FW"
TOR_UID=$(id -u debian-tor 2>/dev/null || id -u tor 2>/dev/null || echo "")
KS_LOG="/run/opsec/debug.log"
mkdir -p /run/opsec 2>/dev/null || true
KS_ERR=$(mktemp /run/opsec/.ks-err.XXXXXX 2>/dev/null || mktemp /tmp/.ks-err.XXXXXX)
KS_FAIL=0
trap 'rm -f "$KS_ERR"' EXIT
ks_log() { echo "[$(date -Is)] [killswitch] $*" >> "$KS_LOG"; }
# Wrapper that logs failures and tracks failure count
ipt() {
if ! iptables "$@" 2>>"$KS_ERR"; then
ks_log "FAILED: iptables $*$(cat "$KS_ERR" 2>/dev/null)"
cat "$KS_ERR" >&2 2>/dev/null
KS_FAIL=$((KS_FAIL + 1))
: > "$KS_ERR" 2>/dev/null
return 1
fi
: > "$KS_ERR" 2>/dev/null
return 0
}
ipt6() {
if ! ip6tables "$@" 2>>"$KS_ERR"; then
ks_log "FAILED: ip6tables $*$(cat "$KS_ERR" 2>/dev/null)"
KS_FAIL=$((KS_FAIL + 1))
: > "$KS_ERR" 2>/dev/null
return 1
fi
: > "$KS_ERR" 2>/dev/null
return 0
}
ks_filter_on() {
opsec_info "Arming OPSEC kill switch (filter-only)..."
ks_log "=== KILL SWITCH FILTER-ONLY ==="
ks_log "TOR_UID=${TOR_UID:-NONE}"
ks_log "ALLOW_DHCP=${ALLOW_DHCP} ALLOW_OPENVPN=${ALLOW_OPENVPN} ALLOW_WIREGUARD=${ALLOW_WIREGUARD}"
ks_log "EXTRA_PORTS=${EXTRA_PORTS:-none}"
iptables -N "$CHAIN" 2>/dev/null || iptables -F "$CHAIN"
ipt -A "$CHAIN" -o lo -j ACCEPT
ipt -A "$CHAIN" -i lo -j ACCEPT
ipt -A "$CHAIN" -d 127.0.0.0/8 -j ACCEPT
ipt -A "$CHAIN" -m state --state ESTABLISHED,RELATED -j ACCEPT
if [ -n "$TOR_UID" ]; then
ipt -A "$CHAIN" -m owner --uid-owner "$TOR_UID" -j ACCEPT
else
ks_log "WARNING: No TOR_UID — Tor will be blocked by filter!"
fi
ipt -A "$CHAIN" -o tun+ -j ACCEPT
ipt -A "$CHAIN" -i tun+ -j ACCEPT
ipt -A "$CHAIN" -o wg+ -j ACCEPT
ipt -A "$CHAIN" -i wg+ -j ACCEPT
[ "$ALLOW_DHCP" = "1" ] && ipt -A "$CHAIN" -p udp --dport 67:68 --sport 67:68 -j ACCEPT
[ "$ALLOW_OPENVPN" = "1" ] && { ipt -A "$CHAIN" -p udp --dport 1194 -j ACCEPT; ipt -A "$CHAIN" -p tcp --dport 1194 -j ACCEPT; }
[ "$ALLOW_WIREGUARD" = "1" ] && ipt -A "$CHAIN" -p udp --dport 51820 -j ACCEPT
if [ -n "$EXTRA_PORTS" ]; then
local IFS=','
for rule in $EXTRA_PORTS; do
local proto=$(echo "$rule" | cut -d: -f1)
local port=$(echo "$rule" | cut -d: -f2)
[ -n "$proto" ] && [ -n "$port" ] && ipt -A "$CHAIN" -p "$proto" --dport "$port" -j ACCEPT
done
fi
ipt -A "$CHAIN" -j DROP
iptables -D OUTPUT -j "$CHAIN" 2>/dev/null
ipt -I OUTPUT 1 -j "$CHAIN"
# IPv6
ip6tables -N "$CHAIN" 2>/dev/null || ip6tables -F "$CHAIN"
ipt6 -A "$CHAIN" -o lo -j ACCEPT
ipt6 -A "$CHAIN" -j DROP
ip6tables -D OUTPUT -j "$CHAIN" 2>/dev/null
ipt6 -I OUTPUT 1 -j "$CHAIN"
ks_log "Filter-only lockdown active (no NAT)"
opsec_green "Kill switch filter ARMED (pre-bootstrap)"
}
ks_nat_on() {
opsec_info "Arming NAT transparent proxy..."
ks_log "=== NAT PROXY ON ==="
local NAT_CHAIN="GP_NAT"
# Remove jump before flush to prevent brief bypass window
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
iptables -t nat -N "$NAT_CHAIN" 2>/dev/null || iptables -t nat -F "$NAT_CHAIN"
# Tor UID RETURN
[ -n "$TOR_UID" ] && ipt -t nat -A "$NAT_CHAIN" -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN
# VPN RETURN — BEFORE DNS redirect
ipt -t nat -A "$NAT_CHAIN" -o tun+ -j RETURN
ipt -t nat -A "$NAT_CHAIN" -o wg+ -j RETURN
# DNS redirect
ipt -t nat -A "$NAT_CHAIN" -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT"
ipt -t nat -A "$NAT_CHAIN" -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT"
# Loopback RETURN
ipt -t nat -A "$NAT_CHAIN" -d 127.0.0.0/8 -j RETURN
# TCP catch-all TransPort
ipt -t nat -A "$NAT_CHAIN" -p tcp -j REDIRECT --to-ports "$TRANS_PORT"
# Install jump
ipt -t nat -I OUTPUT 1 -j "$NAT_CHAIN"
# Verify chain was populated (at least 3 rules: DNS redirect + TCP catch-all + others)
local nat_count
nat_count=$(iptables -t nat -L "$NAT_CHAIN" --line-numbers 2>/dev/null | tail -n +3 | wc -l)
ks_log "NAT chain rule count: ${nat_count}"
if [ "$nat_count" -lt 3 ]; then
ks_log "WARNING: NAT chain has only ${nat_count} rules — falling back to direct OUTPUT rules"
opsec_info "NAT chain underpopulated (${nat_count} rules) — using direct OUTPUT fallback"
# Remove the jump to the empty/broken chain
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
iptables -t nat -F "$NAT_CHAIN" 2>/dev/null
iptables -t nat -X "$NAT_CHAIN" 2>/dev/null
# Direct OUTPUT rules as fallback
[ -n "$TOR_UID" ] && iptables -t nat -A OUTPUT -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN 2>/dev/null
iptables -t nat -A OUTPUT -o tun+ -j RETURN 2>/dev/null
iptables -t nat -A OUTPUT -o wg+ -j RETURN 2>/dev/null
iptables -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null
iptables -t nat -A OUTPUT -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null
iptables -t nat -A OUTPUT -d 127.0.0.0/8 -j RETURN 2>/dev/null
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports "$TRANS_PORT" 2>/dev/null
local fb_count
fb_count=$(iptables -t nat -L OUTPUT --line-numbers 2>/dev/null | tail -n +3 | wc -l)
ks_log "Fallback OUTPUT NAT rule count: ${fb_count}"
fi
if [ "$KS_FAIL" -gt 0 ]; then
ks_log "WARNING: ${KS_FAIL} iptables commands failed during NAT setup"
opsec_info "WARNING: ${KS_FAIL} iptables rule(s) failed — check /run/opsec/debug.log"
return 1
fi
ks_log "NAT transparent proxy armed"
opsec_green "Kill switch fully ARMED — Tor + NAT active"
}
ks_on() {
ks_filter_on
ks_nat_on
}
ks_off() {
opsec_info "Disarming OPSEC kill switch..."
ks_log "=== KILL SWITCH OFF ==="
# Filter chain
iptables -D OUTPUT -j "$CHAIN" 2>/dev/null
iptables -F "$CHAIN" 2>/dev/null
iptables -X "$CHAIN" 2>/dev/null
# NAT chain (config-independent teardown)
local NAT_CHAIN="GP_NAT"
iptables -t nat -D OUTPUT -j "$NAT_CHAIN" 2>/dev/null
iptables -t nat -F "$NAT_CHAIN" 2>/dev/null
iptables -t nat -X "$NAT_CHAIN" 2>/dev/null
# Clean up fallback direct OUTPUT NAT rules (if chain approach failed during arm)
# Loop-delete to catch duplicates — keeps removing until no match left
local _fb_cleaned=0
while iptables -t nat -D OUTPUT -p tcp -j REDIRECT --to-ports "$TRANS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -p udp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -p tcp --dport 53 -j REDIRECT --to-ports "$DNS_PORT" 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -d 127.0.0.0/8 -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -o tun+ -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
while iptables -t nat -D OUTPUT -o wg+ -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
if [ -n "$TOR_UID" ]; then
while iptables -t nat -D OUTPUT -p tcp -m owner --uid-owner "$TOR_UID" -j RETURN 2>/dev/null; do _fb_cleaned=$((_fb_cleaned+1)); done
fi
[ "$_fb_cleaned" -gt 0 ] && ks_log "Cleaned ${_fb_cleaned} fallback NAT OUTPUT rules"
# IPv6
ip6tables -D OUTPUT -j "$CHAIN" 2>/dev/null
ip6tables -F "$CHAIN" 2>/dev/null
ip6tables -X "$CHAIN" 2>/dev/null
# Flush conntrack to clear stale NAT entries
conntrack -F 2>/dev/null && ks_log "conntrack flushed" || true
ks_log "Kill switch disarmed"
opsec_green "Kill switch DISARMED — normal traffic allowed"
}
ks_status() {
if iptables -L "$CHAIN" >/dev/null 2>&1; then
opsec_green "Kill switch is ARMED"
echo ""
echo "IPv4 rules:"
iptables -L "$CHAIN" -v -n --line-numbers
echo ""
echo "NAT chain:"
if iptables -t nat -L GP_NAT >/dev/null 2>&1; then
iptables -t nat -L GP_NAT -v -n --line-numbers
else
echo " (not armed)"
fi
echo ""
echo "IPv6 rules:"
ip6tables -L "$CHAIN" -v -n --line-numbers 2>/dev/null
else
opsec_info "Kill switch is OFF"
fi
}
case "${1}" in
on) ks_on ;;
filter-on) ks_filter_on ;;
nat-on) ks_nat_on ;;
off) ks_off ;;
status) ks_status ;;
*)
echo "Usage: $(basename "$0") on|off|filter-on|nat-on|status"
exit 1
;;
esac
+97
View File
@@ -0,0 +1,97 @@
#!/bin/bash
# /usr/local/bin/opsec-log-rotate.sh — Secure log rotation and cleanup
# Truncates sensitive logs, shreds rotated files, clears journald + shell histories
# Runs via cron (configurable LOG_ROTATION_HOURS in /etc/opsec/opsec.conf)
set -euo pipefail
if [ "$EUID" -ne 0 ]; then
echo "[-] Please run as root"
exit 1
fi
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
_HAS_LIB=false
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
opsec_load_config 2>/dev/null || true
_HAS_LIB=true
else
opsec_green() { echo "[+] $*"; }
opsec_info() { echo "[~] $*"; }
fi
LOG_FILE="/var/log/opsec-log-rotate.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
log() { echo "[$TIMESTAMP] $*" >> "$LOG_FILE"; }
log "Starting secure log rotation"
# ─── TRUNCATE SENSITIVE SYSTEM LOGS ────────────────────────────────────────────
SENSITIVE_LOGS=(
/var/log/auth.log
/var/log/syslog
/var/log/kern.log
/var/log/daemon.log
/var/log/messages
/var/log/user.log
/var/log/mail.log
/var/log/debug
/var/log/wtmp
/var/log/btmp
/var/log/lastlog
/var/log/faillog
)
for logfile in "${SENSITIVE_LOGS[@]}"; do
if [ -f "$logfile" ]; then
truncate -s 0 "$logfile" 2>/dev/null || true
log "Truncated: $logfile"
fi
done
# ─── SHRED ROTATED LOG FILES (SSD-aware) ─────────────────────────────────────
for rotated in /var/log/*.gz /var/log/*.1 /var/log/*.old; do
if [ -f "$rotated" ]; then
if [ "$_HAS_LIB" = "true" ]; then
opsec_secure_delete "$rotated"
else
shred -fuz "$rotated" 2>/dev/null || rm -f "$rotated"
fi
log "Wiped: $rotated"
fi
done
# ─── CLEAR JOURNALD ───────────────────────────────────────────────────────────
if command -v journalctl >/dev/null 2>&1; then
journalctl --vacuum-time=1h 2>/dev/null || true
log "Journald vacuumed (1h retention)"
fi
# ─── CLEAR SHELL HISTORIES ────────────────────────────────────────────────────
for user_home in /home/* /root; do
[ -d "$user_home" ] || continue
for hist_file in .bash_history .zsh_history .python_history .psql_history .mysql_history .lesshst .viminfo; do
if [ -f "${user_home}/${hist_file}" ]; then
if [ "$_HAS_LIB" = "true" ]; then
opsec_secure_delete "${user_home}/${hist_file}"
else
shred -fuz "${user_home}/${hist_file}" 2>/dev/null || truncate -s 0 "${user_home}/${hist_file}" 2>/dev/null || true
fi
log "Cleared: ${user_home}/${hist_file}"
fi
done
done
# ─── CLEAR RECENTLY USED ──────────────────────────────────────────────────────
for user_home in /home/* /root; do
[ -d "$user_home" ] || continue
rm -f "${user_home}/.local/share/recently-used.xbel" 2>/dev/null || true
done
# ─── CLEAR TEMP FILES ─────────────────────────────────────────────────────────
find /tmp -type f -mmin +60 -delete 2>/dev/null || true
find /var/tmp -type f -mmin +60 -delete 2>/dev/null || true
log "Secure log rotation complete"
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
# opsec-mode-toggle — Keyboard shortcut wrapper for opsec-mode on/off
# Toggles between standard and advanced OPSEC mode
# Bind to: Ctrl+Alt+\
STATE_FILE="/var/run/opsec-advanced.enabled"
BOOTSTRAP_STOP="/var/run/opsec-bootstrap-stop"
DEBUG_LOG="/var/log/opsec-debug.log"
toggle_log() {
echo "[$(date -Is)] [toggle] $*" >> "$DEBUG_LOG" 2>/dev/null || true
}
if [ -f "$STATE_FILE" ]; then
# Turning OFF — signal bootstrap loop to stop, then run mode_off
toggle_log "Toggling OFF (state file exists)"
# Create stop signal so mode_on's bootstrap loop exits immediately
# (needs root — use pkexec for a one-liner)
pkexec bash -c "touch ${BOOTSTRAP_STOP} && /usr/local/bin/opsec-mode.sh off"
toggle_log "Toggle OFF complete (exit: $?)"
else
toggle_log "Toggling ON (no state file)"
pkexec /usr/local/bin/opsec-mode.sh on
toggle_log "Toggle ON complete (exit: $?)"
fi
+1210
View File
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
#!/bin/bash
# /usr/local/bin/opsec-monitor.sh — Background connection monitor
# Watches for non-Tor/non-VPN outbound connections, new listening ports,
# unexpected DNS queries. Sends desktop notifications + logs.
# Usage: sudo opsec-monitor.sh start|stop|status
set -euo pipefail
PIDFILE="/var/run/opsec-monitor.pid"
LOGFILE="/var/log/opsec-monitor.log"
INTERVAL=10
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
else
opsec_green() { echo "[+] $*"; }
opsec_red() { echo "[-] $*"; }
opsec_info() { echo "[~] $*"; }
fi
notify() {
local msg="$1" urgency="${2:-normal}"
local real_user="${SUDO_USER:-$USER}"
# Desktop notification
su - "$real_user" -c "DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u "$real_user")/bus notify-send -u '$urgency' 'OPSEC Monitor' '$msg'" 2>/dev/null || true
# Log
echo "[$(date '+%H:%M:%S')] $msg" >> "$LOGFILE"
}
monitor_loop() {
local known_listeners=""
local tor_uid
tor_uid=$(id -u debian-tor 2>/dev/null || id -u tor 2>/dev/null || echo "")
while true; do
# ─── Check for non-Tor/VPN outbound connections ────────────────────
local suspicious
suspicious=$(ss -tunp 2>/dev/null | grep ESTAB | grep -v '127.0.0.1' | grep -v '::1' | \
grep -v "tun\|wg\|tor\|${tor_uid:-NOOP}" | \
grep -v ':9050\|:5353\|:1194\|:51820' || true)
if [ -n "$suspicious" ]; then
local count
count=$(echo "$suspicious" | wc -l)
notify "ALERT: ${count} non-Tor/VPN outbound connection(s) detected" "critical"
fi
# ─── Check for new listening ports ─────────────────────────────────
local current_listeners
current_listeners=$(ss -tlnp 2>/dev/null | tail -n +2 | awk '{print $4}' | sort)
if [ -n "$known_listeners" ] && [ "$current_listeners" != "$known_listeners" ]; then
local new_ports
new_ports=$(comm -13 <(echo "$known_listeners") <(echo "$current_listeners") 2>/dev/null || true)
if [ -n "$new_ports" ]; then
notify "New listening port(s): ${new_ports}" "critical"
fi
fi
known_listeners="$current_listeners"
# ─── Check for unexpected DNS queries ──────────────────────────────
local dns_leaks
dns_leaks=$(ss -tunp 2>/dev/null | grep ':53 ' | grep -v '127.0.0.1' | grep -v '::1' || true)
if [ -n "$dns_leaks" ]; then
notify "DNS LEAK: Non-local DNS query detected" "critical"
fi
sleep "$INTERVAL"
done
}
do_start() {
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
opsec_info "Monitor already running (PID: $(cat "$PIDFILE"))"
return
fi
monitor_loop &
echo $! > "$PIDFILE"
opsec_green "Monitor started (PID: $!, logging to ${LOGFILE})"
}
do_stop() {
if [ -f "$PIDFILE" ]; then
local pid
pid=$(cat "$PIDFILE")
kill "$pid" 2>/dev/null || true
# Kill child processes
pkill -P "$pid" 2>/dev/null || true
rm -f "$PIDFILE"
opsec_green "Monitor stopped"
else
opsec_info "Monitor not running"
fi
}
do_status() {
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
opsec_green "Monitor running (PID: $(cat "$PIDFILE"))"
if [ -f "$LOGFILE" ]; then
echo ""
echo "Last 10 log entries:"
tail -10 "$LOGFILE" 2>/dev/null || true
fi
else
opsec_info "Monitor not running"
fi
}
case "${1:-}" in
start) do_start ;;
stop) do_stop ;;
status) do_status ;;
*)
echo "Usage: $(basename "$0") start|stop|status"
exit 1
;;
esac
+291
View File
@@ -0,0 +1,291 @@
#!/bin/bash
# /usr/local/bin/opsec-preflight.sh — OPSEC Preflight Verification Gate
# Verifies all required security controls are active for the current mode.
#
# Usage:
# opsec-preflight.sh Human-readable HUD
# opsec-preflight.sh --score Pass/fail count
# opsec-preflight.sh --enforce Exit 1 if any applicable check fails
# opsec-preflight.sh --base Only run base checks (standard mode)
# opsec-preflight.sh --full Run base + ghost checks
# opsec-preflight.sh --base --enforce Base checks, exit 1 on fail
# opsec-preflight.sh --full --enforce Full checks, exit 1 on fail
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
OPSEC_CONF="/etc/opsec/opsec.conf"
PREFLIGHT_LOG="/var/log/opsec-preflight.log"
# Source library
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
opsec_load_config 2>/dev/null || true
elif [ -f "$OPSEC_CONF" ]; then
. "$OPSEC_CONF"
fi
# Fallback colors if lib not loaded
type opsec_green >/dev/null 2>&1 || {
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
opsec_cyan() { echo -e "\033[38;5;51m[>] $*\033[0m"; }
opsec_dim() { echo -e "\033[38;5;244m $*\033[0m"; }
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
}
# ─── PARSE ARGS ───────────────────────────────────────────────────────────────
MODE_SCORE=false
MODE_ENFORCE=false
CHECK_LEVEL=""
while [ $# -gt 0 ]; do
case "$1" in
--score) MODE_SCORE=true ;;
--enforce) MODE_ENFORCE=true ;;
--base) CHECK_LEVEL="base" ;;
--full) CHECK_LEVEL="full" ;;
*) echo "Usage: opsec-preflight.sh [--score|--enforce] [--base|--full]"; exit 1 ;;
esac
shift
done
# Auto-detect check level if not specified
if [ -z "$CHECK_LEVEL" ]; then
if [ -f /var/run/opsec-advanced.enabled ]; then
CHECK_LEVEL="full"
else
CHECK_LEVEL="base"
fi
fi
# ─── ENVIRONMENT ──────────────────────────────────────────────────────────────
LEVEL="${DEPLOYMENT_LEVEL:-bare-metal-standard}"
LTYPE="${LEVEL_TYPE:-standard}"
is_cloud() {
case "$LEVEL" in
cloud-*) return 0 ;;
*) return 1 ;;
esac
}
is_bare_metal() {
case "$LEVEL" in
bare-metal-*) return 0 ;;
*) return 1 ;;
esac
}
# ─── CHECK ENGINE ─────────────────────────────────────────────────────────────
PASS=0
FAIL=0
SKIP=0
FAILURES=""
check() {
local name="$1" result="$2" detail="${3:-}"
if [ "$result" = "pass" ]; then
PASS=$((PASS + 1))
$MODE_SCORE || opsec_green "PASS ${name}${detail:+ ${detail}}"
elif [ "$result" = "fail" ]; then
FAIL=$((FAIL + 1))
FAILURES="${FAILURES}\n - ${name}${detail:+: ${detail}}"
$MODE_SCORE || opsec_red "FAIL ${name}${detail:+ ${detail}}"
elif [ "$result" = "skip" ]; then
SKIP=$((SKIP + 1))
$MODE_SCORE || opsec_dim "SKIP ${name}${detail:+ (${detail})}"
fi
}
# ─── BASE CHECKS (always run) ────────────────────────────────────────────────
run_base_checks() {
$MODE_SCORE || opsec_cyan "Base Privacy Checks"
# 1. MAC randomized (bare-metal only)
if is_bare_metal; then
local pif cur_mac first_octet first_dec
pif=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
if [ -n "$pif" ]; then
cur_mac=$(ip link show "$pif" 2>/dev/null | awk '/ether/ {print $2}')
first_octet=$(echo "$cur_mac" | cut -d: -f1)
first_dec=$((16#${first_octet})) 2>/dev/null || first_dec=0
if (( first_dec & 2 )); then
check "MAC randomized" "pass" "${pif}: ${cur_mac}"
else
check "MAC randomized" "fail" "${pif}: ${cur_mac} (hardware address)"
fi
else
check "MAC randomized" "skip" "no primary interface"
fi
else
check "MAC randomized" "skip" "cloud deployment"
fi
# 2. IPv6 disabled
local ipv6_all
ipv6_all=$(sysctl -n net.ipv6.conf.all.disable_ipv6 2>/dev/null || echo "0")
if [ "$ipv6_all" = "1" ]; then
check "IPv6 disabled" "pass"
else
check "IPv6 disabled" "fail" "net.ipv6.conf.all.disable_ipv6=${ipv6_all}"
fi
# 3. Core dumps disabled
local core_pat
core_pat=$(sysctl -n kernel.core_pattern 2>/dev/null)
if [[ "$core_pat" == *"/bin/false"* ]] || [[ "$core_pat" == *"devnull"* ]]; then
check "Core dumps disabled" "pass"
else
check "Core dumps disabled" "fail" "core_pattern=${core_pat}"
fi
# 4. Swap disabled
local swap_count
swap_count=$(swapon --show --noheadings 2>/dev/null | wc -l)
if [ "$swap_count" -eq 0 ]; then
check "Swap disabled" "pass"
else
check "Swap disabled" "fail" "${swap_count} swap device(s) active"
fi
# 5. DNS is privacy resolver (not ISP)
local dns_server
dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null)
case "$dns_server" in
127.0.0.1|127.0.0.53|9.9.9.9|149.112.112.112|1.1.1.1|1.0.0.1)
check "DNS privacy resolver" "pass" "${dns_server}"
;;
*)
check "DNS privacy resolver" "fail" "${dns_server} (possibly ISP)"
;;
esac
# 6. WebRTC blocked (if Firefox present)
if [ -d "$HOME/.mozilla/firefox" ] || [ -d "/root/.mozilla/firefox" ]; then
local userjs_found=false
local profile_dirs
profile_dirs=$(find /home/*/.mozilla/firefox /root/.mozilla/firefox -maxdepth 1 -name '*.default*' 2>/dev/null | head -3)
for pd in $profile_dirs; do
if [ -f "$pd/user.js" ] && grep -q "media.peerconnection.enabled.*false" "$pd/user.js" 2>/dev/null; then
userjs_found=true
break
fi
done
if $userjs_found; then
check "WebRTC blocked" "pass" "user.js configured"
else
check "WebRTC blocked" "fail" "no user.js with WebRTC disable found"
fi
else
check "WebRTC blocked" "skip" "Firefox not detected"
fi
}
# ─── GHOST CHECKS (only when ghost mode active) ──────────────────────────────
run_ghost_checks() {
$MODE_SCORE || opsec_cyan "Ghost Mode Checks"
# 1. VPN or Tor active
local vpn_if tor_ok=false
vpn_if=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | grep -E '^(tun|wg)' | head -1)
ss -tln 2>/dev/null | grep -q ':9050 ' && tor_ok=true
if [ -n "$vpn_if" ] || $tor_ok; then
local detail=""
[ -n "$vpn_if" ] && detail="VPN:${vpn_if}"
$tor_ok && detail="${detail:+${detail} + }Tor:9050"
check "VPN/Tor active" "pass" "$detail"
else
check "VPN/Tor active" "fail" "no tun/wg interface, no Tor on :9050"
fi
# 2. Kill switch armed
if [ -f /var/run/opsec-breakglass.active ]; then
check "Kill switch armed" "fail" "BREAK-GLASS active — kill switch intentionally dropped"
elif iptables -L GP_FW >/dev/null 2>&1; then
check "Kill switch armed" "pass"
else
check "Kill switch armed" "fail" "GP_FW chain not found"
fi
# 3. DNS locked to Tor
local dns_server
dns_server=$(awk '/^nameserver/ {print $2; exit}' /etc/resolv.conf 2>/dev/null)
if [ "$dns_server" = "127.0.0.1" ]; then
check "DNS locked to Tor" "pass"
else
check "DNS locked to Tor" "fail" "DNS=${dns_server} (expected 127.0.0.1)"
fi
# 4. Hostname randomized (bare-metal only)
if is_bare_metal; then
local cur_hostname
cur_hostname=$(hostname)
# Check if hostname looks randomized (not default patterns like kali, debian, localhost)
case "$cur_hostname" in
kali|debian|localhost|ubuntu|parrot)
check "Hostname randomized" "fail" "hostname=${cur_hostname} (appears default)"
;;
*)
check "Hostname randomized" "pass" "${cur_hostname}"
;;
esac
else
check "Hostname randomized" "skip" "cloud deployment"
fi
# 5. resolv.conf immutable
if lsattr /etc/resolv.conf 2>/dev/null | grep -q 'i'; then
check "resolv.conf immutable" "pass"
else
check "resolv.conf immutable" "fail" "chattr +i not set"
fi
}
# ─── EXECUTE CHECKS ──────────────────────────────────────────────────────────
if ! $MODE_SCORE; then
echo ""
opsec_hdr "OPSEC PREFLIGHT — ${CHECK_LEVEL^^}"
opsec_dim "Level: ${LEVEL} (${LTYPE})"
echo ""
fi
run_base_checks
if [ "$CHECK_LEVEL" = "full" ]; then
$MODE_SCORE || echo ""
run_ghost_checks
fi
# ─── RESULTS ──────────────────────────────────────────────────────────────────
TOTAL=$((PASS + FAIL))
if $MODE_SCORE; then
echo "${PASS}/${TOTAL} passed"
[ "$FAIL" -gt 0 ] && exit 1
exit 0
fi
echo ""
opsec_hdr "PREFLIGHT RESULT"
if [ "$FAIL" -eq 0 ]; then
opsec_green "ALL CHECKS PASSED (${PASS}/${TOTAL})"
[ "$SKIP" -gt 0 ] && opsec_dim "${SKIP} checks skipped (not applicable)"
else
opsec_red "FAILED: ${FAIL}/${TOTAL} checks"
opsec_dim "Passed: ${PASS} | Failed: ${FAIL} | Skipped: ${SKIP}"
echo -e "\033[38;5;196mFailures:${FAILURES}\033[0m"
# Log failures
echo "[$(date -Is)] PREFLIGHT ${CHECK_LEVEL^^}: ${PASS}/${TOTAL} passed, ${FAIL} failed${FAILURES}" >> "$PREFLIGHT_LOG" 2>/dev/null || true
fi
echo ""
if $MODE_ENFORCE && [ "$FAIL" -gt 0 ]; then
exit 1
fi
exit 0
+115
View File
@@ -0,0 +1,115 @@
#!/bin/bash
# /usr/local/bin/opsec-sentry.sh — Continuous Threat Sentry Manager
# Manages the sentry daemon for background threat detection
# Usage: opsec-sentry.sh start|stop|status|restart
set -euo pipefail
SENTRY_BIN="${SENTRY_BIN:-/usr/local/bin/sentry-daemon}"
PID_FILE="/var/run/opsec-sentry.pid"
LOG_FILE="/var/log/opsec-sentry.log"
ALERT_HOOK=""
# Source config for alert hook if available
OPSEC_CONF="/etc/opsec/opsec.conf"
[ -f "$OPSEC_CONF" ] && . "$OPSEC_CONF"
# Color output helpers
_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
is_running() {
[ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null
}
sentry_start() {
if is_running; then
_yellow "Sentry already running (PID $(cat "$PID_FILE"))"
return 0
fi
if [ ! -x "$SENTRY_BIN" ]; then
_red "Sentry binary not found at ${SENTRY_BIN}"
return 1
fi
local cmd="$SENTRY_BIN --daemon --sentry-pid $PID_FILE --sentry-log $LOG_FILE"
[ -n "$ALERT_HOOK" ] && cmd="$cmd --alert-hook $ALERT_HOOK"
# Run sentry in daemon mode
$cmd &
local pid=$!
echo "$pid" > "$PID_FILE"
# Verify it started
sleep 1
if kill -0 "$pid" 2>/dev/null; then
_green "Sentry daemon started (PID ${pid})"
_info "Log: ${LOG_FILE}"
else
rm -f "$PID_FILE"
_red "Sentry failed to start — check ${LOG_FILE}"
return 1
fi
}
sentry_stop() {
if ! is_running; then
_yellow "Sentry not running"
rm -f "$PID_FILE"
return 0
fi
local pid
pid=$(cat "$PID_FILE" 2>/dev/null)
kill "$pid" 2>/dev/null
# Wait for graceful shutdown
local i=0
while kill -0 "$pid" 2>/dev/null && [ $i -lt 10 ]; do
sleep 1
i=$((i + 1))
done
if kill -0 "$pid" 2>/dev/null; then
kill -9 "$pid" 2>/dev/null
fi
rm -f "$PID_FILE"
_green "Sentry daemon stopped"
}
sentry_status() {
if is_running; then
local pid
pid=$(cat "$PID_FILE" 2>/dev/null)
_green "Sentry ACTIVE (PID ${pid})"
if [ -f "$LOG_FILE" ]; then
local lines
lines=$(wc -l < "$LOG_FILE" 2>/dev/null || echo "0")
_info "Log entries: ${lines}"
local last
last=$(tail -1 "$LOG_FILE" 2>/dev/null)
[ -n "$last" ] && _info "Last: ${last}"
fi
else
_yellow "Sentry NOT running"
rm -f "$PID_FILE"
fi
}
case "${1:-}" in
start) sentry_start ;;
stop) sentry_stop ;;
restart)
sentry_stop
sleep 1
sentry_start
;;
status) sentry_status ;;
*)
echo "Usage: opsec-sentry.sh start|stop|status|restart"
exit 1
;;
esac
+123
View File
@@ -0,0 +1,123 @@
#!/bin/bash
# /usr/local/bin/opsec-ssh-check.sh — SSH Honeypot Detection
# Checks target SSH banners for known honeypot signatures
# Alias: ssh-safe
#
# Usage: opsec-ssh-check.sh <host> [port]
set -euo pipefail
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
else
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
opsec_cyan() { echo -e "\033[38;5;51m[>] $*\033[0m"; }
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
fi
HOST="${1:-}"
PORT="${2:-22}"
if [ -z "$HOST" ]; then
echo "Usage: $(basename "$0") <host> [port]"
echo " ssh-safe <host> [port]"
exit 1
fi
opsec_hdr "SSH HONEYPOT CHECK: ${HOST}:${PORT}"
echo ""
SUSPICIOUS=0
# ─── GRAB BANNER ───────────────────────────────────────────────────────────────
opsec_info "Grabbing SSH banner..."
BANNER=$(timeout 5 bash -c "echo '' | nc -w 3 $HOST $PORT 2>/dev/null" || true)
if [ -z "$BANNER" ]; then
opsec_yellow "No banner received — port may be filtered or service is not SSH"
exit 1
fi
opsec_cyan "Banner: ${BANNER}"
echo ""
# ─── KNOWN HONEYPOT SIGNATURES ─────────────────────────────────────────────────
# Cowrie
if echo "$BANNER" | grep -qiE 'SSH-2\.0-OpenSSH_6\.(0|1|2|6)p1.*Debian'; then
opsec_red "COWRIE SIGNATURE: Old OpenSSH version commonly used by Cowrie honeypot"
SUSPICIOUS=$((SUSPICIOUS + 3))
fi
# Kippo
if echo "$BANNER" | grep -qi 'SSH-1\.99-OpenSSH_5\.1p1'; then
opsec_red "KIPPO SIGNATURE: SSH-1.99 with OpenSSH_5.1p1 is a known Kippo default"
SUSPICIOUS=$((SUSPICIOUS + 3))
fi
# HonSSH
if echo "$BANNER" | grep -qi 'HonSSH'; then
opsec_red "HONSH SIGNATURE: Banner explicitly mentions HonSSH"
SUSPICIOUS=$((SUSPICIOUS + 5))
fi
# Generic old version check
if echo "$BANNER" | grep -qE 'OpenSSH_[345]\.' ; then
opsec_yellow "SUSPICIOUS: Very old OpenSSH version (common in honeypots)"
SUSPICIOUS=$((SUSPICIOUS + 2))
fi
# Unusual SSH protocol version
if echo "$BANNER" | grep -q 'SSH-1\.'; then
opsec_yellow "SUSPICIOUS: SSHv1 protocol (deprecated, often honeypot)"
SUSPICIOUS=$((SUSPICIOUS + 2))
fi
# ─── KEY EXCHANGE CHECK ────────────────────────────────────────────────────────
opsec_info "Checking key exchange algorithms..."
KEX_OUTPUT=$(timeout 5 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o BatchMode=yes -o ConnectTimeout=3 -v -p "$PORT" "check@${HOST}" 2>&1 || true)
# Check for weak/unusual key types
if echo "$KEX_OUTPUT" | grep -qi 'ssh-dss'; then
opsec_yellow "SUSPICIOUS: DSA host key (often seen in honeypots)"
SUSPICIOUS=$((SUSPICIOUS + 1))
fi
# Check for unusually fast key exchange (honeypots often respond instantly)
if echo "$KEX_OUTPUT" | grep -qi 'Connection reset\|Connection refused'; then
opsec_info "Connection dropped (may be rate-limited or filtered)"
fi
# ─── TIMING CHECK ─────────────────────────────────────────────────────────────
opsec_info "Checking authentication timing..."
AUTH_START=$(date +%s%N)
timeout 3 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o BatchMode=yes -o ConnectTimeout=3 -p "$PORT" "probe@${HOST}" 2>/dev/null || true
AUTH_END=$(date +%s%N)
AUTH_MS=$(( (AUTH_END - AUTH_START) / 1000000 ))
if [ "$AUTH_MS" -lt 50 ] && [ "$AUTH_MS" -gt 0 ]; then
opsec_yellow "SUSPICIOUS: Unusually fast auth response (${AUTH_MS}ms) — possible honeypot"
SUSPICIOUS=$((SUSPICIOUS + 1))
else
opsec_info "Auth response time: ${AUTH_MS}ms"
fi
# ─── VERDICT ───────────────────────────────────────────────────────────────────
echo ""
if [ "$SUSPICIOUS" -ge 3 ]; then
opsec_red "VERDICT: HIGH RISK — Likely honeypot (score: ${SUSPICIOUS})"
opsec_red "DO NOT connect to this host for operations"
elif [ "$SUSPICIOUS" -ge 1 ]; then
opsec_yellow "VERDICT: MODERATE RISK — Some anomalies detected (score: ${SUSPICIOUS})"
opsec_yellow "Proceed with caution"
else
opsec_green "VERDICT: LOW RISK — No honeypot signatures detected (score: ${SUSPICIOUS})"
fi
echo ""
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
# OPSEC Toggle — single entry point for keyboard shortcut + desktop file
# Uses pkexec directly on opsec-mode.sh for proper polkit policy matching
OPSEC_MODE="/usr/local/bin/opsec-mode.sh"
STATE_FILE="/var/run/opsec-advanced.enabled"
[ ! -x "$OPSEC_MODE" ] && exit 1
if [ -f "$STATE_FILE" ]; then
# Turning OFF — opsec-mode.sh off handles bootstrap stop signal internally
if [ "$EUID" -eq 0 ] 2>/dev/null; then
"$OPSEC_MODE" off
else
pkexec "$OPSEC_MODE" off
fi
else
# Turning ON — pkexec prompts for auth via polkit policy
if [ "$EUID" -eq 0 ] 2>/dev/null; then
"$OPSEC_MODE" on
else
pkexec "$OPSEC_MODE" on
fi
fi
+180
View File
@@ -0,0 +1,180 @@
#!/bin/bash
# /usr/local/bin/opsec-traffic-blend.sh — Decoy Traffic Blending Daemon
# Generates background browsing noise through Tor/VPN to blend tool traffic
# with normal-looking web activity. Ghost mode only.
# Usage: opsec-traffic-blend.sh start|stop|status
set -euo pipefail
PID_FILE="/var/run/opsec-traffic-blend.pid"
LOG_FILE="/var/log/opsec-traffic-blend.log"
# Source config
OPSEC_CONF="/etc/opsec/opsec.conf"
[ -f "$OPSEC_CONF" ] && . "$OPSEC_CONF"
SOCKS_PORT="${TOR_SOCKS_PORT:-9050}"
# Mean interval in seconds (Poisson distribution approximation)
BLEND_INTERVAL="${TRAFFIC_BLEND_INTERVAL:-30}"
# Color helpers
_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
# Benign URLs to simulate normal browsing (mix of news, tech, social)
DECOY_URLS=(
"https://en.wikipedia.org/wiki/Special:Random"
"https://www.bbc.com/news"
"https://news.ycombinator.com"
"https://www.reuters.com"
"https://stackoverflow.com/questions"
"https://github.com/trending"
"https://www.reddit.com/r/technology/.json"
"https://www.weather.gov"
"https://httpbin.org/get"
"https://www.kernel.org"
"https://www.python.org"
"https://www.debian.org"
"https://archive.org"
"https://www.mozilla.org"
"https://duckduckgo.com/?q=weather"
"https://lite.cnn.com"
"https://text.npr.org"
)
# Random delay using Poisson-like distribution (exponential inter-arrival)
poisson_delay() {
# Approximate exponential distribution using bash
# -ln(U) * mean where U is uniform(0,1)
local mean="$1"
local rand
rand=$((RANDOM % 1000 + 1))
# Approximate: -ln(rand/1000) * mean
# Using awk for floating point
awk -v r="$rand" -v m="$mean" 'BEGIN {
u = r / 1000.0;
if (u < 0.001) u = 0.001;
delay = -log(u) * m;
if (delay < 5) delay = 5;
if (delay > 120) delay = 120;
printf "%d\n", delay;
}'
}
is_running() {
[ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null
}
blend_loop() {
local url_count=${#DECOY_URLS[@]}
echo "[$(date -Is)] Traffic blend daemon started (mean interval: ${BLEND_INTERVAL}s)" >> "$LOG_FILE"
while true; do
# Pick a random URL
local idx=$((RANDOM % url_count))
local url="${DECOY_URLS[$idx]}"
# Determine proxy method
local curl_opts=("--silent" "--output" "/dev/null" "--max-time" "15")
# Use Tor SOCKS if available
if ss -tln 2>/dev/null | grep -q ":${SOCKS_PORT} "; then
curl_opts+=("--socks5-hostname" "127.0.0.1:${SOCKS_PORT}")
fi
# Add realistic headers
curl_opts+=("-H" "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0")
curl_opts+=("-H" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
curl_opts+=("-H" "Accept-Language: en-US,en;q=0.5")
# Make the request
curl "${curl_opts[@]}" "$url" 2>/dev/null || true
# Calculate next delay (Poisson-distributed)
local delay
delay=$(poisson_delay "$BLEND_INTERVAL")
sleep "$delay"
done
}
blend_start() {
if ! [ -f /var/run/opsec-advanced.enabled ]; then
_yellow "Traffic blending requires ghost mode to be active"
return 1
fi
if is_running; then
_yellow "Traffic blend already running (PID $(cat "$PID_FILE"))"
return 0
fi
# Start the blend loop as a background process
blend_loop &
local pid=$!
echo "$pid" > "$PID_FILE"
disown "$pid"
sleep 1
if kill -0 "$pid" 2>/dev/null; then
_green "Traffic blend started (PID ${pid}, mean interval ${BLEND_INTERVAL}s)"
else
rm -f "$PID_FILE"
_red "Traffic blend failed to start"
return 1
fi
}
blend_stop() {
if ! is_running; then
_yellow "Traffic blend not running"
rm -f "$PID_FILE"
return 0
fi
local pid
pid=$(cat "$PID_FILE" 2>/dev/null)
kill "$pid" 2>/dev/null || true
# Wait for process to stop
local i=0
while kill -0 "$pid" 2>/dev/null && [ $i -lt 5 ]; do
sleep 1
i=$((i + 1))
done
kill -9 "$pid" 2>/dev/null || true
rm -f "$PID_FILE"
echo "[$(date -Is)] Traffic blend daemon stopped" >> "$LOG_FILE"
_green "Traffic blend stopped"
}
blend_status() {
if is_running; then
local pid
pid=$(cat "$PID_FILE" 2>/dev/null)
_green "Traffic blend ACTIVE (PID ${pid})"
_yellow "Mean interval: ${BLEND_INTERVAL}s"
else
_yellow "Traffic blend NOT running"
rm -f "$PID_FILE"
fi
}
case "${1:-}" in
start) blend_start ;;
stop) blend_stop ;;
restart)
blend_stop
sleep 1
blend_start
;;
status) blend_status ;;
*)
echo "Usage: opsec-traffic-blend.sh start|stop|status|restart"
exit 1
;;
esac
+127
View File
@@ -0,0 +1,127 @@
#!/bin/bash
# OPSEC Widget Launcher — positions and launches the Conky widget
# Usage: opsec-widget-launch.sh [position]
#
# Positions:
# tl = top-left tc = top-center tr = top-right
# bl = bottom-left bc = bottom-center br = bottom-right
#
# Default: tr (top-right)
# Uses the theme system — regenerates config from /etc/opsec/themes/
CONKY_DIR="$HOME/.config/conky"
CONF="$CONKY_DIR/conky-opsec-widget.conf"
POS="${1:-tr}"
# Map shorthand to conky alignment + gaps
case "$POS" in
tl) ALIGN="top_left"; GAP_X=15; GAP_Y=15 ;;
tc) ALIGN="top_middle"; GAP_X=0; GAP_Y=15 ;;
tr) ALIGN="top_right"; GAP_X=15; GAP_Y=60 ;;
bl) ALIGN="bottom_left"; GAP_X=15; GAP_Y=60 ;;
bc) ALIGN="bottom_middle"; GAP_X=0; GAP_Y=60 ;;
br) ALIGN="bottom_right"; GAP_X=15; GAP_Y=60 ;;
*)
echo "Usage: $(basename "$0") [tl|tc|tr|bl|bc|br]"
echo ""
echo " tl = top-left tc = top-center tr = top-right"
echo " bl = bottom-left bc = bottom-center br = bottom-right"
exit 1
;;
esac
# Kill existing widget and stale cache daemon
killall conky 2>/dev/null
pkill -f 'conky-opsec-cache\.sh' 2>/dev/null
rm -f /tmp/.opsec-cache/netinfo 2>/dev/null
sleep 0.3
# Load theme from opsec config
OPSEC_CONF="/etc/opsec/opsec.conf"
THEME="default"
[ -f "$OPSEC_CONF" ] && THEME=$(grep "^WIDGET_THEME=" "$OPSEC_CONF" 2>/dev/null | cut -d'"' -f2)
[ -z "$THEME" ] && THEME="default"
THEME_FILE="/etc/opsec/themes/${THEME}.theme"
if [ -f "$THEME_FILE" ]; then
. "$THEME_FILE"
else
echo "Theme '${THEME}' not found, using defaults"
THEME_LABEL="Default"
CONKY_BG="0d1117"
CONKY_COLOR0="df2020"; CONKY_COLOR1="33ff33"; CONKY_COLOR2="3a8fd6"
CONKY_COLOR3="0d1117"; CONKY_COLOR4="1f6feb"; CONKY_COLOR5="58a6ff"
CONKY_COLOR6="79c0ff"; CONKY_COLOR7="c9d1d9"; CONKY_COLOR8="1f6feb"
CONKY_COLOR9="484f58"
fi
# Generate config with theme colors and chosen position
mkdir -p "$CONKY_DIR"
cat > "$CONF" << EOF
-- OPSEC Status Widget — OPSEC Status Widget
-- Theme: ${THEME} (${THEME_LABEL:-Custom})
-- Position: ${ALIGN}
conky.config = {
alignment = '${ALIGN}',
gap_x = ${GAP_X},
gap_y = ${GAP_Y},
minimum_width = 400,
minimum_height = 200,
maximum_width = 420,
own_window = true,
own_window_type = 'normal',
own_window_transparent = false,
own_window_argb_visual = true,
own_window_argb_value = 210,
own_window_colour = '${CONKY_BG:-0d1117}',
own_window_hints = 'undecorated,below,sticky,skip_taskbar,skip_pager',
xinerama_head = 0,
double_buffer = true,
draw_shades = true,
default_shade_color = '000000',
draw_outline = false,
draw_borders = true,
border_inner_margin = 12,
border_outer_margin = 4,
border_width = 1,
border_colour = '${CONKY_COLOR4:-1f6feb}',
stippled_borders = 0,
use_xft = true,
font = 'JetBrains Mono:size=10',
override_utf8_locale = true,
default_color = 'b0b0b0',
color0 = '${CONKY_COLOR0:-df2020}',
color1 = '${CONKY_COLOR1:-33ff33}',
color2 = '${CONKY_COLOR2:-3a8fd6}',
color3 = '${CONKY_COLOR3:-0d1117}',
color4 = '${CONKY_COLOR4:-1f6feb}',
color5 = '${CONKY_COLOR5:-58a6ff}',
color6 = '${CONKY_COLOR6:-79c0ff}',
color7 = '${CONKY_COLOR7:-c9d1d9}',
color8 = '${CONKY_COLOR8:-1f6feb}',
color9 = '${CONKY_COLOR9:-484f58}',
update_interval = 3,
total_run_times = 0,
cpu_avg_samples = 2,
no_buffers = true,
text_buffer_size = 8192,
short_units = true,
};
conky.text = [[
\${execpi 5 ~/.config/conky/conky-opsec-status.sh}
]];
EOF
# Launch
conky -c "$CONF" &
disown
echo "OPSEC widget launched: $ALIGN (theme: $THEME)"
+120
View File
@@ -0,0 +1,120 @@
#!/bin/bash
# /usr/local/bin/opsec-wifi-check.sh — Wireless Evil Twin Detection
# Periodic scan for AP changes, open networks, BSSID anomalies
# Usage: opsec-wifi-check.sh [scan|watch]
set -euo pipefail
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
if [ -f "$OPSEC_LIB" ]; then
. "$OPSEC_LIB"
else
opsec_green() { echo -e "\033[38;5;49m[+] $*\033[0m"; }
opsec_red() { echo -e "\033[38;5;196m[-] $*\033[0m"; }
opsec_yellow() { echo -e "\033[38;5;214m[*] $*\033[0m"; }
opsec_info() { echo -e "\033[38;5;39m[~] $*\033[0m"; }
opsec_cyan() { echo -e "\033[38;5;51m[>] $*\033[0m"; }
opsec_hdr() { echo -e "\033[38;5;51m━━━ \033[38;5;201m$*\033[38;5;51m ━━━\033[0m"; }
fi
STATE_DIR="/var/run/opsec-wifi"
mkdir -p "$STATE_DIR"
notify() {
local msg="$1" urgency="${2:-normal}"
local real_user="${SUDO_USER:-$USER}"
su - "$real_user" -c "DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u "$real_user")/bus notify-send -u '$urgency' 'OPSEC WiFi' '$msg'" 2>/dev/null || true
}
do_scan() {
opsec_hdr "WIRELESS SECURITY SCAN"
echo ""
# Check if connected to WiFi
local connected_ssid connected_bssid connected_security
connected_ssid=$(nmcli -t -f active,ssid dev wifi 2>/dev/null | grep '^yes:' | cut -d: -f2)
connected_bssid=$(nmcli -t -f active,bssid dev wifi 2>/dev/null | grep '^yes:' | cut -d: -f2-)
connected_security=$(nmcli -t -f active,security dev wifi 2>/dev/null | grep '^yes:' | cut -d: -f2)
if [ -z "$connected_ssid" ]; then
opsec_info "Not connected to any WiFi network"
return
fi
opsec_cyan "Connected: ${connected_ssid} (${connected_bssid})"
echo ""
# ─── Check for open/unencrypted network ────────────────────────────────
if [ -z "$connected_security" ] || [ "$connected_security" = "--" ]; then
opsec_red "WARNING: Connected to OPEN (unencrypted) network!"
notify "Connected to OPEN WiFi: ${connected_ssid}" "critical"
elif echo "$connected_security" | grep -qi 'WEP'; then
opsec_red "WARNING: WEP encryption (trivially crackable)"
notify "WiFi using WEP: ${connected_ssid}" "critical"
else
opsec_green "Encryption: ${connected_security}"
fi
# ─── Scan for duplicate SSIDs (evil twin indicators) ───────────────────
opsec_info "Scanning for duplicate SSIDs..."
local scan_results
scan_results=$(nmcli -t -f ssid,bssid,signal,security dev wifi list --rescan yes 2>/dev/null || true)
if [ -n "$scan_results" ]; then
# Find APs with same SSID as connected but different BSSID
local duplicates
duplicates=$(echo "$scan_results" | grep "^${connected_ssid}:" | grep -v "${connected_bssid}" || true)
if [ -n "$duplicates" ]; then
local dup_count
dup_count=$(echo "$duplicates" | wc -l)
opsec_yellow "ALERT: ${dup_count} other AP(s) broadcasting '${connected_ssid}':"
echo "$duplicates" | while IFS=: read -r ssid bssid signal security; do
echo -e " \033[38;5;214m BSSID: ${bssid} Signal: ${signal} Security: ${security}\033[0m"
done
notify "Evil twin risk: ${dup_count} duplicate AP(s) for ${connected_ssid}" "critical"
else
opsec_green "No duplicate SSIDs detected"
fi
fi
# ─── BSSID change detection ────────────────────────────────────────────
local state_file="${STATE_DIR}/${connected_ssid}.bssid"
if [ -f "$state_file" ]; then
local prev_bssid
prev_bssid=$(cat "$state_file")
if [ "$prev_bssid" != "$connected_bssid" ]; then
opsec_red "BSSID CHANGED for '${connected_ssid}'!"
opsec_red " Previous: ${prev_bssid}"
opsec_red " Current: ${connected_bssid}"
notify "BSSID changed for ${connected_ssid}: ${prev_bssid}${connected_bssid}" "critical"
else
opsec_green "BSSID consistent with last scan"
fi
fi
echo "$connected_bssid" > "$state_file"
echo ""
}
do_watch() {
opsec_info "Starting continuous WiFi monitoring (Ctrl+C to stop)..."
while true; do
do_scan
echo ""
opsec_info "Next scan in 60 seconds..."
sleep 60
done
}
case "${1:-scan}" in
scan) do_scan ;;
watch) do_watch ;;
*)
echo "Usage: $(basename "$0") [scan|watch]"
echo ""
echo " scan — One-time wireless security scan (default)"
echo " watch — Continuous monitoring (60s interval)"
exit 1
;;
esac