@pensar/apex 0.0.27 → 0.0.28-canary.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/bin/pensar.js +64 -20
- package/build/benchmark.js +267 -10
- package/build/index.js +1137 -146
- package/build/pentest.js +48198 -0
- package/build/quicktest.js +358 -125
- package/build/swarm.js +319 -124
- package/package.json +3 -2
package/build/swarm.js
CHANGED
|
@@ -41485,13 +41485,171 @@ Remember: You are a focused penetration testing agent assigned a specific target
|
|
|
41485
41485
|
import { exec } from "child_process";
|
|
41486
41486
|
import { promisify } from "util";
|
|
41487
41487
|
import {
|
|
41488
|
-
writeFileSync,
|
|
41488
|
+
writeFileSync as writeFileSync2,
|
|
41489
41489
|
appendFileSync,
|
|
41490
|
-
readdirSync,
|
|
41490
|
+
readdirSync as readdirSync2,
|
|
41491
|
+
readFileSync as readFileSync2,
|
|
41492
|
+
existsSync as existsSync2
|
|
41493
|
+
} from "fs";
|
|
41494
|
+
import { join as join2 } from "path";
|
|
41495
|
+
|
|
41496
|
+
// src/core/agent/sessions/index.ts
|
|
41497
|
+
import {
|
|
41498
|
+
mkdirSync,
|
|
41499
|
+
existsSync,
|
|
41500
|
+
writeFileSync,
|
|
41491
41501
|
readFileSync,
|
|
41492
|
-
|
|
41502
|
+
readdirSync,
|
|
41503
|
+
statSync,
|
|
41504
|
+
rmSync
|
|
41493
41505
|
} from "fs";
|
|
41494
41506
|
import { join } from "path";
|
|
41507
|
+
import { homedir } from "os";
|
|
41508
|
+
|
|
41509
|
+
// src/core/services/rateLimiter/index.ts
|
|
41510
|
+
function sleep(ms) {
|
|
41511
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
41512
|
+
}
|
|
41513
|
+
|
|
41514
|
+
class RateLimiter {
|
|
41515
|
+
tokens;
|
|
41516
|
+
lastRefillTime;
|
|
41517
|
+
rps;
|
|
41518
|
+
bucketSize;
|
|
41519
|
+
msPerToken;
|
|
41520
|
+
queue;
|
|
41521
|
+
constructor(config2) {
|
|
41522
|
+
this.rps = config2?.requestsPerSecond;
|
|
41523
|
+
this.bucketSize = this.rps ? 1 : 0;
|
|
41524
|
+
this.tokens = this.bucketSize;
|
|
41525
|
+
this.lastRefillTime = performance.now();
|
|
41526
|
+
this.msPerToken = this.rps ? 1000 / this.rps : undefined;
|
|
41527
|
+
this.queue = Promise.resolve();
|
|
41528
|
+
}
|
|
41529
|
+
async acquireSlot() {
|
|
41530
|
+
if (!this.rps || !this.msPerToken)
|
|
41531
|
+
return;
|
|
41532
|
+
const previousPromise = this.queue;
|
|
41533
|
+
let resolveCurrentRequest;
|
|
41534
|
+
this.queue = new Promise((resolve2) => {
|
|
41535
|
+
resolveCurrentRequest = resolve2;
|
|
41536
|
+
});
|
|
41537
|
+
await previousPromise;
|
|
41538
|
+
try {
|
|
41539
|
+
const now2 = performance.now();
|
|
41540
|
+
this.refill(now2);
|
|
41541
|
+
if (this.tokens < 1) {
|
|
41542
|
+
const waitTime = (1 - this.tokens) * this.msPerToken;
|
|
41543
|
+
await sleep(waitTime);
|
|
41544
|
+
const nowAfterSleep = performance.now();
|
|
41545
|
+
this.refill(nowAfterSleep);
|
|
41546
|
+
}
|
|
41547
|
+
this.tokens -= 1;
|
|
41548
|
+
} finally {
|
|
41549
|
+
resolveCurrentRequest();
|
|
41550
|
+
}
|
|
41551
|
+
}
|
|
41552
|
+
refill(now2) {
|
|
41553
|
+
if (this.tokens >= this.bucketSize) {
|
|
41554
|
+
this.lastRefillTime = now2;
|
|
41555
|
+
return;
|
|
41556
|
+
}
|
|
41557
|
+
const elapsed = now2 - this.lastRefillTime;
|
|
41558
|
+
const tokensToAdd = elapsed / this.msPerToken;
|
|
41559
|
+
this.tokens = Math.min(this.bucketSize, this.tokens + tokensToAdd);
|
|
41560
|
+
this.lastRefillTime = now2;
|
|
41561
|
+
}
|
|
41562
|
+
isEnabled() {
|
|
41563
|
+
return this.rps !== undefined;
|
|
41564
|
+
}
|
|
41565
|
+
}
|
|
41566
|
+
|
|
41567
|
+
// src/core/agent/sessions/index.ts
|
|
41568
|
+
var DEFAULT_OFFENSIVE_HEADERS = {
|
|
41569
|
+
"User-Agent": "pensar-apex"
|
|
41570
|
+
};
|
|
41571
|
+
function generateSessionId(prefix) {
|
|
41572
|
+
const timestamp = Date.now().toString(36);
|
|
41573
|
+
return `${prefix ? `${prefix}-` : ""}${timestamp}`;
|
|
41574
|
+
}
|
|
41575
|
+
function getPensarDir() {
|
|
41576
|
+
return join(homedir(), ".pensar");
|
|
41577
|
+
}
|
|
41578
|
+
function getExecutionsDir() {
|
|
41579
|
+
return join(getPensarDir(), "executions");
|
|
41580
|
+
}
|
|
41581
|
+
function createSession(target, objective, prefix, config2) {
|
|
41582
|
+
const sessionId = generateSessionId(prefix);
|
|
41583
|
+
const rootPath = join(getExecutionsDir(), sessionId);
|
|
41584
|
+
const findingsPath = join(rootPath, "findings");
|
|
41585
|
+
const scratchpadPath = join(rootPath, "scratchpad");
|
|
41586
|
+
const logsPath = join(rootPath, "logs");
|
|
41587
|
+
ensureDirectoryExists(rootPath);
|
|
41588
|
+
ensureDirectoryExists(findingsPath);
|
|
41589
|
+
ensureDirectoryExists(scratchpadPath);
|
|
41590
|
+
ensureDirectoryExists(logsPath);
|
|
41591
|
+
const session = {
|
|
41592
|
+
id: sessionId,
|
|
41593
|
+
rootPath,
|
|
41594
|
+
findingsPath,
|
|
41595
|
+
scratchpadPath,
|
|
41596
|
+
logsPath,
|
|
41597
|
+
target,
|
|
41598
|
+
objective: objective ?? "",
|
|
41599
|
+
startTime: new Date().toISOString(),
|
|
41600
|
+
config: config2
|
|
41601
|
+
};
|
|
41602
|
+
if (config2?.rateLimiter) {
|
|
41603
|
+
session._rateLimiter = new RateLimiter(config2.rateLimiter);
|
|
41604
|
+
}
|
|
41605
|
+
const metadataPath = join(rootPath, "session.json");
|
|
41606
|
+
writeFileSync(metadataPath, JSON.stringify(session, null, 2));
|
|
41607
|
+
const readmePath = join(rootPath, "README.md");
|
|
41608
|
+
const readme = `# Penetration Test Session
|
|
41609
|
+
|
|
41610
|
+
**Session ID:** ${sessionId}
|
|
41611
|
+
**Target:** ${target}
|
|
41612
|
+
**Objective:** ${objective}
|
|
41613
|
+
**Started:** ${session.startTime}
|
|
41614
|
+
|
|
41615
|
+
## Directory Structure
|
|
41616
|
+
|
|
41617
|
+
- \`findings/\` - Security findings and vulnerabilities
|
|
41618
|
+
- \`scratchpad/\` - Notes and temporary data during testing
|
|
41619
|
+
- \`logs/\` - Execution logs and command outputs
|
|
41620
|
+
- \`session.json\` - Session metadata
|
|
41621
|
+
|
|
41622
|
+
## Findings
|
|
41623
|
+
|
|
41624
|
+
Security findings will be documented in the \`findings/\` directory as individual files.
|
|
41625
|
+
|
|
41626
|
+
## Status
|
|
41627
|
+
|
|
41628
|
+
Testing in progress...
|
|
41629
|
+
`;
|
|
41630
|
+
writeFileSync(readmePath, readme);
|
|
41631
|
+
return session;
|
|
41632
|
+
}
|
|
41633
|
+
function ensureDirectoryExists(path) {
|
|
41634
|
+
if (!existsSync(path)) {
|
|
41635
|
+
mkdirSync(path, { recursive: true });
|
|
41636
|
+
}
|
|
41637
|
+
}
|
|
41638
|
+
function getOffensiveHeaders(session) {
|
|
41639
|
+
const config2 = session.config?.offensiveHeaders;
|
|
41640
|
+
if (!config2 || config2.mode === "none") {
|
|
41641
|
+
return;
|
|
41642
|
+
}
|
|
41643
|
+
if (config2.mode === "default") {
|
|
41644
|
+
return DEFAULT_OFFENSIVE_HEADERS;
|
|
41645
|
+
}
|
|
41646
|
+
if (config2.mode === "custom" && config2.headers) {
|
|
41647
|
+
return config2.headers;
|
|
41648
|
+
}
|
|
41649
|
+
return;
|
|
41650
|
+
}
|
|
41651
|
+
|
|
41652
|
+
// src/core/agent/tools.ts
|
|
41495
41653
|
var execAsync = promisify(exec);
|
|
41496
41654
|
var PayloadSchema = exports_external.object({
|
|
41497
41655
|
payload: exports_external.string(),
|
|
@@ -42585,7 +42743,7 @@ FINDING STRUCTURE:
|
|
|
42585
42743
|
const safeTitle = finding.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 50);
|
|
42586
42744
|
const findingId = `${timestamp.split("T")[0]}-${safeTitle}`;
|
|
42587
42745
|
const filename = `${findingId}.md`;
|
|
42588
|
-
const filepath =
|
|
42746
|
+
const filepath = join2(session.findingsPath, filename);
|
|
42589
42747
|
const markdown = `# ${finding.title}
|
|
42590
42748
|
|
|
42591
42749
|
**Severity:** ${finding.severity}
|
|
@@ -42619,8 +42777,8 @@ ${finding.references}` : ""}
|
|
|
42619
42777
|
|
|
42620
42778
|
*This finding was automatically documented by the Pensar penetration testing agent.*
|
|
42621
42779
|
`;
|
|
42622
|
-
|
|
42623
|
-
const summaryPath =
|
|
42780
|
+
writeFileSync2(filepath, markdown);
|
|
42781
|
+
const summaryPath = join2(session.rootPath, "findings-summary.md");
|
|
42624
42782
|
const summaryEntry = `- [${finding.severity}] ${finding.title} - \`findings/${filename}\`
|
|
42625
42783
|
`;
|
|
42626
42784
|
try {
|
|
@@ -42634,7 +42792,7 @@ ${finding.references}` : ""}
|
|
|
42634
42792
|
## All Findings
|
|
42635
42793
|
|
|
42636
42794
|
`;
|
|
42637
|
-
|
|
42795
|
+
writeFileSync2(summaryPath, header + summaryEntry);
|
|
42638
42796
|
}
|
|
42639
42797
|
return {
|
|
42640
42798
|
success: true,
|
|
@@ -42673,7 +42831,7 @@ The scratchpad is session-specific and helps maintain context during long assess
|
|
|
42673
42831
|
execute: async ({ note, category }) => {
|
|
42674
42832
|
try {
|
|
42675
42833
|
const timestamp = new Date().toISOString();
|
|
42676
|
-
const scratchpadFile =
|
|
42834
|
+
const scratchpadFile = join2(session.scratchpadPath, "notes.md");
|
|
42677
42835
|
const entry = `## ${category.toUpperCase()} - ${timestamp}
|
|
42678
42836
|
|
|
42679
42837
|
${note}
|
|
@@ -42692,7 +42850,7 @@ ${note}
|
|
|
42692
42850
|
---
|
|
42693
42851
|
|
|
42694
42852
|
`;
|
|
42695
|
-
|
|
42853
|
+
writeFileSync2(scratchpadFile, header + entry);
|
|
42696
42854
|
}
|
|
42697
42855
|
return {
|
|
42698
42856
|
success: true,
|
|
@@ -42819,11 +42977,11 @@ The report will be saved as 'pentest-report.md' in the session root directory.`,
|
|
|
42819
42977
|
MEDIUM: 0,
|
|
42820
42978
|
LOW: 0
|
|
42821
42979
|
};
|
|
42822
|
-
if (
|
|
42823
|
-
const findingFiles =
|
|
42980
|
+
if (existsSync2(session.findingsPath)) {
|
|
42981
|
+
const findingFiles = readdirSync2(session.findingsPath).filter((f) => f.endsWith(".json"));
|
|
42824
42982
|
for (const file2 of findingFiles) {
|
|
42825
|
-
const filePath =
|
|
42826
|
-
const content =
|
|
42983
|
+
const filePath = join2(session.findingsPath, file2);
|
|
42984
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
42827
42985
|
const severityMatch = content.match(/\*\*Severity:\*\*\s+(CRITICAL|HIGH|MEDIUM|LOW)/);
|
|
42828
42986
|
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
42829
42987
|
if (severityMatch && titleMatch) {
|
|
@@ -42848,14 +43006,14 @@ The report will be saved as 'pentest-report.md' in the session root directory.`,
|
|
|
42848
43006
|
const totalFindings = findings.length;
|
|
42849
43007
|
const criticalAndHigh = severityCounts.CRITICAL + severityCounts.HIGH;
|
|
42850
43008
|
let scratchpadNotes = "";
|
|
42851
|
-
const scratchpadFile =
|
|
42852
|
-
if (
|
|
42853
|
-
scratchpadNotes =
|
|
43009
|
+
const scratchpadFile = join2(session.scratchpadPath, "notes.md");
|
|
43010
|
+
if (existsSync2(scratchpadFile)) {
|
|
43011
|
+
scratchpadNotes = readFileSync2(scratchpadFile, "utf-8");
|
|
42854
43012
|
}
|
|
42855
43013
|
let testResultsSummary = "";
|
|
42856
|
-
const testResultsFile =
|
|
42857
|
-
if (
|
|
42858
|
-
const testLines =
|
|
43014
|
+
const testResultsFile = join2(session.scratchpadPath, "test-results.jsonl");
|
|
43015
|
+
if (existsSync2(testResultsFile)) {
|
|
43016
|
+
const testLines = readFileSync2(testResultsFile, "utf-8").split(`
|
|
42859
43017
|
`).filter((l) => l.trim());
|
|
42860
43018
|
const testResults = testLines.map((line) => {
|
|
42861
43019
|
try {
|
|
@@ -43029,25 +43187,25 @@ This report should be treated as confidential and distributed only to authorized
|
|
|
43029
43187
|
*Report generated by Pensar Penetration Testing Agent*
|
|
43030
43188
|
*Session: ${session.id}*
|
|
43031
43189
|
`;
|
|
43032
|
-
const reportPath =
|
|
43033
|
-
|
|
43034
|
-
const readmePath =
|
|
43035
|
-
if (
|
|
43036
|
-
let readme =
|
|
43190
|
+
const reportPath = join2(session.rootPath, "pentest-report.md");
|
|
43191
|
+
writeFileSync2(reportPath, report);
|
|
43192
|
+
const readmePath = join2(session.rootPath, "README.md");
|
|
43193
|
+
if (existsSync2(readmePath)) {
|
|
43194
|
+
let readme = readFileSync2(readmePath, "utf-8");
|
|
43037
43195
|
readme = readme.replace("Testing in progress...", `Testing completed on ${endDate.toLocaleString()}
|
|
43038
43196
|
|
|
43039
43197
|
**Final Report:** \`pentest-report.md\``);
|
|
43040
|
-
|
|
43198
|
+
writeFileSync2(readmePath, readme);
|
|
43041
43199
|
}
|
|
43042
|
-
const metadataPath =
|
|
43043
|
-
if (
|
|
43044
|
-
const metadata = JSON.parse(
|
|
43200
|
+
const metadataPath = join2(session.rootPath, "session.json");
|
|
43201
|
+
if (existsSync2(metadataPath)) {
|
|
43202
|
+
const metadata = JSON.parse(readFileSync2(metadataPath, "utf-8"));
|
|
43045
43203
|
metadata.endTime = endTime;
|
|
43046
43204
|
metadata.duration = duration3;
|
|
43047
43205
|
metadata.status = "completed";
|
|
43048
43206
|
metadata.totalFindings = totalFindings;
|
|
43049
43207
|
metadata.severityCounts = severityCounts;
|
|
43050
|
-
|
|
43208
|
+
writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2));
|
|
43051
43209
|
}
|
|
43052
43210
|
return {
|
|
43053
43211
|
success: true,
|
|
@@ -43088,7 +43246,7 @@ async function recordTestResultCore(session, params) {
|
|
|
43088
43246
|
evidence: params.evidence || "",
|
|
43089
43247
|
confidence: params.confidence || "high"
|
|
43090
43248
|
};
|
|
43091
|
-
const testResultsPath =
|
|
43249
|
+
const testResultsPath = join2(session.scratchpadPath, "test-results.jsonl");
|
|
43092
43250
|
const resultLine = JSON.stringify(testResult) + `
|
|
43093
43251
|
`;
|
|
43094
43252
|
appendFileSync(testResultsPath, resultLine);
|
|
@@ -43499,8 +43657,8 @@ Use this when:
|
|
|
43499
43657
|
}),
|
|
43500
43658
|
execute: async ({ objective }) => {
|
|
43501
43659
|
try {
|
|
43502
|
-
const testResultsPath =
|
|
43503
|
-
if (!
|
|
43660
|
+
const testResultsPath = join2(session.scratchpadPath, "test-results.jsonl");
|
|
43661
|
+
if (!existsSync2(testResultsPath)) {
|
|
43504
43662
|
return {
|
|
43505
43663
|
success: true,
|
|
43506
43664
|
totalTests: 0,
|
|
@@ -43509,7 +43667,7 @@ Use this when:
|
|
|
43509
43667
|
suggestions: objective ? `Based on objective "${objective}", consider testing relevant parameters with test_parameter tool.` : "Use test_parameter to test parameters for vulnerabilities."
|
|
43510
43668
|
};
|
|
43511
43669
|
}
|
|
43512
|
-
const fileContent =
|
|
43670
|
+
const fileContent = readFileSync2(testResultsPath, "utf-8");
|
|
43513
43671
|
const testResults = fileContent.trim().split(`
|
|
43514
43672
|
`).filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
43515
43673
|
const parametersTested = new Set;
|
|
@@ -43766,7 +43924,7 @@ ${discovered.map((d) => `- ${d.endpoint} [${d.method}] → HTTP ${d.status}`).jo
|
|
|
43766
43924
|
Not found: ${range.max - range.min + 1 - discovered.length} endpoints returned 404`;
|
|
43767
43925
|
try {
|
|
43768
43926
|
const timestamp = new Date().toISOString();
|
|
43769
|
-
const scratchpadFile =
|
|
43927
|
+
const scratchpadFile = join2(session.scratchpadPath, "notes.md");
|
|
43770
43928
|
const entry = `## RESULT - ${timestamp}
|
|
43771
43929
|
|
|
43772
43930
|
${note}
|
|
@@ -43785,7 +43943,7 @@ ${note}
|
|
|
43785
43943
|
---
|
|
43786
43944
|
|
|
43787
43945
|
`;
|
|
43788
|
-
|
|
43946
|
+
writeFileSync2(scratchpadFile, header + entry);
|
|
43789
43947
|
}
|
|
43790
43948
|
} catch (err) {
|
|
43791
43949
|
console.error("Failed to record to scratchpad:", err);
|
|
@@ -43802,7 +43960,53 @@ ${note}
|
|
|
43802
43960
|
}
|
|
43803
43961
|
});
|
|
43804
43962
|
}
|
|
43963
|
+
function wrapCommandWithHeaders(command, headers) {
|
|
43964
|
+
const userAgent = headers["User-Agent"];
|
|
43965
|
+
if (!userAgent)
|
|
43966
|
+
return command;
|
|
43967
|
+
let wrapped = command;
|
|
43968
|
+
if (command.includes("curl") && !command.includes("User-Agent") && !command.includes("-A")) {
|
|
43969
|
+
wrapped = wrapped.replace(/\bcurl(\s+)/g, `curl -A "${userAgent}" $1`);
|
|
43970
|
+
}
|
|
43971
|
+
if (command.includes("nikto") && !command.includes("-useragent")) {
|
|
43972
|
+
wrapped = wrapped.replace(/\bnikto\s+/, `nikto -useragent "${userAgent}" `);
|
|
43973
|
+
}
|
|
43974
|
+
if (command.includes("nmap") && command.includes("--script") && /--script[= ](.*?http.*?)/.test(command) && !command.includes("http.useragent")) {
|
|
43975
|
+
if (command.includes("--script-args")) {
|
|
43976
|
+
wrapped = wrapped.replace(/--script-args\s+([^\s]+)/, `--script-args $1,http.useragent="${userAgent}"`);
|
|
43977
|
+
} else {
|
|
43978
|
+
wrapped = `${wrapped} --script-args http.useragent="${userAgent}"`;
|
|
43979
|
+
}
|
|
43980
|
+
}
|
|
43981
|
+
if (command.includes("gobuster") && !command.includes("-a ") && !command.includes("--useragent")) {
|
|
43982
|
+
wrapped = wrapped.replace(/\bgobuster\s+/, `gobuster -a "${userAgent}" `);
|
|
43983
|
+
}
|
|
43984
|
+
if (command.includes("ffuf") && !command.includes("User-Agent:")) {
|
|
43985
|
+
wrapped = wrapped.replace(/\bffuf\s+/, `ffuf -H "User-Agent: ${userAgent}" `);
|
|
43986
|
+
}
|
|
43987
|
+
if (command.includes("sqlmap") && !command.includes("--user-agent")) {
|
|
43988
|
+
wrapped = wrapped.replace(/\bsqlmap\s+/, `sqlmap --user-agent="${userAgent}" `);
|
|
43989
|
+
}
|
|
43990
|
+
if (command.includes("wfuzz") && !command.includes("-H") && !command.includes("User-Agent")) {
|
|
43991
|
+
wrapped = wrapped.replace(/\bwfuzz\s+/, `wfuzz -H "User-Agent: ${userAgent}" `);
|
|
43992
|
+
}
|
|
43993
|
+
if (command.includes("dirb") && !command.includes("-a")) {
|
|
43994
|
+
wrapped = wrapped.replace(/\bdirb\s+/, `dirb -a "${userAgent}" `);
|
|
43995
|
+
}
|
|
43996
|
+
if (command.includes("wpscan") && !command.includes("--user-agent")) {
|
|
43997
|
+
wrapped = wrapped.replace(/\bwpscan\s+/, `wpscan --user-agent "${userAgent}" `);
|
|
43998
|
+
}
|
|
43999
|
+
if (command.includes("nuclei") && !command.includes("-H")) {
|
|
44000
|
+
wrapped = wrapped.replace(/\bnuclei\s+/, `nuclei -H "User-Agent: ${userAgent}" `);
|
|
44001
|
+
}
|
|
44002
|
+
if (command.includes("httpx") && !command.includes("-H")) {
|
|
44003
|
+
wrapped = wrapped.replace(/\bhttpx\s+/, `httpx -H "User-Agent: ${userAgent}" `);
|
|
44004
|
+
}
|
|
44005
|
+
return wrapped;
|
|
44006
|
+
}
|
|
43805
44007
|
function createPentestTools(session, model, toolOverride) {
|
|
44008
|
+
const offensiveHeaders = getOffensiveHeaders(session);
|
|
44009
|
+
const rateLimiter = session._rateLimiter;
|
|
43806
44010
|
const executeCommand = tool({
|
|
43807
44011
|
name: "execute_command",
|
|
43808
44012
|
description: `Execute a shell command for penetration testing activities.
|
|
@@ -43847,6 +44051,9 @@ IMPORTANT: Always analyze results and adjust your approach based on findings.`,
|
|
|
43847
44051
|
inputSchema: ExecuteCommandInput,
|
|
43848
44052
|
execute: async ({ command, timeout = 30000, toolCallDescription }) => {
|
|
43849
44053
|
try {
|
|
44054
|
+
if (rateLimiter) {
|
|
44055
|
+
await rateLimiter.acquireSlot();
|
|
44056
|
+
}
|
|
43850
44057
|
if (toolOverride?.execute_command) {
|
|
43851
44058
|
return toolOverride.execute_command({
|
|
43852
44059
|
command,
|
|
@@ -43854,7 +44061,8 @@ IMPORTANT: Always analyze results and adjust your approach based on findings.`,
|
|
|
43854
44061
|
toolCallDescription
|
|
43855
44062
|
});
|
|
43856
44063
|
}
|
|
43857
|
-
const
|
|
44064
|
+
const finalCommand = offensiveHeaders ? wrapCommandWithHeaders(command, offensiveHeaders) : command;
|
|
44065
|
+
const { stdout, stderr } = await execAsync(finalCommand, {
|
|
43858
44066
|
timeout,
|
|
43859
44067
|
maxBuffer: 10 * 1024 * 1024
|
|
43860
44068
|
});
|
|
@@ -43864,7 +44072,7 @@ IMPORTANT: Always analyze results and adjust your approach based on findings.`,
|
|
|
43864
44072
|
|
|
43865
44073
|
(truncated) call the command again with grep / tail to paginate the response` || "(no output)",
|
|
43866
44074
|
stderr: stderr || "",
|
|
43867
|
-
command,
|
|
44075
|
+
command: finalCommand,
|
|
43868
44076
|
error: ""
|
|
43869
44077
|
};
|
|
43870
44078
|
} catch (error46) {
|
|
@@ -43902,6 +44110,9 @@ COMMON TESTING PATTERNS:
|
|
|
43902
44110
|
inputSchema: HttpRequestInput,
|
|
43903
44111
|
execute: async ({ url: url2, method, headers, body, followRedirects, timeout, toolCallDescription }) => {
|
|
43904
44112
|
try {
|
|
44113
|
+
if (rateLimiter) {
|
|
44114
|
+
await rateLimiter.acquireSlot();
|
|
44115
|
+
}
|
|
43905
44116
|
if (toolOverride?.http_request) {
|
|
43906
44117
|
return toolOverride.http_request({
|
|
43907
44118
|
url: url2,
|
|
@@ -43917,7 +44128,10 @@ COMMON TESTING PATTERNS:
|
|
|
43917
44128
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
43918
44129
|
const response = await fetch(url2, {
|
|
43919
44130
|
method,
|
|
43920
|
-
headers:
|
|
44131
|
+
headers: {
|
|
44132
|
+
...offensiveHeaders || {},
|
|
44133
|
+
...headers || {}
|
|
44134
|
+
},
|
|
43921
44135
|
body: body || undefined,
|
|
43922
44136
|
redirect: followRedirects ? "follow" : "manual",
|
|
43923
44137
|
signal: controller.signal
|
|
@@ -43976,82 +44190,6 @@ COMMON TESTING PATTERNS:
|
|
|
43976
44190
|
};
|
|
43977
44191
|
}
|
|
43978
44192
|
|
|
43979
|
-
// src/core/agent/sessions/index.ts
|
|
43980
|
-
import {
|
|
43981
|
-
mkdirSync,
|
|
43982
|
-
existsSync as existsSync2,
|
|
43983
|
-
writeFileSync as writeFileSync2,
|
|
43984
|
-
readFileSync as readFileSync2,
|
|
43985
|
-
readdirSync as readdirSync2,
|
|
43986
|
-
statSync,
|
|
43987
|
-
rmSync
|
|
43988
|
-
} from "fs";
|
|
43989
|
-
import { join as join2 } from "path";
|
|
43990
|
-
import { homedir } from "os";
|
|
43991
|
-
function generateSessionId(prefix) {
|
|
43992
|
-
const timestamp = Date.now().toString(36);
|
|
43993
|
-
return `${prefix ? `${prefix}-` : ""}${timestamp}`;
|
|
43994
|
-
}
|
|
43995
|
-
function getPensarDir() {
|
|
43996
|
-
return join2(homedir(), ".pensar");
|
|
43997
|
-
}
|
|
43998
|
-
function getExecutionsDir() {
|
|
43999
|
-
return join2(getPensarDir(), "executions");
|
|
44000
|
-
}
|
|
44001
|
-
function createSession(target, objective, prefix) {
|
|
44002
|
-
const sessionId = generateSessionId(prefix);
|
|
44003
|
-
const rootPath = join2(getExecutionsDir(), sessionId);
|
|
44004
|
-
const findingsPath = join2(rootPath, "findings");
|
|
44005
|
-
const scratchpadPath = join2(rootPath, "scratchpad");
|
|
44006
|
-
const logsPath = join2(rootPath, "logs");
|
|
44007
|
-
ensureDirectoryExists(rootPath);
|
|
44008
|
-
ensureDirectoryExists(findingsPath);
|
|
44009
|
-
ensureDirectoryExists(scratchpadPath);
|
|
44010
|
-
ensureDirectoryExists(logsPath);
|
|
44011
|
-
const session = {
|
|
44012
|
-
id: sessionId,
|
|
44013
|
-
rootPath,
|
|
44014
|
-
findingsPath,
|
|
44015
|
-
scratchpadPath,
|
|
44016
|
-
logsPath,
|
|
44017
|
-
target,
|
|
44018
|
-
objective: objective ?? "",
|
|
44019
|
-
startTime: new Date().toISOString()
|
|
44020
|
-
};
|
|
44021
|
-
const metadataPath = join2(rootPath, "session.json");
|
|
44022
|
-
writeFileSync2(metadataPath, JSON.stringify(session, null, 2));
|
|
44023
|
-
const readmePath = join2(rootPath, "README.md");
|
|
44024
|
-
const readme = `# Penetration Test Session
|
|
44025
|
-
|
|
44026
|
-
**Session ID:** ${sessionId}
|
|
44027
|
-
**Target:** ${target}
|
|
44028
|
-
**Objective:** ${objective}
|
|
44029
|
-
**Started:** ${session.startTime}
|
|
44030
|
-
|
|
44031
|
-
## Directory Structure
|
|
44032
|
-
|
|
44033
|
-
- \`findings/\` - Security findings and vulnerabilities
|
|
44034
|
-
- \`scratchpad/\` - Notes and temporary data during testing
|
|
44035
|
-
- \`logs/\` - Execution logs and command outputs
|
|
44036
|
-
- \`session.json\` - Session metadata
|
|
44037
|
-
|
|
44038
|
-
## Findings
|
|
44039
|
-
|
|
44040
|
-
Security findings will be documented in the \`findings/\` directory as individual files.
|
|
44041
|
-
|
|
44042
|
-
## Status
|
|
44043
|
-
|
|
44044
|
-
Testing in progress...
|
|
44045
|
-
`;
|
|
44046
|
-
writeFileSync2(readmePath, readme);
|
|
44047
|
-
return session;
|
|
44048
|
-
}
|
|
44049
|
-
function ensureDirectoryExists(path) {
|
|
44050
|
-
if (!existsSync2(path)) {
|
|
44051
|
-
mkdirSync(path, { recursive: true });
|
|
44052
|
-
}
|
|
44053
|
-
}
|
|
44054
|
-
|
|
44055
44193
|
// src/core/agent/utils.ts
|
|
44056
44194
|
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
44057
44195
|
import { execSync } from "child_process";
|
|
@@ -44866,9 +45004,10 @@ function runAgent(opts) {
|
|
|
44866
45004
|
silent,
|
|
44867
45005
|
authConfig,
|
|
44868
45006
|
toolOverride,
|
|
44869
|
-
messages
|
|
45007
|
+
messages,
|
|
45008
|
+
sessionConfig
|
|
44870
45009
|
} = opts;
|
|
44871
|
-
const session = opts.session || createSession(target, objective);
|
|
45010
|
+
const session = opts.session || createSession(target, objective, undefined, sessionConfig);
|
|
44872
45011
|
const pocsPath = join4(session.rootPath, "pocs");
|
|
44873
45012
|
if (!existsSync6(pocsPath)) {
|
|
44874
45013
|
mkdirSync4(pocsPath, { recursive: true });
|
|
@@ -45175,7 +45314,7 @@ var TargetSchema = exports_external.array(exports_external.object({
|
|
|
45175
45314
|
objective: exports_external.string().describe("The objective of the pentest")
|
|
45176
45315
|
}));
|
|
45177
45316
|
async function swarm(options) {
|
|
45178
|
-
const { targets, model, silent } = options;
|
|
45317
|
+
const { targets, model, silent, headerMode = "default", customHeaders } = options;
|
|
45179
45318
|
let targetsArray = [];
|
|
45180
45319
|
if (typeof targets === "string") {
|
|
45181
45320
|
const result = TargetSchema.safeParse(JSON.parse(targets));
|
|
@@ -45222,12 +45361,19 @@ async function swarm(options) {
|
|
|
45222
45361
|
console.log();
|
|
45223
45362
|
}
|
|
45224
45363
|
try {
|
|
45364
|
+
const sessionConfig = {
|
|
45365
|
+
offensiveHeaders: {
|
|
45366
|
+
mode: headerMode,
|
|
45367
|
+
headers: headerMode === "custom" ? customHeaders : undefined
|
|
45368
|
+
}
|
|
45369
|
+
};
|
|
45225
45370
|
const { streamResult } = runAgent({
|
|
45226
45371
|
session,
|
|
45227
45372
|
target: target.target,
|
|
45228
45373
|
objective: target.objective,
|
|
45229
45374
|
model,
|
|
45230
|
-
silent
|
|
45375
|
+
silent,
|
|
45376
|
+
sessionConfig
|
|
45231
45377
|
});
|
|
45232
45378
|
for await (const delta of streamResult.fullStream) {
|
|
45233
45379
|
if (delta.type === "text-delta") {} else if (delta.type === "tool-call") {} else if (delta.type === "tool-result") {}
|
|
@@ -45284,11 +45430,18 @@ async function main() {
|
|
|
45284
45430
|
console.error("Usage: pensar swarm <targets> [options]");
|
|
45285
45431
|
console.error();
|
|
45286
45432
|
console.error("Arguments:");
|
|
45287
|
-
console.error(" <targets>
|
|
45433
|
+
console.error(" <targets> JSON string or path to JSON file");
|
|
45288
45434
|
console.error();
|
|
45289
45435
|
console.error("Options:");
|
|
45290
|
-
console.error(" --model <model>
|
|
45291
|
-
console.error(" --silent
|
|
45436
|
+
console.error(" --model <model> AI model to use (default: claude-sonnet-4-5)");
|
|
45437
|
+
console.error(" --silent Suppress all output");
|
|
45438
|
+
console.error(" --headers <mode> Header mode: none, default, or custom (default: default)");
|
|
45439
|
+
console.error(" --header <name:value> Add custom header (requires --headers custom, can be repeated)");
|
|
45440
|
+
console.error();
|
|
45441
|
+
console.error("Header Modes:");
|
|
45442
|
+
console.error(" none No custom headers added to requests");
|
|
45443
|
+
console.error(" default Add 'User-Agent: pensar-apex' to all offensive requests");
|
|
45444
|
+
console.error(" custom Use custom headers defined with --header flag");
|
|
45292
45445
|
console.error();
|
|
45293
45446
|
console.error("Targets format (JSON array):");
|
|
45294
45447
|
console.error(" [");
|
|
@@ -45305,9 +45458,9 @@ async function main() {
|
|
|
45305
45458
|
console.error("Examples:");
|
|
45306
45459
|
console.error(" pensar swarm targets.json");
|
|
45307
45460
|
console.error(" pensar swarm targets.json --model gpt-4o");
|
|
45308
|
-
console.error(" pensar swarm targets.json --
|
|
45461
|
+
console.error(" pensar swarm targets.json --headers none");
|
|
45462
|
+
console.error(" pensar swarm targets.json --headers custom --header 'User-Agent: pensar_client123'");
|
|
45309
45463
|
console.error(` pensar swarm '[{"target":"api.example.com","objective":"Test API"}]'`);
|
|
45310
|
-
console.error(` pensar swarm '[{"target":"api.example.com","objective":"Test API"}]' --model gpt-4o`);
|
|
45311
45464
|
process.exit(1);
|
|
45312
45465
|
}
|
|
45313
45466
|
const targetsInput = args[0];
|
|
@@ -45322,6 +45475,46 @@ async function main() {
|
|
|
45322
45475
|
model = modelArg;
|
|
45323
45476
|
}
|
|
45324
45477
|
const silent = args.includes("--silent");
|
|
45478
|
+
const headersIndex = args.indexOf("--headers");
|
|
45479
|
+
let headerMode = "default";
|
|
45480
|
+
if (headersIndex !== -1) {
|
|
45481
|
+
const headersArg = args[headersIndex + 1];
|
|
45482
|
+
if (!headersArg || !["none", "default", "custom"].includes(headersArg)) {
|
|
45483
|
+
console.error("Error: --headers must be 'none', 'default', or 'custom'");
|
|
45484
|
+
process.exit(1);
|
|
45485
|
+
}
|
|
45486
|
+
headerMode = headersArg;
|
|
45487
|
+
}
|
|
45488
|
+
const customHeaders = {};
|
|
45489
|
+
for (let i = 0;i < args.length; i++) {
|
|
45490
|
+
if (args[i] === "--header") {
|
|
45491
|
+
const headerArg = args[i + 1];
|
|
45492
|
+
if (!headerArg) {
|
|
45493
|
+
console.error("Error: --header must be followed by 'Name: Value'");
|
|
45494
|
+
process.exit(1);
|
|
45495
|
+
}
|
|
45496
|
+
const colonIndex = headerArg.indexOf(":");
|
|
45497
|
+
if (colonIndex === -1) {
|
|
45498
|
+
console.error("Error: --header must be in format 'Name: Value'");
|
|
45499
|
+
process.exit(1);
|
|
45500
|
+
}
|
|
45501
|
+
const name18 = headerArg.substring(0, colonIndex).trim();
|
|
45502
|
+
const value = headerArg.substring(colonIndex + 1).trim();
|
|
45503
|
+
if (!name18) {
|
|
45504
|
+
console.error("Error: Header name cannot be empty");
|
|
45505
|
+
process.exit(1);
|
|
45506
|
+
}
|
|
45507
|
+
customHeaders[name18] = value;
|
|
45508
|
+
}
|
|
45509
|
+
}
|
|
45510
|
+
if (headerMode !== "custom" && Object.keys(customHeaders).length > 0) {
|
|
45511
|
+
console.error("Error: --header flag requires --headers custom");
|
|
45512
|
+
process.exit(1);
|
|
45513
|
+
}
|
|
45514
|
+
if (headerMode === "custom" && Object.keys(customHeaders).length === 0) {
|
|
45515
|
+
console.error("Error: --headers custom requires at least one --header flag");
|
|
45516
|
+
process.exit(1);
|
|
45517
|
+
}
|
|
45325
45518
|
let targetsJson;
|
|
45326
45519
|
if (targetsInput.startsWith("[") || targetsInput.startsWith("{")) {
|
|
45327
45520
|
targetsJson = targetsInput;
|
|
@@ -45337,7 +45530,9 @@ async function main() {
|
|
|
45337
45530
|
const session = await swarm({
|
|
45338
45531
|
targets: targetsJson,
|
|
45339
45532
|
model,
|
|
45340
|
-
silent
|
|
45533
|
+
silent,
|
|
45534
|
+
headerMode,
|
|
45535
|
+
...headerMode === "custom" && { customHeaders }
|
|
45341
45536
|
});
|
|
45342
45537
|
if (!session) {
|
|
45343
45538
|
if (!silent) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pensar/apex",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.28-canary.0",
|
|
4
4
|
"description": "AI-powered penetration testing CLI tool with terminal UI",
|
|
5
5
|
"module": "src/tui/index.tsx",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -19,13 +19,14 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
|
-
"build": "bun build src/tui/index.tsx --outdir build --target node --format esm --external sharp && bun build scripts/benchmark.ts --outdir build --target node --format esm --external sharp && bun build scripts/quicktest.ts --outdir build --target node --format esm --external sharp && bun build scripts/swarm.ts --outdir build --target node --format esm --external sharp",
|
|
22
|
+
"build": "bun build src/tui/index.tsx --outdir build --target node --format esm --external sharp && bun build scripts/benchmark.ts --outdir build --target node --format esm --external sharp && bun build scripts/quicktest.ts --outdir build --target node --format esm --external sharp && bun build scripts/pentest.ts --outdir build --target node --format esm --external sharp && bun build scripts/swarm.ts --outdir build --target node --format esm --external sharp",
|
|
23
23
|
"dev": "bun run scripts/watch.ts",
|
|
24
24
|
"dev:debug": "SHOW_CONSOLE=true bun run scripts/watch.ts",
|
|
25
25
|
"start": "bun run src/tui/index.tsx",
|
|
26
26
|
"pensar": "node bin/pensar.js",
|
|
27
27
|
"benchmark": "bun run scripts/benchmark.ts",
|
|
28
28
|
"quicktest": "bun run scripts/quicktest.ts",
|
|
29
|
+
"pentest": "bun run scripts/pentest.ts",
|
|
29
30
|
"swarm": "bun run scripts/swarm.ts",
|
|
30
31
|
"test": "vitest run",
|
|
31
32
|
"test:watch": "vitest",
|