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
6 changes: 6 additions & 0 deletions microservices/bounty-security-service/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
dist
.git
coverage
.env
*.log
29 changes: 29 additions & 0 deletions microservices/bounty-security-service/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Service
SERVICE_PORT=3030
NODE_ENV=development

# PostgreSQL
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=password
DB_NAME=bounty_security_db
DB_SYNC=true

# Reputation settings
REPUTATION_ACCEPTED_CRITICAL=100
REPUTATION_ACCEPTED_HIGH=60
REPUTATION_ACCEPTED_MEDIUM=30
REPUTATION_ACCEPTED_LOW=10
REPUTATION_REJECTED_PENALTY=5

# Default reward tiers (USD) — used when a bounty has no per-severity tier
DEFAULT_REWARD_CRITICAL=5000
DEFAULT_REWARD_HIGH=2000
DEFAULT_REWARD_MEDIUM=750
DEFAULT_REWARD_LOW=250
DEFAULT_REWARD_INFO=50

# Workflow SLAs (hours) — for monitoring, not enforcement
SLA_TRIAGE_HOURS=48
SLA_VERIFY_HOURS=120
6 changes: 6 additions & 0 deletions microservices/bounty-security-service/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
dist
coverage
*.env
.env
!.env.example
1 change: 1 addition & 0 deletions microservices/bounty-security-service/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "singleQuote": true, "trailingComma": "all" }
14 changes: 14 additions & 0 deletions microservices/bounty-security-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM node:20-alpine AS builder
WORKDIR /usr/src/app
COPY package.json package-lock.json* ./
RUN npm install
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /usr/src/app
COPY package.json package-lock.json* ./
RUN npm install --omit=dev
COPY --from=builder /usr/src/app/dist ./dist
EXPOSE 3030
CMD ["node", "dist/main"]
198 changes: 198 additions & 0 deletions microservices/bounty-security-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Bounty Security Service

A NestJS microservice for managing a security bug-bounty program end-to-end:

- Vulnerability reports submitted by security researchers
- Automated **severity assessment** (CVSS-aware, explainable)
- Bounty programs with configurable **reward tiers** per severity
- Strict **workflow state machine**: `NEW → TRIAGED → VERIFIED → FIXED`
- Resettable **researcher reputation** (streaks, ranks, lifetime earnings)
- **Reward distribution** workflow: `PENDING → APPROVED → PAID`
- **Leaderboards** — top researchers globally, per-bounty, or per time window
- Independent PostgreSQL database, deployable with `docker compose up`

## Quick start

```bash
cp .env.example .env
# (edit DB creds if you want)
docker compose up --build
# -> http://localhost:3030/api (Swagger)
# -> http://localhost:3030/health
```

For local development without Docker:

```bash
npm install
DB_HOST=localhost npm run start:dev
```

## Architecture

```
src/
├── entities/
│ ├── report.entity.ts # VulnerabilityReport (the workflow doc)
│ ├── bounty.entity.ts # Bounty (scope, tiers, lifecycle)
│ ├── reward.entity.ts # Reward (PENDING → APPROVED → PAID)
│ ├── researcher.entity.ts # Researcher (reputation snapshot)
│ └── report-status.enum.ts # SeverityTier, ReportStatus, RewardStatus...
├── dto/ # class-validator DTOs grouped by feature
├── services/
│ ├── severity.service.ts # CVSS-aware severity engine
│ ├── reports.service.ts # Submission + workflow transitions
│ ├── bounties.service.ts # Bounty CRUD + reward tier resolution
│ ├── rewards.service.ts # Reward lifecycle + researcher earnings
│ ├── researchers.service.ts # Researcher CRUD + reputation tracking
│ └── leaderboard.service.ts # Rankings (by reputation / by bounty)
├── controllers/
│ ├── reports.controller.ts
│ ├── bounties.controller.ts
│ ├── researchers.controller.ts
│ ├── rewards.controller.ts
│ ├── leaderboard.controller.ts
│ └── health.controller.ts
├── app.module.ts
└── main.ts # Bootstrap w/ Swagger + ValidationPipe
```

## REST Endpoints (highlights)

| Method | Path | Description |
| ------ | ------------------------------------- | ------------------------------------------ |
| `POST` | `/reports` | Submit a vulnerability report |
| `POST` | `/reports/:id/transition` | Walk a report through the workflow |
| `POST` | `/bounties` | Create a bounty program |
| `GET` | `/bounties/by-slug/:slug` | Look up a bounty by URL slug |
| `POST` | `/researchers` | Register a researcher |
| `PATCH`| `/rewards/:id/approve` | Approve a pending reward |
| `PATCH`| `/rewards/:id/pay` | Mark an approved reward paid |
| `GET` | `/leaderboard/researchers?period=week`| Top researchers (time-window scoped) |
| `GET` | `/leaderboard/bounties/:id/researchers` | Top researchers per bounty |
| `GET` | `/health` | Liveness + stat snapshot |

Open `/api` for full Swagger.

## Workflow

