@naraya/cli 0.1.0 → 0.4.1
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/LICENSE +20 -0
- package/README.md +184 -93
- package/bin/naraya-native.mjs +4 -0
- package/bin/naraya.mjs +1 -142
- package/bin/undici-timeout.mjs +1 -0
- package/dist/assets.pack.gz +0 -0
- package/dist/mcp/config-loader.js +32 -0
- package/dist/mcp/lifecycle.js +90 -0
- package/dist/mcp/tool-mapper.js +31 -0
- package/dist/mcp/transport.js +30 -0
- package/dist/pentest/catalog/catalog-loader.js +45 -0
- package/dist/pentest/catalog/index.js +1 -0
- package/dist/pentest/cli.js +117 -0
- package/dist/pentest/command-builder/command-builder.js +90 -0
- package/dist/pentest/command-builder/index.js +1 -0
- package/dist/pentest/index.js +10 -0
- package/dist/pentest/installer/index.js +1 -0
- package/dist/pentest/installer/tool-installer.js +90 -0
- package/dist/pentest/manager.js +125 -0
- package/dist/pentest/mode/index.js +1 -0
- package/dist/pentest/mode/mode-selector.js +127 -0
- package/dist/pentest/selector/index.js +1 -0
- package/dist/pentest/selector/tool-selector.js +66 -0
- package/dist/pentest/skill-bridge/index.js +1 -0
- package/dist/pentest/skill-bridge/skill-bridge.js +66 -0
- package/dist/pentest/skills/generator/index.js +1 -0
- package/dist/pentest/skills/generator/skill-generator.js +310 -0
- package/dist/pentest/skills/index.js +3 -0
- package/dist/pentest/skills/loader/index.js +1 -0
- package/dist/pentest/skills/loader/skill-loader.js +167 -0
- package/dist/pentest/skills/register/index.js +1 -0
- package/dist/pentest/skills/register/skill-register.js +162 -0
- package/dist/pentest/skills/types.js +1 -0
- package/dist/pentest/types.js +90 -0
- package/package.json +42 -14
- package/src/assets-pack.mjs +1 -0
- package/src/banner.mjs +5 -0
- package/src/clipboard.mjs +1 -0
- package/src/config.mjs +1 -40
- package/src/goodbye.mjs +7 -0
- package/src/login.mjs +7 -49
- package/src/mcp/config-loader.ts +50 -0
- package/src/mcp/lifecycle.ts +113 -0
- package/src/mcp/tool-mapper.ts +42 -0
- package/src/mcp/transport.ts +38 -0
- package/src/mcp-cli.mjs +5 -0
- package/src/pentest/catalog/catalog-loader.ts +55 -0
- package/src/pentest/catalog/index.ts +1 -0
- package/src/pentest/cli.ts +130 -0
- package/src/pentest/command-builder/command-builder.ts +109 -0
- package/src/pentest/command-builder/index.ts +1 -0
- package/src/pentest/index.ts +11 -0
- package/src/pentest/installer/index.ts +1 -0
- package/src/pentest/installer/tool-installer.ts +107 -0
- package/src/pentest/manager.ts +167 -0
- package/src/pentest/mode/index.ts +1 -0
- package/src/pentest/mode/mode-selector.ts +159 -0
- package/src/pentest/selector/index.ts +1 -0
- package/src/pentest/selector/tool-selector.ts +87 -0
- package/src/pentest/skill-bridge/index.ts +1 -0
- package/src/pentest/skill-bridge/skill-bridge.ts +86 -0
- package/src/pentest/skills/generator/index.ts +1 -0
- package/src/pentest/skills/generator/skill-generator.ts +373 -0
- package/src/pentest/skills/index.ts +4 -0
- package/src/pentest/skills/loader/index.ts +1 -0
- package/src/pentest/skills/loader/skill-loader.ts +206 -0
- package/src/pentest/skills/register/index.ts +1 -0
- package/src/pentest/skills/register/skill-register.ts +196 -0
- package/src/pentest/skills/types.ts +66 -0
- package/src/pentest/types.ts +341 -0
- package/src/seed.mjs +1 -36
- package/src/splash.mjs +4 -0
- package/src/status.mjs +2 -71
- package/assets/APPEND-SYSTEM.md +0 -9
- package/assets/extensions/naraya-brand.ts +0 -251
- package/assets/extensions/naraya-gate.ts +0 -23
- package/assets/naraya-logo.txt +0 -5
- package/assets/skills/narabuild/SKILL.md +0 -156
- package/assets/skills/naradroid/SKILL.md +0 -118
- package/assets/skills/naraexplore/SKILL.md +0 -71
- package/assets/skills/narafe/SKILL.md +0 -94
- package/assets/skills/naraplan/SKILL.md +0 -47
- package/assets/skills/narasearch/SKILL.md +0 -141
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PentestMode,
|
|
3
|
+
ModeConfig,
|
|
4
|
+
ModeSelectorOptions,
|
|
5
|
+
ModeSelectionResult,
|
|
6
|
+
} from "../types.js"
|
|
7
|
+
import { MODE_PRESETS } from "../types.js"
|
|
8
|
+
|
|
9
|
+
const WEB_EXTENSIONS = [".php", ".asp", ".aspx", ".jsp", ".cgi", ".pl", ".py", ".rb"]
|
|
10
|
+
const NETWORK_PORTS = [22, 23, 3389, 445, 139, 135, 1433, 3306, 5432, 27017]
|
|
11
|
+
const CTF_INDICATORS = ["ctf", "chall", "pwn", "crypto", "forensics", "rev", "misc", "web"]
|
|
12
|
+
const RED_TEAM_INDICATORS = ["ad", "active-directory", "kerberos", "ldap", "smb", "rdp"]
|
|
13
|
+
|
|
14
|
+
export function detectMode(target: string): { mode: PentestMode; reason: string } {
|
|
15
|
+
const lower = target.toLowerCase()
|
|
16
|
+
|
|
17
|
+
for (const indicator of CTF_INDICATORS) {
|
|
18
|
+
if (lower.includes(indicator)) {
|
|
19
|
+
return { mode: "ctf", reason: `CTF indicator detected: ${indicator}` }
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const indicator of RED_TEAM_INDICATORS) {
|
|
24
|
+
if (lower.includes(indicator)) {
|
|
25
|
+
return { mode: "red-team", reason: `Red team indicator detected: ${indicator}` }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const ext of WEB_EXTENSIONS) {
|
|
30
|
+
if (lower.endsWith(ext)) {
|
|
31
|
+
return { mode: "bug-bounty", reason: `Web extension detected: ${ext}` }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const portMatch = lower.match(/:(\d+)/)
|
|
36
|
+
if (portMatch) {
|
|
37
|
+
const port = parseInt(portMatch[1], 10)
|
|
38
|
+
if (NETWORK_PORTS.includes(port)) {
|
|
39
|
+
return { mode: "red-team", reason: `Network service port detected: ${port}` }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (lower.includes("http://") || lower.includes("https://")) {
|
|
44
|
+
return { mode: "bug-bounty", reason: "Web target detected (HTTP/HTTPS)" }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ipMatch = lower.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
|
|
48
|
+
if (ipMatch) {
|
|
49
|
+
return { mode: "red-team", reason: `IP address target: ${ipMatch[0]}` }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { mode: "auto", reason: "No specific indicators detected, using adaptive mode" }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function selectMode(options: ModeSelectorOptions = {}): ModeSelectionResult {
|
|
56
|
+
if (options.preferred_mode && options.preferred_mode !== "auto") {
|
|
57
|
+
const config = MODE_PRESETS[options.preferred_mode]
|
|
58
|
+
return {
|
|
59
|
+
selected_mode: options.preferred_mode,
|
|
60
|
+
config,
|
|
61
|
+
auto_detected: false,
|
|
62
|
+
detection_reason: `User selected mode: ${options.preferred_mode}`,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (options.auto_detect !== false && options.target) {
|
|
67
|
+
const detection = detectMode(options.target)
|
|
68
|
+
if (detection.mode !== "auto") {
|
|
69
|
+
const config = MODE_PRESETS[detection.mode]
|
|
70
|
+
return {
|
|
71
|
+
selected_mode: detection.mode,
|
|
72
|
+
config,
|
|
73
|
+
auto_detected: true,
|
|
74
|
+
detection_reason: detection.reason,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const config = MODE_PRESETS["auto"]
|
|
80
|
+
return {
|
|
81
|
+
selected_mode: "auto",
|
|
82
|
+
config,
|
|
83
|
+
auto_detected: false,
|
|
84
|
+
detection_reason: "No specific mode detected, using adaptive auto mode",
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getModeConfig(mode: PentestMode): ModeConfig {
|
|
89
|
+
return MODE_PRESETS[mode]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function getToolsForMode(
|
|
93
|
+
mode: PentestMode,
|
|
94
|
+
availableTools: readonly string[],
|
|
95
|
+
): string[] {
|
|
96
|
+
const config = MODE_PRESETS[mode]
|
|
97
|
+
const priority = config.tool_priority
|
|
98
|
+
|
|
99
|
+
const prioritized: string[] = []
|
|
100
|
+
const remaining: string[] = []
|
|
101
|
+
|
|
102
|
+
for (const tool of availableTools) {
|
|
103
|
+
const toolCategory = getToolCategory(tool)
|
|
104
|
+
if (toolCategory && (priority as readonly string[]).includes(toolCategory)) {
|
|
105
|
+
prioritized.push(tool)
|
|
106
|
+
} else {
|
|
107
|
+
remaining.push(tool)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
prioritized.sort((a, b) => {
|
|
112
|
+
const catA = getToolCategory(a)
|
|
113
|
+
const catB = getToolCategory(b)
|
|
114
|
+
if (!catA || !catB) return 0
|
|
115
|
+
return (priority as readonly string[]).indexOf(catA) - (priority as readonly string[]).indexOf(catB)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return [...prioritized, ...remaining]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getToolCategory(toolName: string): string | undefined {
|
|
122
|
+
const categoryMap: Record<string, string> = {
|
|
123
|
+
subfinder: "recon", amass: "recon", assetfinder: "recon", httpx: "recon",
|
|
124
|
+
naabu: "recon", massdns: "recon", nmap: "enumeration", nuclei: "enumeration",
|
|
125
|
+
ffuf: "enumeration", dirsearch: "enumeration", gobuster: "enumeration",
|
|
126
|
+
feroxbuster: "enumeration", whatweb: "enumeration", wafw00f: "enumeration",
|
|
127
|
+
nikto: "enumeration", burpsuite: "enumeration", owasp_zap: "enumeration",
|
|
128
|
+
sqlmap: "exploitation", commix: "exploitation", hydra: "exploitation",
|
|
129
|
+
hashcat: "exploitation", john: "exploitation", metasploit: "exploitation",
|
|
130
|
+
pwntools: "exploitation",
|
|
131
|
+
curl: "utility", jq: "utility", anew: "utility", grep: "utility",
|
|
132
|
+
sort: "utility", uniq: "utility", notify: "reporting",
|
|
133
|
+
}
|
|
134
|
+
return categoryMap[toolName]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function getSkillsForMode(mode: PentestMode): string[] {
|
|
138
|
+
return [...MODE_PRESETS[mode].skill_chain]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getLoopConfig(mode: PentestMode) {
|
|
142
|
+
return MODE_PRESETS[mode].loop_config
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function getSafetyConfig(mode: PentestMode) {
|
|
146
|
+
return MODE_PRESETS[mode].safety_constraints
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function getReportFormat(mode: PentestMode) {
|
|
150
|
+
return MODE_PRESETS[mode].report_format
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function isModeStealth(mode: PentestMode): boolean {
|
|
154
|
+
return MODE_PRESETS[mode].stealth
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function getModeParallelism(mode: PentestMode): number {
|
|
158
|
+
return MODE_PRESETS[mode].parallelism
|
|
159
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./tool-selector.js"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ToolsCatalog, ToolEntry, ToolSelectorOptions, PentestPhase } from "../types.js"
|
|
2
|
+
|
|
3
|
+
export function selectTools(catalog: ToolsCatalog, options: ToolSelectorOptions): ToolEntry[] {
|
|
4
|
+
let results = [...catalog.tools]
|
|
5
|
+
|
|
6
|
+
if (options.category) {
|
|
7
|
+
results = results.filter(t => t.category === options.category)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (options.phase) {
|
|
11
|
+
results = results.filter(t => t.phase.includes(options.phase!))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (options.tags && options.tags.length > 0) {
|
|
15
|
+
results = results.filter(t =>
|
|
16
|
+
options.tags!.some(tag => t.tags.includes(tag))
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (options.requires_root !== undefined) {
|
|
21
|
+
results = results.filter(t => t.requires_root === options.requires_root)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return results
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function selectToolsByPhase(catalog: ToolsCatalog, phase: "recon" | "enumeration" | "exploitation" | "reporting"): ToolEntry[] {
|
|
28
|
+
return selectTools(catalog, { phase })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function selectToolsByCategory(catalog: ToolsCatalog, category: "recon" | "enumeration" | "exploitation" | "reporting" | "utility"): ToolEntry[] {
|
|
32
|
+
return selectTools(catalog, { category })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function selectToolsByTags(catalog: ToolsCatalog, tags: readonly string[]): ToolEntry[] {
|
|
36
|
+
return selectTools(catalog, { tags })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function groupToolsByPhase(catalog: ToolsCatalog): Record<string, ToolEntry[]> {
|
|
40
|
+
const groups: Record<string, ToolEntry[]> = {
|
|
41
|
+
recon: [],
|
|
42
|
+
enumeration: [],
|
|
43
|
+
exploitation: [],
|
|
44
|
+
reporting: [],
|
|
45
|
+
utility: []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const tool of catalog.tools) {
|
|
49
|
+
for (const phase of tool.phase) {
|
|
50
|
+
if (groups[phase]) {
|
|
51
|
+
groups[phase].push(tool)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return groups
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function groupToolsByCategory(catalog: ToolsCatalog): Record<string, ToolEntry[]> {
|
|
60
|
+
const groups: Record<string, ToolEntry[]> = {}
|
|
61
|
+
|
|
62
|
+
for (const category of catalog.categories) {
|
|
63
|
+
groups[category] = []
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const tool of catalog.tools) {
|
|
67
|
+
if (groups[tool.category]) {
|
|
68
|
+
groups[tool.category].push(tool)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return groups
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function findAlternatives(catalog: ToolsCatalog, toolName: string): ToolEntry[] {
|
|
76
|
+
const tool = catalog.tools.find(t => t.tools_name === toolName)
|
|
77
|
+
if (!tool?.alternatives?.length) return []
|
|
78
|
+
|
|
79
|
+
return catalog.tools.filter(t => tool.alternatives!.includes(t.tools_name))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getToolPipeline(catalog: ToolsCatalog, toolName: string): ToolEntry[] {
|
|
83
|
+
const tool = catalog.tools.find(t => t.tools_name === toolName)
|
|
84
|
+
if (!tool?.command.pipes?.length) return []
|
|
85
|
+
|
|
86
|
+
return catalog.tools.filter(t => tool.command.pipes!.includes(t.tools_name))
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./skill-bridge.js"
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ToolEntry, SkillBridgeResult } from "../types.js"
|
|
2
|
+
import { existsSync } from "fs"
|
|
3
|
+
import { join } from "path"
|
|
4
|
+
|
|
5
|
+
const SKILL_DIRS = [
|
|
6
|
+
".agents/skills",
|
|
7
|
+
".opencode/skills",
|
|
8
|
+
".claude/skills"
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
export function resolveSkillPath(skillName: string, baseDir: string = process.cwd()): string | null {
|
|
12
|
+
for (const dir of SKILL_DIRS) {
|
|
13
|
+
const skillPath = join(baseDir, dir, skillName, "SKILL.md")
|
|
14
|
+
if (existsSync(skillPath)) {
|
|
15
|
+
return skillPath
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function loadToolSkill(tool: ToolEntry, baseDir: string = process.cwd()): SkillBridgeResult {
|
|
22
|
+
const skillPath = resolveSkillPath(tool.skills_loader, baseDir)
|
|
23
|
+
|
|
24
|
+
if (!skillPath) {
|
|
25
|
+
return {
|
|
26
|
+
skill_name: tool.skills_loader,
|
|
27
|
+
loaded: false,
|
|
28
|
+
error: `Skill not found: ${tool.skills_loader}`
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
skill_name: tool.skills_loader,
|
|
34
|
+
skill_path: skillPath,
|
|
35
|
+
loaded: true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function loadToolSkills(tools: readonly ToolEntry[], baseDir: string = process.cwd()): SkillBridgeResult[] {
|
|
40
|
+
return tools.map(tool => loadToolSkill(tool, baseDir))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getSkillForTool(tools: readonly ToolEntry[], toolName: string): SkillBridgeResult | null {
|
|
44
|
+
const tool = tools.find(t => t.tools_name === toolName)
|
|
45
|
+
if (!tool) return null
|
|
46
|
+
return loadToolSkill(tool)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function groupToolsBySkill(tools: readonly ToolEntry[]): Record<string, ToolEntry[]> {
|
|
50
|
+
const groups: Record<string, ToolEntry[]> = {}
|
|
51
|
+
|
|
52
|
+
for (const tool of tools) {
|
|
53
|
+
const skill = tool.skills_loader
|
|
54
|
+
if (!groups[skill]) {
|
|
55
|
+
groups[skill] = []
|
|
56
|
+
}
|
|
57
|
+
groups[skill].push(tool)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return groups
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getToolsForSkill(tools: readonly ToolEntry[], skillName: string): ToolEntry[] {
|
|
64
|
+
return tools.filter(t => t.skills_loader === skillName)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface SkillManifest {
|
|
68
|
+
readonly skill_name: string
|
|
69
|
+
readonly skill_path: string | null
|
|
70
|
+
readonly tools: readonly ToolEntry[]
|
|
71
|
+
readonly available: boolean
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function buildSkillManifest(tools: readonly ToolEntry[], baseDir: string = process.cwd()): SkillManifest[] {
|
|
75
|
+
const grouped = groupToolsBySkill(tools)
|
|
76
|
+
|
|
77
|
+
return Object.entries(grouped).map(([skillName, skillTools]) => {
|
|
78
|
+
const skillPath = resolveSkillPath(skillName, baseDir)
|
|
79
|
+
return {
|
|
80
|
+
skill_name: skillName,
|
|
81
|
+
skill_path: skillPath,
|
|
82
|
+
tools: skillTools,
|
|
83
|
+
available: skillPath !== null
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./skill-generator.js"
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import type { PentestSkill, GeneratedSkill, SkillGenerationOptions } from "../types.js"
|
|
2
|
+
import type { ToolEntry, ToolsCatalog, PentestPhase, ToolCategory } from "../../types.js"
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs"
|
|
4
|
+
import { join, dirname } from "path"
|
|
5
|
+
|
|
6
|
+
const DEFAULT_OUTPUT_DIR = ".agents/skills"
|
|
7
|
+
|
|
8
|
+
const TEMPLATES: Record<string, (opts: SkillGenerationOptions, tools: readonly ToolEntry[]) => string> = {
|
|
9
|
+
basic: generateBasicTemplate,
|
|
10
|
+
recon: generateReconTemplate,
|
|
11
|
+
exploitation: generateExploitationTemplate,
|
|
12
|
+
reporting: generateReportingTemplate,
|
|
13
|
+
custom: generateBasicTemplate,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function generateSkill(
|
|
17
|
+
options: SkillGenerationOptions,
|
|
18
|
+
catalog?: ToolsCatalog,
|
|
19
|
+
): GeneratedSkill {
|
|
20
|
+
const templateName = options.template ?? "basic"
|
|
21
|
+
const templateFn = TEMPLATES[templateName] ?? TEMPLATES.basic
|
|
22
|
+
|
|
23
|
+
const catalogTools = catalog
|
|
24
|
+
? catalog.tools.filter(t => options.tools.includes(t.tools_name))
|
|
25
|
+
: []
|
|
26
|
+
|
|
27
|
+
const content = templateFn(options, catalogTools)
|
|
28
|
+
const outputDir = options.output_dir ?? DEFAULT_OUTPUT_DIR
|
|
29
|
+
const skillDir = join(outputDir, options.name)
|
|
30
|
+
const skillPath = join(skillDir, "SKILL.md")
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
name: options.name,
|
|
34
|
+
content,
|
|
35
|
+
path: skillPath,
|
|
36
|
+
tools_referenced: options.tools,
|
|
37
|
+
phase: options.phase,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function generateAndSaveSkill(
|
|
42
|
+
options: SkillGenerationOptions,
|
|
43
|
+
catalog?: ToolsCatalog,
|
|
44
|
+
): string {
|
|
45
|
+
const skill = generateSkill(options, catalog)
|
|
46
|
+
const dir = dirname(skill.path)
|
|
47
|
+
|
|
48
|
+
if (!existsSync(dir)) {
|
|
49
|
+
mkdirSync(dir, { recursive: true })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
writeFileSync(skill.path, skill.content)
|
|
53
|
+
return skill.path
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function generateSkillsForPhase(
|
|
57
|
+
phase: PentestPhase,
|
|
58
|
+
catalog: ToolsCatalog,
|
|
59
|
+
outputDir?: string,
|
|
60
|
+
): GeneratedSkill[] {
|
|
61
|
+
const phaseTools = catalog.tools.filter(t => t.phase.includes(phase))
|
|
62
|
+
const skills: GeneratedSkill[] = []
|
|
63
|
+
|
|
64
|
+
for (const tool of phaseTools) {
|
|
65
|
+
const template = phaseToTemplate(phase)
|
|
66
|
+
const skill = generateSkill({
|
|
67
|
+
name: `${tool.tools_name}-${phase}`,
|
|
68
|
+
description: `Automated ${phase} skill using ${tool.tools_name}: ${tool.description}`,
|
|
69
|
+
phase: [phase],
|
|
70
|
+
category: [tool.category],
|
|
71
|
+
tools: [tool.tools_name],
|
|
72
|
+
tags: [...tool.tags],
|
|
73
|
+
output_dir: outputDir,
|
|
74
|
+
template,
|
|
75
|
+
}, catalog)
|
|
76
|
+
|
|
77
|
+
skills.push(skill)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return skills
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function generateSkillsForTool(
|
|
84
|
+
toolName: string,
|
|
85
|
+
catalog: ToolsCatalog,
|
|
86
|
+
outputDir?: string,
|
|
87
|
+
): GeneratedSkill[] {
|
|
88
|
+
const tool = catalog.tools.find(t => t.tools_name === toolName)
|
|
89
|
+
if (!tool) return []
|
|
90
|
+
|
|
91
|
+
return tool.phase.map(phase => {
|
|
92
|
+
return generateSkill({
|
|
93
|
+
name: `${toolName}-${phase}`,
|
|
94
|
+
description: `${tool.description} — ${phase} phase skill`,
|
|
95
|
+
phase: [phase],
|
|
96
|
+
category: [tool.category],
|
|
97
|
+
tools: [toolName],
|
|
98
|
+
tags: [...tool.tags, phase],
|
|
99
|
+
output_dir: outputDir,
|
|
100
|
+
template: phaseToTemplate(phase),
|
|
101
|
+
}, catalog)
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function generateFullPentestSuite(
|
|
106
|
+
catalog: ToolsCatalog,
|
|
107
|
+
outputDir?: string,
|
|
108
|
+
): GeneratedSkill[] {
|
|
109
|
+
const allSkills: GeneratedSkill[] = []
|
|
110
|
+
|
|
111
|
+
for (const category of catalog.categories) {
|
|
112
|
+
const skills = generateSkillsForPhase(category as PentestPhase, catalog, outputDir)
|
|
113
|
+
allSkills.push(...skills)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return allSkills
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function saveGeneratedSkills(skills: readonly GeneratedSkill[]): string[] {
|
|
120
|
+
const paths: string[] = []
|
|
121
|
+
|
|
122
|
+
for (const skill of skills) {
|
|
123
|
+
const dir = dirname(skill.path)
|
|
124
|
+
if (!existsSync(dir)) {
|
|
125
|
+
mkdirSync(dir, { recursive: true })
|
|
126
|
+
}
|
|
127
|
+
writeFileSync(skill.path, skill.content)
|
|
128
|
+
paths.push(skill.path)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return paths
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function phaseToTemplate(phase: PentestPhase): "basic" | "recon" | "exploitation" | "reporting" {
|
|
135
|
+
switch (phase) {
|
|
136
|
+
case "recon":
|
|
137
|
+
case "enumeration":
|
|
138
|
+
return "recon"
|
|
139
|
+
case "exploitation":
|
|
140
|
+
return "exploitation"
|
|
141
|
+
case "reporting":
|
|
142
|
+
return "reporting"
|
|
143
|
+
default:
|
|
144
|
+
return "basic"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function buildFrontmatter(options: SkillGenerationOptions): string {
|
|
149
|
+
const lines = [
|
|
150
|
+
"---",
|
|
151
|
+
`name: ${options.name}`,
|
|
152
|
+
`description: "${options.description}"`,
|
|
153
|
+
`version: 1.0.0`,
|
|
154
|
+
`phase: [${options.phase.map(p => `"${p}"`).join(", ")}]`,
|
|
155
|
+
`category: [${options.category.map(c => `"${c}"`).join(", ")}]`,
|
|
156
|
+
`tools: [${options.tools.map(t => `"${t}"`).join(", ")}]`,
|
|
157
|
+
`tags: [${(options.tags ?? []).map(t => `"${t}"`).join(", ")}]`,
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
if (options.author) {
|
|
161
|
+
lines.push(`author: "${options.author}"`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
lines.push("---")
|
|
165
|
+
return lines.join("\n")
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function buildToolReferenceSection(tools: readonly ToolEntry[]): string {
|
|
169
|
+
if (tools.length === 0) return ""
|
|
170
|
+
|
|
171
|
+
const lines = ["\n## Tools Reference\n"]
|
|
172
|
+
|
|
173
|
+
for (const tool of tools) {
|
|
174
|
+
lines.push(`### ${tool.tools_name}`)
|
|
175
|
+
lines.push(`${tool.description}\n`)
|
|
176
|
+
lines.push(`**Command:** \`${tool.command.base}\``)
|
|
177
|
+
lines.push(`**Category:** ${tool.category}`)
|
|
178
|
+
lines.push(`**Phase:** ${tool.phase.join(", ")}`)
|
|
179
|
+
lines.push(`**Requires root:** ${tool.requires_root ? "Yes" : "No"}`)
|
|
180
|
+
|
|
181
|
+
if (tool.command.flags.length > 0) {
|
|
182
|
+
lines.push("\n**Key flags:**")
|
|
183
|
+
for (const flag of tool.command.flags.slice(0, 5)) {
|
|
184
|
+
const req = flag.required ? " (required)" : ""
|
|
185
|
+
lines.push(`- \`${flag.name}\` — ${flag.description}${req}`)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (tool.homepage) {
|
|
190
|
+
lines.push(`\n**Homepage:** ${tool.homepage}`)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
lines.push("")
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return lines.join("\n")
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function generateBasicTemplate(options: SkillGenerationOptions, tools: readonly ToolEntry[]): string {
|
|
200
|
+
const fm = buildFrontmatter(options)
|
|
201
|
+
const toolRef = buildToolReferenceSection(tools)
|
|
202
|
+
|
|
203
|
+
return `${fm}
|
|
204
|
+
|
|
205
|
+
# ${options.name}
|
|
206
|
+
|
|
207
|
+
${options.description}
|
|
208
|
+
|
|
209
|
+
## Prerequisites
|
|
210
|
+
|
|
211
|
+
${tools.map(t => `- ${t.tools_name} must be installed and accessible`).join("\n")}
|
|
212
|
+
|
|
213
|
+
## Usage
|
|
214
|
+
|
|
215
|
+
\`\`\`bash
|
|
216
|
+
# Step 1: Run the tool(s)
|
|
217
|
+
${tools.map(t => `${t.command.base} ${t.command.flags.filter(f => f.required).map(f => `${f.name} <value>`).join(" ")}`).join("\n")}
|
|
218
|
+
\`\`\`
|
|
219
|
+
|
|
220
|
+
## Expected Output
|
|
221
|
+
|
|
222
|
+
Describe expected output and how to interpret results.
|
|
223
|
+
|
|
224
|
+
## Next Steps
|
|
225
|
+
|
|
226
|
+
Describe follow-up actions based on findings.
|
|
227
|
+
${toolRef}`
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function generateReconTemplate(options: SkillGenerationOptions, tools: readonly ToolEntry[]): string {
|
|
231
|
+
const fm = buildFrontmatter(options)
|
|
232
|
+
const toolRef = buildToolReferenceSection(tools)
|
|
233
|
+
|
|
234
|
+
return `${fm}
|
|
235
|
+
|
|
236
|
+
# ${options.name} — Reconnaissance Skill
|
|
237
|
+
|
|
238
|
+
${options.description}
|
|
239
|
+
|
|
240
|
+
## Phase: Reconnaissance
|
|
241
|
+
|
|
242
|
+
### Objectives
|
|
243
|
+
- Discover target attack surface
|
|
244
|
+
- Enumerate subdomains, ports, and services
|
|
245
|
+
- Identify technologies and potential entry points
|
|
246
|
+
|
|
247
|
+
### Prerequisites
|
|
248
|
+
${tools.map(t => `- **${t.tools_name}**: ${t.description}`).join("\n")}
|
|
249
|
+
|
|
250
|
+
### Step 1: Subdomain Discovery
|
|
251
|
+
\`\`\`bash
|
|
252
|
+
${tools.filter(t => t.tags.includes("subdomain")).map(t =>
|
|
253
|
+
`${t.command.base} -d <target-domain> -o subdomains.txt`
|
|
254
|
+
).join("\n")}
|
|
255
|
+
\`\`\`
|
|
256
|
+
|
|
257
|
+
### Step 2: Port Scanning
|
|
258
|
+
\`\`\`bash
|
|
259
|
+
${tools.filter(t => t.tags.includes("port-scan") || t.tags.includes("port")).map(t =>
|
|
260
|
+
`${t.command.base} -host <targets> -o ports.json`
|
|
261
|
+
).join("\n")}
|
|
262
|
+
\`\`\`
|
|
263
|
+
|
|
264
|
+
### Step 3: Service Detection
|
|
265
|
+
\`\`\`bash
|
|
266
|
+
${tools.filter(t => t.tags.includes("http") || t.tags.includes("service")).map(t =>
|
|
267
|
+
`${t.command.base} -l subdomains.txt -json -o services.json`
|
|
268
|
+
).join("\n")}
|
|
269
|
+
\`\`\`
|
|
270
|
+
|
|
271
|
+
### Output Analysis
|
|
272
|
+
- Review discovered subdomains for interesting targets
|
|
273
|
+
- Correlate open ports with identified services
|
|
274
|
+
- Prioritize targets for exploitation phase
|
|
275
|
+
|
|
276
|
+
### Handoff
|
|
277
|
+
Pass results to exploitation phase with prioritized target list.
|
|
278
|
+
${toolRef}`
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function generateExploitationTemplate(options: SkillGenerationOptions, tools: readonly ToolEntry[]): string {
|
|
282
|
+
const fm = buildFrontmatter(options)
|
|
283
|
+
const toolRef = buildToolReferenceSection(tools)
|
|
284
|
+
|
|
285
|
+
return `${fm}
|
|
286
|
+
|
|
287
|
+
# ${options.name} — Exploitation Skill
|
|
288
|
+
|
|
289
|
+
${options.description}
|
|
290
|
+
|
|
291
|
+
## Phase: Exploitation
|
|
292
|
+
|
|
293
|
+
### Objectives
|
|
294
|
+
- Validate identified vulnerabilities
|
|
295
|
+
- Develop proof-of-concept exploits
|
|
296
|
+
- Document impact and severity
|
|
297
|
+
|
|
298
|
+
### Prerequisites
|
|
299
|
+
${tools.map(t => `- **${t.tools_name}**: ${t.description}`).join("\n")}
|
|
300
|
+
|
|
301
|
+
### Step 1: Vulnerability Validation
|
|
302
|
+
\`\`\`bash
|
|
303
|
+
# Validate each finding from reconnaissance
|
|
304
|
+
${tools.map(t => `${t.command.base} <target> <parameters>`).join("\n")}
|
|
305
|
+
\`\`\`
|
|
306
|
+
|
|
307
|
+
### Step 2: PoC Development
|
|
308
|
+
For each validated vulnerability:
|
|
309
|
+
1. Document the exact attack vector
|
|
310
|
+
2. Create minimal reproducible PoC
|
|
311
|
+
3. Assess impact (data access, privilege escalation, etc.)
|
|
312
|
+
|
|
313
|
+
### Step 3: Severity Assessment
|
|
314
|
+
| Finding | CVSS | Impact | Exploitability |
|
|
315
|
+
|---------|------|--------|----------------|
|
|
316
|
+
| _fill_ | _fill_ | _fill_ | _fill_ |
|
|
317
|
+
|
|
318
|
+
### Safety Rules
|
|
319
|
+
- Only test targets within authorized scope
|
|
320
|
+
- Do not exfiltrate real user data
|
|
321
|
+
- Document all actions for the report
|
|
322
|
+
${toolRef}`
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function generateReportingTemplate(options: SkillGenerationOptions, _tools: readonly ToolEntry[]): string {
|
|
326
|
+
const fm = buildFrontmatter(options)
|
|
327
|
+
|
|
328
|
+
return `${fm}
|
|
329
|
+
|
|
330
|
+
# ${options.name} — Reporting Skill
|
|
331
|
+
|
|
332
|
+
${options.description}
|
|
333
|
+
|
|
334
|
+
## Phase: Reporting
|
|
335
|
+
|
|
336
|
+
### Report Structure
|
|
337
|
+
|
|
338
|
+
#### Executive Summary
|
|
339
|
+
- Brief overview of engagement scope
|
|
340
|
+
- Key findings summary (critical/high count)
|
|
341
|
+
- Risk assessment
|
|
342
|
+
|
|
343
|
+
#### Findings Detail
|
|
344
|
+
For each finding:
|
|
345
|
+
|
|
346
|
+
##### Finding: [Title]
|
|
347
|
+
- **Severity:** Critical / High / Medium / Low / Info
|
|
348
|
+
- **CVSS Score:** X.X (vector string)
|
|
349
|
+
- **Affected Asset:** URL / endpoint / component
|
|
350
|
+
- **Description:** Clear explanation of the vulnerability
|
|
351
|
+
|
|
352
|
+
**Proof of Concept:**
|
|
353
|
+
\`\`\`
|
|
354
|
+
[Step-by-step reproduction]
|
|
355
|
+
\`\`\`
|
|
356
|
+
|
|
357
|
+
**Impact:**
|
|
358
|
+
What an attacker could achieve.
|
|
359
|
+
|
|
360
|
+
**Remediation:**
|
|
361
|
+
Specific fix recommendation.
|
|
362
|
+
|
|
363
|
+
#### Methodology
|
|
364
|
+
- Tools used: ${options.tools.join(", ")}
|
|
365
|
+
- Phases covered: ${options.phase.join(", ")}
|
|
366
|
+
- Scope tested: [define scope]
|
|
367
|
+
|
|
368
|
+
#### Appendices
|
|
369
|
+
- Raw scan outputs
|
|
370
|
+
- Screenshots
|
|
371
|
+
- Timeline of testing
|
|
372
|
+
`
|
|
373
|
+
}
|