Skip to content

fix(discord): /fleety blocked for everyone — was calling the auth-only rate limiter - #260

Merged
mdenner1234 merged 1 commit into
mainfrom
fix/discord-fleety-ratelimit
Aug 20, 2026
Merged

fix(discord): /fleety blocked for everyone — was calling the auth-only rate limiter#260
mdenner1234 merged 1 commit into
mainfrom
fix/discord-fleety-ratelimit

Conversation

@mdenner1234

Copy link
Copy Markdown
Collaborator

Root cause (definitive)

Discord /fleety has posted "🚦 You've asked Fleety a lot in the last hour" and nothing else, for everyone, for days. It was never a real rate limit.

The adapter's underRateLimit called public.check_rate_limit — which is hardened for auth flows only:

  • it rejects any identifier that isn't a 64-char SHA-256 hash (^[a-f0-9]{64}$, migration 20260606… line 16), and
  • it rejects any action outside its login/signup/reset whitelist (line 20).

Called with discord:<userid> + action "fleety", it returns {allowed:false} unconditionally, on every call/fleety always hit the throttle branch and never reached the answer.

Fix

Point underRateLimit at the generic public.check_edge_rate_limit (the same limiter fleety-extract and other edge endpoints use): it hashes the identifier itself, accepts any action, and is a clean sliding window (no stuck-block state). Still fails open on error. Cap raised 10 → 20 / hour (10 was flagged as too tight for real use).

Proof

  • vitest run fleety-discord-ratelimit.smoke.test.ts3 passed (uses check_edge_rate_limit; does NOT use check_rate_limit; still fails open)
  • BDD @regression scenario added to fleety-2.1-discord.feature
  • deno check discord-interactions/index.ts → clean

Deploy + the one remaining step

Merging deploys discord-interactions. Two notes:

  1. check_edge_rate_limit must exist in prod (migration 20260809170000). If it doesn't, the RPC errors and the limiter fails open/fleety still works, just unthrottled — so this fix unblocks it either way.
  2. After this, /fleety reaches the 2.0 brain via FLEETY_INTERNAL_SECRET. If that secret isn't set, members will now see a generic "⚠️ error" instead of the throttle message — that's the remaining 2.1 config step (set FLEETY_INTERNAL_SECRET), not this bug.

🤖 Generated with Claude Code

…te limiter

Root cause: the /fleety adapter's underRateLimit called public.check_rate_limit, which is
hardened for AUTH flows only — it rejects any identifier that isn't a 64-char SHA-256 hash
(migration 20260606…, line 16) AND any action outside its login/signup/reset whitelist
(line 20). Called with `discord:<id>` + action "fleety", it returned {allowed:false} on EVERY
invocation, so /fleety only ever posted "🚦 You've asked Fleety a lot in the last hour" and
never reached the answer path — for days, for all users.

Fix: point underRateLimit at the GENERIC public.check_edge_rate_limit (the same limiter
fleety-extract + other edge endpoints use). It hashes the identifier itself, accepts any
action, and is a clean sliding-window counter (no stuck-block state). Kept fail-open on error.
Bumped the cap 10 -> 20 per hour (owner had flagged 10 as too tight for real use).

Guards:
- src/test/smoke/fleety-discord-ratelimit.smoke.test.ts (3 cases): asserts the adapter uses
  check_edge_rate_limit, NOT check_rate_limit, and still fails open.
- BDD @regression scenario in fleety-2.1-discord.feature.

Follow-on (config, owner): once deployed, /fleety passes the limiter and calls the 2.0 brain
via FLEETY_INTERNAL_SECRET. If that secret is unset, members will now see the generic
"⚠️ error" instead — that's the remaining 2.1 config step, not this bug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🛡️ Penetration test results

Pen-test suite: sast

Generated 2026-04-29T03:40:44.503Z

Totals: ✅ 49 pass · ❌ 0 fail · ⊘ 0 skip · ⚠️ 0 error

