Skip to content

Add external API abuse controls#16

Open
metajinglun wants to merge 4 commits into
masterfrom
feat/api-abuse-controls
Open

Add external API abuse controls#16
metajinglun wants to merge 4 commits into
masterfrom
feat/api-abuse-controls

Conversation

@metajinglun

@metajinglun metajinglun commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add env-driven anonymous rate limits, trusted request context, aggregate anonymous ceiling, emergency restriction gates, disabled-path kill switch, rate-limit headers, and restriction metrics.
  • Document the lean operational model: Cloudflare covers normal edge per-IP abuse; the app layer focuses on trusted identity, aggregate anonymous pressure, and emergency backend protection.
  • Fix additional-token account derivation for off-curve recipients and cover it with a regression test.

Verification

  • git diff --check
  • bun test tests/restriction.test.ts tests/rateLimit.test.ts tests/config.test.ts
  • bun run build
  • bun test
  • Restricted-mode HTTP smoke: anonymous /api/tickers -> 503 SERVICE_RESTRICTED, trusted /api/tickers -> 200, /health -> 200

Notes

  • Emergency controls remain env + restart only; no runtime admin endpoint is added.
  • Restriction mode preserves trusted server-side access while shedding anonymous traffic.
  • GLOBAL_RATE_LIMIT_MAX remains off by default and can be enabled when aggregate anonymous origin pressure matters.

Greptile Summary

This PR adds an abuse-control layer to the external API: anonymous and global rate limits (env-driven), a request-context middleware that validates API keys with timing-safe comparison, an emergency restriction mode that sheds anonymous traffic while preserving trusted and health access, and Prometheus metrics for restriction events. It also fixes getAssociatedTokenAddress to pass allowOwnerOffCurve=true for PDA recipients in the launchpad service.

  • Rate limiting is refactored into src/middleware/rateLimit.ts with a check-then-charge design that prevents per-IP quota from being consumed by global-ceiling rejections; rate-limit headers (X-RateLimit-*, Retry-After) are now included on all responses.
  • Restriction middleware (src/middleware/restriction.ts) gates on RESTRICTION_MODE and RESTRICTION_DISABLED_PATHS, always exempting /health, /api/health, and /metrics; controls are env + restart only with no runtime admin endpoint.
  • Launchpad fix adds the missing allowOwnerOffCurve=true argument to getAssociatedTokenAddress and covers the regression with a new test.

Confidence Score: 5/5

Safe to merge; the new middleware layer is well-structured, tested, and the single finding is a minor path-matching edge case in an internal guard.

All three middleware pieces (client context, rate limiting, restriction) are logically correct and covered by tests. The rate limiter correctly implements check-before-charge to avoid phantom quota consumption. The timing-safe key comparison iterates all keys without early exit. The launchpad fix is small, targeted, and regression-tested. The only gap is that alwaysAllowedPaths uses prefix matching where exact matching was intended, but no current route collides with those prefixes, so it has no impact today.

src/middleware/restriction.ts — the alwaysAllowedPaths exact-vs-prefix matching issue is worth fixing before new routes are added near /health or /metrics.

Important Files Changed

Filename Overview
src/middleware/restriction.ts New emergency restriction middleware; logic is correct but alwaysAllowedPaths uses prefix matching instead of exact matching, which could silently exempt unintended paths.
src/middleware/rateLimit.ts Refactored rate limiter with global anonymous ceiling, rate-limit headers, and check-then-charge ordering that correctly avoids burning per-IP quota on global-ceiling rejections.
src/middleware/clientContext.ts New middleware to classify requests as anon/trusted; timing-safe key lookup iterates all keys without early exit, correctly addressing the previous review concern.
src/config.ts Adds config for rate limit window, global rate limit, and restriction mode/paths; replaces inline parseInt calls with a validated parseInteger helper.
src/services/launchpadService.ts Passes allowOwnerOffCurve=true to getAssociatedTokenAddress so PDA recipients no longer throw during token account derivation.
src/utils/timingSafe.ts New utility that hashes both operands through SHA-256 before calling Node's timingSafeEqual, ensuring constant-time string comparison regardless of string length differences.
src/services/metricsService.ts Adds Prometheus counter for restriction rejections (by reason label) and a gauge for current restriction mode; straightforward metric additions.
tests/services/launchpadService.test.ts Adds a regression test that exercises the off-curve recipient path, verifying the token account address is derived with allowOwnerOffCurve=true.

Reviews (3): Last reviewed commit: "Trim duplicate edge controls from API sa..." | Re-trigger Greptile

