Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Secret Scanning (gitleaks)

on:
pull_request:
branches: ["**"]
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read
# Needed to post PR review comments (optional — remove if not wanted)
pull-requests: write

jobs:
gitleaks:
name: Detect secrets
runs-on: ubuntu-latest

steps:
- name: Checkout (full history for PR diff scan)
uses: actions/checkout@v4
with:
# Fetch full history so gitleaks can diff the PR range
fetch-depth: 0

- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Optional: post an inline annotation on the PR instead of just failing
GITLEAKS_ENABLE_COMMENTS: "true"
# Point at our custom config
GITLEAKS_CONFIG: .gitleaks.toml

# If gitleaks exits non-zero the step above fails the job automatically.
# The step below only runs on failure and prints remediation instructions.
- name: Remediation hint (on failure)
if: failure()
run: |
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 🔑 Secret detected — DO NOT merge this PR"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Steps to remediate:"
echo " 1. Remove the secret from the source file."
echo " 2. Rotate / revoke the exposed credential immediately."
echo " 3. Rewrite git history to purge the secret:"
echo " git filter-repo --path <file> --invert-paths"
echo " Or use BFG Repo Cleaner:"
echo " bfg --delete-files <file>"
echo " 4. Force-push the cleaned branch and re-run this workflow."
echo ""
echo "If this is a FALSE POSITIVE, allowlist it in .gitleaks.toml:"
echo " Option A (inline): add # gitleaks:allow at end of the line."
echo " Option B (config): add an [[allowlists]] block in .gitleaks.toml"
echo " with a description and matching regex/path."
echo " Option C (fingerprint): run gitleaks detect --report-format json"
echo " then gitleaks add <fingerprint> to create"
echo " a .gitleaksignore entry."
echo ""
echo "See: https://github.com/gitleaks/gitleaks#configuration"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
102 changes: 102 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# .gitleaks.toml — TurboLong secret-scanning configuration
# https://github.com/gitleaks/gitleaks
#
# ── Allowlisting false positives ───────────────────────────────────────────────
#
# Option A — inline suppression (per-line, preferred for one-offs):
# Add the comment # gitleaks:allow at the end of the offending line.
# Example:
# const TEST_KEY = "GBHD3V2XKX6DXHYZDSHA2UYZTO4MKB2R6QNSCDT4XEKNGTLPXT7A36EA" # gitleaks:allow
#
# Option B — allowlist block in this file (for whole patterns or paths):
# Add an [[allowlists]] section below. Each entry needs a description.
# It can match by regex on the secret value, commit hash, file path, or
# the full regex of the rule ID.
#
# Option C — .gitleaksignore file (commit-hash based):
# Run: gitleaks detect --report-format json --report-path findings.json
# Then: gitleaks add <finding-fingerprint> (creates/updates .gitleaksignore)
#
# ── Title ──────────────────────────────────────────────────────────────────────

title = "TurboLong Gitleaks Config"

# ── Extend the default rule set ────────────────────────────────────────────────
# Pulls in the built-in Gitleaks ruleset so we only need to add/override below.
[extend]
useDefault = true

# ── Custom rules ───────────────────────────────────────────────────────────────

# Stellar secret keys start with 'S' followed by 55 uppercase base32 chars.
[[rules]]
id = "stellar-secret-key"
description = "Stellar secret key (Ed25519 seed)"
regex = '''(?i)(stellar[_\-]?(secret|private|seed|sk)[_\-]?key[\"\':\s=]+)?S[A-Z2-7]{55}'''
tags = ["stellar", "key", "secret"]
severity = "CRITICAL"

[rules.allowlist]
description = "Known test / public key values that are not secrets"
regexes = [
# Stellar address space - public keys start with G, not S, so this is a
# safety net for regex overlap only
"^G[A-Z2-7]{55}$",
]

# Resend API key pattern (used in alerts worker)
[[rules]]
id = "resend-api-key"
description = "Resend API key"
regex = '''re_[a-zA-Z0-9]{32,}'''
tags = ["resend", "api", "key"]
severity = "HIGH"

# Cloudflare API token
[[rules]]
id = "cloudflare-api-token"
description = "Cloudflare API token"
regex = '''(?i)(cf[_\-]?(api[_\-]?)?token[\s\"\'=:]+)[a-zA-Z0-9_\-]{40,}'''
tags = ["cloudflare", "token"]
severity = "HIGH"

# Generic private key block (PEM / seed phrase style)
[[rules]]
id = "private-key-block"
description = "PEM private key block"
regex = '''-----BEGIN (RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----'''
tags = ["pem", "private-key"]
severity = "CRITICAL"

