@kaademos/secure-sdlc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/.claude/agents/ai-security-engineer.md +209 -0
  2. package/.claude/agents/appsec-engineer.md +131 -0
  3. package/.claude/agents/cloud-platform-engineer.md +119 -0
  4. package/.claude/agents/dev-lead.md +138 -0
  5. package/.claude/agents/grc-analyst.md +143 -0
  6. package/.claude/agents/product-manager.md +100 -0
  7. package/.claude/agents/release-manager.md +126 -0
  8. package/.claude/agents/security-champion.md +148 -0
  9. package/.cursor/rules/secure-sdlc.mdc +98 -0
  10. package/.github/workflows/secure-sdlc-gate.yml +325 -0
  11. package/CHANGELOG.md +49 -0
  12. package/CLAUDE.md +195 -0
  13. package/LICENSE +21 -0
  14. package/README.md +394 -0
  15. package/cli/bin/secure-sdlc.js +95 -0
  16. package/cli/src/commands/gate.js +129 -0
  17. package/cli/src/commands/init.js +219 -0
  18. package/cli/src/commands/install-mcp.js +121 -0
  19. package/cli/src/commands/kickoff.js +261 -0
  20. package/cli/src/commands/paths.js +33 -0
  21. package/cli/src/commands/review.js +53 -0
  22. package/cli/src/commands/status.js +122 -0
  23. package/cli/src/utils/banner.js +43 -0
  24. package/cli/src/utils/package-root.js +23 -0
  25. package/cli/src/utils/phase-detect.js +107 -0
  26. package/cli/src/utils/stack-detect.js +138 -0
  27. package/docs/templates/compliance-attestation.md +159 -0
  28. package/docs/templates/infra-security-review.md +133 -0
  29. package/docs/templates/release-sign-off.md +119 -0
  30. package/docs/templates/risk-register.md +72 -0
  31. package/docs/templates/sast-findings.md +110 -0
  32. package/docs/templates/security-requirements.md +98 -0
  33. package/docs/templates/test-security-report.md +143 -0
  34. package/docs/templates/threat-model.md +129 -0
  35. package/hooks/install.sh +37 -0
  36. package/hooks/pre-commit +208 -0
  37. package/hooks/pre-push +127 -0
  38. package/mcp/README.md +116 -0
  39. package/mcp/package.json +23 -0
  40. package/mcp/src/server.js +638 -0
  41. package/package.json +67 -0
  42. package/stacks/django.md +216 -0
  43. package/stacks/express.md +229 -0
  44. package/stacks/fastapi.md +247 -0
  45. package/stacks/nextjs.md +198 -0
  46. package/stacks/nodejs.md +28 -0
  47. package/stacks/rails.md +247 -0
  48. package/warp-workflows/README.md +25 -0
  49. package/warp-workflows/feature-kickoff.yaml +49 -0
  50. package/warp-workflows/pr-security-review.yaml +47 -0
  51. package/warp-workflows/release-gate.yaml +44 -0
  52. package/warp-workflows/sdlc-status.yaml +48 -0
  53. package/warp-workflows/threat-model.yaml +56 -0
