@kaademos/secure-sdlc 1.1.0 → 1.2.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.
@@ -29,7 +29,9 @@ Maintain awareness of applicable controls from:
29
29
  - **PCI DSS v4.0** (if payment card data is in scope)
30
30
  - **OWASP ASVS** (as the technical requirements anchor)
31
31
  - **GDPR / UK GDPR** (if personal data is processed)
32
- - **DORA** (if applicable to financial services)
32
+ - **HIPAA** (Security & Privacy Rules — if protected health information is in scope)
33
+ - **DORA** (if applicable to EU financial services)
34
+ - **FedRAMP** (NIST SP 800-53 baseline — if selling to US federal agencies)
33
35
 
34
36
  ---
35
37
 
@@ -54,10 +56,14 @@ When invoked at the start of a project or feature:
54
56
  ```markdown
55
57
  ## Control Mapping
56
58
 
57
- | ASVS Ref | Requirement | SOC 2 | ISO 27001 | NIST CSF | PCI DSS |
58
- |----------|-------------|-------|-----------|----------|---------|
59
- | V2.1.1 | Password complexity | CC6.1 | A.8.5 | PR.AC-1 | Req 8.3 |
60
- | V6.1.1 | Encryption at rest | CC6.7 | A.8.24 | PR.DS-1 | Req 3.5 |
59
+ | ASVS Ref | Requirement | SOC 2 | ISO 27001 | NIST CSF | PCI DSS | HIPAA | DORA | FedRAMP |
60
+ |----------|-------------|-------|-----------|----------|---------|-------|------|---------|
61
+ | V6.2.1 | Password complexity | CC6.1 | A.8.5 | PR.AC-1 | Req 8.3 | §164.312(d) | Art. 9 | IA-5 |
62
+ | V14.1.1 | Encryption at rest | CC6.7 | A.8.24 | PR.DS-1 | Req 3.5 | §164.312(a)(2)(iv) | Art. 9 | SC-28 |
63
+
64
+ > Only populate columns for frameworks selected in `secure-sdlc.yaml`. Add HIPAA when
65
+ > protected health information is processed, DORA for EU financial entities, and FedRAMP
66
+ > (NIST SP 800-53 control families: AC, AU, IA, SC, …) when targeting US federal agencies.
61
67
  ```
62
68
 
63
69
  ---
@@ -51,9 +51,9 @@ For each feature, produce a `docs/security-requirements.md` using this structure
51
51
 
52
52
  | ID | Requirement | ASVS Ref | Priority | Acceptance Criteria |
53
53
  |----|-------------|----------|----------|---------------------|
54
- | SR-001 | All API endpoints require authentication | V4.1.1 | MUST | Unauthenticated requests return HTTP 401 |
55
- | SR-002 | Passwords must meet complexity requirements | V2.1.1 | MUST | Passwords < 8 chars or common passwords rejected |
56
- | SR-003 | Sensitive data encrypted at rest | V6.1.1 | MUST | AES-256 or equivalent; key management documented |
54
+ | SR-001 | All API endpoints require authentication | V8.3.1 | MUST | Unauthenticated requests return HTTP 401 |
55
+ | SR-002 | Passwords must meet complexity requirements | V6.2.1 | MUST | Passwords < 8 chars or common passwords rejected |
56
+ | SR-003 | Sensitive data encrypted at rest | V14.1.1 | MUST | AES-256 or equivalent; key management documented |
57
57
 
58
58
  ### Privacy Requirements