Status Test Control Evidence
SAST-NO-DANGEROUSLY-SET-INNER-HTML-WITHOUT-SANITIZE XSS prevention via DOMPurify 6 usage(s), all sanitized.
SAST-NO-EVAL Code injection
SAST-NO-DOCUMENT-WRITE DOM clobbering
SAST-NO-DANGEROUS-DOM-SINKS DOM-based XSS sink prevention 0 DOM sink usage(s) audited.
SAST-DOMPURIFY-DOM-CLOBBERING-GUARDS DOM clobbering prevention
SAST-TARGET-BLANK-NOOPENER Reverse-tabnabbing
SAST-NO-HARDCODED-SECRETS Secret leakage
SAST-NO-CONSOLE-LOG-OF-SECRETS Secret logging
SAST-NO-STACKTRACE-TO-CLIENT Safe error handling
SAST-NO-INSECURE-RANDOM-FOR-CRYPTO Cryptographic randomness
SAST-NO-WEAK-HASH Cryptographic strength
SAST-NO-MANUAL-PASSWORD-STORAGE Password storage delegation
SAST-CLIENT-COOKIE-SAMESITE-SECURE Cookie theft and CSRF mitigation 1 client-set cookie(s) audited.
SAST-NO-CLIENT-STORAGE-AUTHZ Authorization must be server-side
SAST-JWT-VERIFY-NOT-DECODE-ONLY JWT verification
SAST-IDOR-OBJECT-ACCESS-HELPER IDOR and multi-tenant object access
SAST-NO-SECURITY-QUESTION-AUTH Authentication recovery strength
SAST-EMAIL-VALIDATION-CENTRALIZED Email validation and verification
SAST-FORGOT-PASSWORD-NON-ENUMERATING Forgot password enumeration resistance
SAST-PUBLIC-EDGE-ROUTE-JUSTIFICATION Attack surface review 3 public route(s) justified.
SAST-SHARED-JSON-SECURITY-HEADERS AJAX response hardening
SAST-REST-QUERY-PARAMETERIZATION-GUARDS REST query parameterization and SQL injection prevention
SAST-NO-HTTP-URLS-IN-SOURCE Mixed-content
SAST-EDGE-JSON-REQUESTS-USE-BOUNDED-PARSER CSRF, deserialization, and DoS request parsing 49 edge function(s) audited; adopted helpers may not be bypassed.
SAST-NO-RAW-SQL-EXECUTION Database security least privilege
SAST-MASS-ASSIGNMENT-ALLOWLIST-HELPERS Mass assignment prevention
SAST-SBOM-SCRIPT-PRESENT Dependency graph and SBOM inventory
SAST-FILE-UPLOAD-ALLOWLISTS File upload validation
SAST-NO-SVG-UPLOADS Scriptable upload content rejection
SAST-PROTOTYPE-POLLUTION-RECURSIVE-GUARDS Prototype pollution prevention
SAST-CSS-UNTRUSTED-STYLING-GUARDS CSS injection and UI redress prevention
SAST-NO-GRAPHQL-PROD-INTROSPECTION GraphQL attack surface reduction
SAST-HTML5-POSTMESSAGE-ORIGIN HTML5 messaging origin safety
SAST-IAC-NO-PERMISSIVE-PATTERNS Infrastructure as Code secure defaults 5 infrastructure/config file(s) audited.
SAST-SSRF-CLOUD-BOUNDARY-GUARDS SSRF, network segmentation, and secure cloud architecture
SAST-GITHUB-ACTIONS-NO-UNTRUSTED-CONTEXT-IN-RUN CI/CD command injection prevention
SAST-GITHUB-ACTIONS-PINNED-BY-SHA CI/CD supply-chain integrity
SAST-GITHUB-ACTIONS-LEAST-PRIVILEGE-PERMISSIONS CI/CD least privilege
SAST-EDGE-FUNCTIONS-VERIFY-JWT-OR-EXPLICIT Edge-function auth posture 49 edge functions audited.
SAST-LOGGING-STRUCTURED-REDACTED Logging vocabulary and sensitive data redaction
SAST-DISCORD-NOTIFY-PAYLOAD-SANITIZED Outbound webhook injection prevention
SAST-LLM-MCP-TOOL-GUARDS LLM prompt injection and MCP tool safety
SAST-SESSION-MANAGEMENT-GUARDS Session management server-side posture
SAST-REDIRECT-TLS-THIRD-PARTY-GUARDS Redirect, TLS, and third-party JavaScript safety
SAST-PAYMENT-TRANSACTION-AUTH-GUARDS Third-party payment and transaction authorization integrity
SAST-WEBSOCKET-WEB-SERVICE-XXE-GUARDS WebSocket, web service, and XML parser safety
SAST-PRIVACY-VIRTUAL-PATCH-ZERO-TRUST-GUARDS Privacy protection, virtual patching, and zero-trust authorization
SAST-DEPENDENCY-RISK-ACCEPTANCE-GUARDS Vulnerable dependency management
SAST-RLS-MIGRATIONS-PRESENT RLS coverage 213 migration(s) audited.

@mdenner1234
mdenner1234 merged commit 895622d into main Aug 20, 2026
53 checks passed
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.

1 participant