pentesting 0.12.13 → 0.16.2

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.
@@ -1,130 +0,0 @@
1
- import {
2
- __require
3
- } from "./chunk-3RG5ZIWI.js";
4
-
5
- // src/core/replay/session-replay.ts
6
- import { existsSync, readFileSync } from "fs";
7
- function parseWireFile(filePath) {
8
- if (!existsSync(filePath)) return [];
9
- const events = [];
10
- const content = readFileSync(filePath, "utf-8");
11
- for (const line of content.split("\n")) {
12
- if (!line.trim()) continue;
13
- try {
14
- const record = JSON.parse(line);
15
- events.push({
16
- type: record.message.type,
17
- timestamp: record.message.timestamp,
18
- data: record.message.data
19
- });
20
- } catch {
21
- }
22
- }
23
- return events;
24
- }
25
- function getReplaySummary(events) {
26
- if (events.length === 0) {
27
- return { events, duration: 0, toolCalls: 0, findings: 0 };
28
- }
29
- const firstTs = events[0].timestamp;
30
- const lastTs = events[events.length - 1].timestamp;
31
- const duration = (lastTs - firstTs) / 1e3;
32
- let toolCalls = 0;
33
- let findings = 0;
34
- for (const event of events) {
35
- if (event.type === "tool_call") toolCalls++;
36
- if (event.type === "status_update") {
37
- const data = event.data;
38
- if (data.event === "finding") findings++;
39
- }
40
- }
41
- return { events, duration, toolCalls, findings };
42
- }
43
- function formatReplayEvent(event) {
44
- const time = new Date(event.timestamp).toLocaleTimeString();
45
- switch (event.type) {
46
- case "turn_begin":
47
- return `[${time}] \u{1F504} Turn started`;
48
- case "step_begin": {
49
- const data = event.data;
50
- return `[${time}] \u{1F4CD} Step ${data.stepIndex + 1}`;
51
- }
52
- case "content_part": {
53
- const data = event.data;
54
- const preview = data.content.slice(0, 50).replace(/\n/g, " ");
55
- const icon = data.isThinking ? "\u{1F4AD}" : "\u{1F4AC}";
56
- return `[${time}] ${icon} ${preview}...`;
57
- }
58
- case "tool_call": {
59
- const data = event.data;
60
- return `[${time}] Tool: ${data.toolName}`;
61
- }
62
- case "tool_result": {
63
- const data = event.data;
64
- const icon = data.isError ? "\u2717" : "\u2713";
65
- return `[${time}] ${icon} Tool result`;
66
- }
67
- case "status_update": {
68
- const data = event.data;
69
- if (data.event === "finding") {
70
- return `[${time}] \u{1F3AF} Finding: ${data.title}`;
71
- }
72
- if (data.event === "phase_change") {
73
- return `[${time}] \u{1F4CD} Phase: ${data.phase}`;
74
- }
75
- return `[${time}] \u{1F4CA} ${data.event}`;
76
- }
77
- case "turn_end":
78
- return `[${time}] \u2713 Turn complete`;
79
- default:
80
- return `[${time}] ${event.type}`;
81
- }
82
- }
83
- async function replaySession(wirePath, options = {}) {
84
- const events = parseWireFile(wirePath);
85
- const summary = getReplaySummary(events);
86
- if (events.length === 0) return summary;
87
- const speed = options.speed ?? 0;
88
- let lastTs = events[0].timestamp;
89
- for (const event of events) {
90
- const formatted = formatReplayEvent(event);
91
- if (options.onEvent) {
92
- options.onEvent(event, formatted);
93
- }
94
- if (speed > 0) {
95
- const delay = (event.timestamp - lastTs) / speed;
96
- if (delay > 0 && delay < 5e3) {
97
- await new Promise((r) => setTimeout(r, delay));
98
- }
99
- lastTs = event.timestamp;
100
- }
101
- }
102
- return summary;
103
- }
104
- function findRecentWireFiles(sessionDir, limit = 10) {
105
- const { readdirSync, statSync } = __require("fs");
106
- const { join } = __require("path");
107
- if (!existsSync(sessionDir)) return [];
108
- const files = [];
109
- try {
110
- const entries = readdirSync(sessionDir, { withFileTypes: true });
111
- for (const entry of entries) {
112
- if (entry.isFile() && entry.name.endsWith(".jsonl")) {
113
- const filePath = join(sessionDir, entry.name);
114
- const stat = statSync(filePath);
115
- files.push({ path: filePath, mtime: stat.mtimeMs });
116
- }
117
- }
118
- } catch {
119
- return [];
120
- }
121
- files.sort((a, b) => b.mtime - a.mtime);
122
- return files.slice(0, limit).map((f) => f.path);
123
- }
124
- export {
125
- findRecentWireFiles,
126
- formatReplayEvent,
127
- getReplaySummary,
128
- parseWireFile,
129
- replaySession
130
- };
@@ -1,416 +0,0 @@
1
- import "./chunk-3RG5ZIWI.js";
2
-
3
- // src/core/skill/flow.ts
4
- var FlowError = class extends Error {
5
- constructor(message) {
6
- super(message);
7
- this.name = "FlowError";
8
- }
9
- };
10
- var FlowParseError = class extends FlowError {
11
- constructor(message) {
12
- super(message);
13
- this.name = "FlowParseError";
14
- }
15
- };
16
- var FlowValidationError = class extends FlowError {
17
- constructor(message) {
18
- super(message);
19
- this.name = "FlowValidationError";
20
- }
21
- };
22
- function parseChoice(text) {
23
- const match = text.match(/<choice>([^<]*)<\/choice>/);
24
- return match ? match[1].trim() : null;
25
- }
26
- function validateFlow(nodes, outgoing) {
27
- const beginNodes = Array.from(nodes.values()).filter((n) => n.kind === "begin");
28
- const endNodes = Array.from(nodes.values()).filter((n) => n.kind === "end");
29
- if (beginNodes.length !== 1) {
30
- throw new FlowValidationError(`Expected exactly one BEGIN node, found ${beginNodes.length}`);
31
- }
32
- if (endNodes.length !== 1) {
33
- throw new FlowValidationError(`Expected exactly one END node, found ${endNodes.length}`);
34
- }
35
- const beginId = beginNodes[0].id;
36
- const endId = endNodes[0].id;
37
- const reachable = /* @__PURE__ */ new Set();
38
- const queue = [beginId];
39
- while (queue.length > 0) {
40
- const nodeId = queue.pop();
41
- if (reachable.has(nodeId)) continue;
42
- reachable.add(nodeId);
43
- const edges = outgoing.get(nodeId) || [];
44
- for (const edge of edges) {
45
- if (!reachable.has(edge.dst)) {
46
- queue.push(edge.dst);
47
- }
48
- }
49
- }
50
- if (!reachable.has(endId)) {
51
- throw new FlowValidationError("END node is not reachable from BEGIN");
52
- }
53
- for (const node of nodes.values()) {
54
- if (!reachable.has(node.id)) continue;
55
- const edges = outgoing.get(node.id) || [];
56
- if (edges.length <= 1) continue;
57
- const labels = [];
58
- for (const edge of edges) {
59
- if (!edge.label?.trim()) {
60
- throw new FlowValidationError(`Node "${node.id}" has an unlabeled edge`);
61
- }
62
- labels.push(edge.label);
63
- }
64
- if (new Set(labels).size !== labels.length) {
65
- throw new FlowValidationError(`Node "${node.id}" has duplicate edge labels`);
66
- }
67
- }
68
- return { beginId, endId };
69
- }
70
- function parseMermaidFlowchart(code) {
71
- const nodes = /* @__PURE__ */ new Map();
72
- const outgoing = /* @__PURE__ */ new Map();
73
- const lines = code.split("\n").map((l) => l.trim()).filter((l) => l);
74
- for (const line of lines) {
75
- if (line.match(/^flowchart|^graph/i)) continue;
76
- const nodeMatch = line.match(/^([A-Za-z0-9_]+)(\[([^\]]+)\]|\{([^\}]+)\}|\(\(([^\)]+)\)\)|\(\[([^\]]+)\]\))?$/);
77
- if (nodeMatch && !line.includes("-->")) {
78
- const id = nodeMatch[1];
79
- const label = nodeMatch[3] || nodeMatch[4] || nodeMatch[5] || nodeMatch[6] || id;
80
- let kind = "task";
81
- if (nodeMatch[4]) kind = "decision";
82
- else if (nodeMatch[5] || label.toLowerCase().includes("start") || label.toLowerCase().includes("begin")) kind = "begin";
83
- else if (nodeMatch[6] || label.toLowerCase().includes("end")) kind = "end";
84
- nodes.set(id, { id, label, kind });
85
- continue;
86
- }
87
- const edgeMatch = line.match(/^([A-Za-z0-9_]+)\s*-->\s*(?:\|([^\|]+)\|\s*)?([A-Za-z0-9_]+)/);
88
- if (edgeMatch) {
89
- const src = edgeMatch[1];
90
- const label = edgeMatch[2] || null;
91
- const dst = edgeMatch[3];
92
- if (!nodes.has(src)) nodes.set(src, { id: src, label: src, kind: "task" });
93
- if (!nodes.has(dst)) nodes.set(dst, { id: dst, label: dst, kind: "task" });
94
- const edges = outgoing.get(src) || [];
95
- edges.push({ src, dst, label });
96
- outgoing.set(src, edges);
97
- }
98
- }
99
- const { beginId, endId } = validateFlow(nodes, outgoing);
100
- return { nodes, outgoing, beginId, endId };
101
- }
102
- function parseD2Flowchart(code) {
103
- const nodes = /* @__PURE__ */ new Map();
104
- const outgoing = /* @__PURE__ */ new Map();
105
- const lines = code.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
106
- for (const line of lines) {
107
- const edgeMatch = line.match(/^([A-Za-z0-9_]+)\s*->\s*([A-Za-z0-9_]+)(?:\s*:\s*(.+))?$/);
108
- if (edgeMatch) {
109
- const src = edgeMatch[1];
110
- const dst = edgeMatch[2];
111
- const label = edgeMatch[3] || null;
112
- if (!nodes.has(src)) {
113
- const kind = src.toLowerCase().includes("start") ? "begin" : "task";
114
- nodes.set(src, { id: src, label: src, kind });
115
- }
116
- if (!nodes.has(dst)) {
117
- const kind = dst.toLowerCase().includes("end") ? "end" : "task";
118
- nodes.set(dst, { id: dst, label: dst, kind });
119
- }
120
- const edges = outgoing.get(src) || [];
121
- edges.push({ src, dst, label });
122
- outgoing.set(src, edges);
123
- continue;
124
- }
125
- const nodeMatch = line.match(/^([A-Za-z0-9_]+)\s*:\s*"?([^"{}]+)"?\s*(?:\{([^}]+)\})?$/);
126
- if (nodeMatch) {
127
- const id = nodeMatch[1];
128
- const label = nodeMatch[2].trim();
129
- const attrs = nodeMatch[3] || "";
130
- let kind = "task";
131
- if (attrs.includes("diamond")) kind = "decision";
132
- else if (attrs.includes("oval") && label.toLowerCase().includes("start")) kind = "begin";
133
- else if (attrs.includes("oval") && label.toLowerCase().includes("end")) kind = "end";
134
- nodes.set(id, { id, label, kind });
135
- }
136
- }
137
- const { beginId, endId } = validateFlow(nodes, outgoing);
138
- return { nodes, outgoing, beginId, endId };
139
- }
140
- var FlowExecutor = class {
141
- flow;
142
- currentNodeId;
143
- moveCount = 0;
144
- maxMoves;
145
- constructor(flow, maxMoves = 1e3) {
146
- this.flow = flow;
147
- this.currentNodeId = flow.beginId;
148
- this.maxMoves = maxMoves;
149
- }
150
- get currentNode() {
151
- return this.flow.nodes.get(this.currentNodeId);
152
- }
153
- get isComplete() {
154
- return this.currentNodeId === this.flow.endId;
155
- }
156
- get availableChoices() {
157
- const edges = this.flow.outgoing.get(this.currentNodeId) || [];
158
- return edges.filter((e) => e.label).map((e) => e.label);
159
- }
160
- /**
161
- * Move to next node
162
- */
163
- move(choice) {
164
- if (this.isComplete) {
165
- throw new FlowError("Flow is already complete");
166
- }
167
- if (this.moveCount >= this.maxMoves) {
168
- throw new FlowError(`Max moves (${this.maxMoves}) exceeded`);
169
- }
170
- const edges = this.flow.outgoing.get(this.currentNodeId) || [];
171
- if (edges.length === 0) {
172
- throw new FlowError(`No outgoing edges from node "${this.currentNodeId}"`);
173
- }
174
- let nextEdge;
175
- if (edges.length === 1) {
176
- nextEdge = edges[0];
177
- } else if (choice) {
178
- nextEdge = edges.find((e) => e.label?.toLowerCase() === choice.toLowerCase());
179
- if (!nextEdge) {
180
- throw new FlowError(`Invalid choice "${choice}". Available: ${this.availableChoices.join(", ")}`);
181
- }
182
- } else {
183
- throw new FlowError(`Choice required. Available: ${this.availableChoices.join(", ")}`);
184
- }
185
- this.currentNodeId = nextEdge.dst;
186
- this.moveCount++;
187
- return this.currentNode;
188
- }
189
- /**
190
- * Reset flow
191
- */
192
- reset() {
193
- this.currentNodeId = this.flow.beginId;
194
- this.moveCount = 0;
195
- }
196
- };
197
-
198
- // src/core/skill/skill.ts
199
- import { existsSync, readdirSync, readFileSync } from "fs";
200
- import { join } from "path";
201
- import { homedir } from "os";
202
- function getBuiltinSkillsDir() {
203
- return join(__dirname, "..", "..", "skills");
204
- }
205
- function getUserSkillsDirCandidates() {
206
- return [
207
- join(homedir(), ".config", "agents", "skills"),
208
- join(homedir(), ".agents", "skills"),
209
- join(homedir(), ".pentest", "skills"),
210
- join(homedir(), ".claude", "skills")
211
- ];
212
- }
213
- function getProjectSkillsDirCandidates(workDir) {
214
- return [
215
- join(workDir, ".agents", "skills"),
216
- join(workDir, ".pentest", "skills"),
217
- join(workDir, ".claude", "skills")
218
- ];
219
- }
220
- function findFirstExistingDir(candidates) {
221
- for (const candidate of candidates) {
222
- if (existsSync(candidate)) {
223
- return candidate;
224
- }
225
- }
226
- return null;
227
- }
228
- function parseFrontmatter(content) {
229
- const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
230
- if (!match) return {};
231
- const yaml = match[1];
232
- const result = {};
233
- for (const line of yaml.split("\n")) {
234
- const colonIndex = line.indexOf(":");
235
- if (colonIndex > 0) {
236
- const key = line.slice(0, colonIndex).trim();
237
- const value = line.slice(colonIndex + 1).trim().replace(/^["']|["']$/g, "");
238
- result[key] = value;
239
- }
240
- }
241
- return result;
242
- }
243
- function extractCodeBlocks(content) {
244
- const blocks = [];
245
- const regex = /```(\w+)?\n([\s\S]*?)```/g;
246
- let match;
247
- while ((match = regex.exec(content)) !== null) {
248
- blocks.push({
249
- lang: (match[1] || "").toLowerCase(),
250
- code: match[2].trim()
251
- });
252
- }
253
- return blocks;
254
- }
255
- function parseSkillText(content, dirPath) {
256
- const frontmatter = parseFrontmatter(content);
257
- const name = frontmatter.name || dirPath.split("/").pop() || "unnamed";
258
- const description = frontmatter.description || "No description provided.";
259
- const skillType = frontmatter.type || "standard";
260
- let flow;
261
- if (skillType === "flow") {
262
- try {
263
- const codeBlocks = extractCodeBlocks(content);
264
- for (const block of codeBlocks) {
265
- if (block.lang === "mermaid") {
266
- flow = parseMermaidFlowchart(block.code);
267
- break;
268
- } else if (block.lang === "d2") {
269
- flow = parseD2Flowchart(block.code);
270
- break;
271
- }
272
- }
273
- if (!flow) {
274
- throw new FlowError("Flow skills require a mermaid or d2 code block");
275
- }
276
- } catch (e) {
277
- console.error(`Failed to parse flow skill ${name}:`, e);
278
- return {
279
- name,
280
- description,
281
- type: "standard",
282
- dir: dirPath,
283
- content
284
- };
285
- }
286
- }
287
- return {
288
- name,
289
- description,
290
- type: skillType,
291
- dir: dirPath,
292
- flow,
293
- content
294
- };
295
- }
296
- function discoverSkills(skillsDir) {
297
- if (!existsSync(skillsDir)) return [];
298
- const skills = [];
299
- try {
300
- const entries = readdirSync(skillsDir, { withFileTypes: true });
301
- for (const entry of entries) {
302
- if (!entry.isDirectory()) continue;
303
- const skillDir = join(skillsDir, entry.name);
304
- const skillMd = join(skillDir, "SKILL.md");
305
- if (!existsSync(skillMd)) continue;
306
- try {
307
- const content = readFileSync(skillMd, "utf-8");
308
- const skill = parseSkillText(content, skillDir);
309
- skills.push(skill);
310
- } catch {
311
- }
312
- }
313
- } catch {
314
- }
315
- return skills.sort((a, b) => a.name.localeCompare(b.name));
316
- }
317
- function discoverSkillsFromRoots(skillsDirs) {
318
- const skillsByName = /* @__PURE__ */ new Map();
319
- for (const dir of skillsDirs) {
320
- for (const skill of discoverSkills(dir)) {
321
- skillsByName.set(skill.name.toLowerCase(), skill);
322
- }
323
- }
324
- return Array.from(skillsByName.values()).sort((a, b) => a.name.localeCompare(b.name));
325
- }
326
- function resolveSkillRoots(workDir) {
327
- const roots = [];
328
- const builtinDir = getBuiltinSkillsDir();
329
- if (existsSync(builtinDir)) {
330
- roots.push(builtinDir);
331
- }
332
- const userDir = findFirstExistingDir(getUserSkillsDirCandidates());
333
- if (userDir) {
334
- roots.push(userDir);
335
- }
336
- const projectDir = findFirstExistingDir(getProjectSkillsDirCandidates(workDir));
337
- if (projectDir) {
338
- roots.push(projectDir);
339
- }
340
- return roots;
341
- }
342
- var SkillManager = class {
343
- skills = /* @__PURE__ */ new Map();
344
- workDir;
345
- constructor(workDir) {
346
- this.workDir = workDir || process.cwd();
347
- }
348
- /**
349
- * Load all skills
350
- */
351
- load() {
352
- const roots = resolveSkillRoots(this.workDir);
353
- const skills = discoverSkillsFromRoots(roots);
354
- this.skills.clear();
355
- for (const skill of skills) {
356
- this.skills.set(skill.name.toLowerCase(), skill);
357
- }
358
- }
359
- /**
360
- * Find skill by name
361
- */
362
- find(name) {
363
- return this.skills.get(name.toLowerCase());
364
- }
365
- /**
366
- * List all skills
367
- */
368
- list() {
369
- return Array.from(this.skills.values());
370
- }
371
- /**
372
- * Get skill content
373
- */
374
- getContent(name) {
375
- const skill = this.find(name);
376
- return skill?.content || null;
377
- }
378
- /**
379
- * Format skills for system prompt
380
- */
381
- formatForPrompt() {
382
- const skills = this.list();
383
- if (skills.length === 0) return "No skills found.";
384
- return skills.map(
385
- (s) => `- ${s.name}
386
- - Type: ${s.type}
387
- - Description: ${s.description}`
388
- ).join("\n");
389
- }
390
- };
391
- var skillManager = null;
392
- function getSkillManager(workDir) {
393
- if (!skillManager) {
394
- skillManager = new SkillManager(workDir);
395
- skillManager.load();
396
- }
397
- return skillManager;
398
- }
399
- export {
400
- FlowError,
401
- FlowExecutor,
402
- FlowParseError,
403
- FlowValidationError,
404
- SkillManager,
405
- discoverSkills,
406
- discoverSkillsFromRoots,
407
- extractCodeBlocks,
408
- getSkillManager,
409
- parseChoice,
410
- parseD2Flowchart,
411
- parseFrontmatter,
412
- parseMermaidFlowchart,
413
- parseSkillText,
414
- resolveSkillRoots,
415
- validateFlow
416
- };
@@ -1,24 +0,0 @@
1
- import {
2
- checkForUpdate,
3
- checkForUpdateAsync,
4
- compareSemver,
5
- doUpdate,
6
- fetchLatestVersion,
7
- formatUpdateNotification,
8
- readVersionCache,
9
- semverTuple,
10
- writeVersionCache
11
- } from "./chunk-5IKQY4A4.js";
12
- import "./chunk-6IXHQS2A.js";
13
- import "./chunk-3RG5ZIWI.js";
14
- export {
15
- checkForUpdate,
16
- checkForUpdateAsync,
17
- compareSemver,
18
- doUpdate,
19
- fetchLatestVersion,
20
- formatUpdateNotification,
21
- readVersionCache,
22
- semverTuple,
23
- writeVersionCache
24
- };
@@ -1,43 +0,0 @@
1
- import {
2
- closeBrowser,
3
- ctfResearch,
4
- deepSearch,
5
- fetchUrlContent,
6
- searchADWriteups,
7
- searchBing,
8
- searchBrave,
9
- searchByScenario,
10
- searchCVE,
11
- searchDuckDuckGo,
12
- searchExploits,
13
- searchGoogle,
14
- searchLinuxPrivesc,
15
- searchMachineWriteup,
16
- searchWindowsPrivesc,
17
- searchWriteups,
18
- searchYahoo,
19
- securityResearch,
20
- unifiedSearch
21
- } from "./chunk-AOJBE232.js";
22
- import "./chunk-3RG5ZIWI.js";
23
- export {
24
- closeBrowser,
25
- ctfResearch,
26
- deepSearch,
27
- fetchUrlContent,
28
- searchADWriteups,
29
- searchBing,
30
- searchBrave,
31
- searchByScenario,
32
- searchCVE,
33
- searchDuckDuckGo,
34
- searchExploits,
35
- searchGoogle,
36
- searchLinuxPrivesc,
37
- searchMachineWriteup,
38
- searchWindowsPrivesc,
39
- searchWriteups,
40
- searchYahoo,
41
- securityResearch,
42
- unifiedSearch
43
- };
@@ -1,79 +0,0 @@
1
- version: 1
2
- agent:
3
- name: crypto
4
- description: Cryptography & Password Cracking Expert
5
- extends: ./default.yaml
6
-
7
- system_prompt: |
8
- # Cryptography Expert
9
-
10
- You specialize in cryptographic analysis and password cracking.
11
-
12
- ## Hash Identification
13
- ```bash
14
- hashid HASH
15
- hash-identifier
16
- ```
17
-
18
- ## Hash Types & Hashcat Modes
19
-
20
- | Hash Type | Example | Hashcat Mode |
21
- |-----------|---------|--------------|
22
- | MD5 | 32 hex chars | 0 |
23
- | SHA1 | 40 hex chars | 100 |
24
- | SHA256 | 64 hex chars | 1400 |
25
- | NTLM | 32 hex chars | 1000 |
26
- | bcrypt | $2a$... | 3200 |
27
- | Kerberos TGS | $krb5tgs$... | 13100 |
28
-
29
- ## Cracking Strategy
30
-
31
- ### 1. Try Common Passwords First
32
- ```bash
33
- # rockyou top 1000
34
- hashcat -m MODE hash.txt /usr/share/wordlists/rockyou.txt --force
35
- ```
36
-
37
- ### 2. Apply Rules
38
- ```bash
39
- hashcat -m MODE hash.txt wordlist.txt -r /usr/share/hashcat/rules/best64.rule
40
- ```
41
-
42
- ### 3. Targeted Wordlists
43
- - Company name variations
44
- - Username + common patterns
45
- - Previously leaked passwords
46
-
47
- ## Encoding Detection
48
- - Base64: ends with = or ==
49
- - URL encoding: %XX format
50
- - Hex: only 0-9, a-f
51
- - ROT13: Caesar cipher
52
-
53
- ## Output Format
54
- ```
55
- 🔐 CRYPTO ANALYSIS
56
- ==================
57
- Hash: [hash value]
58
- Type: [detected type]
59
-
60
- 🔓 Cracking Attempt:
61
- - Method: [dictionary/rules/bruteforce]
62
- - Wordlist: [wordlist used]
63
- - Status: [CRACKED/IN PROGRESS/FAILED]
64
-
65
- ✅ Result:
66
- [plaintext if cracked]
67
-
68
- 💡 Next Steps:
69
- - [try different wordlist]
70
- - [apply more rules]
71
- ```
72
-
73
- tools:
74
- - bash
75
- - hashcat
76
- - john
77
- - hashid
78
- - base64
79
- - openssl