@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.
- package/.claude/agents/ai-security-engineer.md +209 -0
- package/.claude/agents/appsec-engineer.md +131 -0
- package/.claude/agents/cloud-platform-engineer.md +119 -0
- package/.claude/agents/dev-lead.md +138 -0
- package/.claude/agents/grc-analyst.md +143 -0
- package/.claude/agents/product-manager.md +100 -0
- package/.claude/agents/release-manager.md +126 -0
- package/.claude/agents/security-champion.md +148 -0
- package/.cursor/rules/secure-sdlc.mdc +98 -0
- package/.github/workflows/secure-sdlc-gate.yml +325 -0
- package/CHANGELOG.md +49 -0
- package/CLAUDE.md +195 -0
- package/LICENSE +21 -0
- package/README.md +394 -0
- package/cli/bin/secure-sdlc.js +95 -0
- package/cli/src/commands/gate.js +129 -0
- package/cli/src/commands/init.js +219 -0
- package/cli/src/commands/install-mcp.js +121 -0
- package/cli/src/commands/kickoff.js +261 -0
- package/cli/src/commands/paths.js +33 -0
- package/cli/src/commands/review.js +53 -0
- package/cli/src/commands/status.js +122 -0
- package/cli/src/utils/banner.js +43 -0
- package/cli/src/utils/package-root.js +23 -0
- package/cli/src/utils/phase-detect.js +107 -0
- package/cli/src/utils/stack-detect.js +138 -0
- package/docs/templates/compliance-attestation.md +159 -0
- package/docs/templates/infra-security-review.md +133 -0
- package/docs/templates/release-sign-off.md +119 -0
- package/docs/templates/risk-register.md +72 -0
- package/docs/templates/sast-findings.md +110 -0
- package/docs/templates/security-requirements.md +98 -0
- package/docs/templates/test-security-report.md +143 -0
- package/docs/templates/threat-model.md +129 -0
- package/hooks/install.sh +37 -0
- package/hooks/pre-commit +208 -0
- package/hooks/pre-push +127 -0
- package/mcp/README.md +116 -0
- package/mcp/package.json +23 -0
- package/mcp/src/server.js +638 -0
- package/package.json +67 -0
- package/stacks/django.md +216 -0
- package/stacks/express.md +229 -0
- package/stacks/fastapi.md +247 -0
- package/stacks/nextjs.md +198 -0
- package/stacks/nodejs.md +28 -0
- package/stacks/rails.md +247 -0
- package/warp-workflows/README.md +25 -0
- package/warp-workflows/feature-kickoff.yaml +49 -0
- package/warp-workflows/pr-security-review.yaml +47 -0
- package/warp-workflows/release-gate.yaml +44 -0
- package/warp-workflows/sdlc-status.yaml +48 -0
- package/warp-workflows/threat-model.yaml +56 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { mkdirSync, copyFileSync, writeFileSync, existsSync, readdirSync, readFileSync } from "fs";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import { detectStack } from "../utils/stack-detect.js";
|
|
6
|
+
import { printBanner, printSuccess, printWarn, printError, printInfo } from "../utils/banner.js";
|
|
7
|
+
import { getPackageRoot } from "../utils/package-root.js";
|
|
8
|
+
|
|
9
|
+
const REPO_ROOT = getPackageRoot();
|
|
10
|
+
|
|
11
|
+
export default async function init(options) {
|
|
12
|
+
const projectRoot = resolve(options.path || process.cwd());
|
|
13
|
+
|
|
14
|
+
printBanner();
|
|
15
|
+
console.log(chalk.bold(`Initialising Secure SDLC in: ${projectRoot}\n`));
|
|
16
|
+
|
|
17
|
+
// Detect stack
|
|
18
|
+
const stack = detectStack(projectRoot);
|
|
19
|
+
if (stack.name !== "unknown") {
|
|
20
|
+
printSuccess(`Detected stack: ${chalk.bold(stack.display)} (${stack.language})`);
|
|
21
|
+
} else {
|
|
22
|
+
printWarn("Could not detect stack — using generic security guidance");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// 1. Create docs directory structure
|
|
26
|
+
const spinner = ora("Creating docs directory structure").start();
|
|
27
|
+
try {
|
|
28
|
+
mkdirSync(join(projectRoot, "docs", "audit-evidence"), { recursive: true });
|
|
29
|
+
spinner.succeed("Docs directory created");
|
|
30
|
+
} catch (err) {
|
|
31
|
+
spinner.fail(`Failed to create docs directory: ${err.message}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Copy templates
|
|
36
|
+
const templatesDir = join(REPO_ROOT, "docs", "templates");
|
|
37
|
+
const docsDir = join(projectRoot, "docs");
|
|
38
|
+
|
|
39
|
+
if (existsSync(templatesDir)) {
|
|
40
|
+
const spinner2 = ora("Copying document templates").start();
|
|
41
|
+
let copied = 0;
|
|
42
|
+
for (const file of readdirSync(templatesDir)) {
|
|
43
|
+
const dest = join(docsDir, file);
|
|
44
|
+
if (!existsSync(dest)) {
|
|
45
|
+
copyFileSync(join(templatesDir, file), dest);
|
|
46
|
+
copied++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
spinner2.succeed(`${copied} templates copied to docs/`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 3. Create secure-sdlc.yaml config
|
|
53
|
+
const configPath = join(projectRoot, "secure-sdlc.yaml");
|
|
54
|
+
if (!existsSync(configPath)) {
|
|
55
|
+
const spinner3 = ora("Creating secure-sdlc.yaml config").start();
|
|
56
|
+
const config = generateConfig(projectRoot, stack);
|
|
57
|
+
writeFileSync(configPath, config);
|
|
58
|
+
spinner3.succeed("secure-sdlc.yaml created");
|
|
59
|
+
} else {
|
|
60
|
+
printInfo("secure-sdlc.yaml already exists — skipping");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 4. Install git hooks
|
|
64
|
+
if (!options.skipHooks) {
|
|
65
|
+
const hooksDir = join(projectRoot, ".git", "hooks");
|
|
66
|
+
if (existsSync(join(projectRoot, ".git"))) {
|
|
67
|
+
const spinner4 = ora("Installing git hooks").start();
|
|
68
|
+
try {
|
|
69
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
70
|
+
const srcHooks = join(REPO_ROOT, "hooks");
|
|
71
|
+
for (const hookFile of ["pre-commit", "pre-push"]) {
|
|
72
|
+
const src = join(srcHooks, hookFile);
|
|
73
|
+
const dest = join(hooksDir, hookFile);
|
|
74
|
+
if (existsSync(src)) {
|
|
75
|
+
copyFileSync(src, dest);
|
|
76
|
+
// Make executable
|
|
77
|
+
const { chmodSync } = await import("fs");
|
|
78
|
+
chmodSync(dest, 0o755);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
spinner4.succeed("Git hooks installed (pre-commit, pre-push)");
|
|
82
|
+
} catch (err) {
|
|
83
|
+
spinner4.warn(`Could not install git hooks: ${err.message}`);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
printWarn("Not a git repo — skipping hook installation");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 5. Generate GitHub Actions workflow
|
|
91
|
+
if (!options.skipCi) {
|
|
92
|
+
const workflowsDir = join(projectRoot, ".github", "workflows");
|
|
93
|
+
const workflowPath = join(workflowsDir, "secure-sdlc-gate.yml");
|
|
94
|
+
|
|
95
|
+
if (!existsSync(workflowPath)) {
|
|
96
|
+
const spinner5 = ora("Generating GitHub Actions workflow").start();
|
|
97
|
+
try {
|
|
98
|
+
mkdirSync(workflowsDir, { recursive: true });
|
|
99
|
+
const srcWorkflow = join(REPO_ROOT, ".github", "workflows", "secure-sdlc-gate.yml");
|
|
100
|
+
if (existsSync(srcWorkflow)) {
|
|
101
|
+
copyFileSync(srcWorkflow, workflowPath);
|
|
102
|
+
spinner5.succeed("GitHub Actions workflow created (.github/workflows/secure-sdlc-gate.yml)");
|
|
103
|
+
} else {
|
|
104
|
+
spinner5.warn("GitHub Actions template not found — skipping");
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
spinner5.warn(`Could not create workflow: ${err.message}`);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
printInfo("GitHub Actions workflow already exists — skipping");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 6. Cursor MCP setup
|
|
115
|
+
if (options.cursor) {
|
|
116
|
+
const spinner6 = ora("Configuring Cursor MCP integration").start();
|
|
117
|
+
try {
|
|
118
|
+
const cursorDir = join(projectRoot, ".cursor");
|
|
119
|
+
mkdirSync(cursorDir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
const mcpConfig = {
|
|
122
|
+
mcpServers: {
|
|
123
|
+
"secure-sdlc": {
|
|
124
|
+
command: "node",
|
|
125
|
+
args: [join(REPO_ROOT, "mcp", "src", "server.js")],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
writeFileSync(
|
|
130
|
+
join(cursorDir, "mcp.json"),
|
|
131
|
+
JSON.stringify(mcpConfig, null, 2)
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Copy Cursor rules
|
|
135
|
+
const rulesDir = join(REPO_ROOT, ".cursor", "rules");
|
|
136
|
+
const destRulesDir = join(cursorDir, "rules");
|
|
137
|
+
if (existsSync(rulesDir)) {
|
|
138
|
+
mkdirSync(destRulesDir, { recursive: true });
|
|
139
|
+
for (const file of readdirSync(rulesDir)) {
|
|
140
|
+
copyFileSync(join(rulesDir, file), join(destRulesDir, file));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
spinner6.succeed("Cursor MCP config and rules created (.cursor/)");
|
|
145
|
+
} catch (err) {
|
|
146
|
+
spinner6.warn(`Cursor setup failed: ${err.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Print summary
|
|
151
|
+
console.log("\n" + chalk.bold.green("✓ Secure SDLC initialised successfully\n"));
|
|
152
|
+
console.log(chalk.bold("Next steps:\n"));
|
|
153
|
+
console.log(` ${chalk.cyan("1.")} Start the Plan phase:\n`);
|
|
154
|
+
console.log(
|
|
155
|
+
chalk.dim(` claude --agent product-manager "Define security requirements for [your feature]"\n`)
|
|
156
|
+
);
|
|
157
|
+
console.log(
|
|
158
|
+
chalk.dim(` # OR if using MCP: sdlc_plan_feature({ feature_description: "..." })\n`)
|
|
159
|
+
);
|
|
160
|
+
console.log(` ${chalk.cyan("2.")} Check your current status:\n`);
|
|
161
|
+
console.log(chalk.dim(` secure-sdlc status\n`));
|
|
162
|
+
console.log(` ${chalk.cyan("3.")} Run the feature kickoff wizard:\n`);
|
|
163
|
+
console.log(chalk.dim(` secure-sdlc kickoff\n`));
|
|
164
|
+
|
|
165
|
+
if (stack.name !== "unknown") {
|
|
166
|
+
const { getStackSecurityNotes } = await import("../utils/stack-detect.js");
|
|
167
|
+
const notes = getStackSecurityNotes(stack.name);
|
|
168
|
+
if (notes.length) {
|
|
169
|
+
console.log(chalk.bold(`\n${stack.display} security notes for your team:\n`));
|
|
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`));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function generateConfig(projectRoot, stack) {
|
|
177
|
+
return `# Secure SDLC Configuration
|
|
178
|
+
# Generated by: secure-sdlc init
|
|
179
|
+
# Documentation: https://github.com/Kaademos/secure-sdlc-agents
|
|
180
|
+
|
|
181
|
+
project:
|
|
182
|
+
name: "${projectRoot.split("/").pop()}"
|
|
183
|
+
stack: "${stack.display}"
|
|
184
|
+
|
|
185
|
+
security:
|
|
186
|
+
asvs_level: L2 # L1 (basic), L2 (standard), L3 (high-assurance)
|
|
187
|
+
|
|
188
|
+
# Compliance frameworks applicable to this project
|
|
189
|
+
# Options: SOC2, ISO27001, NIST_CSF, PCI_DSS, GDPR, HIPAA, DORA, FedRAMP, NIS2
|
|
190
|
+
frameworks: []
|
|
191
|
+
|
|
192
|
+
# Severity thresholds that block CI/CD gates
|
|
193
|
+
gates:
|
|
194
|
+
build_to_test:
|
|
195
|
+
block_on: [CRITICAL, HIGH]
|
|
196
|
+
test_to_release:
|
|
197
|
+
block_on: [CRITICAL, HIGH]
|
|
198
|
+
release:
|
|
199
|
+
block_on: [CRITICAL]
|
|
200
|
+
warn_on: [HIGH, MEDIUM]
|
|
201
|
+
|
|
202
|
+
# Agents to invoke automatically in CI
|
|
203
|
+
ci:
|
|
204
|
+
on_pr: [dev-lead, appsec-engineer]
|
|
205
|
+
on_merge_to_main: [appsec-engineer, cloud-platform-engineer]
|
|
206
|
+
on_release: [release-manager, grc-analyst]
|
|
207
|
+
|
|
208
|
+
# Paths to key artefacts (relative to project root)
|
|
209
|
+
artefacts:
|
|
210
|
+
security_requirements: docs/security-requirements.md
|
|
211
|
+
risk_register: docs/risk-register.md
|
|
212
|
+
threat_model: docs/threat-model.md
|
|
213
|
+
infra_review: docs/infra-security-review.md
|
|
214
|
+
sast_findings: docs/sast-findings.md
|
|
215
|
+
test_report: docs/test-security-report.md
|
|
216
|
+
release_sign_off: docs/release-security-sign-off.md
|
|
217
|
+
audit_evidence: docs/audit-evidence/
|
|
218
|
+
`;
|
|
219
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
2
|
+
import { join, homedir } from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { printBanner, printSuccess, printWarn } from "../utils/banner.js";
|
|
5
|
+
import { getMcpServerPath } from "../utils/package-root.js";
|
|
6
|
+
|
|
7
|
+
const SERVER_PATH = getMcpServerPath();
|
|
8
|
+
|
|
9
|
+
const MCP_CONFIG = {
|
|
10
|
+
"secure-sdlc": {
|
|
11
|
+
command: "node",
|
|
12
|
+
args: [SERVER_PATH],
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default async function installMCP(options) {
|
|
17
|
+
const tool = options.tool || "all";
|
|
18
|
+
|
|
19
|
+
printBanner();
|
|
20
|
+
console.log(chalk.bold("Secure SDLC MCP Server Installation\n"));
|
|
21
|
+
console.log(chalk.dim(`Server path: ${SERVER_PATH}\n`));
|
|
22
|
+
|
|
23
|
+
if (!existsSync(SERVER_PATH)) {
|
|
24
|
+
console.log(chalk.red(`✗ MCP server not found at ${SERVER_PATH}`));
|
|
25
|
+
console.log(chalk.dim(" Run: npm install (from the secure-sdlc package root)"));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const installers = {
|
|
30
|
+
cursor: installCursor,
|
|
31
|
+
"claude-code": installClaudeCode,
|
|
32
|
+
windsurf: installWindsurf,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const targets = tool === "all" ? Object.keys(installers) : [tool];
|
|
36
|
+
|
|
37
|
+
for (const t of targets) {
|
|
38
|
+
if (installers[t]) {
|
|
39
|
+
await installers[t]();
|
|
40
|
+
} else {
|
|
41
|
+
printWarn(`Unknown tool: ${t}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(chalk.bold("\nAvailable MCP tools after installation:\n"));
|
|
46
|
+
const tools = [
|
|
47
|
+
"sdlc_plan_feature — ASVS requirements + risk register",
|
|
48
|
+
"sdlc_threat_model — STRIDE/LINDDUN threat model",
|
|
49
|
+
"sdlc_review_pr — Security review pull requests",
|
|
50
|
+
"sdlc_review_infra — IaC security review",
|
|
51
|
+
"sdlc_triage_sast — Triage SAST findings",
|
|
52
|
+
"sdlc_release_gate — Pre-release go/no-go gate",
|
|
53
|
+
"sdlc_check_compliance — Compliance gap analysis",
|
|
54
|
+
"sdlc_security_champion — Quick security Q&A",
|
|
55
|
+
"sdlc_ai_security_review — AI/LLM feature security review",
|
|
56
|
+
];
|
|
57
|
+
tools.forEach((t) => console.log(chalk.dim(` • ${t}`)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function installCursor() {
|
|
61
|
+
console.log(chalk.bold("Installing for Cursor...\n"));
|
|
62
|
+
|
|
63
|
+
// Global Cursor MCP config
|
|
64
|
+
const cursorConfigDir = join(homedir(), ".cursor");
|
|
65
|
+
const configPath = join(cursorConfigDir, "mcp.json");
|
|
66
|
+
|
|
67
|
+
mkdirSync(cursorConfigDir, { recursive: true });
|
|
68
|
+
|
|
69
|
+
let existing = {};
|
|
70
|
+
if (existsSync(configPath)) {
|
|
71
|
+
try {
|
|
72
|
+
existing = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
73
|
+
} catch {
|
|
74
|
+
// ignore parse error
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const updated = {
|
|
79
|
+
...existing,
|
|
80
|
+
mcpServers: {
|
|
81
|
+
...(existing.mcpServers || {}),
|
|
82
|
+
...MCP_CONFIG,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
writeFileSync(configPath, JSON.stringify(updated, null, 2));
|
|
87
|
+
printSuccess(`Cursor global MCP config updated: ${configPath}`);
|
|
88
|
+
console.log(chalk.dim(" Restart Cursor to load the new tools.\n"));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function installClaudeCode() {
|
|
92
|
+
console.log(chalk.bold("Installing for Claude Code...\n"));
|
|
93
|
+
console.log(chalk.dim("Run this command to register the MCP server:\n"));
|
|
94
|
+
console.log(chalk.cyan(` claude mcp add secure-sdlc -- node ${SERVER_PATH}\n`));
|
|
95
|
+
console.log(chalk.dim("Then verify with:\n"));
|
|
96
|
+
console.log(chalk.cyan(" claude mcp list\n"));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function installWindsurf() {
|
|
100
|
+
console.log(chalk.bold("Installing for Windsurf / Cascade...\n"));
|
|
101
|
+
|
|
102
|
+
const windsurfConfigDir = join(homedir(), ".codeium", "windsurf");
|
|
103
|
+
const configPath = join(windsurfConfigDir, "mcp_config.json");
|
|
104
|
+
|
|
105
|
+
if (existsSync(windsurfConfigDir)) {
|
|
106
|
+
mkdirSync(windsurfConfigDir, { recursive: true });
|
|
107
|
+
let existing = {};
|
|
108
|
+
if (existsSync(configPath)) {
|
|
109
|
+
try { existing = JSON.parse(readFileSync(configPath, "utf-8")); } catch { /* */ }
|
|
110
|
+
}
|
|
111
|
+
const updated = {
|
|
112
|
+
...existing,
|
|
113
|
+
mcpServers: { ...(existing.mcpServers || {}), ...MCP_CONFIG },
|
|
114
|
+
};
|
|
115
|
+
writeFileSync(configPath, JSON.stringify(updated, null, 2));
|
|
116
|
+
printSuccess(`Windsurf MCP config updated: ${configPath}`);
|
|
117
|
+
} else {
|
|
118
|
+
console.log(chalk.dim("Windsurf config directory not found. Add this to your Windsurf MCP config manually:\n"));
|
|
119
|
+
console.log(chalk.cyan(JSON.stringify({ mcpServers: MCP_CONFIG }, null, 2)));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { resolve } from "path";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import inquirer from "inquirer";
|
|
4
|
+
import { printBanner, printPhase } from "../utils/banner.js";
|
|
5
|
+
import { detectStack } from "../utils/stack-detect.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Interactive feature kickoff wizard.
|
|
9
|
+
*
|
|
10
|
+
* Guides the developer through the information needed to kick off a new
|
|
11
|
+
* feature with full Secure SDLC coverage, then generates the exact
|
|
12
|
+
* claude --agent commands (or MCP calls) to execute each phase.
|
|
13
|
+
*/
|
|
14
|
+
export default async function kickoff(options) {
|
|
15
|
+
const projectRoot = resolve(options.path || process.cwd());
|
|
16
|
+
|
|
17
|
+
printBanner();
|
|
18
|
+
console.log(chalk.bold("Feature Security Kickoff Wizard\n"));
|
|
19
|
+
console.log(chalk.dim("Answer a few questions to generate your Secure SDLC plan.\n"));
|
|
20
|
+
|
|
21
|
+
const stack = detectStack(projectRoot);
|
|
22
|
+
|
|
23
|
+
const answers = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: "input",
|
|
26
|
+
name: "featureName",
|
|
27
|
+
message: "Feature name:",
|
|
28
|
+
validate: (v) => v.trim().length > 2 || "Please enter a feature name",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: "input",
|
|
32
|
+
name: "featureDescription",
|
|
33
|
+
message: "What does this feature do? (be specific about actors, data, integrations):",
|
|
34
|
+
validate: (v) => v.trim().length > 10 || "Please describe the feature",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: "input",
|
|
38
|
+
name: "stack",
|
|
39
|
+
message: "Technology stack:",
|
|
40
|
+
default: stack.display !== "Unknown" ? stack.display : undefined,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: "list",
|
|
44
|
+
name: "asvsLevel",
|
|
45
|
+
message: "OWASP ASVS assurance level:",
|
|
46
|
+
choices: [
|
|
47
|
+
{ name: "L1 — Basic (low-risk internal tools)", value: "L1" },
|
|
48
|
+
{ name: "L2 — Standard (most applications) ← recommended", value: "L2" },
|
|
49
|
+
{ name: "L3 — High-assurance (regulated, high-risk)", value: "L3" },
|
|
50
|
+
],
|
|
51
|
+
default: "L2",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: "checkbox",
|
|
55
|
+
name: "complianceFrameworks",
|
|
56
|
+
message: "Compliance frameworks in scope (select all that apply):",
|
|
57
|
+
choices: [
|
|
58
|
+
{ name: "SOC 2 Type II", value: "SOC2" },
|
|
59
|
+
{ name: "ISO 27001:2022", value: "ISO27001" },
|
|
60
|
+
{ name: "GDPR / UK GDPR", value: "GDPR" },
|
|
61
|
+
{ name: "PCI DSS v4.0", value: "PCI-DSS" },
|
|
62
|
+
{ name: "HIPAA", value: "HIPAA" },
|
|
63
|
+
{ name: "NIST CSF 2.0", value: "NIST_CSF" },
|
|
64
|
+
{ name: "DORA", value: "DORA" },
|
|
65
|
+
{ name: "FedRAMP", value: "FedRAMP" },
|
|
66
|
+
{ name: "None / Not sure", value: "none" },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: "checkbox",
|
|
71
|
+
name: "dataTypes",
|
|
72
|
+
message: "What data does this feature handle?",
|
|
73
|
+
choices: [
|
|
74
|
+
{ name: "User credentials (passwords, tokens)", value: "credentials" },
|
|
75
|
+
{ name: "Personal Identifiable Information (PII)", value: "pii" },
|
|
76
|
+
{ name: "Payment / financial data", value: "payment" },
|
|
77
|
+
{ name: "Health / medical data (PHI)", value: "phi" },
|
|
78
|
+
{ name: "File uploads", value: "files" },
|
|
79
|
+
{ name: "Third-party API integrations", value: "integrations" },
|
|
80
|
+
{ name: "AI / LLM model calls", value: "ai" },
|
|
81
|
+
{ name: "No sensitive data", value: "none" },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "confirm",
|
|
86
|
+
name: "hasInfra",
|
|
87
|
+
message: "Does this feature include infrastructure changes (Terraform, K8s, AWS config, etc.)?",
|
|
88
|
+
default: false,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: "list",
|
|
92
|
+
name: "toolPreference",
|
|
93
|
+
message: "How do you invoke agents?",
|
|
94
|
+
choices: [
|
|
95
|
+
{ name: "Claude Code (claude --agent ...)", value: "claude-code" },
|
|
96
|
+
{ name: "Cursor MCP (sdlc_* tools)", value: "cursor-mcp" },
|
|
97
|
+
{ name: "Both", value: "both" },
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
// Generate the plan
|
|
103
|
+
const frameworks = answers.complianceFrameworks.filter((f) => f !== "none");
|
|
104
|
+
const hasPii = answers.dataTypes.includes("pii") || answers.dataTypes.includes("phi");
|
|
105
|
+
const hasAI = answers.dataTypes.includes("ai");
|
|
106
|
+
const hasFiles = answers.dataTypes.includes("files");
|
|
107
|
+
|
|
108
|
+
console.log("\n");
|
|
109
|
+
console.log(chalk.bold.green("═".repeat(60)));
|
|
110
|
+
console.log(chalk.bold.green(` Secure SDLC Plan: ${answers.featureName}`));
|
|
111
|
+
console.log(chalk.bold.green("═".repeat(60)));
|
|
112
|
+
|
|
113
|
+
// ── PLAN ──────────────────────────────────────────────────────
|
|
114
|
+
printPhase("PLAN", "Define secure requirements and risk register");
|
|
115
|
+
|
|
116
|
+
if (answers.toolPreference !== "cursor-mcp") {
|
|
117
|
+
console.log(chalk.bold("1. Product Manager — Security Requirements:"));
|
|
118
|
+
console.log(chalk.cyan(`
|
|
119
|
+
claude --agent product-manager \\
|
|
120
|
+
"Define security requirements for: ${answers.featureDescription}. \\
|
|
121
|
+
Stack: ${answers.stack}. ASVS ${answers.asvsLevel}.${
|
|
122
|
+
frameworks.length ? ` Compliance: ${frameworks.join(", ")}.` : ""
|
|
123
|
+
}"
|
|
124
|
+
`));
|
|
125
|
+
|
|
126
|
+
console.log(chalk.bold("2. GRC Analyst — Risk Register:"));
|
|
127
|
+
console.log(chalk.cyan(`
|
|
128
|
+
claude --agent grc-analyst \\
|
|
129
|
+
"Initialise risk register for ${answers.featureName}.${
|
|
130
|
+
frameworks.length ? ` Map to ${frameworks.join(", ")}.` : ""
|
|
131
|
+
}"
|
|
132
|
+
`));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (answers.toolPreference !== "claude-code") {
|
|
136
|
+
console.log(chalk.bold("MCP equivalent:"));
|
|
137
|
+
console.log(chalk.dim(`
|
|
138
|
+
sdlc_plan_feature({
|
|
139
|
+
feature_description: "${answers.featureDescription}",
|
|
140
|
+
stack: "${answers.stack}",
|
|
141
|
+
asvs_level: "${answers.asvsLevel}",
|
|
142
|
+
${frameworks.length ? `compliance_frameworks: [${frameworks.map((f) => `"${f}"`).join(", ")}],` : ""}
|
|
143
|
+
project_root: "${projectRoot}"
|
|
144
|
+
})
|
|
145
|
+
`));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── DESIGN ────────────────────────────────────────────────────
|
|
149
|
+
printPhase("DESIGN", "Threat model and infrastructure review");
|
|
150
|
+
console.log(chalk.dim("(Run after requirements are written and architecture is defined)\n"));
|
|
151
|
+
|
|
152
|
+
if (answers.toolPreference !== "cursor-mcp") {
|
|
153
|
+
console.log(chalk.bold("3. AppSec Engineer — Threat Model:"));
|
|
154
|
+
console.log(chalk.cyan(`
|
|
155
|
+
claude --agent appsec-engineer \\
|
|
156
|
+
"Threat model ${answers.featureName} using STRIDE.${hasPii ? " Also run LINDDUN — PII in scope." : ""} \\
|
|
157
|
+
Architecture: [describe your components and data flows]"
|
|
158
|
+
`));
|
|
159
|
+
|
|
160
|
+
if (answers.hasInfra) {
|
|
161
|
+
console.log(chalk.bold("4. Cloud/Platform Engineer — Infra Review:"));
|
|
162
|
+
console.log(chalk.cyan(`
|
|
163
|
+
claude --agent cloud-platform-engineer \\
|
|
164
|
+
"Review infrastructure changes for ${answers.featureName}: [describe IaC changes]"
|
|
165
|
+
`));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (answers.toolPreference !== "claude-code") {
|
|
170
|
+
console.log(chalk.bold("MCP equivalent:"));
|
|
171
|
+
console.log(chalk.dim(`
|
|
172
|
+
sdlc_threat_model({
|
|
173
|
+
architecture_description: "[describe architecture]",
|
|
174
|
+
pii_in_scope: ${hasPii},
|
|
175
|
+
project_root: "${projectRoot}"
|
|
176
|
+
})
|
|
177
|
+
`));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── BUILD ─────────────────────────────────────────────────────
|
|
181
|
+
printPhase("BUILD", "Secure code review on every PR");
|
|
182
|
+
|
|
183
|
+
if (answers.toolPreference !== "cursor-mcp") {
|
|
184
|
+
console.log(chalk.bold("5. Dev Lead — PR Review (run on every PR):"));
|
|
185
|
+
console.log(chalk.cyan(`
|
|
186
|
+
claude --agent dev-lead "Review PR #[N] — [brief PR description]"
|
|
187
|
+
claude --agent appsec-engineer "Triage any SAST findings for PR #[N]"
|
|
188
|
+
`));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (answers.toolPreference !== "claude-code") {
|
|
192
|
+
console.log(chalk.bold("MCP equivalent:"));
|
|
193
|
+
console.log(chalk.dim(`
|
|
194
|
+
sdlc_review_pr({
|
|
195
|
+
pr_description: "[what the PR does]",
|
|
196
|
+
code_diff: "[paste diff or key code sections]",
|
|
197
|
+
language_stack: "${answers.stack}"
|
|
198
|
+
})
|
|
199
|
+
`));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Special cases
|
|
203
|
+
if (hasFiles) {
|
|
204
|
+
console.log(chalk.yellow(`\n ⚠ File upload detected — ensure:`));
|
|
205
|
+
console.log(chalk.dim(` • Content-type validated by magic bytes, not client MIME`));
|
|
206
|
+
console.log(chalk.dim(` • Original filenames never used as storage keys`));
|
|
207
|
+
console.log(chalk.dim(` • SVG explicitly blocked (it's XML, can contain scripts)`));
|
|
208
|
+
console.log(chalk.dim(` • Malware scan before files become accessible to others\n`));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (hasAI) {
|
|
212
|
+
console.log(chalk.yellow(`\n ⚠ AI/LLM features detected — also run:`));
|
|
213
|
+
console.log(chalk.cyan(`
|
|
214
|
+
claude --agent ai-security-engineer \\
|
|
215
|
+
"Security review the AI feature: [describe model usage, inputs, tools/functions it can call]"
|
|
216
|
+
# OR: sdlc_ai_security_review({ ai_feature_description: "..." })
|
|
217
|
+
`));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── TEST ──────────────────────────────────────────────────────
|
|
221
|
+
printPhase("TEST", "DAST, penetration testing, and audit evidence");
|
|
222
|
+
|
|
223
|
+
if (answers.toolPreference !== "cursor-mcp") {
|
|
224
|
+
console.log(chalk.bold("6. AppSec Engineer — DAST Findings:"));
|
|
225
|
+
console.log(chalk.cyan(`
|
|
226
|
+
claude --agent appsec-engineer \\
|
|
227
|
+
"Interpret these DAST findings for ${answers.featureName}: [paste ZAP/Burp output]"
|
|
228
|
+
`));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── RELEASE ───────────────────────────────────────────────────
|
|
232
|
+
printPhase("RELEASE", "Go/no-go security gate");
|
|
233
|
+
|
|
234
|
+
if (answers.toolPreference !== "cursor-mcp") {
|
|
235
|
+
console.log(chalk.bold("7. Release Manager — Security Gate:"));
|
|
236
|
+
console.log(chalk.cyan(`
|
|
237
|
+
claude --agent release-manager \\
|
|
238
|
+
"Run pre-release security checklist for v[X.Y.Z] — ${answers.featureName}"
|
|
239
|
+
`));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (answers.toolPreference !== "claude-code") {
|
|
243
|
+
console.log(chalk.bold("MCP equivalent:"));
|
|
244
|
+
console.log(chalk.dim(`
|
|
245
|
+
sdlc_release_gate({
|
|
246
|
+
version: "v1.0.0",
|
|
247
|
+
docs_path: "${projectRoot}/docs"
|
|
248
|
+
})
|
|
249
|
+
`));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ── SEVERITY REMINDERS ────────────────────────────────────────
|
|
253
|
+
console.log("\n" + chalk.bold("Severity gates:\n"));
|
|
254
|
+
console.log(chalk.red(" CRITICAL → blocks all gates, no exceptions"));
|
|
255
|
+
console.log(chalk.yellow(" HIGH → blocks Build→Test and Test→Release without documented accepted risk"));
|
|
256
|
+
console.log(chalk.blue(" MEDIUM → requires remediation plan or accepted risk before release"));
|
|
257
|
+
console.log(chalk.dim(" LOW → tracked in risk register, does not block\n"));
|
|
258
|
+
|
|
259
|
+
console.log(chalk.bold("Track progress:\n"));
|
|
260
|
+
console.log(chalk.dim(" secure-sdlc status # Check which artefacts are complete\n"));
|
|
261
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { printBanner } from "../utils/banner.js";
|
|
4
|
+
import { getPackageRoot, getMcpServerPath } from "../utils/package-root.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Print resolved paths after npm install -g (for MCP / Cursor configuration).
|
|
8
|
+
*/
|
|
9
|
+
export default async function pathsCmd() {
|
|
10
|
+
const root = getPackageRoot();
|
|
11
|
+
printBanner();
|
|
12
|
+
console.log(chalk.bold("Package install paths\n"));
|
|
13
|
+
console.log(`${chalk.cyan("PACKAGE_ROOT")}=${root}`);
|
|
14
|
+
console.log(`${chalk.cyan("MCP_SERVER")}=${getMcpServerPath()}`);
|
|
15
|
+
console.log(`${chalk.cyan("TEMPLATES")}=${join(root, "docs", "templates")}`);
|
|
16
|
+
console.log(`${chalk.cyan("HOOKS")}=${join(root, "hooks")}`);
|
|
17
|
+
console.log(chalk.dim("\nCursor MCP snippet (copy args path):\n"));
|
|
18
|
+
console.log(
|
|
19
|
+
JSON.stringify(
|
|
20
|
+
{
|
|
21
|
+
mcpServers: {
|
|
22
|
+
"secure-sdlc": {
|
|
23
|
+
command: "node",
|
|
24
|
+
args: [getMcpServerPath()],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
null,
|
|
29
|
+
2
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
console.log();
|
|
33
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { printBanner } from "../utils/banner.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Prints a ready-to-paste prompt for dev-lead / MCP PR review.
|
|
8
|
+
* Does not call an LLM — use this to drive Claude Code or Cursor manually.
|
|
9
|
+
*/
|
|
10
|
+
export default async function review(target, options) {
|
|
11
|
+
const projectRoot = resolve(options.path || process.cwd());
|
|
12
|
+
const type = options.type || "code";
|
|
13
|
+
|
|
14
|
+
printBanner();
|
|
15
|
+
console.log(chalk.bold("Security review — copy the block below into Claude Code or Cursor\n"));
|
|
16
|
+
|
|
17
|
+
let codeBlock = "";
|
|
18
|
+
if (target) {
|
|
19
|
+
const abs = resolve(projectRoot, target);
|
|
20
|
+
if (existsSync(abs)) {
|
|
21
|
+
codeBlock = readFileSync(abs, "utf-8");
|
|
22
|
+
} else {
|
|
23
|
+
console.log(chalk.yellow(`File not found: ${abs}\n`));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const stack = options.stack || "(describe your stack)";
|
|
28
|
+
|
|
29
|
+
console.log(chalk.dim("─── Paste from here ───\n"));
|
|
30
|
+
console.log(
|
|
31
|
+
[
|
|
32
|
+
`Run a ${type} security review for this project.`,
|
|
33
|
+
"",
|
|
34
|
+
stack ? `Stack: ${stack}` : "",
|
|
35
|
+
target ? `Scope: file ${target}` : "Scope: recent changes / describe what to review",
|
|
36
|
+
"",
|
|
37
|
+
codeBlock
|
|
38
|
+
? "Code:\n```\n" + codeBlock.slice(0, 12000) + (codeBlock.length > 12000 ? "\n... (truncated)" : "") + "\n```"
|
|
39
|
+
: "(Attach diff or paste code.)",
|
|
40
|
+
"",
|
|
41
|
+
"Reference docs/security-requirements.md if it exists.",
|
|
42
|
+
"",
|
|
43
|
+
"Claude Code:",
|
|
44
|
+
' claude --agent dev-lead "Review the above for secure coding issues and dependency risks"',
|
|
45
|
+
"",
|
|
46
|
+
"Cursor (MCP):",
|
|
47
|
+
" sdlc_review_pr({ pr_description: \"...\", code_diff: \"...\", language_stack: \"...\" })",
|
|
48
|
+
]
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.join("\n")
|
|
51
|
+
);
|
|
52
|
+
console.log(chalk.dim("\n─── End ───\n"));
|
|
53
|
+
}
|