From 0b74c39a88b943941f079730fb193492c4e4f4d9 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 5 Oct 2026 21:20:40 +0700 Subject: [PATCH] security: lefthook + gitleaks secret gate, remove hardcoded credentials - fpga/esp32_wifi_bridge: Wi-Fi AP credentials move to a gitignored secrets.h (secrets.h.example added); connection guide uses placeholders - deploy/trinity-nexus/canvas and src/trinity_node main_gui.zig: wallet password read from the environment - tools/telegram-bridge/2fa-neuro-sage.sh: 2FA password from the environment - .gitleaks.toml: shared rules (canonical copy gHashTag/t27 PR #6433) plus narrow allowlists for synthetic Kaggle data, public contract addresses, archived spec examples, QA fixtures and three verified lines - lefthook.yml: secret-gate on pre-commit and pre-push - .github/workflows/secret-scan.yml: CI twin over the PR range - .gitignore: secrets.h, .creds; README: secrets section Same fixes as gHashTag/trinity-fpga PR #900. Closes #1430 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/secret-scan.yml | 37 ++++++++++++++++++++ .gitignore | 5 +++ README.md | 14 ++++++++ deploy/trinity-nexus/canvas/src/main_gui.zig | 3 +- fpga/COMPLETE_CONNECTION_GUIDE.md | 4 +-- fpga/esp32_wifi_bridge/esp32_wifi_bridge.ino | 5 +-- fpga/esp32_wifi_bridge/secrets.h.example | 4 +++ lefthook.yml | 9 +++++ src/trinity_node/main_gui.zig | 4 +-- tools/telegram-bridge/2fa-neuro-sage.sh | 2 +- 10 files changed, 79 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/secret-scan.yml create mode 100644 fpga/esp32_wifi_bridge/secrets.h.example diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000000..ba19344fd8 --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,37 @@ +name: secret-scan + +on: + pull_request: {} + push: + branches: [main] + +permissions: + contents: read + +jobs: + # Server-side twin of the lefthook secret gate (lefthook.yml): the same + # gitleaks rules (.gitleaks.toml) over every commit the PR or push adds, + # so a commit made with --no-verify still cannot land a credential. + gitleaks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install gitleaks + run: | + curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.30.0/gitleaks_8.30.0_linux_x64.tar.gz | tar -xz gitleaks + sudo mv gitleaks /usr/local/bin/ + + - name: Scan the commits this event adds + env: + BASE: ${{ github.event.pull_request.base.sha || github.event.before }} + run: | + set -euo pipefail + if [ -n "${BASE:-}" ] && git cat-file -e "$BASE^{commit}" 2>/dev/null; then + RANGE="$BASE..HEAD" + else + RANGE="HEAD~1..HEAD" + fi + gitleaks git --no-banner --redact --config .gitleaks.toml --log-opts="$RANGE" . diff --git a/.gitignore b/.gitignore index d3db0799e0..f5ee03b5ad 100644 --- a/.gitignore +++ b/.gitignore @@ -469,3 +469,8 @@ fpga-flash # Local credential files: never commit (values come from Railway/Infisical) .trinity/ralph/queue/.creds ralph/queue/.creds + +# Local credentials - never commit (2026-10-05 leak cleanup) +secrets.h +.creds +**/.creds diff --git a/README.md b/README.md index a80ca77160..cc5a4b6668 100644 --- a/README.md +++ b/README.md @@ -1283,6 +1283,20 @@ Attribution for listed docs and packages is checked by [`src/tri/author_attribut --- +## Secrets never enter the repository + +No password, API key, token or credentials file is committed, not even in docs or examples. Read secrets from the environment or from a gitignored file (firmware Wi-Fi credentials: copy `secrets.h.example` to the gitignored `secrets.h`). + +The gate has three layers, all driven by [`.gitleaks.toml`](.gitleaks.toml): + +1. **pre-commit** (lefthook) scans staged changes with gitleaks. +2. **pre-push** (lefthook) scans every commit that is not yet on a remote. +3. **CI** ([`secret-scan`](.github/workflows/secret-scan.yml)) scans the PR range, so `--no-verify` does not get a secret past it. + +Set up once per clone: `brew install gitleaks lefthook && git config core.hooksPath .githooks` (the tracked hooks in `.githooks` call lefthook; on a clone without `core.hooksPath`, `lefthook install` works too). + +A secret that was ever pushed is compromised. Removing it from the tree does not unpublish it, so rotate it at the provider. + ## License MIT -- see [LICENSE](LICENSE) diff --git a/deploy/trinity-nexus/canvas/src/main_gui.zig b/deploy/trinity-nexus/canvas/src/main_gui.zig index 70009d00c7..d080d73226 100644 --- a/deploy/trinity-nexus/canvas/src/main_gui.zig +++ b/deploy/trinity-nexus/canvas/src/main_gui.zig @@ -40,7 +40,8 @@ pub fn main() !void { const wallet_path = try config_mod.Config.getWalletPath(allocator); defer allocator.free(wallet_path); - const password = "trinity123"; // DEFERRED (v12): prompt for password + // Wallet password comes from the environment, never from source. + const password = std.posix.getenv("TRINITY_WALLET_PASSWORD") orelse return error.MissingWalletPassword; var wallet = wallet_mod.Wallet.loadOrCreate(wallet_path, password) catch |err| { std.debug.print("Failed to load wallet: {}\n", .{err}); diff --git a/fpga/COMPLETE_CONNECTION_GUIDE.md b/fpga/COMPLETE_CONNECTION_GUIDE.md index 0c2bd1bf61..7404e71149 100644 --- a/fpga/COMPLETE_CONNECTION_GUIDE.md +++ b/fpga/COMPLETE_CONNECTION_GUIDE.md @@ -282,8 +282,8 @@ OK: LED blinking enabled #include // WiFi credentials -const char* ssid = "Trinity-FPGA"; -const char* password = "trinity2026"; +const char* ssid = "your-ssid"; // keep real values in a gitignored secrets.h +const char* password = "your-wifi-password"; WebServer server(80); WebSocketsServer webSocket = WebSocketsServer(81); diff --git a/fpga/esp32_wifi_bridge/esp32_wifi_bridge.ino b/fpga/esp32_wifi_bridge/esp32_wifi_bridge.ino index 9fd8d1393f..a3bcedb1fc 100644 --- a/fpga/esp32_wifi_bridge/esp32_wifi_bridge.ino +++ b/fpga/esp32_wifi_bridge/esp32_wifi_bridge.ino @@ -32,8 +32,9 @@ // ============================================================================ // WiFi Access Point -const char* AP_SSID = "Trinity-FPGA"; -const char* AP_PASSWORD = "trinity2026"; +// AP_SSID and AP_PASSWORD live in secrets.h, which is gitignored. +// Copy secrets.h.example to secrets.h and fill it in before flashing. +#include "secrets.h" // mDNS hostname const char* HOSTNAME = "trinity"; diff --git a/fpga/esp32_wifi_bridge/secrets.h.example b/fpga/esp32_wifi_bridge/secrets.h.example new file mode 100644 index 0000000000..65173403da --- /dev/null +++ b/fpga/esp32_wifi_bridge/secrets.h.example @@ -0,0 +1,4 @@ +// Copy to secrets.h (gitignored) and fill in. Never commit secrets.h. +#pragma once +static const char* AP_SSID = "your-ap-ssid"; +static const char* AP_PASSWORD = "your-ap-password"; diff --git a/lefthook.yml b/lefthook.yml index 2be5e2a228..2828e18092 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,3 +1,8 @@ +# Git hooks for trinity. Install once per clone: `lefthook install` +# Requires gitleaks (`brew install gitleaks`). A missing gitleaks fails the +# hook on purpose: the secret gate must not silently turn itself off. +# Rules live in .gitleaks.toml; the CI twin is .github/workflows/secret-scan.yml. + commit-msg: commands: english-only: @@ -10,6 +15,8 @@ commit-msg: pre-commit: commands: + secret-gate: + run: gitleaks git --staged --no-banner --redact --config .gitleaks.toml . queen-bilingual-contract: root: "apps/website/" run: npm run check:queen-languages @@ -19,6 +26,8 @@ pre-commit: pre-push: commands: + secret-gate: + run: gitleaks git --no-banner --redact --config .gitleaks.toml --log-opts="HEAD --not --remotes" . branch-name: run: | branch=$(git rev-parse --abbrev-ref HEAD) diff --git a/src/trinity_node/main_gui.zig b/src/trinity_node/main_gui.zig index a9b962b73b..d080d73226 100644 --- a/src/trinity_node/main_gui.zig +++ b/src/trinity_node/main_gui.zig @@ -40,8 +40,8 @@ pub fn main() !void { const wallet_path = try config_mod.Config.getWalletPath(allocator); defer allocator.free(wallet_path); - // DEFERRED (v12): Prompt for password via stdin (readLine, hide input) - const password = "trinity123"; + // Wallet password comes from the environment, never from source. + const password = std.posix.getenv("TRINITY_WALLET_PASSWORD") orelse return error.MissingWalletPassword; var wallet = wallet_mod.Wallet.loadOrCreate(wallet_path, password) catch |err| { std.debug.print("Failed to load wallet: {}\n", .{err}); diff --git a/tools/telegram-bridge/2fa-neuro-sage.sh b/tools/telegram-bridge/2fa-neuro-sage.sh index 717e33ac26..727b5d36a5 100644 --- a/tools/telegram-bridge/2fa-neuro-sage.sh +++ b/tools/telegram-bridge/2fa-neuro-sage.sh @@ -6,4 +6,4 @@ echo "Verifying 2FA..." curl --max-time 30 -s -X POST "$BRIDGE/api/v1/auth/2fa" \ -H "Content-Type: application/json" \ -H "X-Session-ID: $SESSION" \ - -d '{"password": "Vishnu8087"}' + -d "{\"password\": \"${TELEGRAM_2FA_PASSWORD:?set TELEGRAM_2FA_PASSWORD in the environment}\"}"