diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 000000000..dd441e69b --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,23 @@ +name: Secret Scan + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + +jobs: + gitleaks: + name: Scan for secrets + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index e29597bd0..21aaf23bd 100644 --- a/.gitignore +++ b/.gitignore @@ -23,8 +23,21 @@ config.yaml.bak *.tmp *.swp +# Secrets and credentials (global) +.env +.env.* +!.env.example +*.key +*.pem +*.p12 +*.pfx +*.crt +credentials.* +secrets.* +secrets/ +*.keystore + # Token Spy runtime -token-spy/.env token-spy/data/ token-spy/*.db token-spy/*.sqlite diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 000000000..238baff8e --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,5 @@ +# Gitleaks ignore file +# Add fingerprints of known false positives here (one per line) +# Get fingerprints from gitleaks output when a false positive is detected +# +# Example: abc123def456... diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..cc65c1747 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +# Pre-commit hooks for secret scanning +# Install: pip install pre-commit && pre-commit install +# Run manually: pre-commit run --all-files + +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.21.2 + hooks: + - id: gitleaks + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: detect-private-key + - id: check-added-large-files + args: ['--maxkb=500'] diff --git a/README.md b/README.md index 13865245e..a41896276 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,24 @@ The most important four lines in the entire repo. Without them, OpenClaw sends p These are already set in `configs/openclaw.json`. Just copy it and go. +### Gateway Config (Security Note) + +The golden config includes gateway settings for LAN access: + +```json +"gateway": { + "bind": "lan", + "controlUi": { + "allowInsecureAuth": true, + "dangerouslyDisableDeviceAuth": true + } +} +``` + +**`dangerouslyDisableDeviceAuth: true`** — Disables the device authorization flow that normally requires confirming new devices via the OpenClaw UI. Set to `true` here because local/headless setups (SSH, systemd) can't complete the interactive auth prompt. **If you expose your gateway to the internet, set this to `false`.** + +**`allowInsecureAuth: true`** — Allows HTTP (non-HTTPS) auth on LAN. Safe for local networks, not for public-facing deployments. + --- ## How It Works diff --git a/install.ps1 b/install.ps1 index b24127e0e..d2a29f196 100644 --- a/install.ps1 +++ b/install.ps1 @@ -51,17 +51,48 @@ if ($Help) { exit 0 } -# ── Parse YAML (minimal parser) ─────────────────────────────── +# ── Parse YAML (section-aware parser) ───────────────────────── +# Usage: Parse-Yaml "section.key" "default" — reads key within a section +# Parse-Yaml "key" "default" — reads top-level key (legacy) function Parse-Yaml { - param([string]$Key, [string]$Default) + param([string]$Input, [string]$Default) if (-not (Test-Path $Config)) { return $Default } - $match = Select-String -Path $Config -Pattern "^\s*${Key}:" | Select-Object -First 1 - if ($match) { - $value = ($match.Line -split ":\s*", 2)[1].Trim().Trim('"').Trim("'") - $value = ($value -split "\s*#")[0].Trim() - if ($value -and $value -ne '""' -and $value -ne "''") { return $value } + + $section = "" + $key = $Input + if ($Input -match "^(.+)\.(.+)$") { + $section = $Matches[1] + $key = $Matches[2] + } + + if ($section) { + $lines = Get-Content $Config + $inSection = $false + foreach ($line in $lines) { + if ($line -match "^${section}:") { + $inSection = $true + continue + } + if ($inSection -and $line -match "^[a-zA-Z_]") { + break + } + if ($inSection -and $line -match "^\s+${key}:") { + $value = ($line -split ":\s*", 2)[1].Trim().Trim('"').Trim("'") + $value = ($value -split "\s*#")[0].Trim() + if ($value -and $value -ne '""' -and $value -ne "''") { return $value } + return $Default + } + } + return $Default + } else { + $match = Select-String -Path $Config -Pattern "^\s*${key}:" | Select-Object -First 1 + if ($match) { + $value = ($match.Line -split ":\s*", 2)[1].Trim().Trim('"').Trim("'") + $value = ($value -split "\s*#")[0].Trim() + if ($value -and $value -ne '""' -and $value -ne "''") { return $value } + } + return $Default } - return $Default } # ── Load config ──────────────────────────────────────────────── @@ -73,26 +104,29 @@ if (-not (Test-Path $Config)) { Info "Loading config from $Config" -$OpenClawDir = Parse-Yaml "openclaw_dir" "$env:USERPROFILE\.openclaw" +# Session cleanup settings +$OpenClawDir = Parse-Yaml "session_cleanup.openclaw_dir" "$env:USERPROFILE\.openclaw" $OpenClawDir = $OpenClawDir -replace "^~", $env:USERPROFILE -$SessionsPath = Parse-Yaml "sessions_path" "agents\main\sessions" -$MaxSessionSize = Parse-Yaml "max_session_size" "256000" -$IntervalMinutes = Parse-Yaml "interval_minutes" "60" -$ProxyPort = Parse-Yaml "port" "8003" -$VllmUrl = Parse-Yaml "vllm_url" "http://localhost:8000" +$SessionsPath = Parse-Yaml "session_cleanup.sessions_path" "agents\main\sessions" +$MaxSessionSize = Parse-Yaml "session_cleanup.max_session_size" "256000" +$IntervalMinutes = Parse-Yaml "session_cleanup.interval_minutes" "60" + +# Proxy settings +$ProxyPort = Parse-Yaml "tool_proxy.port" "8003" +$VllmUrl = Parse-Yaml "tool_proxy.vllm_url" "http://localhost:8000" $SessionsDir = Join-Path $OpenClawDir $SessionsPath # Token Spy settings -$TsEnabled = Parse-Yaml "enabled" "false" -$TsAgentName = Parse-Yaml "agent_name" "my-agent" -$TsPort = Parse-Yaml "port" "9110" -$TsHost = Parse-Yaml "host" "0.0.0.0" -$TsAnthropicUpstream = Parse-Yaml "anthropic_upstream" "https://api.anthropic.com" -$TsOpenaiUpstream = Parse-Yaml "openai_upstream" "" -$TsApiProvider = Parse-Yaml "api_provider" "anthropic" -$TsDbBackend = Parse-Yaml "db_backend" "sqlite" -$TsSessionCharLimit = Parse-Yaml "session_char_limit" "200000" +$TsEnabled = Parse-Yaml "token_spy.enabled" "false" +$TsAgentName = Parse-Yaml "token_spy.agent_name" "my-agent" +$TsPort = Parse-Yaml "token_spy.port" "9110" +$TsHost = Parse-Yaml "token_spy.host" "0.0.0.0" +$TsAnthropicUpstream = Parse-Yaml "token_spy.anthropic_upstream" "https://api.anthropic.com" +$TsOpenaiUpstream = Parse-Yaml "token_spy.openai_upstream" "" +$TsApiProvider = Parse-Yaml "token_spy.api_provider" "anthropic" +$TsDbBackend = Parse-Yaml "token_spy.db_backend" "sqlite" +$TsSessionCharLimit = Parse-Yaml "token_spy.session_char_limit" "200000" Write-Host "" Info "Configuration:" @@ -259,7 +293,7 @@ Write-Output "[`$(Get-Date)] Cleanup complete: removed `$removedInactive inactiv } $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$CleanupScript`"" - $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes) + $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $IntervalMinutes) -RepetitionDuration ([TimeSpan]::MaxValue) $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable $principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Limited diff --git a/install.sh b/install.sh index 2b500969d..8375b03cb 100644 --- a/install.sh +++ b/install.sh @@ -65,12 +65,31 @@ echo -e "${CYAN} LightHeart OpenClaw - Installer${NC}" echo -e "${CYAN}═══════════════════════════════════════════════════════════${NC}" echo "" -# ── Parse config (basic YAML parser — no dependencies needed) ── +# ── Parse config (section-aware YAML parser — no dependencies needed) ── +# Usage: parse_yaml "section.key" "default" — reads key within a section +# parse_yaml "key" "default" — reads top-level key (legacy) parse_yaml() { - local key="$1" + local input="$1" local default="$2" - local value - value=$(grep -E "^\s*${key}:" "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*:\s*//' | sed 's/\s*#.*//' | sed 's/^"//' | sed 's/"$//' | sed "s/^'//" | sed "s/'$//" | xargs) + local section="" key="" value="" + + if [[ "$input" == *.* ]]; then + section="${input%%.*}" + key="${input#*.}" + else + key="$input" + fi + + if [ -n "$section" ]; then + # Extract lines between "section:" and the next top-level key (non-indented) + value=$(sed -n "/^${section}:/,/^[a-zA-Z_]/{/^${section}:/d;/^[a-zA-Z_]/d;p;}" "$CONFIG_FILE" \ + | grep -E "^\s+${key}:" | head -1 \ + | sed 's/.*:\s*//' | sed 's/\s*#.*//' | sed 's/^"//' | sed 's/"$//' | sed "s/^'//" | sed "s/'$//" | xargs) + else + value=$(grep -E "^\s*${key}:" "$CONFIG_FILE" 2>/dev/null | head -1 \ + | sed 's/.*:\s*//' | sed 's/\s*#.*//' | sed 's/^"//' | sed 's/"$//' | sed "s/^'//" | sed "s/'$//" | xargs) + fi + if [ -z "$value" ] || [ "$value" = '""' ] || [ "$value" = "''" ]; then echo "$default" else @@ -88,34 +107,34 @@ fi info "Loading config from $CONFIG_FILE" # Session cleanup settings -CLEANUP_ENABLED=$(parse_yaml "enabled" "true") -OPENCLAW_DIR=$(parse_yaml "openclaw_dir" "~/.openclaw") +CLEANUP_ENABLED=$(parse_yaml "session_cleanup.enabled" "true") +OPENCLAW_DIR=$(parse_yaml "session_cleanup.openclaw_dir" "~/.openclaw") OPENCLAW_DIR="${OPENCLAW_DIR/#\~/$HOME}" -SESSIONS_PATH=$(parse_yaml "sessions_path" "agents/main/sessions") -MAX_SESSION_SIZE=$(parse_yaml "max_session_size" "256000") -INTERVAL_MINUTES=$(parse_yaml "interval_minutes" "60") -BOOT_DELAY=$(parse_yaml "boot_delay_minutes" "5") +SESSIONS_PATH=$(parse_yaml "session_cleanup.sessions_path" "agents/main/sessions") +MAX_SESSION_SIZE=$(parse_yaml "session_cleanup.max_session_size" "256000") +INTERVAL_MINUTES=$(parse_yaml "session_cleanup.interval_minutes" "60") +BOOT_DELAY=$(parse_yaml "session_cleanup.boot_delay_minutes" "5") # Proxy settings -PROXY_ENABLED=$(parse_yaml "enabled" "true") -PROXY_PORT=$(parse_yaml "port" "8003") -PROXY_HOST=$(parse_yaml "host" "0.0.0.0") -VLLM_URL=$(parse_yaml "vllm_url" "http://localhost:8000") -LOG_FILE=$(parse_yaml "log_file" "~/vllm-proxy.log") +PROXY_ENABLED=$(parse_yaml "tool_proxy.enabled" "true") +PROXY_PORT=$(parse_yaml "tool_proxy.port" "8003") +PROXY_HOST=$(parse_yaml "tool_proxy.host" "0.0.0.0") +VLLM_URL=$(parse_yaml "tool_proxy.vllm_url" "http://localhost:8000") +LOG_FILE=$(parse_yaml "tool_proxy.log_file" "~/vllm-proxy.log") LOG_FILE="${LOG_FILE/#\~/$HOME}" # Token Spy settings -TS_ENABLED=$(parse_yaml "enabled" "false") -TS_AGENT_NAME=$(parse_yaml "agent_name" "my-agent") -TS_PORT=$(parse_yaml "port" "9110") -TS_HOST=$(parse_yaml "host" "0.0.0.0") -TS_ANTHROPIC_UPSTREAM=$(parse_yaml "anthropic_upstream" "https://api.anthropic.com") -TS_OPENAI_UPSTREAM=$(parse_yaml "openai_upstream" "") -TS_API_PROVIDER=$(parse_yaml "api_provider" "anthropic") -TS_DB_BACKEND=$(parse_yaml "db_backend" "sqlite") -TS_SESSION_CHAR_LIMIT=$(parse_yaml "session_char_limit" "200000") -TS_AGENT_SESSION_DIRS=$(parse_yaml "agent_session_dirs" "") -TS_LOCAL_MODEL_AGENTS=$(parse_yaml "local_model_agents" "") +TS_ENABLED=$(parse_yaml "token_spy.enabled" "false") +TS_AGENT_NAME=$(parse_yaml "token_spy.agent_name" "my-agent") +TS_PORT=$(parse_yaml "token_spy.port" "9110") +TS_HOST=$(parse_yaml "token_spy.host" "0.0.0.0") +TS_ANTHROPIC_UPSTREAM=$(parse_yaml "token_spy.anthropic_upstream" "https://api.anthropic.com") +TS_OPENAI_UPSTREAM=$(parse_yaml "token_spy.openai_upstream" "") +TS_API_PROVIDER=$(parse_yaml "token_spy.api_provider" "anthropic") +TS_DB_BACKEND=$(parse_yaml "token_spy.db_backend" "sqlite") +TS_SESSION_CHAR_LIMIT=$(parse_yaml "token_spy.session_char_limit" "200000") +TS_AGENT_SESSION_DIRS=$(parse_yaml "token_spy.agent_session_dirs" "") +TS_LOCAL_MODEL_AGENTS=$(parse_yaml "token_spy.local_model_agents" "") # System user SYSTEM_USER=$(parse_yaml "system_user" "")