Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
node_modules
dist
.git
.github
coverage
tests
docs
scripts
*.md
.env
.env.*
!.env.example
.editorconfig
.prettierrc.json
.prettierignore
.gitignore
eslint.config.js
vitest.config.ts
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ REDIS_URL=redis://localhost:6379
# Prefix used for Redis keys so multiple environments can share one Redis cluster
SERVICE_NAME=arce

# Optional API key for authenticating protected endpoints.
# When set, all /check-limit, /consume, and /api/dashboard-data requests
# must include a matching x-api-key header. When empty, auth is disabled.
API_KEY=

# Baseline limit applied to normal traffic
DEFAULT_LIMIT_PER_MINUTE=100

Expand Down
13 changes: 12 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ jobs:
ci:
runs-on: ubuntu-latest

services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10

steps:
- uses: actions/checkout@v4

Expand All @@ -23,4 +34,4 @@ jobs:
- run: npm ci
- run: npm run build
- run: npm run lint
- run: npm test
- run: npm test
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Changelog

All notable changes to this project will be documented in this file.

## [1.1.0] — 2026-04-29

### Security

- **API key authentication** — protected endpoints (`/check-limit`, `/consume`, `/api/dashboard-data`) now require a valid `x-api-key` header when `API_KEY` is configured. Authentication is disabled when the variable is empty, preserving the local development workflow.
- **Dashboard XSS fix** — replaced all `innerHTML` usage in the dashboard with safe DOM construction (`textContent` and `createElement`). User-controlled data can no longer execute scripts in the operator's browser.
- **Input length limits** — `userId` (max 256), `ip` (max 64), and `identifier` (max 256) fields now enforce maximum length to prevent memory exhaustion via oversized Redis keys.
- **Error handler hardened** — `ZodError` returns 400 with safe validation details. All other errors return a generic 500 without leaking internal messages or stack traces.
- **Security headers** — all responses now include `X-Content-Type-Options`, `X-Frame-Options`, `X-XSS-Protection`, and `Referrer-Policy`.

### Changed

- **Parallel I/O on hot path** — `recordOutcome` and `recordDecision` now execute via `Promise.all()` instead of sequential awaits, reducing latency by one Redis round-trip.
- **Graceful shutdown timeout** — the server now force-exits after 10 seconds if `server.close()` hangs on connection draining.
- **Redis reconnect strategy** — the Redis client now retries with exponential backoff (up to 10 attempts) instead of silently logging errors.

### Added

- **Comprehensive test suite** — added 50+ tests across 7 new test files:
- Abuse detector edge cases and threshold boundaries
- Schema validation for all `superRefine` rules and field constraints
- Identity utility tests (all `resolveSubject` branches, `buildFingerprint` paths)
- Hashing determinism and format tests
- Time bucketing tests
- Express middleware tests (allow, deny, error forwarding)
- **`.dockerignore`** — excludes dev-only files from the build context.
- **Docker hardening** — non-root user (`USER node`), `HEALTHCHECK` directive, `npm ci` for reproducible installs, only copies necessary runtime assets.
- **CI improvements** — added `format:check`, `typecheck`, `npm audit`, Redis service container, and Node 20 + 22 matrix testing.

## [1.0.0] — 2026-04-29

Initial release. Distributed adaptive traffic control with Redis-backed rate limiting, abuse detection, and operator dashboard.
14 changes: 11 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm ci
COPY tsconfig.json ./
COPY configs ./configs
COPY apps ./apps
Expand All @@ -11,8 +11,16 @@ RUN npm run build
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
RUN npm ci --omit=dev

COPY --from=build /app/dist ./dist
COPY --from=build /app/apps ./apps
COPY --from=build /app/apps/dashboard/public ./apps/dashboard/public

EXPOSE 4000

USER node

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:4000/health || exit 1

CMD ["node", "dist/apps/server/main.js"]
38 changes: 29 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Additional design notes:

- [docs/architecture.md](./docs/architecture.md)
- [docs/design-decisions.md](./docs/design-decisions.md)
- [docs/system-diagram.md](./docs/system-diagram.md)

## Design Decisions

Expand Down Expand Up @@ -208,6 +209,21 @@ http://localhost:4000/dashboard

If you need non-default settings, use the variables documented in [.env.example](./.env.example).

## Authentication

When `API_KEY` is set in the environment, ARCE requires all protected endpoints (`/check-limit`, `/consume`, `/api/dashboard-data`) to include a matching `x-api-key` header.

When `API_KEY` is not set, authentication is disabled so local development remains frictionless.

```bash
curl -X POST http://localhost:4000/consume \
-H "Content-Type: application/json" \
-H "x-api-key: your-secret-key" \
-d '{"algorithm":"token_bucket","route":"/api/orders","method":"GET","ip":"203.0.113.8","scope":"ip"}'
```

Public endpoints (`/`, `/health`, `/dashboard`, `/static/*`) do not require authentication.

## API Example

Check a limit without consuming:
Expand Down Expand Up @@ -251,7 +267,8 @@ Example request shape:
import { ArceClient } from "./src/sdk/client";

const client = new ArceClient({
baseUrl: "http://localhost:4000"
baseUrl: "http://localhost:4000",
headers: { "x-api-key": process.env.ARCE_API_KEY ?? "" }
});

const decision = await client.consume({
Expand Down Expand Up @@ -297,26 +314,29 @@ app.use(

## Configuration

| Variable | Default | Purpose |
| ----------------------------- | ------------------------ | ---------------------------------------------------- |
| `PORT` | `4000` | HTTP port for the ARCE server |
| `REDIS_URL` | `redis://localhost:6379` | Redis connection string |
| `SERVICE_NAME` | `arce` | Prefix for Redis keys |
| `DEFAULT_LIMIT_PER_MINUTE` | `100` | Baseline limit for normal traffic |
| `SUSPICIOUS_LIMIT_PER_MINUTE` | `20` | Lower bound for suspicious traffic |
| `BLOCK_DURATION_SECONDS` | `300` | Temporary block duration for critical abuse patterns |
| Variable | Default | Purpose |
| ----------------------------- | ------------------------ | ---------------------------------------------------------------- |
| `PORT` | `4000` | HTTP port for the ARCE server |
| `REDIS_URL` | `redis://localhost:6379` | Redis connection string |
| `SERVICE_NAME` | `arce` | Prefix for Redis keys |
| `API_KEY` | _(empty, auth disabled)_ | API key for protecting `/check-limit`, `/consume`, and dashboard |
| `DEFAULT_LIMIT_PER_MINUTE` | `100` | Baseline limit for normal traffic |
| `SUSPICIOUS_LIMIT_PER_MINUTE` | `20` | Lower bound for suspicious traffic |
| `BLOCK_DURATION_SECONDS` | `300` | Temporary block duration for critical abuse patterns |

## Development

```bash
npm run build
npm run typecheck
npm run lint
npm run format:check
npm run test
npm run smoke
```

See [CONTRIBUTING.md](./CONTRIBUTING.md) for contribution and PR expectations.
See [CHANGELOG.md](./CHANGELOG.md) for a history of changes.

## Roadmap

Expand Down
Loading
Loading