```
┌─────────┐
│ NEW │ ← researcher submits report
└────┬────┘
│ triage
┌───────────┼─────────────┐
▼ ▼ ▼
┌────────┐ ┌─────────┐ ┌──────────┐
│TRIAGED │ │REJECTED │ │DUPLICATE │
└────┬───┘ └─────────┘ └──────────┘
│ verified
┌─────────┐
│VERIFIED │ ← severity locked, reputation credited
└────┬────┘
│ patch deployed
┌───────┐
│ FIXED │ ← reward row auto-created in PENDING
└───────┘
```

State transitions are enforced by the `TRANSITIONS` matrix in
`services/reports.service.ts`. Any `VALID` transition also triggers:

- **TRIAGED** → severity is recomputed by the engine; rationale stored on the row.
- **VERIFIED / FIXED / REJECTED / DUPLICATE** → researcher reputation and streak are updated.
- **FIXED** → a `Reward` row is created at `PENDING` with the bounty-tier midpoint.

## Severity assessment

`SeverityService.assess()` takes the researcher's claimed severity, an optional
CVSS score or vector, the affected component, and a high-level impact tag.
It folds in component criticality (auth/payment/admin… get a 1.25× boost) and
an impact multiplier (RCE = 1.5×, best-practice = 0.5×).

Output:

```ts
{
severity: 'critical' | 'high' | 'medium' | 'low' | 'info',
score: 0–100,
rationale: {
cvss?, componentBoost, impactMultiplier, notes: string[]
}
}
```

The full rationale is persisted on the report (`severityRationale`) so an
auditor can later see *why* a severity was assigned.

## Reputation

Points awarded per accepted report, configurable via env:

| Severity | Default Δ |
| -------- | --------- |
| CRITICAL | +100 |
| HIGH | +60 |
| MEDIUM | +30 |
| LOW | +10 |
| REJECTED | -5 |

Researcher ranks are derived from reputation:

| Reputation | Rank |
| ---------- | --------- |
| ≥ 5000 | diamond |
| ≥ 2000 | platinum |
| ≥ 1000 | gold |
| ≥ 300 | silver |
| else | bronze |

## Reward tiers

Bounty authors configure a tier per severity:

```json
{
"tiers": [
{ "severity": "critical", "minAmount": 5000, "maxAmount": 10000, "currency": "USD" },
{ "severity": "high", "minAmount": 1000, "maxAmount": 2500, "currency": "USD" },
{ "severity": "medium", "minAmount": 250, "maxAmount": 750, "currency": "USD" }
]
}
```

If a bounty has no tiers, the global defaults from `.env` are applied.
The reward service uses the tier midpoint as the suggested payout, so the
security team can adjust before approving.

## Environment variables

See `.env.example`. Notable knobs:

- `DEFAULT_REWARD_{CRITICAL|HIGH|MEDIUM|LOW|INFO}` — global fallback tier amounts.
- `REPUTATION_ACCEPTED_*`, `REPUTATION_REJECTED_PENALTY` — reputation deltas.
- `SLA_TRIAGE_HOURS`, `SLA_VERIFY_HOURS` — informational SLAs (used by future reporting).
- `DB_SYNC=true` — for dev only. In production, prefer proper TypeORM migrations.

## Tests

```bash
npm test
```

Coverage focuses on:

- `SeverityService` — severity boundaries, CVSS parsing, overrides.
- `ReportsService` — state machine enforcement, audit trail, reward auto-creation.

## Production hardening

This scaffold uses `DB_SYNC=true` for local development. For production:

1. Disable `DB_SYNC`, generate real TypeORM migrations (`migration:generate`).
2. Move `transactionRef` integration to your real payment provider (Tremendous, Coinbase Commerce, etc.) behind an outbox pattern.
3. Add rate-limiting + CAPTCHA on `/reports` to deflect spam submissions.
4. Enforce content security on `/reports` — restrict file sizes, mime types, and run uploaded PoCs through a malware scanner.
5. Add SSO / `researcher_handle` gating through your identity service before awarding rewards.
53 changes: 53 additions & 0 deletions microservices/bounty-security-service/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
version: '3.9'

services:
bounty-security-service:
build: .
container_name: bounty-security-service
ports:
- "3030:3030"
environment:
- SERVICE_PORT=3030
- NODE_ENV=development
- DB_HOST=postgres
- DB_PORT=5432
- DB_USER=postgres
- DB_PASSWORD=password
- DB_NAME=bounty_security_db
- DB_SYNC=true
- REPUTATION_ACCEPTED_CRITICAL=100
- REPUTATION_ACCEPTED_HIGH=60
- REPUTATION_ACCEPTED_MEDIUM=30
- REPUTATION_ACCEPTED_LOW=10
- REPUTATION_REJECTED_PENALTY=5
- DEFAULT_REWARD_CRITICAL=5000
- DEFAULT_REWARD_HIGH=2000
- DEFAULT_REWARD_MEDIUM=750
- DEFAULT_REWARD_LOW=250
- DEFAULT_REWARD_INFO=50
- SLA_TRIAGE_HOURS=48
- SLA_VERIFY_HOURS=120
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped

postgres:
image: postgres:15-alpine
container_name: bounty-security-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: bounty_security_db
ports:
- "5437:5432"
volumes:
- bounty_pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

volumes:
bounty_pg_data:
1 change: 1 addition & 0 deletions microservices/bounty-security-service/nest-cli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "collection": "@nestjs/schematics", "sourceRoot": "src" }
Loading