Skip to content

Add CAPTCHA bypass detection, IP-reputation decay, and fix zero-rule secret scanning - #462

Merged
maugauwi-hash merged 1 commit into
ethos-protocol:mainfrom
Qavahpaul:fix/captcha-backoff-ip-decay-secret-scan-audit
Aug 31, 2026
Merged

Add CAPTCHA bypass detection, IP-reputation decay, and fix zero-rule secret scanning#462
maugauwi-hash merged 1 commit into
ethos-protocol:mainfrom
Qavahpaul:fix/captcha-backoff-ip-decay-secret-scan-audit

Conversation

@Qavahpaul

Copy link
Copy Markdown

Summary

Fixes three issues. While implementing #392, discovered that backend/src/captcha.rs and backend/src/ip_reputation.rs existed on disk but were never declared in lib.rs or wired into main.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_success track consecutive verification failures per IP.
  • After 3 consecutive failures, progressive backoff kicks in (doubling, capped at 15 min); POST /captcha/verify rejects a backed-off IP with 429 Too Many Requests (new AppError::TooManyRequests variant) before attempting verification.
  • After 5 consecutive failures, the IP is flagged in the IP-reputation subsystem via a new ip_reputation::apply_local_penalty integration point.
  • Tests cover failure-count tracking, backoff triggering/growth, the reputation-flag integration, and the 429 response.

#393 — Implement IP Reputation Score Decay

  • ip_reputation::apply_score_decay pulls 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.
  • Wired into GET /admin/ip-reputation so a locally-penalized score actually decays between lookups instead of resetting to 0 every time there's no authoritative AbuseIPDB check.
  • Tests cover decay in both directions, baseline clamping, disabled decay, and the local-penalty integration — all using simulated elapsed time (no sleeping).
  • Decay model documented in docs/security.md.

#397 — Secret Scanning Pattern Coverage Audit

  • Main finding: .gitleaks.toml had 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.
  • Added 4 custom rules for secret formats specific to this repo's own dependencies/integrations (not covered by the built-in ruleset): Stellar/Soroban secret seeds, DB connection strings with embedded credentials, AbuseIPDB API keys, FCM legacy server keys.
  • Added an allowlist entry for the intentionally simple local-dev docker-compose.yml credentials (verified these were the only real findings across the whole working tree and all 496 commits of git history once rules were turned on).
  • Added scripts/secret-scan-fixtures/{positive,negative}/ with one fixture per pattern (plus the built-in generic-api-key and private-key rules), and a new Secret Scan Fixture Coverage CI job in .github/workflows/security.yml that 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.md updated with the coverage table and audit findings.

All gitleaks changes were validated locally against the real gitleaks binary (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 in backend/src/captcha.rs and backend/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 tree
  • gitleaks detect --source . --config .gitleaks.toml (full history, 496 commits) — 0 findings
  • scripts/pre-commit-secret-scan.sh against this PR's staged diff — passes
  • Every fixture under scripts/secret-scan-fixtures/ verified individually against scripts/secret-scan-fixtures/gitleaks-fixtures.toml

Closes #392
Closes #393
Closes #397

…-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
@drips-wave

drips-wave Bot commented Aug 30, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@maugauwi-hash
maugauwi-hash merged commit 787304e into ethos-protocol:main Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Secret Scanning Pre-Commit Hook Coverage Audit Implement IP Reputation Score Decay Add CAPTCHA Bypass Detection for Automated Clients

2 participants