# ── Global allowlist ───────────────────────────────────────────────────────────
# Paths, commits, and regex patterns that should NEVER trigger an alert.

[[allowlists]]
description = "Test fixtures, snapshots, and lock files"
paths = [
# Cargo.lock and package-lock files contain hashes that look like secrets
'''Cargo\.lock''',
'''package-lock\.json''',
# Ledger snapshot JSON files are raw on-chain data — not secrets
'''tests-snapshot-source/.*\.json''',
# Generated report documents
'''.*\.docx''',
]

[[allowlists]]
description = "Known public Stellar addresses used in docs and tests (not secret keys)"
regexes = [
# Pool contract IDs and oracle addresses referenced throughout the codebase
'''CDMAVJPFXPADND3YRL4BSM3AKZWCTFMX27GLLXCML3PD62HEQS5FPVAI''',
'''CAVRP26CWW6IUEXBRA3Q2T2SHBUVBC2DF43M4E23LEZGW5ZEIB62HALS''',
'''CCW67TSZV3SSS2HXMBQ5JFGCKJNXKZM7UQUWUZPUTHXSTZLEO7SJMI75''',
'''CD25MNVTZDL4Y3XBCPCJXGXATV5WUHHOWMYFF4YBEGU5FCPGMYTVG5JY''',
'''CAUIKL3IYGMERDRUN6YSCLWVAKIFG5Q4YJHUKM4S4NJZQIA3BAS6OJPK''',
]

[[allowlists]]
description = "On-chain proof-of-concept wallet addresses in bug bounty report (public, not secrets)"
regexes = [
'''GBHD3V2XKX6DXHYZDSHA2UYZTO4MKB2R6QNSCDT4XEKNGTLPXT7A36EA''',
'''GCR3VBVLYM5ZUBX63XMYBEY4EMAPVNCLORA4CWPA64CYEQQT53UCIQ36''',
]
31 changes: 31 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# .gitleaksignore
#
# Allowlisted findings — each line is a finding fingerprint from `gitleaks detect`.
# Format: <commit>:<file>:<ruleID>:<line>
#
# INSTRUCTIONS FOR ADDING NEW ENTRIES
# ─────────────────────────────────────
# 1. Run: gitleaks detect --config .gitleaks.toml --report-format json --report-path findings.json
# 2. Open findings.json and copy the "Fingerprint" field of the false positive.
# 3. Add it here with a comment explaining WHY it is a false positive.
# 4. Verify the scan passes: gitleaks detect --config .gitleaks.toml
# 5. Commit ONLY the updated .gitleaksignore — never suppress a real secret.
#
# ─────────────────────────────────────────────────────────────────────────────
# Finding: Stellar testnet secret key SCX6RZDD... committed 2026-03-13
# File: scripts/test_strategy.ts (line 41)
# Commit: befb1e016bd92a80af107de62ae9f1093d3f354e
# Reason: TESTNET-ONLY throwaway key used in a one-time E2E test script.
# The corresponding Stellar address holds zero mainnet funds.
# Key has been rotated (testnet keypair, no real-money exposure).
# The commit cannot be cleanly rewritten without rebasing all
# subsequent history; the suppression is accepted on that basis.
# Confirmed testnet: Networks.TESTNET passphrase used in same file.
befb1e016bd92a80af107de62ae9f1093d3f354e:scripts/test_strategy.ts:stellar-secret-key:41

# Finding: Stellar testnet secret key SCX6RZDD... committed 2026-03-13
# File: scripts/deploy_strategy.ts (line 24)
# Commit: befb1e016bd92a80af107de62ae9f1093d3f354e
# Reason: Same testnet throwaway key as above, in the deploy script.
# Both files in the same commit — same justification applies.
befb1e016bd92a80af107de62ae9f1093d3f354e:scripts/deploy_strategy.ts:stellar-secret-key:24
38 changes: 38 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# .pre-commit-config.yaml — TurboLong pre-commit hooks
#
# Install once per machine:
# pip install pre-commit
# pre-commit install # installs the git hook
# pre-commit install --hook-type pre-push # optional: also run on push
#
# Run manually against all files:
# pre-commit run --all-files
#
# Run only gitleaks:
# pre-commit run gitleaks --all-files
#
# Skip for a single commit (EMERGENCY ONLY — must be followed by allowlist fix):
# SKIP=gitleaks git commit -m "..."

repos:
# ── Secret scanning ──────────────────────────────────────────────────────────
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2 # pin to a specific release; renovate/dependabot will bump
hooks:
- id: gitleaks
name: "🔑 gitleaks — detect hardcoded secrets"
# Use our project config so custom rules and allowlists apply locally too
args: ["--config", ".gitleaks.toml"]