@@ -0,0 +1,247 @@
1
+ # FastAPI Security Profile
2
+
3
+ **Framework:** FastAPI
4
+ **Language:** Python 3.10+
5
+ **ASVS Baseline:** L2
6
+
7
+ ---
8
+
9
+ ## Critical Security Areas for FastAPI
10
+
11
+ ### Authentication — Dependency Injection
12
+
13
+ FastAPI has **no global auth middleware by default**. Every endpoint must declare an auth dependency:
14
+
15
+ ```python
16
+ from fastapi import FastAPI, Depends, HTTPException, status
17
+ from fastapi.security import OAuth2PasswordBearer
18
+ from jose import JWTError, jwt
19
+
20
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
21
+
22
+ async def get_current_user(token: str = Depends(oauth2_scheme)):
23
+ credentials_exception = HTTPException(
24
+ status_code=status.HTTP_401_UNAUTHORIZED,
25
+ detail="Could not validate credentials",
26
+ headers={"WWW-Authenticate": "Bearer"},
27
+ )
28
+ try:
29
+ payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
30
+ user_id: str = payload.get("sub")
31
+ if user_id is None:
32
+ raise credentials_exception
33
+ except JWTError:
34
+ raise credentials_exception
35
+
36
+ user = await get_user(user_id)
37
+ if user is None:
38
+ raise credentials_exception
39
+ return user
40
+
41
+ # ✓ Every protected endpoint must declare the dependency
42
+ @app.get("/users/me")
43
+ async def read_users_me(current_user: User = Depends(get_current_user)):
44
+ return current_user
45
+
46
+ # ✗ Missing Depends — this is public even if you think it isn't
47
+ @app.get("/admin/users")
48
+ async def list_all_users():
49
+ return await db.users.find_many()
50
+ ```
51
+
52
+ ### Object-Level Authorisation (IDOR Prevention)
53
+
54
+ ```python
55
+ # ✗ IDOR vulnerability — any authenticated user can get any post
56
+ @app.get("/posts/{post_id}")
57
+ async def get_post(post_id: int, current_user: User = Depends(get_current_user)):
58
+ post = await db.posts.find_unique(where={"id": post_id})
59
+ if not post:
60
+ raise HTTPException(status_code=404)
61
+ return post # ✗ Returns any user's post
62
+
63
+ # ✓ Check ownership
64
+ @app.get("/posts/{post_id}")
65
+ async def get_post(post_id: int, current_user: User = Depends(get_current_user)):
66
+ post = await db.posts.find_unique(where={"id": post_id})
67
+ if not post:
68
+ raise HTTPException(status_code=404)
69
+ if post.author_id != current_user.id:
70
+ raise HTTPException(status_code=403)
71
+ return post
72
+ ```
73
+
74
+ ### Input Validation with Pydantic
75
+
76
+ FastAPI's Pydantic integration is excellent — use it fully:
77
+
78
+ ```python
79
+ from pydantic import BaseModel, Field, field_validator
80
+ import re
81
+
82
+ class CreateUserRequest(BaseModel):
83
+ username: str = Field(min_length=3, max_length=50, pattern=r'^[a-zA-Z0-9_]+$')
84
+ email: str = Field(max_length=255)
85
+ password: str = Field(min_length=8, max_length=128)
86
+
87
+ @field_validator('email')
88
+ @classmethod
89
+ def validate_email(cls, v: str) -> str:
90
+ # Use email-validator library for proper validation
91
+ from email_validator import validate_email, EmailNotValidError
92
+ try:
93
+ info = validate_email(v, check_deliverability=False)
94
+ return info.normalized
95
+ except EmailNotValidError:
96
+ raise ValueError('Invalid email address')
97
+
98
+ @field_validator('password')
99
+ @classmethod
100
+ def validate_password_strength(cls, v: str) -> str:
101
+ if not re.search(r'[A-Z]', v):
102
+ raise ValueError('Password must contain uppercase letter')
103
+ if not re.search(r'[0-9]', v):
104
+ raise ValueError('Password must contain a number')
105
+ return v
106
+
107
+ # Using response_model prevents accidental data leakage
108
+ class UserResponse(BaseModel):
109
+ id: int
110
+ username: str
111
+ email: str
112
+ # password_hash is NOT in the response model — it won't be returned
113
+
114
+ @app.post("/users", response_model=UserResponse)
115
+ async def create_user(request: CreateUserRequest):
116
+ ...
117
+ ```
118
+
119
+ **Never use `response_model=None`** on endpoints that handle sensitive data. Pydantic response models
120
+ are your last-line-of-defence against accidentally returning internal fields.
121
+
122
+ ### CORS — Never Use Wildcard for Authenticated APIs
123
+
124
+ ```python
125
+ from fastapi.middleware.cors import CORSMiddleware
126
+
127
+ # ✗ Wildcard — any origin can make authenticated requests
128
+ app.add_middleware(
129
+ CORSMiddleware,
130
+ allow_origins=["*"],
131
+ allow_credentials=True, # ✗ CRITICAL: credentials + wildcard is forbidden by spec but some browsers allow it
132
+ )
133
+
134
+ # ✓ Explicit origins
135
+ app.add_middleware(
136
+ CORSMiddleware,
137
+ allow_origins=[
138
+ "https://app.yourdomain.com",
139
+ "https://admin.yourdomain.com",
140
+ ],
141
+ allow_credentials=True,
142
+ allow_methods=["GET", "POST", "PUT", "DELETE"],
143
+ allow_headers=["Authorization", "Content-Type"],
144
+ )
145
+ ```
146
+
147
+ ### Error Handling — No Internal State Leakage
148
+
149
+ ```python
150
+ from fastapi import Request
151
+ from fastapi.responses import JSONResponse
152
+ import logging
153
+
154
+ logger = logging.getLogger(__name__)
155
+
156
+ # ✓ Global exception handler — generic response to client, detailed log server-side
157
+ @app.exception_handler(Exception)
158
+ async def global_exception_handler(request: Request, exc: Exception):
159
+ logger.error(
160
+ "Unhandled exception",
161
+ exc_info=exc,
162
+ extra={
163
+ "path": request.url.path,
164
+ "method": request.method,
165
+ "user_id": getattr(request.state, "user_id", "anonymous"),
166
+ }
167
+ )
168
+ return JSONResponse(
169
+ status_code=500,
170
+ content={"detail": "An internal error occurred"} # No stack trace
171
+ )
172
+
173
+ # ✗ Default exception behaviour in debug mode exposes internals
174
+ # NEVER set debug=True in production
175
+ app = FastAPI(debug=False)
176
+ ```
177
+
178
+ ### Rate Limiting
179
+
180
+ FastAPI has no built-in rate limiting. Add it:
181
+
182
+ ```python
183
+ from slowapi import Limiter, _rate_limit_exceeded_handler
184
+ from slowapi.util import get_remote_address
185
+ from slowapi.errors import RateLimitExceeded
186
+
187
+ limiter = Limiter(key_func=get_remote_address)
188
+ app.state.limiter = limiter
189
+ app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
190
+
191
+ @app.post("/auth/login")
192
+ @limiter.limit("5/minute") # 5 attempts per minute per IP
193
+ async def login(request: Request, credentials: LoginRequest):
194
+ ...
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Secrets Management
200
+
201
+ ```python
202
+ # ✗ Never do this
203
+ SECRET_KEY = "my-super-secret-key-that-is-in-git"
204
+
205
+ # ✓ Use pydantic-settings for typed, validated config
206
+ from pydantic_settings import BaseSettings
207
+ from pydantic import SecretStr
208
+
209
+ class Settings(BaseSettings):
210
+ secret_key: SecretStr # SecretStr prevents accidental logging
211
+ database_url: SecretStr
212
+ allowed_origins: list[str] = []
213
+
214
+ class Config:
215
+ env_file = ".env" # For local dev only — never commit .env
216
+
217
+ settings = Settings()
218
+ # Access: settings.secret_key.get_secret_value()
219
+ ```
220
+
221
+ ---
222
+
223
+ ## ASVS Controls for FastAPI Projects
224
+
225
+ | ASVS Ref | Control | FastAPI Implementation |
226
+ |----------|---------|----------------------|
227
+ | V4.1.1 | Auth on all endpoints | `Depends(get_current_user)` on every protected endpoint |
228
+ | V4.2.1 | Object-level authorisation | `resource.owner_id == current_user.id` check |
229
+ | V5.1.3 | Input validation | Pydantic models with `Field` constraints |
230
+ | V8.3.4 | Don't confirm resource existence | Return 404 (not 403) for resources the user can't see |
231
+ | V13.2.5 | Rate limiting | slowapi or similar |
232
+ | V14.4.1 | Security headers | Add `SecurityHeadersMiddleware` |
233
+
234
+ ---
235
+
236
+ ## Recommended Security Stack (2026)
237
+
238
+ | Category | Recommended |
239
+ |----------|-------------|
240
+ | Auth | python-jose + passlib, or Authlib |
241
+ | Input validation | Pydantic v2 (built-in) |
242
+ | Rate limiting | slowapi, fastapi-limiter (Redis-backed) |
243
+ | Security headers | secure (adds headers middleware) |
244
+ | Password hashing | passlib[bcrypt] or argon2-cffi |
245
+ | Secrets | pydantic-settings + AWS SM / Vault |
246
+ | ORM (injection-safe) | SQLAlchemy 2.0, Tortoise ORM |
247
+ | Secret scanning CI | gitleaks, detect-secrets |
@@ -0,0 +1,198 @@
1
+ # Next.js Security Profile
2
+
3
+ **Framework:** Next.js (App Router + Pages Router)
4
+ **Language:** TypeScript / JavaScript
5
+ **ASVS Baseline:** L2
6
+
7
+ ---
8
+
9
+ ## Critical Security Areas for Next.js
10
+
11
+ ### Server Actions
12
+
13
+ Server Actions are POST endpoints. They share the same attack surface as API routes:
14
+
15
+ ```typescript
16
+ // ✗ Missing auth check — any user can invoke this action
17
+ 'use server'
18
+ export async function deletePost(postId: string) {
19
+ await db.posts.delete({ where: { id: postId } });
20
+ }
21
+
22
+ // ✓ Correct — validate session and ownership
23
+ 'use server'
24
+ export async function deletePost(postId: string) {
25
+ const session = await getServerSession(authOptions);
26
+ if (!session?.user?.id) throw new Error('Unauthorized');
27
+
28
+ const post = await db.posts.findUnique({ where: { id: postId } });
29
+ if (post?.authorId !== session.user.id) throw new Error('Forbidden');
30
+
31
+ await db.posts.delete({ where: { id: postId } });
32
+ }
33
+ ```
34
+
35
+ **CSRF on Server Actions:** Next.js 14+ includes CSRF protection for Server Actions by default
36
+ via `Origin` header validation. Do not disable this. If using custom fetch with `cache: 'force-cache'`,
37
+ be aware that CSRF protection may not apply.
38
+
39
+ ### API Routes — App Router
40
+
41
+ ```typescript
42
+ // ✗ No authentication
43
+ export async function GET(request: Request) {
44
+ const data = await db.users.findMany();
45
+ return Response.json(data);
46
+ }
47
+
48
+ // ✓ Authenticate every API route handler
49
+ export async function GET(request: Request) {
50
+ const session = await getServerSession(authOptions);
51
+ if (!session) return new Response('Unauthorized', { status: 401 });
52
+
53
+ // IDOR check: only return the requesting user's data
54
+ const data = await db.users.findUnique({ where: { id: session.user.id } });
55
+ return Response.json(data);
56
+ }
57
+ ```
58
+
59
+ There is **no middleware-level auth applied to all API routes by default** in Next.js.
60
+ Every route handler must explicitly authenticate.
61
+
62
+ ### Middleware — Correct and Incorrect Use
63
+
64
+ ```typescript
65
+ // next/middleware.ts
66
+
67
+ // ✓ Use middleware for: redirect to login, edge auth checks, rate limiting
68
+ export function middleware(request: NextRequest) {
69
+ const token = request.cookies.get('next-auth.session-token');
70
+ if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
71
+ return NextResponse.redirect(new URL('/login', request.url));
72
+ }
73
+ }
74
+
75
+ // ✗ Do NOT rely on middleware as your ONLY auth check — it runs at the edge
76
+ // and can be bypassed. Always validate auth in your route handlers too.
77
+ ```
78
+
79
+ ### Server Components vs Client Components — Secret Leakage
80
+
81
+ ```typescript
82
+ // ✗ CRITICAL: Secrets passed to Client Components are sent to the browser
83
+ async function Page() {
84
+ const apiKey = process.env.EXTERNAL_API_KEY; // This is fine as a server-side value
85
+ return <ClientComponent apiKey={apiKey} />; // ✗ Now it's in the browser
86
+ }
87
+
88
+ // ✓ Fetch server-side, pass only the result
89
+ async function Page() {
90
+ const data = await fetchWithApiKey(process.env.EXTERNAL_API_KEY);
91
+ return <ClientComponent data={data} />; // ✓ Only the result goes to client
92
+ }
93
+ ```
94
+
95
+ **Rule:** `NEXT_PUBLIC_` prefix exposes variables to the browser. Never put secrets there.
96
+
97
+ ### next.config.js — Security Headers
98
+
99
+ Add security headers. Without these, browsers have no CSP, HSTS, or clickjacking protection:
100
+
101
+ ```javascript
102
+ // next.config.js
103
+ const securityHeaders = [
104
+ { key: 'X-DNS-Prefetch-Control', value: 'on' },
105
+ { key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
106
+ { key: 'X-Frame-Options', value: 'SAMEORIGIN' },
107
+ { key: 'X-Content-Type-Options', value: 'nosniff' },
108
+ { key: 'Referrer-Policy', value: 'origin-when-cross-origin' },
109
+ {
110
+ key: 'Content-Security-Policy',
111
+ value: [
112
+ "default-src 'self'",
113
+ "script-src 'self' 'nonce-{NONCE}'", // Use nonces for inline scripts
114
+ "style-src 'self' 'unsafe-inline'", // Tighten if possible
115
+ "img-src 'self' data: https:",
116
+ "font-src 'self'",
117
+ "connect-src 'self' https://your-api.com",
118
+ ].join('; ')
119
+ }
120
+ ];
121
+
122
+ module.exports = {
123
+ async headers() {
124
+ return [{ source: '/(.*)', headers: securityHeaders }];
125
+ }
126
+ };
127
+ ```
128
+
129
+ ### Input Validation with Zod
130
+
131
+ **Validate Server Action and API route inputs with Zod** — client-side validation is UX, not security:
132
+
133
+ ```typescript
134
+ import { z } from 'zod';
135
+
136
+ const CreatePostSchema = z.object({
137
+ title: z.string().min(1).max(200),
138
+ content: z.string().min(1).max(10000),
139
+ slug: z.string().regex(/^[a-z0-9-]+$/), // Allowlist pattern
140
+ });
141
+
142
+ 'use server'
143
+ export async function createPost(formData: FormData) {
144
+ const session = await getServerSession(authOptions);
145
+ if (!session) throw new Error('Unauthorized');
146
+
147
+ const parsed = CreatePostSchema.safeParse({
148
+ title: formData.get('title'),
149
+ content: formData.get('content'),
150
+ slug: formData.get('slug'),
151
+ });
152
+
153
+ if (!parsed.success) {
154
+ return { error: parsed.error.flatten() };
155
+ }
156
+
157
+ await db.posts.create({ data: { ...parsed.data, authorId: session.user.id } });
158
+ }
159
+ ```
160
+
161
+ ---
162
+
163
+ ## ASVS Controls for Next.js Projects
164
+
165
+ | ASVS Ref | Control | Next.js Implementation |
166
+ |----------|---------|----------------------|
167
+ | V4.1.1 | Authentication on all endpoints | `getServerSession()` in every route handler and Server Action |
168
+ | V4.2.1 | Object-level authorisation | Check `resource.userId === session.user.id` before returning/modifying |
169
+ | V5.1.3 | Input validation | Zod schemas on all Server Action inputs |
170
+ | V7.1.1 | Log security events | Log auth events in NextAuth callbacks |
171
+ | V9.1.1 | TLS everywhere | Enforced by Vercel/host; add HSTS header |
172
+ | V14.4.1 | Security headers | `securityHeaders` in next.config.js |
173
+
174
+ ---
175
+
176
+ ## Common Next.js Vulnerabilities (2026)
177
+
178
+ 1. **Missing auth on Server Actions** — the most common finding in Next.js apps
179
+ 2. **IDOR via ID in URL params** — `/api/users/[id]` without ownership check
180
+ 3. **Secrets leaked to Client Components** — passed as props or in `getServerSideProps` return
181
+ 4. **No rate limiting on Server Actions** — can be abused for enumeration or spam
182
+ 5. **Unsafe redirect in Next.js redirects** — `redirect(userSuppliedUrl)` allows open redirect
183
+ 6. **Environment variables in client bundle** — `NEXT_PUBLIC_` prefix used for secrets
184
+
185
+ ---
186
+
187
+ ## Recommended Security Stack (2026)
188
+
189
+ | Category | Recommended |
190
+ |----------|-------------|
191
+ | Authentication | NextAuth.js v5 / Auth.js, Clerk, Lucia |
192
+ | Input validation | Zod, Valibot |
193
+ | Rate limiting | Upstash Ratelimit, @arcjet/next |
194
+ | Security headers | next-safe (generates CSP automatically) |
195
+ | CSRF | Built-in for Server Actions; csrf-csrf for API routes |
196
+ | Secrets | Vercel Environment Variables, Doppler, Infisical |
197
+ | ORM (injection-safe) | Prisma, Drizzle ORM |
198
+ | Image upload validation | sharp + file-type |
@@ -0,0 +1,28 @@
1
+ # Node.js (generic) Security Profile
2
+
3
+ **Runtime:** Node.js 18+ (LTS recommended)
4
+ **ASVS Baseline:** L2
5
+
6
+ Use this profile when `package.json` exists but no specific framework (Next.js, Express, etc.)
7
+ was detected. Prefer a stack-specific profile from `stacks/` when you know your framework.
8
+
9
+ ---
10
+
11
+ ## Core practices
12
+
13
+ - Validate all inputs server-side; use **Zod**, **Joi**, or **express-validator** as appropriate.
14
+ - Use **parameterised queries** or an ORM (**Prisma**, **Drizzle**, **TypeORM**); never concatenate user input into SQL.
15
+ - Store secrets in environment variables loaded at runtime, or a secrets manager — never commit `.env` with real values.
16
+ - Use **bcrypt** (cost ≥ 12) or **Argon2id** for password hashing.
17
+ - Prefer **helmet** (Express) or framework-specific security middleware for headers.
18
+ - Run **npm audit** / **Snyk** / **Dependabot** on every PR.
19
+
20
+ ---
21
+
22
+ ## When to switch to a stack profile
23
+
24
+ | If you use | Read |
25
+ |---|---|
26
+ | Next.js | `stacks/nextjs.md` |
27
+ | Express | `stacks/express.md` |
28
+ | NestJS | Nest docs + OWASP ASVS; align with Express patterns for middleware |