Add CAPTCHA bypass detection, IP-reputation decay, and fix zero-rule secret scanning - #462
Merged
maugauwi-hash merged 1 commit intoAug 31, 2026
Conversation
…-rule secret scanning Restores backend/src/captcha.rs and backend/src/ip_reputation.rs to the module tree (they existed but were never declared in lib.rs/wired into main.rs, so none of this code ever ran or compiled as part of the crate), then builds the three requested features on top: ethos-protocol#392 - CAPTCHA bypass detection: track consecutive verification failures per IP, apply progressive backoff (doubling, capped at 15 min) once a threshold is hit, and flag the IP in the IP-reputation subsystem after repeated failures via a new ip_reputation::apply_local_penalty integration point. POST /captcha/verify now rejects backed-off IPs with 429 (new AppError::TooManyRequests variant). ethos-protocol#393 - IP reputation score decay: scores pull back toward a configurable neutral baseline over time via independently configurable up/down hourly rates (IpReputationConfig::decay_rate_up_per_hour / decay_rate_down_per_hour), so a temporarily bad-behaving IP doesn't stay flagged forever. Documented in docs/security.md. ethos-protocol#397 - Secret scanning coverage audit: .gitleaks.toml had no [[rules]] and no [extend] block, which per gitleaks' semantics means it was running with *zero* detection rules in both CI and the pre-commit hook this whole time (verified locally with the actual gitleaks binary - restoring `useDefault = true` was the fix). Added [extend] useDefault = true, four custom rules for secret formats specific to this repo's own dependencies (Stellar/Soroban secret seeds, DB connection strings with credentials, AbuseIPDB keys, FCM legacy server keys), an allowlist entry for the intentionally simple local-dev docker-compose.yml credentials, positive/ negative fixtures per pattern under scripts/secret-scan-fixtures/, and a new "Secret Scan Fixture Coverage" CI job that regression-tests detection against those fixtures. Verified the full repo (working tree + all 496 commits of git history) is clean under the restored ruleset. Also fixes captcha.rs's test-only dummy_state() helper, which predated several AppState fields added since and would not have compiled once the module was wired back in. Closes ethos-protocol#392 Closes ethos-protocol#393 Closes ethos-protocol#397
|
@Qavahpaul Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes three issues. While implementing #392, discovered that
backend/src/captcha.rsandbackend/src/ip_reputation.rsexisted on disk but were never declared inlib.rsor wired intomain.rs::build_router— so none of that code compiled or ran as part of the crate. Restoring that wiring (plus the model types it depends on, which were also missing) was a prerequisite for #392/#393 to do anything real, so it's included here.#392 — Add CAPTCHA Bypass Detection for Automated Clients
captcha::record_captcha_failure/record_captcha_successtrack consecutive verification failures per IP.POST /captcha/verifyrejects a backed-off IP with429 Too Many Requests(newAppError::TooManyRequestsvariant) before attempting verification.ip_reputation::apply_local_penaltyintegration point.#393 — Implement IP Reputation Score Decay
ip_reputation::apply_score_decaypulls a score back toward a configurable neutral baseline over time, with independently configurable hourly rates for moving up vs. down (IpReputationConfig::decay_rate_up_per_hour/decay_rate_down_per_hour), so a temporarily bad-behaving IP (e.g. shared NAT gateway) doesn't stay flagged indefinitely.GET /admin/ip-reputationso a locally-penalized score actually decays between lookups instead of resetting to 0 every time there's no authoritative AbuseIPDB check.docs/security.md.#397 — Secret Scanning Pattern Coverage Audit
.gitleaks.tomlhad only an[allowlist]— no[[rules]]and no[extend]block. Per gitleaks' own config semantics, that means it was running with zero detection rules, in CI and in the local pre-commit hook, this whole time. Verified this locally with the actual gitleaks binary. Fixed with[extend]\n useDefault = true, restoring the ~180-rule built-in ruleset.docker-compose.ymlcredentials (verified these were the only real findings across the whole working tree and all 496 commits of git history once rules were turned on).scripts/secret-scan-fixtures/{positive,negative}/with one fixture per pattern (plus the built-ingeneric-api-keyandprivate-keyrules), and a new Secret Scan Fixture Coverage CI job in.github/workflows/security.ymlthat runs gitleaks against every fixture and fails if a positive fixture goes undetected or a negative one is false-flagged — turning this into an ongoing regression check instead of a one-time audit.docs/secret-scanning.mdupdated with the coverage table and audit findings.All gitleaks changes were validated locally against the real
gitleaksbinary (not just reasoned about): full working-tree scan, full git-history scan (496 commits), the pre-commit hook against this PR's own staged diff, and every fixture file individually — all pass as expected.Test plan
cargo test --package ethos-protocol-backend(new tests inbackend/src/captcha.rsandbackend/src/ip_reputation.rs; not run here — no Rust toolchain available in this environment)gitleaks detect --source . --config .gitleaks.toml --no-git— 0 findings on working treegitleaks detect --source . --config .gitleaks.toml(full history, 496 commits) — 0 findingsscripts/pre-commit-secret-scan.shagainst this PR's staged diff — passesscripts/secret-scan-fixtures/verified individually againstscripts/secret-scan-fixtures/gitleaks-fixtures.tomlCloses #392
Closes #393
Closes #397