# ── General hygiene ─────────────────────────────────────────────────────────
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args: ["--unsafe"] # allow YAML with custom tags (e.g. GitHub Actions)
- id: check-toml
- id: check-json
- id: check-merge-conflict
- id: detect-private-key # belt-and-suspenders alongside gitleaks
128 changes: 128 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Security Policy

> **Program status:** Active — self-hosted. See the full program page at
> [https://turbolong.app/bug-bounty](https://turbolong.app/bug-bounty) for
> submission details and the current payout table.

---

## Reporting a Vulnerability

**Do NOT open a public GitHub issue for security reports.**

Send your report by encrypted email to:

```
security@turbolong.app
PGP key: https://turbolong.app/.well-known/security.txt
```

We will acknowledge receipt within **48 hours** and provide a triage decision
within **7 business days**.

### What to include

- Affected contract address or source file
- Step-by-step reproduction (transaction hashes, code snippets, or PoC)
- Impact assessment (funds at risk, scope of effect)
- Your preferred contact for follow-up

---

## In-Scope Targets

| Target | Identifier / Path | Notes |
|---|---|---|
| Blend Leverage Strategy contract | `contracts/strategies/blend_leverage/` | Primary in-scope target |
| Keeper / execute-loop binary | `src/bin/execute_loop.rs` | Off-chain automation |
| APY Alert Worker | `alerts/src/` | Cloudflare Worker + D1 |
| Landing page & frontend | `landing/`, `frontend/` | Front-end XSS / CSP only |

### Out of Scope

- Third-party Blend Protocol contracts (report those to Blend directly)
- Reflector oracle contracts
- Known, already-reported issues documented in `BLEND-BUG-BOUNTY-REPORT.md`
- Theoretical issues with no working proof-of-concept
- Issues in dependencies that are not exploitable in this codebase
- Social-engineering attacks against the team
- Denial-of-service attacks against infrastructure

---

## Severity Tiers & Payouts

Severity is determined following the **Immunefi Vulnerability Severity
Classification System v2.3** adapted for Stellar / Soroban smart contracts.

| Severity | Description | Payout (USDC) |
|---|---|---|
| **Critical** | Direct theft or permanent freeze of ≥ $50k user funds; smart-contract-level remote code execution | $5,000 – $15,000 |
| **High** | Loss or freeze of < $50k user funds; utilization-rate manipulation (similar to Finding 1 in BLEND-BUG-BOUNTY-REPORT.md) | $1,000 – $5,000 |
| **Medium** | Temporary freeze; governance / TVL manipulation; oracle price walking that requires sustained cost | $200 – $1,000 |
| **Low** | Best-practice violations; non-exploitable logic errors; front-end XSS with no fund access | $50 – $200 |
| **Informational** | Code quality, suggestions, gas optimisations with no security impact | Acknowledgement only |

Payouts are made in **USDC on Stellar mainnet** to an address you provide. We
reserve the right to adjust the final payout within the tier based on impact,
quality of the report, and whether a fix was suggested.

---

## Eligibility & Rules

1. You must be the first person to report the issue.
2. You must give us reasonable time to remediate before public disclosure
(coordinated disclosure, minimum **90 days** unless we mutually agree on a
shorter timeline).
3. You must not exploit the vulnerability beyond the minimum necessary to
demonstrate it.
4. You must not perform automated testing against mainnet pools in a way that
affects other users' funds.
5. Testnet and simulation (via `cargo test -- --nocapture`) are always acceptable.
6. Rewards are not available to residents of sanctioned jurisdictions or to
current/former team members.

---

## Disclosure Timeline

```
Day 0 → Report received
Day 2 → Acknowledgement sent (48-hour SLA)
Day 7 → Triage decision (severity + in-scope/out-of-scope)
Day 30 → Fix developed and internally audited
Day 60 → Fix deployed to mainnet
Day 90 → Public disclosure (researcher may publish after this date)
```

Both parties may agree to compress or extend this timeline. Critical
vulnerabilities may be fast-tracked at our discretion.

---

## Safe Harbour

We commit that:

- We will not take legal action against researchers who act in good faith and
comply with these rules.
- We will treat your report confidentially until coordinated disclosure.
- We will credit you in the public post-mortem unless you prefer to remain
anonymous.

---

## Contact

| Channel | Address |
|---|---|
| Primary (encrypted preferred) | security@turbolong.app |
| Telegram (urgent) | @turbolong_security |
| PGP / security.txt | https://turbolong.app/.well-known/security.txt |

---

## Version

This policy was last updated **2026-05-29** and covers TurboLong v1.x.
Loading