#23418: Pairing: add persistent sender and IP backoff controls
size: M
Cluster:
Messaging Platform Improvements
## Summary
Describe the problem and fix in 2–5 bullets:
- Problem: pairing request creation had no sender/IP attempt backoff, so repeated unknown-sender traffic could churn pairing writes and logs.
- Why it matters: pairing endpoints are an abuse surface; repeated attempts should be throttled with persistent state.
- What changed: added persistent backoff state in pairing store for `sender` and optional `sourceIp`, with normalized IP handling (including IPv4-mapped IPv6), retry windows, and stale-entry pruning.
- What did NOT change (scope boundary): approval semantics, allowFrom behavior, code format, or pairing channel policy decisions.
## Change Type (select all)
- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Docs
- [x] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [x] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [ ] Integrations
- [x] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #
- Related #
## User-visible / Behavior Changes
Rapid repeated pairing request attempts can now be throttled (`created=false`, `throttled=true`, `retryAfterMs`) for the same sender and for the same source IP when provided.
## Security Impact (required)
- New permissions/capabilities? (`No`)
- Secrets/tokens handling changed? (`No`)
- New/changed network calls? (`No`)
- Command/tool execution surface changed? (`No`)
- Data access scope changed? (`No`)
- If any `Yes`, explain risk + mitigation:
## Repro + Verification
### Environment
- OS: macOS
- Runtime/container: Node 22 + pnpm
- Model/provider: N/A
- Integration/channel (if any): Pairing store + inbound access control
- Relevant config (redacted): default pairing policy
### Steps
1. Create pairing request for sender A.
2. Clear/approve request and immediately request again for sender A.
3. Observe throttled response; then advance past retry and request again.
### Expected
- Immediate repeat attempts are throttled; later attempts after backoff are accepted.
### Actual
- Matches expected behavior.
## Evidence
Attach at least one:
- [x] Failing test/log before + passing after
- [ ] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers (if relevant)
## Human Verification (required)
What you personally verified (not just CI), and how:
- Verified scenarios: sender backoff, IP backoff across senders, TTL expiry flow, pairing CLI + inbound access-control compatibility.
- Edge cases checked: IPv4/IPv4-mapped IPv6 key normalization and idempotent behavior for existing pending requests.
- What you did **not** verify: full live channel matrix (Discord/Telegram/Slack runtime flows).
## Compatibility / Migration
- Backward compatible? (`Yes`)
- Config/env changes? (`No`)
- Migration needed? (`No`)
- If yes, exact upgrade steps:
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly: revert this PR.
- Files/config to restore: `src/pairing/pairing-store.ts` and related tests.
- Known bad symptoms reviewers should watch for: pairing requests unexpectedly throttled when retries happen too quickly.
## Risks and Mitigations
- Risk: over-aggressive backoff could suppress legitimate rapid reattempts.
- Mitigation: short initial window, bounded max delay, and reset/prune windows.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Added persistent backoff controls for pairing request creation to prevent abuse from repeated unknown-sender traffic. The implementation tracks sender and IP-based attempts separately, applies exponential backoff with bounded delays, normalizes IPv4-mapped IPv6 addresses to their IPv4 equivalents, and prunes stale entries after 24 hours.
**Key changes:**
- Sender backoff: tracks attempts per sender ID within account scope, starts at 15s and doubles up to 15min
- IP backoff: tracks attempts per source IP across different senders (when `sourceIp` is provided)
- IPv4-mapped IPv6 normalization: `::ffff:203.0.113.10` and `203.0.113.10` are treated as the same IP
- Reset window: backoff resets if 10+ minutes pass between attempts
- Retention: stale backoff entries are pruned after 24 hours
**Test coverage:**
- Tests verify sender backoff after approval + immediate reattempt
- Tests verify IP backoff across different senders with IPv4-mapped IPv6 normalization
- Tests verify TTL expiry for pending requests
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge with low risk
- The implementation is well-structured with proper exponential backoff logic, IP normalization, and stale entry pruning. Test coverage includes key scenarios (sender backoff, IP backoff, IPv4-mapped IPv6 normalization). The backoff parameters are reasonable (15s base, 15min max, 10min reset window, 24h retention). The feature is additive and backward compatible - existing pairing flows continue to work unchanged.
- No files require special attention
<sub>Last reviewed commit: 7125652</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#23692: fix(pairing): add brute-force protection for DM pairing codes
by widingmarcus-cyber · 2026-02-22
76.4%
#16773: fix(security): OC-100 add rate limiting to pairing code verification
by aether-ai-agent · 2026-02-15
73.7%
#23503: fix: preserve pairing state on device token mismatch + migrate lega...
by dorukardahan · 2026-02-22
73.2%
#21148: fix(gateway): add request-aware pairing recovery hints and docs
by cluster2600 · 2026-02-19
72.1%
#6846: fix: bridge node.pair.* tools to device pairing store
by cortexuvula · 2026-02-02
71.3%
#21697: fix(gateway): unblock local spawn pairing and gated private-LAN ws
by rjuanluis · 2026-02-20
71.1%
#17425: fix(gateway): auto-approve scope/role upgrades for already-paired d...
by sauerdaniel · 2026-02-15
71.1%
#23690: fix(gateway): subagent sessions fail with pairing required on loopb...
by yinghaosang · 2026-02-22
70.7%
#20456: fix(control-ui): show device pairing approval prompt when a new dev...
by mmaghsoodnia · 2026-02-18
70.5%
#11249: fix(whatsapp): prevent pairing-mode auto-replies to unknown DMs
by liuxiaopai-ai · 2026-02-07
70.3%