Skip to content

Commit 94b4412

Browse files
authored
Merge pull request #390 from BigMick03/MindFlowInteractive-assesssment
feat: implement bounty security service with vulnerability workflow a…
2 parents 35388fe + 288b0f3 commit 94b4412

36 files changed

Lines changed: 12837 additions & 0 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
dist
3+
.git
4+
coverage
5+
.env
6+
*.log
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Service
2+
SERVICE_PORT=3030
3+
NODE_ENV=development
4+
5+
# PostgreSQL
6+
DB_HOST=localhost
7+
DB_PORT=5432
8+
DB_USER=postgres
9+
DB_PASSWORD=password
10+
DB_NAME=bounty_security_db
11+
DB_SYNC=true
12+
13+
# Reputation settings
14+
REPUTATION_ACCEPTED_CRITICAL=100
15+
REPUTATION_ACCEPTED_HIGH=60
16+
REPUTATION_ACCEPTED_MEDIUM=30
17+
REPUTATION_ACCEPTED_LOW=10
18+
REPUTATION_REJECTED_PENALTY=5
19+
20+
# Default reward tiers (USD) — used when a bounty has no per-severity tier
21+
DEFAULT_REWARD_CRITICAL=5000
22+
DEFAULT_REWARD_HIGH=2000
23+
DEFAULT_REWARD_MEDIUM=750
24+
DEFAULT_REWARD_LOW=250
25+
DEFAULT_REWARD_INFO=50
26+
27+
# Workflow SLAs (hours) — for monitoring, not enforcement
28+
SLA_TRIAGE_HOURS=48
29+
SLA_VERIFY_HOURS=120
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
dist
3+
coverage
4+
*.env
5+
.env
6+
!.env.example
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "singleQuote": true, "trailingComma": "all" }
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM node:20-alpine AS builder
2+
WORKDIR /usr/src/app
3+
COPY package.json package-lock.json* ./
4+
RUN npm install
5+
COPY . .
6+
RUN npm run build
7+
8+
FROM node:20-alpine AS runner
9+
WORKDIR /usr/src/app
10+
COPY package.json package-lock.json* ./
11+
RUN npm install --omit=dev
12+
COPY --from=builder /usr/src/app/dist ./dist
13+
EXPOSE 3030
14+
CMD ["node", "dist/main"]
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Bounty Security Service
2+
3+
A NestJS microservice for managing a security bug-bounty program end-to-end:
4+
5+
- Vulnerability reports submitted by security researchers
6+
- Automated **severity assessment** (CVSS-aware, explainable)
7+
- Bounty programs with configurable **reward tiers** per severity
8+
- Strict **workflow state machine**: `NEW → TRIAGED → VERIFIED → FIXED`
9+
- Resettable **researcher reputation** (streaks, ranks, lifetime earnings)
10+
- **Reward distribution** workflow: `PENDING → APPROVED → PAID`
11+
- **Leaderboards** — top researchers globally, per-bounty, or per time window
12+
- Independent PostgreSQL database, deployable with `docker compose up`
13+
14+
## Quick start
15+
16+
```bash
17+
cp .env.example .env
18+
# (edit DB creds if you want)
19+
docker compose up --build
20+
# -> http://localhost:3030/api (Swagger)
21+
# -> http://localhost:3030/health
22+
```
23+
24+
For local development without Docker:
25+
26+
```bash
27+
npm install
28+
DB_HOST=localhost npm run start:dev
29+
```
30+
31+
## Architecture
32+
33+
```
34+
src/
35+
├── entities/
36+
│ ├── report.entity.ts # VulnerabilityReport (the workflow doc)
37+
│ ├── bounty.entity.ts # Bounty (scope, tiers, lifecycle)
38+
│ ├── reward.entity.ts # Reward (PENDING → APPROVED → PAID)
39+
│ ├── researcher.entity.ts # Researcher (reputation snapshot)
40+
│ └── report-status.enum.ts # SeverityTier, ReportStatus, RewardStatus...
41+
├── dto/ # class-validator DTOs grouped by feature
42+
├── services/
43+
│ ├── severity.service.ts # CVSS-aware severity engine
44+
│ ├── reports.service.ts # Submission + workflow transitions
45+
│ ├── bounties.service.ts # Bounty CRUD + reward tier resolution
46+
│ ├── rewards.service.ts # Reward lifecycle + researcher earnings
47+
│ ├── researchers.service.ts # Researcher CRUD + reputation tracking
48+
│ └── leaderboard.service.ts # Rankings (by reputation / by bounty)
49+
├── controllers/
50+
│ ├── reports.controller.ts
51+
│ ├── bounties.controller.ts
52+
│ ├── researchers.controller.ts
53+
│ ├── rewards.controller.ts
54+
│ ├── leaderboard.controller.ts
55+
│ └── health.controller.ts
56+
├── app.module.ts
57+
└── main.ts # Bootstrap w/ Swagger + ValidationPipe
58+
```
59+
60+
## REST Endpoints (highlights)
61+
62+
| Method | Path | Description |
63+
| ------ | ------------------------------------- | ------------------------------------------ |
64+
| `POST` | `/reports` | Submit a vulnerability report |
65+
| `POST` | `/reports/:id/transition` | Walk a report through the workflow |
66+
| `POST` | `/bounties` | Create a bounty program |
67+
| `GET` | `/bounties/by-slug/:slug` | Look up a bounty by URL slug |
68+
| `POST` | `/researchers` | Register a researcher |
69+
| `PATCH`| `/rewards/:id/approve` | Approve a pending reward |
70+
| `PATCH`| `/rewards/:id/pay` | Mark an approved reward paid |
71+
| `GET` | `/leaderboard/researchers?period=week`| Top researchers (time-window scoped) |
72+
| `GET` | `/leaderboard/bounties/:id/researchers` | Top researchers per bounty |
73+
| `GET` | `/health` | Liveness + stat snapshot |
74+
75+
Open `/api` for full Swagger.
76+
77+
## Workflow
78+
79+
```
80+
┌─────────┐
81+
│ NEW │ ← researcher submits report
82+
└────┬────┘
83+
│ triage
84+
┌───────────┼─────────────┐
85+
▼ ▼ ▼
86+
┌────────┐ ┌─────────┐ ┌──────────┐
87+
│TRIAGED │ │REJECTED │ │DUPLICATE │
88+
└────┬───┘ └─────────┘ └──────────┘
89+
│ verified
90+
91+
┌─────────┐
92+
│VERIFIED │ ← severity locked, reputation credited
93+
└────┬────┘
94+
│ patch deployed
95+
96+
┌───────┐
97+
│ FIXED │ ← reward row auto-created in PENDING
98+
└───────┘
99+
```
100+
101+
State transitions are enforced by the `TRANSITIONS` matrix in
102+
`services/reports.service.ts`. Any `VALID` transition also triggers:
103+
104+
- **TRIAGED** → severity is recomputed by the engine; rationale stored on the row.
105+
- **VERIFIED / FIXED / REJECTED / DUPLICATE** → researcher reputation and streak are updated.
106+
- **FIXED** → a `Reward` row is created at `PENDING` with the bounty-tier midpoint.
107+
108+
## Severity assessment
109+
110+
`SeverityService.assess()` takes the researcher's claimed severity, an optional
111+
CVSS score or vector, the affected component, and a high-level impact tag.
112+
It folds in component criticality (auth/payment/admin… get a 1.25× boost) and
113+
an impact multiplier (RCE = 1.5×, best-practice = 0.5×).
114+
115+
Output:
116+
117+
```ts
118+
{
119+
severity: 'critical' | 'high' | 'medium' | 'low' | 'info',
120+
score: 0100,
121+
rationale: {
122+
cvss?, componentBoost, impactMultiplier, notes: string[]
123+
}
124+
}
125+
```
126+
127+
The full rationale is persisted on the report (`severityRationale`) so an
128+
auditor can later see *why* a severity was assigned.
129+
130+
## Reputation
131+
132+
Points awarded per accepted report, configurable via env:
133+
134+
| Severity | Default Δ |
135+
| -------- | --------- |
136+
| CRITICAL | +100 |
137+
| HIGH | +60 |
138+
| MEDIUM | +30 |
139+
| LOW | +10 |
140+
| REJECTED | -5 |
141+
142+
Researcher ranks are derived from reputation:
143+
144+
| Reputation | Rank |
145+
| ---------- | --------- |
146+
| ≥ 5000 | diamond |
147+
| ≥ 2000 | platinum |
148+
| ≥ 1000 | gold |
149+
| ≥ 300 | silver |
150+
| else | bronze |
151+
152+
## Reward tiers
153+
154+
Bounty authors configure a tier per severity:
155+
156+
```json
157+
{
158+
"tiers": [
159+
{ "severity": "critical", "minAmount": 5000, "maxAmount": 10000, "currency": "USD" },
160+
{ "severity": "high", "minAmount": 1000, "maxAmount": 2500, "currency": "USD" },
161+
{ "severity": "medium", "minAmount": 250, "maxAmount": 750, "currency": "USD" }
162+
]
163+
}
164+
```
165+
166+
If a bounty has no tiers, the global defaults from `.env` are applied.
167+
The reward service uses the tier midpoint as the suggested payout, so the
168+
security team can adjust before approving.
169+
170+
## Environment variables
171+
172+
See `.env.example`. Notable knobs:
173+
174+
- `DEFAULT_REWARD_{CRITICAL|HIGH|MEDIUM|LOW|INFO}` — global fallback tier amounts.
175+
- `REPUTATION_ACCEPTED_*`, `REPUTATION_REJECTED_PENALTY` — reputation deltas.
176+
- `SLA_TRIAGE_HOURS`, `SLA_VERIFY_HOURS` — informational SLAs (used by future reporting).
177+
- `DB_SYNC=true` — for dev only. In production, prefer proper TypeORM migrations.
178+
179+
## Tests
180+
181+
```bash
182+
npm test
183+
```
184+
185+
Coverage focuses on:
186+
187+
- `SeverityService` — severity boundaries, CVSS parsing, overrides.
188+
- `ReportsService` — state machine enforcement, audit trail, reward auto-creation.
189+
190+
## Production hardening
191+
192+
This scaffold uses `DB_SYNC=true` for local development. For production:
193+
194+
1. Disable `DB_SYNC`, generate real TypeORM migrations (`migration:generate`).
195+
2. Move `transactionRef` integration to your real payment provider (Tremendous, Coinbase Commerce, etc.) behind an outbox pattern.
196+
3. Add rate-limiting + CAPTCHA on `/reports` to deflect spam submissions.
197+
4. Enforce content security on `/reports` — restrict file sizes, mime types, and run uploaded PoCs through a malware scanner.
198+
5. Add SSO / `researcher_handle` gating through your identity service before awarding rewards.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
version: '3.9'
2+
3+
services:
4+
bounty-security-service:
5+
build: .
6+
container_name: bounty-security-service
7+
ports:
8+
- "3030:3030"
9+
environment:
10+
- SERVICE_PORT=3030
11+
- NODE_ENV=development
12+
- DB_HOST=postgres
13+
- DB_PORT=5432
14+
- DB_USER=postgres
15+
- DB_PASSWORD=password
16+
- DB_NAME=bounty_security_db
17+
- DB_SYNC=true
18+
- REPUTATION_ACCEPTED_CRITICAL=100
19+
- REPUTATION_ACCEPTED_HIGH=60
20+
- REPUTATION_ACCEPTED_MEDIUM=30
21+
- REPUTATION_ACCEPTED_LOW=10
22+
- REPUTATION_REJECTED_PENALTY=5
23+
- DEFAULT_REWARD_CRITICAL=5000
24+
- DEFAULT_REWARD_HIGH=2000
25+
- DEFAULT_REWARD_MEDIUM=750
26+
- DEFAULT_REWARD_LOW=250
27+
- DEFAULT_REWARD_INFO=50
28+
- SLA_TRIAGE_HOURS=48
29+
- SLA_VERIFY_HOURS=120
30+
depends_on:
31+
postgres:
32+
condition: service_healthy
33+
restart: unless-stopped
34+
35+
postgres:
36+
image: postgres:15-alpine
37+
container_name: bounty-security-postgres
38+
environment:
39+
POSTGRES_USER: postgres
40+
POSTGRES_PASSWORD: password
41+
POSTGRES_DB: bounty_security_db
42+
ports:
43+
- "5437:5432"
44+
volumes:
45+
- bounty_pg_data:/var/lib/postgresql/data
46+
healthcheck:
47+
test: ["CMD-SHELL", "pg_isready -U postgres"]
48+
interval: 10s
49+
timeout: 5s
50+
retries: 5
51+
52+
volumes:
53+
bounty_pg_data:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{ "collection": "@nestjs/schematics", "sourceRoot": "src" }

0 commit comments

Comments
 (0)