Constraint: Emergency controls must be env-and-restart only with no runtime admin surface.
Rejected: Runtime mutation endpoint | Adds a new mutating attack surface for a public API.
Confidence: high
Scope-risk: moderate
Directive: Keep trusted-key traffic preserved unless product explicitly chooses a trusted-traffic kill switch.
Tested: bun test; bun run build; git diff --check
Not-tested: LSP diagnostics unavailable because TypeScript LSP is not installed.
Constraint: Additional-token recipients may be program-derived addresses.
Rejected: Dropping tokenAccountAddress on derivation failure | Hides a valid off-curve recipient path.
Confidence: high
Scope-risk: narrow
Directive: Keep additional-token ATA derivation aligned with other off-curve vault derivations.
Tested: bun test; bun run build; git diff --check
@metajinglun metajinglun requested a review from metanallok as a code owner June 18, 2026 01:22
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

Package Protection

  • Lockfile + Socket scan: pass
  • Repo guard: pass
  • Phantom deps: pass

Repository Guard

Exact dependency versions

  • Status: pass
  • All dependencies, devDependencies, optionalDependencies, and peerDependencies use exact versions or workspace:*.

Package minimum age

  • Status: pass
  • All pinned external packages are at least 14 days old.

Sensitive wallet / program changes

  • Status: warn
  • Suspicious wallet routing, signing-path, or program-ID changes detected. This is a review hint, not a merge gate — CODEOWNERS + branch protection enforce the actual review requirement. Please take a closer look at the lines below.
  • High-sensitivity files touched: src/config.ts, src/app.ts, src/services/launchpadService.ts
  • Matching diff lines:
  • tests/services/launchpadService.test.ts:18 Hardcoded Solana address or program literal; Public key construction change -> +const LAUNCH = new PublicKey('5FPGRzY9ArJFwY2Hp2y2eqMzVewyWCBox7esmpuZfCvE');
  • tests/services/launchpadService.test.ts:19 Hardcoded Solana address or program literal; Public key construction change -> +const DAO = new PublicKey('11111111111111111111111111111111');
  • tests/services/launchpadService.test.ts:59 Wallet or destination routing change -> + it('derives the additional-token account when the recipient is off-curve', async () => {
  • tests/services/launchpadService.test.ts:61 Wallet or destination routing change -> + const [recipient] = PublicKey.findProgramAddressSync(
  • tests/services/launchpadService.test.ts:62 Wallet or destination routing change -> + [Buffer.from('additional-recipient'), MINT.toBuffer()],
  • tests/services/launchpadService.test.ts:65 Wallet or destination routing change -> + const expectedTokenAccount = await getAssociatedTokenAddress(MINT, recipient, true);
  • tests/services/launchpadService.test.ts:82 Wallet or destination routing change -> + additionalTokensRecipient: recipient,
  • tests/services/launchpadService.test.ts:90 Wallet or destination routing change -> + expect(breakdown.additionalTokenAllocation?.recipient.equals(recipient)).toBe(true);

Overall status: pass

Transitive supply-chain threats are covered by the Socket scanner (via @socketsecurity/bun-security-scanner in bunfig.toml), which runs on every bun install. This guard blocks merge on direct-dep pinning and age policy; the sensitive-diff section is a review hint, not a merge gate (CODEOWNERS handles the actual review requirement).

Comment thread src/middleware/rateLimit.ts Outdated
Comment thread src/middleware/clientContext.ts Outdated
Comment thread src/middleware/clientContext.ts
- rateLimit: check both per-IP and global ceilings before charging either, so a
  request rejected by one limit never consumes the other's quota (check-then-
  charge via describeBucket; adds a regression test).
- clientContext: parse exemptCidrs once at app creation via a factory instead of
  re-parsing on every request.
- clientContext: iterate all trusted keys without early-exit so the constant-time
  comparison doesn't leak the matching key's position.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@metajinglun metajinglun requested a review from meta-reid June 19, 2026 00:05
Cloudflare handles normal per-IP edge throttling, so this keeps the app layer focused on trusted identity, aggregate anonymous pressure, and emergency backend protection.

Constraint: Public API controls should avoid extra operational bypasses or duplicate modes unless they materially protect downstream systems.
Rejected: Origin allowlist, CIDR bypass, and lockdown mode | They duplicated edge policy or behaved the same as restricted while adding public surface area.
Confidence: high
Scope-risk: narrow
Directive: Keep future app-layer controls focused on behavior Cloudflare cannot express safely: trusted identity, aggregate anonymous ceilings, and endpoint restriction.
Tested: git diff --check; bun test tests/restriction.test.ts tests/rateLimit.test.ts tests/config.test.ts; bun run build; bun test; restricted-mode HTTP smoke
Not-tested: LSP diagnostics unavailable because TypeScript LSP is not installed.
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