59
59
  - [ ] Data minimisation: only collect fields required for this feature
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "secure-sdlc-agents",
3
+ "owner": {
4
+ "name": "Kaademos",
5
+ "email": "kaademos@github.com"
6
+ },
7
+ "metadata": {
8
+ "description": "A team of 8 AI security specialists embedded in your coding workflow — covering every phase of the Secure SDLC from requirements to release gating.",
9
+ "version": "1.2.0"
10
+ },
11
+ "plugins": [
12
+ {
13
+ "name": "secure-sdlc-agents",
14
+ "source": {
15
+ "source": "github",
16
+ "repo": "Kaademos/secure-sdlc-agents"
17
+ },
18
+ "description": "8 AI security specialist agents for the full Secure SDLC: threat modelling, AppSec, GRC, IaC review, AI/LLM security, and release gating. Works with Claude Code, Cursor, Windsurf, and any MCP-compatible tool.",
19
+ "version": "1.2.0",
20
+ "author": {
21
+ "name": "Kaademos"
22
+ },
23
+ "homepage": "https://github.com/Kaademos/secure-sdlc-agents",
24
+ "repository": "https://github.com/Kaademos/secure-sdlc-agents",
25
+ "license": "MIT",
26
+ "keywords": [
27
+ "security",
28
+ "appsec",
29
+ "sdlc",
30
+ "owasp",
31
+ "asvs",
32
+ "compliance",
33
+ "threat-modeling",
34
+ "secure-coding",
35
+ "devsecops",
36
+ "grc"
37
+ ],
38
+ "category": "security",
39
+ "tags": [
40
+ "security",
41
+ "appsec",
42
+ "devsecops",
43
+ "owasp",
44
+ "compliance",
45
+ "threat-modeling",
46
+ "agent-skills"
47
+ ],
48
+ "strict": true
49
+ }
50
+ ]
51
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "secure-sdlc-agents",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "A team of AI security specialists embedded in your coding workflow. 8 agents covering every phase of the Secure SDLC: requirements, threat modelling, code review, IaC security, compliance, and release gating. Works with Claude Code, Cursor, Windsurf, and any MCP-compatible tool.",
5
5
  "author": {
6
6
  "name": "Kaademos",
@@ -241,7 +241,7 @@ jobs:
241
241
  strategy:
242
242
  fail-fast: false
243
243
  matrix:
244
- language: ['javascript-typescript', 'python']
244
+ language: ['javascript-typescript', 'python', 'ruby', 'go', 'java-kotlin']
245
245
  steps:
246
246
  - name: Checkout
247
247
  uses: actions/checkout@v4
@@ -250,25 +250,64 @@ jobs:
250
250
  id: check-lang
251
251
  run: |
252
252
  LANG="${{ matrix.language }}"
253
+ # EXISTS gates every downstream step so absent languages skip cleanly.
254
+ # BUILD_MODE tells CodeQL how to build: interpreted languages need no
255
+ # build ("none"); Go must be built ("autobuild"); java-kotlin can use
256
+ # "none" for pure-Java repos but MUST build when Kotlin is present
257
+ # (Kotlin analysis has no build-mode: none support).
258
+ EXISTS=false
259
+ BUILD_MODE=none
253
260
  if [ "$LANG" = "javascript-typescript" ]; then
254
- find . -name "*.js" -o -name "*.ts" | grep -v node_modules | grep -q . && echo "EXISTS=true" >> $GITHUB_OUTPUT || echo "EXISTS=false" >> $GITHUB_OUTPUT
261
+ find . -name "*.js" -o -name "*.ts" | grep -v node_modules | grep -q . && EXISTS=true
255
262
  elif [ "$LANG" = "python" ]; then
256
- find . -name "*.py" | grep -q . && echo "EXISTS=true" >> $GITHUB_OUTPUT || echo "EXISTS=false" >> $GITHUB_OUTPUT
263
+ find . -name "*.py" | grep -q . && EXISTS=true
264
+ elif [ "$LANG" = "ruby" ]; then
265
+ find . -name "*.rb" | grep -q . && EXISTS=true
266
+ elif [ "$LANG" = "go" ]; then
267
+ find . -name "*.go" | grep -q . && EXISTS=true
268
+ BUILD_MODE=autobuild
269
+ elif [ "$LANG" = "java-kotlin" ]; then
270
+ find . -name "*.java" -o -name "*.kt" | grep -q . && EXISTS=true
271
+ # Kotlin requires a build; pure-Java repos skip it for reliability.
272
+ if find . -name "*.kt" | grep -q .; then
273
+ BUILD_MODE=autobuild
274
+ fi
257
275
  else
258
- echo "EXISTS=true" >> $GITHUB_OUTPUT
276
+ EXISTS=true
259
277
  fi
278
+ echo "EXISTS=$EXISTS" >> $GITHUB_OUTPUT
279
+ echo "BUILD_MODE=$BUILD_MODE" >> $GITHUB_OUTPUT
280
+ echo "Language=$LANG Exists=$EXISTS BuildMode=$BUILD_MODE"
281
+
282
+ # Set up the toolchain BEFORE CodeQL so autobuild can resolve the build.
283
+ # Without a matching toolchain, autobuild is the #1 cause of CodeQL CI
284
+ # failures on enterprise Go/Maven/Gradle projects.
285
+ - name: Set up Go
286
+ if: steps.check-lang.outputs.EXISTS == 'true' && matrix.language == 'go'
287
+ uses: actions/setup-go@v5
288
+ with:
289
+ go-version: stable
290
+ cache: false # no go.sum path assumptions; CodeQL only needs a build
291
+
292
+ - name: Set up JDK
293
+ if: steps.check-lang.outputs.EXISTS == 'true' && matrix.language == 'java-kotlin' && steps.check-lang.outputs.BUILD_MODE == 'autobuild'
294
+ uses: actions/setup-java@v4
295
+ with:
296
+ distribution: temurin
297
+ # Latest LTS. If your project targets an older JDK (11/17) and the
298
+ # build fails, change this to match — that's the one knob to turn.
299
+ java-version: '21'
260
300
 
261
301
  - name: Initialize CodeQL
262
302
  if: steps.check-lang.outputs.EXISTS == 'true'
263
303
  uses: github/codeql-action/init@v3
264
304
  with:
265
305
  languages: ${{ matrix.language }}
306
+ build-mode: ${{ steps.check-lang.outputs.BUILD_MODE }}
266
307
  queries: security-and-quality
267
308
 
268
- - name: Autobuild
269
- if: steps.check-lang.outputs.EXISTS == 'true'
270
- uses: github/codeql-action/autobuild@v3
271
-
309
+ # No standalone Autobuild step: build-mode "autobuild" runs the autobuilder
310
+ # during analysis, and build-mode "none" needs no build at all.
272
311
  - name: Perform CodeQL Analysis
273
312
  if: steps.check-lang.outputs.EXISTS == 'true'
274
313
  uses: github/codeql-action/analyze@v3
package/CHANGELOG.md CHANGED
@@ -6,6 +6,30 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
7
  ---
8
8
 
9
+ ## [1.2.0] — 2026-06-26
10
+
11
+ ### Added
12
+ - **Go stack profile** (`stacks/golang.md`) — dense, code-driven security guidance for Go (net/http, Gin, Echo, Fiber): `html/template` XSS, `database/sql`/GORM parameterisation, CORS, security headers, `gosec`/`govulncheck`
13
+ - **Go security notes** in `getStackSecurityNotes()` plus a `getStackProfile()` resolver so detected `gin`/`echo`/`fiber` projects map to the `golang` profile and notes
14
+ - **Worked example `04-oauth-flow`** — OAuth 2.0 / OIDC social login (authorization-code + PKCE); `redirect_uri` exact matching, `state` vs `nonce`, ID-token validation, anchored to ASVS 5.0 V10 and RFC 9700
15
+ - **Worked example `05-payment-processing`** — redirect-based hosted checkout (PCI DSS SAQ A); webhook signature verification, idempotency, server-side amount, reflecting the Jan 2025 SAQ A changes
16
+ - **HIPAA, DORA, and FedRAMP** control tables in `compliance-attestation.md` and the GRC agent's control-mapping example
17
+ - **Automated test suite** (`test/`, Node built-in runner, zero new dependencies) — guards version sync across manifests, agent frontmatter, and the stack-detection ↔ `stacks/*.md` mapping
18
+ - **CI workflow** (`.github/workflows/ci.yml`) — runs the suite on Node 18, 20, and 22 plus an `npm pack` content check
19
+ - **Release workflow** (`.github/workflows/release.yml`) — publishes to npm with provenance and creates a GitHub Release on `v*` tags
20
+ - **`CODE_OF_CONDUCT.md`** (Contributor Covenant 2.1), **`.editorconfig`**, npm/CI/Node README badges, and a committed `package-lock.json`
21
+
22
+ ### Changed
23
+ - **CodeQL SAST** (`secure-sdlc-gate.yml`) — matrix expanded to `ruby`, `go`, and `java-kotlin`; per-language `build-mode` with toolchain setup so compiled-language scans are reliable on enterprise repos (pure-Java uses `build-mode: none`)
24
+ - **ASVS references migrated from 4.0 to 5.0** repo-wide using the official OWASP `mapping_v4.0.3_to_v5.0.0` mapping (stack profiles, examples, agents, templates, skill, PR template)
25
+ - **`secure-sdlc init`** only prints a `stacks/<name>.md` pointer when that profile actually ships
26
+
27
+ ### Fixed
28
+ - Pre-existing CSRF control mislabel in the Django, Express, Rails, and Go stack profiles (`V14.4.5`/HSTS → real CSRF control `V3.5.1`)
29
+ - Broken `stacks/<gin|echo|fiber>.md` reference — Go framework projects now resolve to `stacks/golang.md`
30
+
31
+ ---
32
+
9
33
  ## [1.0.2]
10
34
 
11
35
  ---
package/CLAUDE.md CHANGED
@@ -175,6 +175,7 @@ If the project uses one of these stacks, reference the relevant profile in `stac
175
175
  | Django | `stacks/django.md` |
176
176
  | Express.js | `stacks/express.md` |
177
177
  | Ruby on Rails | `stacks/rails.md` |
178
+ | Go (net/http, Gin, Echo, Fiber) | `stacks/golang.md` |
178
179
 
179
180
  Stack profiles contain framework-specific vulnerability patterns, secure coding examples,
180
181
  and recommended libraries. Reference them when the dev-lead or appsec-engineer agents
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
- ![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)
2
- ![Claude Code](https://img.shields.io/badge/Claude_Code-Sub--Agents-blueviolet)
3
- ![Cursor MCP](https://img.shields.io/badge/Cursor-MCP%20Ready-blue)
4
- ![OWASP ASVS](https://img.shields.io/badge/OWASP-ASVS%20L2-orange)
1
+ [![CI](https://github.com/Kaademos/secure-sdlc-agents/actions/workflows/ci.yml/badge.svg)](https://github.com/Kaademos/secure-sdlc-agents/actions/workflows/ci.yml)
2
+ [![npm version](https://img.shields.io/npm/v/@kaademos/secure-sdlc?logo=npm)](https://www.npmjs.com/package/@kaademos/secure-sdlc)
3
+ [![node](https://img.shields.io/node/v/@kaademos/secure-sdlc)](https://nodejs.org)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
5
+ ![OWASP ASVS](https://img.shields.io/badge/OWASP-ASVS%205.0-orange)
5
6
  ![Works With](https://img.shields.io/badge/Works%20With-Claude%20%7C%20Cursor%20%7C%20Windsurf%20%7C%20Warp-brightgreen)
6
7
 
7
8
  # Secure SDLC Agents
@@ -9,6 +10,7 @@
9
10
  **8 AI security specialists. Invoked at the exact phase where each vulnerability would have been caught.**
10
11
 
11
12
  Requirements → threat modelling → code review → IaC → compliance → release gate.
13
+
12
14
  Works in Claude Code, Cursor, Windsurf, Warp, and any MCP-compatible tool.
13
15
 
14
16
  ---
@@ -92,10 +94,12 @@ What are you working on?
92
94
 
93
95
  ## Quick start
94
96
 
95
- ### Option 0 — Claude Code Plugin Marketplace (one command, nothing to install)
97
+ ### Option 0 — Claude Code Plugin Marketplace
96
98
 
97
99
  ```bash
98
100
  /plugin marketplace add Kaademos/secure-sdlc-agents
101
+
102
+ /plugin install secure-sdlc-agents@secure-sdlc-agents
99
103
  ```
100
104
 
101
105
  All 8 agents are immediately available in your session. No cloning, no npm, no file copying.
@@ -331,6 +335,7 @@ Deep, framework-specific security guidance in `stacks/`:
331
335
  | Django | [`stacks/django.md`](stacks/django.md) — CSRF, strong params, ORM injection, production settings |
332
336
  | Express.js | [`stacks/express.md`](stacks/express.md) — helmet, rate limiting, CSRF, Zod validation |
333
337
  | Ruby on Rails | [`stacks/rails.md`](stacks/rails.md) — Brakeman, Pundit, strong parameters, credentials |
338
+ | Go (net/http, Gin, Echo, Fiber) | [`stacks/golang.md`](stacks/golang.md) — html/template XSS, database/sql & GORM injection, CORS, gosec/govulncheck |
334
339
 
335
340
  ---
336
341
 
@@ -372,6 +377,8 @@ In `warp-workflows/` — import into Warp for one-click SDLC automation:
372
377
  | [`01-login-feature/`](examples/01-login-feature/) | Auth flow (bcrypt, MFA, sessions) | JWT alg:none, hardcoded secrets, cost factor |
373
378
  | [`02-api-endpoint/`](examples/02-api-endpoint/) | Public REST API | IDOR via UUID path param, IAM over-privilege |
374
379
  | [`03-file-upload/`](examples/03-file-upload/) | File upload to S3 | SVG XSS, magic byte validation, public bucket |
380
+ | [`04-oauth-flow/`](examples/04-oauth-flow/) | OAuth 2.0 / OIDC social login | redirect_uri exact match, PKCE, state vs nonce, ID-token validation |
381
+ | [`05-payment-processing/`](examples/05-payment-processing/) | Card checkout (hosted page) | PCI DSS SAQ A scoping, webhook signature, idempotency, amount tampering |
375
382
 
376
383
  ---
377
384
 
@@ -163,12 +163,17 @@ export default async function init(options) {
163
163
  console.log(chalk.dim(` secure-sdlc kickoff\n`));
164
164
 
165
165
  if (stack.name !== "unknown") {
166
- const { getStackSecurityNotes } = await import("../utils/stack-detect.js");
166
+ const { getStackSecurityNotes, getStackProfile } = await import("../utils/stack-detect.js");
167
167
  const notes = getStackSecurityNotes(stack.name);
168
168
  if (notes.length) {
169
169
  console.log(chalk.bold(`\n${stack.display} security notes for your team:\n`));
170
170
  notes.slice(0, 3).forEach((n) => console.log(chalk.dim(` • ${n}`)));
171
- console.log(chalk.dim(` (see stacks/${stack.name}.md for full guidance)\n`));
171
+ // Only point to a profile that actually ships — avoids a broken reference
172
+ // for stacks that have notes but no dedicated stacks/<name>.md (e.g. terraform).
173
+ const profile = getStackProfile(stack.name);
174
+ if (existsSync(join(REPO_ROOT, "stacks", `${profile}.md`))) {
175
+ console.log(chalk.dim(` (see stacks/${profile}.md for full guidance)\n`));
176
+ }
172
177
  }
173
178
  }
174
179
  }
@@ -79,10 +79,29 @@ export function detectStack(projectRoot) {
79
79
  return { name: "unknown", display: "Unknown", language: "Unknown" };
80
80
  }
81
81
 
82
+ /**
83
+ * Maps a detected stack name to the stack that owns its security guidance.
84
+ * Framework variants (e.g. Gin/Echo/Fiber) share their language's profile and notes.
85
+ */
86
+ const STACK_ALIASES = {
87
+ gin: "golang",
88
+ echo: "golang",
89
+ fiber: "golang",
90
+ };
91
+
92
+ /**
93
+ * Resolves the stack profile (notes key + stacks/<name>.md) for a detected stack.
94
+ * Falls back to the stack name itself when no alias applies.
95
+ */
96
+ export function getStackProfile(stackName) {
97
+ return STACK_ALIASES[stackName] || stackName;
98
+ }
99
+
82
100
  /**
83
101
  * Returns the top security considerations for a given stack.
84
102
  */
85
103
  export function getStackSecurityNotes(stackName) {
104
+ stackName = getStackProfile(stackName);
86
105
  const notes = {
87
106
  nextjs: [
88
107
  "Review Server Actions for CSRF and authorisation — they're POST endpoints by default",
@@ -120,6 +139,13 @@ export function getStackSecurityNotes(stackName) {
120
139
  "Audit before_action filters for auth — ensure every controller action is covered",
121
140
  "Brakeman is the standard Rails SAST tool — run on every PR",
122
141
  ],
142
+ golang: [
143
+ "Render HTML with html/template (context-aware escaping) — never text/template, and avoid template.HTML on user input",
144
+ "Use database/sql placeholders ($1/?) or GORM's ? conditions — never fmt.Sprintf user input into queries",
145
+ "CORS: set an explicit AllowedOrigins list — never combine wildcard/AllowAllOrigins with AllowCredentials:true",
146
+ "net/http ships no security headers — add CSP, HSTS, X-Content-Type-Options via middleware (e.g. unrolled/secure)",
147
+ "Run gosec (SAST) and govulncheck (vulnerable deps) in CI; hash passwords with bcrypt (cost ≥ 12) or argon2id",
148
+ ],
123
149
  terraform: [
124
150
  "Pin provider versions with ~> constraints, not latest",
125
151
  "Use terraform-aws-modules/terraform-google-modules — don't write IAM from scratch",
@@ -3,7 +3,7 @@
3
3
  **Release version:** v[X.Y.Z]
4
4
  **Date:** [YYYY-MM-DD]
5
5
  **Author:** GRC Analyst Agent + [Human GRC lead]
6
- **Frameworks in scope:** [SOC 2 / ISO 27001 / NIST CSF / PCI DSS / GDPR — delete inapplicable]
6
+ **Frameworks in scope:** [SOC 2 / ISO 27001 / NIST CSF / PCI DSS / GDPR / HIPAA / DORA / FedRAMP — delete inapplicable]
7
7
  **Status:** Draft / Review / Approved
8
8
 
9
9
  ---
@@ -110,6 +110,45 @@ ISO/IEC 27001:2022 Annex A, NIST CSF 2.0]
110
110
 
111
111
  ---
112
112
 
113
+ ### HIPAA *(complete only if protected health information (PHI) is in scope)*
114
+
115
+ | Standard / Rule | Safeguard | Status | Evidence Reference | Notes |
116
+ |-----------------|-----------|--------|--------------------|-------|
117
+ | §164.308(a)(1) | Security Rule — Security management process (risk analysis) | ✅ Met / ⚠️ Gap / 🚫 Fail | | |
118
+ | §164.312(a)(1) | Security Rule — Access control (technical safeguards) | | | |
119
+ | §164.312(b) | Security Rule — Audit controls | | | |
120
+ | §164.312(e)(1) | Security Rule — Transmission security (encryption in transit) | | | |
121
+ | §164.502(b) | Privacy Rule — Minimum necessary use and disclosure | | | |
122
+
123
+ *Extend with additional Security/Privacy Rule standards (e.g. §164.308 administrative, §164.310 physical) relevant to the systems in scope.*
124
+
125
+ ---
126
+
127
+ ### DORA *(complete only if an EU financial entity or critical ICT third-party provider)*
128
+
129
+ | Article / Pillar | Requirement | Status | Evidence Reference | Notes |
130
+ |------------------|-------------|--------|--------------------|-------|
131
+ | Art. 5–15 | ICT risk management framework | ✅ Met / ⚠️ Gap / 🚫 Fail | | |
132
+ | Art. 17–23 | ICT-related incident management, classification & reporting | | | |
133
+ | Art. 24–27 | Digital operational resilience testing (incl. TLPT) | | | |
134
+ | Art. 28–30 | ICT third-party risk management | | | |
135
+
136
+ ---
137
+
138
+ ### FedRAMP *(complete only if selling to US federal agencies — NIST SP 800-53 baseline)*
139
+
140
+ | Control Family | Control | Status | Evidence Reference | Notes |
141
+ |----------------|---------|--------|--------------------|-------|
142
+ | AC — Access Control | AC-2 Account management | ✅ Met / ⚠️ Gap / 🚫 Fail | | |
143
+ | AU — Audit & Accountability | AU-2 Event logging | | | |
144
+ | IA — Identification & Authentication | IA-2 Identification and authentication (organisational users) | | | |
145
+ | SC — System & Communications Protection | SC-7 Boundary protection | | | |
146
+ | SC — System & Communications Protection | SC-28 Protection of information at rest | | | |
147
+
148
+ *Select the impact baseline (Low / Moderate / High) and extend with the corresponding 800-53 control families (CM, CP, IR, RA, SI, …).*
149
+
150
+ ---
151
+
113
152
  ## Gaps
114
153
 
115
154
  Controls that are not fully met at the time of this attestation:
@@ -116,7 +116,7 @@ Priority list of mitigations to carry into the build phase:
116
116
 
117
117
  | Priority | Threat ID(s) | Mitigation | Owner | ASVS Ref |
118
118
  |----------|-------------|------------|-------|----------|
119
- | 1 | T-001, T-004 | Implement account lockout and generic error responses | Dev Lead | V2.2.1, V8.3.4 |
119
+ | 1 | T-001, T-004 | Implement account lockout and generic error responses | Dev Lead | V6.3.1, V14.1.1 |
120
120
  | 2 | | | | |
121
121
 
122
122
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaademos/secure-sdlc",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Secure SDLC agent team — CLI to scaffold docs, hooks, CI, and MCP-ready security workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,6 +29,7 @@
29
29
  "scripts": {
30
30
  "prepack": "node cli/bin/secure-sdlc.js --version",
31
31
  "sdlc": "node cli/bin/secure-sdlc.js",
32
+ "test": "node --test test/*.test.js",
32
33
  "test:pack": "npm pack --dry-run --ignore-scripts 2>&1"
33
34
  },
34
35
  "keywords": [
@@ -66,9 +66,9 @@ Produce a control mapping table that connects ASVS requirements to applicable fr
66
66
  ```markdown
67
67
  | ASVS Ref | Requirement | SOC 2 | ISO 27001 | NIST CSF | PCI DSS |
68
68
  |----------|-------------|-------|-----------|----------|---------|
69
- | V2.1.1 | Password complexity ≥ 12 chars | CC6.1 | A.8.5 | PR.AC-1 | Req 8.3 |
70
- | V6.1.1 | Encryption at rest (AES-256) | CC6.7 | A.8.24 | PR.DS-1 | Req 3.5 |
71
- | V9.1.1 | TLS 1.2+ for all external comms | CC6.7 | A.8.20 | PR.DS-2 | Req 4.2 |
69
+ | V6.2.1 | Password complexity ≥ 12 chars | CC6.1 | A.8.5 | PR.AC-1 | Req 8.3 |
70
+ | V14.1.1 | Encryption at rest (AES-256) | CC6.7 | A.8.24 | PR.DS-1 | Req 3.5 |
71
+ | V12.2.1 | TLS 1.2+ for all external comms | CC6.7 | A.8.20 | PR.DS-2 | Req 4.2 |
72
72
  ```
73
73
 
74
74
  ### Step 4 — Collect audit evidence at time of implementation
package/stacks/django.md CHANGED
@@ -194,13 +194,13 @@ DATABASE_URL = env('DATABASE_URL')
194
194
 
195
195
  | ASVS Ref | Control | Django Implementation |
196
196
  |----------|---------|----------------------|
197
- | V2.1.1 | Password complexity | `AUTH_PASSWORD_VALIDATORS` |
198
- | V3.3.1 | Session invalidation on logout | `django.contrib.auth.logout()` clears session |
199
- | V4.1.1 | Auth on endpoints | `@login_required` / `permission_classes` |
200
- | V4.2.1 | Object-level auth | Explicit ownership checks before returning objects |
201
- | V5.3.4 | No SQL injection | Use ORM; avoid `.raw()` with user input |
202
- | V14.4.1 | Security headers | Django SecurityMiddleware |
203
- | V14.4.5 | CSRF | CsrfViewMiddleware (enabled by default) |
197
+ | V6.2.1 | Password complexity | `AUTH_PASSWORD_VALIDATORS` |
198
+ | V7.4.1 | Session invalidation on logout | `django.contrib.auth.logout()` clears session |
199
+ | V8.3.1 | Auth on endpoints | `@login_required` / `permission_classes` |
200
+ | V8.2.2 | Object-level auth | Explicit ownership checks before returning objects |
201
+ | V1.2.4 | No SQL injection | Use ORM; avoid `.raw()` with user input |
202
+ | V4.1.1 | Security headers | Django SecurityMiddleware |
203
+ | V3.5.1 | CSRF | CsrfViewMiddleware (enabled by default) |
204
204
 
205
205
  ---
206
206
 
package/stacks/express.md CHANGED
@@ -204,12 +204,12 @@ const user = await prisma.user.findUnique({ where: { id: userId } });
204
204
 
205
205
  | ASVS Ref | Control | Express Implementation |
206
206
  |----------|---------|----------------------|
207
- | V4.1.1 | Auth middleware | passport.js + per-route authentication middleware |
208
- | V4.2.1 | Object-level auth | Ownership check in every route handler |
209
- | V5.1.3 | Input validation | Zod validation middleware |
210
- | V13.2.5 | Rate limiting | express-rate-limit per endpoint |
211
- | V14.4.1 | Security headers | helmet() |
212
- | V14.4.5 | CSRF | csrf-csrf or csurf |
207
+ | V8.3.1 | Auth middleware | passport.js + per-route authentication middleware |
208
+ | V8.2.2 | Object-level auth | Ownership check in every route handler |
209
+ | V2.2.1 | Input validation | Zod validation middleware |
210
+ | V3.5.2 | Rate limiting | express-rate-limit per endpoint |
211
+ | V4.1.1 | Security headers | helmet() |
212
+ | V3.5.1 | CSRF | csrf-csrf or csurf |
213
213
 
214
214
  ---
215
215
 
package/stacks/fastapi.md CHANGED
@@ -224,12 +224,12 @@ settings = Settings()
224
224
 
225
225
  | ASVS Ref | Control | FastAPI Implementation |
226
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` |
227
+ | V8.3.1 | Auth on all endpoints | `Depends(get_current_user)` on every protected endpoint |
228
+ | V8.2.2 | Object-level authorisation | `resource.owner_id == current_user.id` check |
229
+ | V2.2.1 | Input validation | Pydantic models with `Field` constraints |
230
+ | V14.1.1 | Don't confirm resource existence | Return 404 (not 403) for resources the user can't see |
231
+ | V3.5.2 | Rate limiting | slowapi or similar |
232
+ | V4.1.1 | Security headers | Add `SecurityHeadersMiddleware` |
233
233
 
234
234
  ---
235
235
 
@@ -0,0 +1,274 @@
1
+ # Go Security Profile
2
+
3
+ **Language:** Go 1.22+
4
+ **Frameworks:** net/http (stdlib), Gin, Echo, Fiber
5
+ **ASVS Baseline:** L2
6
+
7
+ ---
8
+
9
+ ## Go's Standard Library Is Safe — Until You Reach Around It
10
+
11
+ Go's stdlib gives you memory safety, `html/template` context-aware escaping, and
12
+ `database/sql` placeholders for free. Almost every Go web vulnerability comes from
13
+ *opting out* of these: using `text/template` for HTML, building SQL with `fmt.Sprintf`,
14
+ running shell strings through `exec.Command("sh", "-c", ...)`, or enabling a wildcard CORS
15
+ policy alongside credentials. This profile focuses on those opt-out traps.
16
+
17
+ ---
18
+
19
+ ## XSS — Use `html/template`, Never `text/template`, for HTML
20
+
21
+ `html/template` escapes output based on context (HTML body, attribute, JS, URL, CSS).
22
+ `text/template` performs **zero** escaping and must never render attacker-influenced HTML.
23
+
24
+ ```go
25
+ import (
26
+ "html/template" // ✓ context-aware auto-escaping
27
+ // "text/template" // ✗ NO escaping — XSS if used for HTML
28
+ )
29
+
30
+ // ✓ Safe — html/template escapes .Name in HTML context
31
+ var tmpl = template.Must(template.New("p").Parse(`<p>Hello {{ .Name }}</p>`))
32
+ tmpl.Execute(w, map[string]string{"Name": userInput}) // <script> becomes &lt;script&gt;
33
+ ```
34
+
35
+ ```go
36
+ // ✗ Unsafe — template.HTML tells the engine "this is already safe", disabling escaping
37
+ tmpl.Execute(w, map[string]any{"Bio": template.HTML(user.Bio)}) // stored XSS
38
+
39
+ // ✓ Safe — sanitise untrusted HTML first, then mark trusted
40
+ import "github.com/microcosm-cc/bluemonday"
41
+
42
+ p := bluemonday.UGCPolicy()
43
+ safe := template.HTML(p.Sanitize(user.Bio)) // allow-list of tags/attrs only
44
+ ```
45
+
46
+ **Other escaping bypasses to flag in review:** `template.JS`, `template.URL`,
47
+ `template.CSS`, `template.HTMLAttr` — each disables escaping for that context. They are
48
+ only safe with values your code fully controls, never with user input.
49
+
50
+ > Framework note: Gin's `c.HTML()`, Echo's `c.Render()`, and Fiber's `html/v2` engine all
51
+ > build on `html/template`, so context escaping applies. Setting `Content-Type: text/html`
52
+ > by hand and writing a `fmt.Sprintf`'d string with `c.String()`/`w.Write()` bypasses it.
53
+
54
+ ---
55
+
56
+ ## SQL Injection — Placeholders, Never `fmt.Sprintf`
57
+
58
+ ### `database/sql`
59
+
60
+ ```go
61
+ // ✗ SQL injection — string formatting builds the query
62
+ q := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
63
+ rows, err := db.Query(q)
64
+
65
+ // ✓ Parameterised — driver sends value separately from SQL
66
+ rows, err := db.Query("SELECT * FROM users WHERE email = $1", email) // pq / pgx
67
+ rows, err := db.Query("SELECT * FROM users WHERE email = ?", email) // MySQL / SQLite
68
+ err := db.QueryRow("SELECT id FROM users WHERE email = $1", email).Scan(&id)
69
+ ```
70
+
71
+ Identifiers (table/column names, `ORDER BY`) **cannot** be parameterised — allow-list them:
72
+
73
+ ```go
74
+ // ✗ Unsafe — attacker controls the ORDER BY clause
75
+ db.Query("SELECT * FROM users ORDER BY " + r.URL.Query().Get("sort"))
76
+
77
+ // ✓ Safe — validate against a fixed set before interpolating
78
+ var allowedSort = map[string]string{"name": "name", "created": "created_at"}
79
+ col, ok := allowedSort[r.URL.Query().Get("sort")]
80
+ if !ok {
81
+ col = "created_at"
82
+ }
83
+ db.Query("SELECT * FROM users ORDER BY " + col) // col is now a known-safe literal
84
+ ```
85
+
86
+ ### GORM
87
+
88
+ ```go
89
+ // ✓ Safe — ? placeholders are escaped by GORM
90
+ db.Where("email = ?", email).First(&user)
91
+ db.Raw("SELECT * FROM users WHERE email = ?", email).Scan(&user)
92
+
93
+ // ✗ Unsafe — Sprintf into the condition string defeats GORM's escaping
94
+ db.Where(fmt.Sprintf("email = '%s'", email)).First(&user)
95
+
96
+ // ✗ Unsafe — user-controlled column name in a struct/map update can corrupt other fields
97
+ db.Model(&user).Updates(userControlledMap) // allow-list keys, or use a typed struct
98
+ ```
99
+
100
+ ---
101
+
102
+ ## CORS — Never Wildcard Origin *with* Credentials
103
+
104
+ `Access-Control-Allow-Origin: *` combined with `Allow-Credentials: true` is rejected by
105
+ browsers, so the usual "fix" is reflecting the request `Origin` — which silently allows
106
+ **every** site. Always use an explicit allow-list.
107
+
108
+ ```go
109
+ // stdlib via github.com/rs/cors
110
+ import "github.com/rs/cors"
111
+
112
+ // ✗ Unsafe — allows any origin to make credentialed requests
113
+ c := cors.New(cors.Options{
114
+ AllowedOrigins: []string{"*"},
115
+ AllowCredentials: true,
116
+ })
117
+
118
+ // ✓ Safe — explicit origins, credentials only for those
119
+ c := cors.New(cors.Options{
120
+ AllowedOrigins: []string{"https://app.example.com"},
121
+ AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
122
+ AllowCredentials: true,
123
+ MaxAge: 300,
124
+ })
125
+ handler := c.Handler(mux)
126
+ ```
127
+
128
+ ```go
129
+ // Gin — github.com/gin-contrib/cors
130
+ import "github.com/gin-contrib/cors"
131
+
132
+ // ✗ Unsafe — AllowAllOrigins + credentials reflects every Origin
133
+ r.Use(cors.New(cors.Config{AllowAllOrigins: true, AllowCredentials: true}))
134
+
135
+ // ✓ Safe — explicit list
136
+ r.Use(cors.New(cors.Config{
137
+ AllowOrigins: []string{"https://app.example.com"},
138
+ AllowCredentials: true,
139
+ }))
140
+ ```
141
+
142
+ ```go
143
+ // Echo — middleware.CORSWithConfig (AllowOrigins) and
144
+ // Fiber — github.com/gofiber/fiber/v2/middleware/cors (AllowOrigins) follow the same rule:
145
+ // list real origins; never set AllowOrigins:"*" together with AllowCredentials:true.
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Security Headers — Stdlib Has None
151
+
152
+ `net/http` sends no security headers. Add them via middleware on every response.
153
+
154
+ ```go
155
+ func secureHeaders(next http.Handler) http.Handler {
156
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
157
+ h := w.Header()
158
+ h.Set("Content-Security-Policy", "default-src 'self'")
159
+ h.Set("X-Content-Type-Options", "nosniff")
160
+ h.Set("X-Frame-Options", "DENY")
161
+ h.Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload")
162
+ h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
163
+ next.ServeHTTP(w, r)
164
+ })
165
+ }
166
+ // Or use github.com/unrolled/secure for a configurable equivalent.
167
+ // Gin: gin-contrib/secure · Echo: middleware.Secure() · Fiber: middleware/helmet
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Authentication & Passwords
173
+
174
+ ```go
175
+ // ✓ Password hashing — bcrypt (cost ≥ 12) or argon2id
176
+ import "golang.org/x/crypto/bcrypt"
177
+
178
+ hash, err := bcrypt.GenerateFromPassword([]byte(pw), 12)
179
+ err = bcrypt.CompareHashAndPassword(hash, []byte(pw)) // constant-time
180
+
181
+ // ✗ Never — fast/unsalted hashes for passwords
182
+ sum := sha256.Sum256([]byte(pw)) // brute-forceable
183
+ ```
184
+
185
+ ```go
186
+ // ✓ Constant-time comparison for tokens/HMACs — avoid timing leaks
187
+ import "crypto/subtle"
188
+
189
+ if subtle.ConstantTimeCompare(got, want) == 1 { /* match */ }
190
+
191
+ // ✗ Unsafe — == short-circuits and leaks length/prefix via timing
192
+ if string(got) == string(want) { /* ... */ }
193
+ ```
194
+
195
+ JWT: pin the expected algorithm in the keyfunc to defeat `alg: none` / algorithm-confusion:
196
+
197
+ ```go
198
+ token, err := jwt.Parse(raw, func(t *jwt.Token) (any, error) {
199
+ if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { // ✓ reject unexpected alg
200
+ return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
201
+ }
202
+ return secret, nil
203
+ })
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Command Injection & SSRF
209
+
210
+ ```go
211
+ import "os/exec"
212
+
213
+ // ✗ Command injection — user input in a shell string
214
+ exec.Command("sh", "-c", "convert "+userFile+" out.png").Run()
215
+
216
+ // ✓ Safe — pass args as a slice; no shell, no word-splitting
217
+ exec.Command("convert", userFile, "out.png").Run()
218
+ ```
219
+
220
+ ```go
221
+ // ✗ SSRF — fetching a user-supplied URL lets attackers hit internal services / metadata
222
+ http.Get(r.FormValue("url"))
223
+
224
+ // ✓ Resolve + validate the host against an allow-list and block private/link-local IPs
225
+ // before dialing (deny 169.254.169.254, 10/8, 127/8, ::1, etc.).
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Secrets & Config
231
+
232
+ ```go
233
+ // ✓ Read secrets from the environment / a secrets manager — never hardcode
234
+ dsn := os.Getenv("DATABASE_URL")
235
+ if dsn == "" {
236
+ log.Fatal("DATABASE_URL is required")
237
+ }
238
+
239
+ // ✗ Never commit credentials
240
+ const apiKey = "sk_live_8f2b..." // gitleaks/gosec will flag this
241
+ ```
242
+
243
+ Run `gitleaks` in CI and keep `.env` out of git. Use `errors.Is`/`%w` and return generic
244
+ client errors — never write `err.Error()` (which can leak DSNs, paths, SQL) into responses.
245
+
246
+ ---
247
+
248
+ ## ASVS Controls for Go Projects
249
+
250
+ | ASVS Ref | Control | Go Implementation |
251
+ |----------|---------|-------------------|
252
+ | V2.2.1 | Input validation | `go-playground/validator` on bound structs |
253
+ | V1.3.1 | Output encoding (XSS) | `html/template`; sanitise with `bluemonday` |
254
+ | V1.2.4 | No SQL injection | `database/sql` placeholders; GORM `?` params |
255
+ | V1.2.5 | No OS command injection | `exec.Command` with arg slices, no `sh -c` |
256
+ | V11.4.2 | Password storage | `bcrypt` (cost ≥ 12) or `argon2id` |
257
+ | V3.5.1 | CSRF | `gorilla/csrf` for cookie-based sessions |
258
+ | V4.1.1 | Security headers | `unrolled/secure` middleware |
259
+
260
+ ---
261
+
262
+ ## Recommended Security Tooling (2026)
263
+
264
+ | Category | Tool |
265
+ |----------|------|
266
+ | SAST | `gosec` (`securego/gosec`) |
267
+ | Vulnerable dependencies | `govulncheck` (official, stdlib-aware) |
268
+ | Secret scanning | `gitleaks` |
269
+ | Input validation | `go-playground/validator` |
270
+ | HTML sanitisation | `bluemonday` |
271
+ | Security headers | `unrolled/secure` |
272
+ | CSRF | `gorilla/csrf` |
273
+ | Password hashing | `golang.org/x/crypto/bcrypt`, `argon2` |
274
+ | CORS | `rs/cors`, `gin-contrib/cors` |
package/stacks/nextjs.md CHANGED
@@ -164,12 +164,12 @@ export async function createPost(formData: FormData) {
164
164
 
165
165
  | ASVS Ref | Control | Next.js Implementation |
166
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 |
167
+ | V8.3.1 | Authentication on all endpoints | `getServerSession()` in every route handler and Server Action |
168
+ | V8.2.2 | Object-level authorisation | Check `resource.userId === session.user.id` before returning/modifying |
169
+ | V2.2.1 | Input validation | Zod schemas on all Server Action inputs |
170
+ | V16.2.5 | Log security events | Log auth events in NextAuth callbacks |
171
+ | V12.2.1 | TLS everywhere | Enforced by Vercel/host; add HSTS header |
172
+ | V4.1.1 | Security headers | `securityHeaders` in next.config.js |
173
173
 
174
174
  ---
175
175
 
package/stacks/rails.md CHANGED
@@ -225,12 +225,12 @@ session fixation, and many other Rails-specific issues.
225
225
 
226
226
  | ASVS Ref | Control | Rails Implementation |
227
227
  |----------|---------|---------------------|
228
- | V2.1.1 | Password complexity | Devise validates :password strength |
229
- | V2.2.1 | Account lockout | Devise :lockable |
230
- | V4.1.1 | Auth on all actions | `before_action :authenticate_user!` |
231
- | V4.2.1 | Object-level auth | Pundit policies with `authorize @resource` |
232
- | V5.3.4 | No SQL injection | ActiveRecord ORM; never string-interpolate in where() |
233
- | V14.4.5 | CSRF | `protect_from_forgery` (default) |
228
+ | V6.2.1 | Password complexity | Devise validates :password strength |
229
+ | V6.3.1 | Account lockout | Devise :lockable |
230
+ | V8.3.1 | Auth on all actions | `before_action :authenticate_user!` |
231
+ | V8.2.2 | Object-level auth | Pundit policies with `authorize @resource` |
232
+ | V1.2.4 | No SQL injection | ActiveRecord ORM; never string-interpolate in where() |
233
+ | V3.5.1 | CSRF | `protect_from_forgery` (default) |
234
234
 
235
235
  ---
236
236