@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/quicktest.js
CHANGED
|
@@ -41485,13 +41485,170 @@ 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
|
+
// src/core/agent/tools.ts
|
|
41495
41652
|
var execAsync = promisify(exec);
|
|
41496
41653
|
var PayloadSchema = exports_external.object({
|
|
41497
41654
|
payload: exports_external.string(),
|
|
@@ -42585,7 +42742,7 @@ FINDING STRUCTURE:
|
|
|
42585
42742
|
const safeTitle = finding.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 50);
|
|
42586
42743
|
const findingId = `${timestamp.split("T")[0]}-${safeTitle}`;
|
|
42587
42744
|
const filename = `${findingId}.md`;
|
|
42588
|
-
const filepath =
|
|
42745
|
+
const filepath = join2(session.findingsPath, filename);
|
|
42589
42746
|
const markdown = `# ${finding.title}
|
|
42590
42747
|
|
|
42591
42748
|
**Severity:** ${finding.severity}
|
|
@@ -42619,8 +42776,8 @@ ${finding.references}` : ""}
|
|
|
42619
42776
|
|
|
42620
42777
|
*This finding was automatically documented by the Pensar penetration testing agent.*
|
|
42621
42778
|
`;
|
|
42622
|
-
|
|
42623
|
-
const summaryPath =
|
|
42779
|
+
writeFileSync2(filepath, markdown);
|
|
42780
|
+
const summaryPath = join2(session.rootPath, "findings-summary.md");
|
|
42624
42781
|
const summaryEntry = `- [${finding.severity}] ${finding.title} - \`findings/${filename}\`
|
|
42625
42782
|
`;
|
|
42626
42783
|
try {
|
|
@@ -42634,7 +42791,7 @@ ${finding.references}` : ""}
|
|
|
42634
42791
|
## All Findings
|
|
42635
42792
|
|
|
42636
42793
|
`;
|
|
42637
|
-
|
|
42794
|
+
writeFileSync2(summaryPath, header + summaryEntry);
|
|
42638
42795
|
}
|
|
42639
42796
|
return {
|
|
42640
42797
|
success: true,
|
|
@@ -42673,7 +42830,7 @@ The scratchpad is session-specific and helps maintain context during long assess
|
|
|
42673
42830
|
execute: async ({ note, category }) => {
|
|
42674
42831
|
try {
|
|
42675
42832
|
const timestamp = new Date().toISOString();
|
|
42676
|
-
const scratchpadFile =
|
|
42833
|
+
const scratchpadFile = join2(session.scratchpadPath, "notes.md");
|
|
42677
42834
|
const entry = `## ${category.toUpperCase()} - ${timestamp}
|
|
42678
42835
|
|
|
42679
42836
|
${note}
|
|
@@ -42692,7 +42849,7 @@ ${note}
|
|
|
42692
42849
|
---
|
|
42693
42850
|
|
|
42694
42851
|
`;
|
|
42695
|
-
|
|
42852
|
+
writeFileSync2(scratchpadFile, header + entry);
|
|
42696
42853
|
}
|
|
42697
42854
|
return {
|
|
42698
42855
|
success: true,
|
|
@@ -42819,11 +42976,11 @@ The report will be saved as 'pentest-report.md' in the session root directory.`,
|
|
|
42819
42976
|
MEDIUM: 0,
|
|
42820
42977
|
LOW: 0
|
|
42821
42978
|
};
|
|
42822
|
-
if (
|
|
42823
|
-
const findingFiles =
|
|
42979
|
+
if (existsSync2(session.findingsPath)) {
|
|
42980
|
+
const findingFiles = readdirSync2(session.findingsPath).filter((f) => f.endsWith(".json"));
|
|
42824
42981
|
for (const file2 of findingFiles) {
|
|
42825
|
-
const filePath =
|
|
42826
|
-
const content =
|
|
42982
|
+
const filePath = join2(session.findingsPath, file2);
|
|
42983
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
42827
42984
|
const severityMatch = content.match(/\*\*Severity:\*\*\s+(CRITICAL|HIGH|MEDIUM|LOW)/);
|
|
42828
42985
|
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
42829
42986
|
if (severityMatch && titleMatch) {
|
|
@@ -42848,14 +43005,14 @@ The report will be saved as 'pentest-report.md' in the session root directory.`,
|
|
|
42848
43005
|
const totalFindings = findings.length;
|
|
42849
43006
|
const criticalAndHigh = severityCounts.CRITICAL + severityCounts.HIGH;
|
|
42850
43007
|
let scratchpadNotes = "";
|
|
42851
|
-
const scratchpadFile =
|
|
42852
|
-
if (
|
|
42853
|
-
scratchpadNotes =
|
|
43008
|
+
const scratchpadFile = join2(session.scratchpadPath, "notes.md");
|
|
43009
|
+
if (existsSync2(scratchpadFile)) {
|
|
43010
|
+
scratchpadNotes = readFileSync2(scratchpadFile, "utf-8");
|
|
42854
43011
|
}
|
|
42855
43012
|
let testResultsSummary = "";
|
|
42856
|
-
const testResultsFile =
|
|
42857
|
-
if (
|
|
42858
|
-
const testLines =
|
|
43013
|
+
const testResultsFile = join2(session.scratchpadPath, "test-results.jsonl");
|
|
43014
|
+
if (existsSync2(testResultsFile)) {
|
|
43015
|
+
const testLines = readFileSync2(testResultsFile, "utf-8").split(`
|
|
42859
43016
|
`).filter((l) => l.trim());
|
|
42860
43017
|
const testResults = testLines.map((line) => {
|
|
42861
43018
|
try {
|
|
@@ -43029,25 +43186,25 @@ This report should be treated as confidential and distributed only to authorized
|
|
|
43029
43186
|
*Report generated by Pensar Penetration Testing Agent*
|
|
43030
43187
|
*Session: ${session.id}*
|
|
43031
43188
|
`;
|
|
43032
|
-
const reportPath =
|
|
43033
|
-
|
|
43034
|
-
const readmePath =
|
|
43035
|
-
if (
|
|
43036
|
-
let readme =
|
|
43189
|
+
const reportPath = join2(session.rootPath, "pentest-report.md");
|
|
43190
|
+
writeFileSync2(reportPath, report);
|
|
43191
|
+
const readmePath = join2(session.rootPath, "README.md");
|
|
43192
|
+
if (existsSync2(readmePath)) {
|
|
43193
|
+
let readme = readFileSync2(readmePath, "utf-8");
|
|
43037
43194
|
readme = readme.replace("Testing in progress...", `Testing completed on ${endDate.toLocaleString()}
|
|
43038
43195
|
|
|
43039
43196
|
**Final Report:** \`pentest-report.md\``);
|
|
43040
|
-
|
|
43197
|
+
writeFileSync2(readmePath, readme);
|
|
43041
43198
|
}
|
|
43042
|
-
const metadataPath =
|
|
43043
|
-
if (
|
|
43044
|
-
const metadata = JSON.parse(
|
|
43199
|
+
const metadataPath = join2(session.rootPath, "session.json");
|
|
43200
|
+
if (existsSync2(metadataPath)) {
|
|
43201
|
+
const metadata = JSON.parse(readFileSync2(metadataPath, "utf-8"));
|
|
43045
43202
|
metadata.endTime = endTime;
|
|
43046
43203
|
metadata.duration = duration3;
|
|
43047
43204
|
metadata.status = "completed";
|
|
43048
43205
|
metadata.totalFindings = totalFindings;
|
|
43049
43206
|
metadata.severityCounts = severityCounts;
|
|
43050
|
-
|
|
43207
|
+
writeFileSync2(metadataPath, JSON.stringify(metadata, null, 2));
|
|
43051
43208
|
}
|
|
43052
43209
|
return {
|
|
43053
43210
|
success: true,
|
|
@@ -43088,7 +43245,7 @@ async function recordTestResultCore(session, params) {
|
|
|
43088
43245
|
evidence: params.evidence || "",
|
|
43089
43246
|
confidence: params.confidence || "high"
|
|
43090
43247
|
};
|
|
43091
|
-
const testResultsPath =
|
|
43248
|
+
const testResultsPath = join2(session.scratchpadPath, "test-results.jsonl");
|
|
43092
43249
|
const resultLine = JSON.stringify(testResult) + `
|
|
43093
43250
|
`;
|
|
43094
43251
|
appendFileSync(testResultsPath, resultLine);
|
|
@@ -43499,8 +43656,8 @@ Use this when:
|
|
|
43499
43656
|
}),
|
|
43500
43657
|
execute: async ({ objective }) => {
|
|
43501
43658
|
try {
|
|
43502
|
-
const testResultsPath =
|
|
43503
|
-
if (!
|
|
43659
|
+
const testResultsPath = join2(session.scratchpadPath, "test-results.jsonl");
|
|
43660
|
+
if (!existsSync2(testResultsPath)) {
|
|
43504
43661
|
return {
|
|
43505
43662
|
success: true,
|
|
43506
43663
|
totalTests: 0,
|
|
@@ -43509,7 +43666,7 @@ Use this when:
|
|
|
43509
43666
|
suggestions: objective ? `Based on objective "${objective}", consider testing relevant parameters with test_parameter tool.` : "Use test_parameter to test parameters for vulnerabilities."
|
|
43510
43667
|
};
|
|
43511
43668
|
}
|
|
43512
|
-
const fileContent =
|
|
43669
|
+
const fileContent = readFileSync2(testResultsPath, "utf-8");
|
|
43513
43670
|
const testResults = fileContent.trim().split(`
|
|
43514
43671
|
`).filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
43515
43672
|
const parametersTested = new Set;
|
|
@@ -43766,7 +43923,7 @@ ${discovered.map((d) => `- ${d.endpoint} [${d.method}] → HTTP ${d.status}`).jo
|
|
|
43766
43923
|
Not found: ${range.max - range.min + 1 - discovered.length} endpoints returned 404`;
|
|
43767
43924
|
try {
|
|
43768
43925
|
const timestamp = new Date().toISOString();
|
|
43769
|
-
const scratchpadFile =
|
|
43926
|
+
const scratchpadFile = join2(session.scratchpadPath, "notes.md");
|
|
43770
43927
|
const entry = `## RESULT - ${timestamp}
|
|
43771
43928
|
|
|
43772
43929
|
${note}
|
|
@@ -43785,7 +43942,7 @@ ${note}
|
|
|
43785
43942
|
---
|
|
43786
43943
|
|
|
43787
43944
|
`;
|
|
43788
|
-
|
|
43945
|
+
writeFileSync2(scratchpadFile, header + entry);
|
|
43789
43946
|
}
|
|
43790
43947
|
} catch (err) {
|
|
43791
43948
|
console.error("Failed to record to scratchpad:", err);
|
|
@@ -43802,7 +43959,53 @@ ${note}
|
|
|
43802
43959
|
}
|
|
43803
43960
|
});
|
|
43804
43961
|
}
|
|
43962
|
+
function wrapCommandWithHeaders(command, headers) {
|
|
43963
|
+
const userAgent = headers["User-Agent"];
|
|
43964
|
+
if (!userAgent)
|
|
43965
|
+
return command;
|
|
43966
|
+
let wrapped = command;
|
|
43967
|
+
if (command.includes("curl") && !command.includes("User-Agent") && !command.includes("-A")) {
|
|
43968
|
+
wrapped = wrapped.replace(/\bcurl(\s+)/g, `curl -A "${userAgent}" $1`);
|
|
43969
|
+
}
|
|
43970
|
+
if (command.includes("nikto") && !command.includes("-useragent")) {
|
|
43971
|
+
wrapped = wrapped.replace(/\bnikto\s+/, `nikto -useragent "${userAgent}" `);
|
|
43972
|
+
}
|
|
43973
|
+
if (command.includes("nmap") && command.includes("--script") && /--script[= ](.*?http.*?)/.test(command) && !command.includes("http.useragent")) {
|
|
43974
|
+
if (command.includes("--script-args")) {
|
|
43975
|
+
wrapped = wrapped.replace(/--script-args\s+([^\s]+)/, `--script-args $1,http.useragent="${userAgent}"`);
|
|
43976
|
+
} else {
|
|
43977
|
+
wrapped = `${wrapped} --script-args http.useragent="${userAgent}"`;
|
|
43978
|
+
}
|
|
43979
|
+
}
|
|
43980
|
+
if (command.includes("gobuster") && !command.includes("-a ") && !command.includes("--useragent")) {
|
|
43981
|
+
wrapped = wrapped.replace(/\bgobuster\s+/, `gobuster -a "${userAgent}" `);
|
|
43982
|
+
}
|
|
43983
|
+
if (command.includes("ffuf") && !command.includes("User-Agent:")) {
|
|
43984
|
+
wrapped = wrapped.replace(/\bffuf\s+/, `ffuf -H "User-Agent: ${userAgent}" `);
|
|
43985
|
+
}
|
|
43986
|
+
if (command.includes("sqlmap") && !command.includes("--user-agent")) {
|
|
43987
|
+
wrapped = wrapped.replace(/\bsqlmap\s+/, `sqlmap --user-agent="${userAgent}" `);
|
|
43988
|
+
}
|
|
43989
|
+
if (command.includes("wfuzz") && !command.includes("-H") && !command.includes("User-Agent")) {
|
|
43990
|
+
wrapped = wrapped.replace(/\bwfuzz\s+/, `wfuzz -H "User-Agent: ${userAgent}" `);
|
|
43991
|
+
}
|
|
43992
|
+
if (command.includes("dirb") && !command.includes("-a")) {
|
|
43993
|
+
wrapped = wrapped.replace(/\bdirb\s+/, `dirb -a "${userAgent}" `);
|
|
43994
|
+
}
|
|
43995
|
+
if (command.includes("wpscan") && !command.includes("--user-agent")) {
|
|
43996
|
+
wrapped = wrapped.replace(/\bwpscan\s+/, `wpscan --user-agent "${userAgent}" `);
|
|
43997
|
+
}
|
|
43998
|
+
if (command.includes("nuclei") && !command.includes("-H")) {
|
|
43999
|
+
wrapped = wrapped.replace(/\bnuclei\s+/, `nuclei -H "User-Agent: ${userAgent}" `);
|
|
44000
|
+
}
|
|
44001
|
+
if (command.includes("httpx") && !command.includes("-H")) {
|
|
44002
|
+
wrapped = wrapped.replace(/\bhttpx\s+/, `httpx -H "User-Agent: ${userAgent}" `);
|
|
44003
|
+
}
|
|
44004
|
+
return wrapped;
|
|
44005
|
+
}
|
|
43805
44006
|
function createPentestTools(session, model, toolOverride) {
|
|
44007
|
+
const offensiveHeaders = getOffensiveHeaders(session);
|
|
44008
|
+
const rateLimiter = session._rateLimiter;
|
|
43806
44009
|
const executeCommand = tool({
|
|
43807
44010
|
name: "execute_command",
|
|
43808
44011
|
description: `Execute a shell command for penetration testing activities.
|
|
@@ -43847,6 +44050,9 @@ IMPORTANT: Always analyze results and adjust your approach based on findings.`,
|
|
|
43847
44050
|
inputSchema: ExecuteCommandInput,
|
|
43848
44051
|
execute: async ({ command, timeout = 30000, toolCallDescription }) => {
|
|
43849
44052
|
try {
|
|
44053
|
+
if (rateLimiter) {
|
|
44054
|
+
await rateLimiter.acquireSlot();
|
|
44055
|
+
}
|
|
43850
44056
|
if (toolOverride?.execute_command) {
|
|
43851
44057
|
return toolOverride.execute_command({
|
|
43852
44058
|
command,
|
|
@@ -43854,7 +44060,8 @@ IMPORTANT: Always analyze results and adjust your approach based on findings.`,
|
|
|
43854
44060
|
toolCallDescription
|
|
43855
44061
|
});
|
|
43856
44062
|
}
|
|
43857
|
-
const
|
|
44063
|
+
const finalCommand = offensiveHeaders ? wrapCommandWithHeaders(command, offensiveHeaders) : command;
|
|
44064
|
+
const { stdout, stderr } = await execAsync(finalCommand, {
|
|
43858
44065
|
timeout,
|
|
43859
44066
|
maxBuffer: 10 * 1024 * 1024
|
|
43860
44067
|
});
|
|
@@ -43864,7 +44071,7 @@ IMPORTANT: Always analyze results and adjust your approach based on findings.`,
|
|
|
43864
44071
|
|
|
43865
44072
|
(truncated) call the command again with grep / tail to paginate the response` || "(no output)",
|
|
43866
44073
|
stderr: stderr || "",
|
|
43867
|
-
command,
|
|
44074
|
+
command: finalCommand,
|
|
43868
44075
|
error: ""
|
|
43869
44076
|
};
|
|
43870
44077
|
} catch (error46) {
|
|
@@ -43902,6 +44109,9 @@ COMMON TESTING PATTERNS:
|
|
|
43902
44109
|
inputSchema: HttpRequestInput,
|
|
43903
44110
|
execute: async ({ url: url2, method, headers, body, followRedirects, timeout, toolCallDescription }) => {
|
|
43904
44111
|
try {
|
|
44112
|
+
if (rateLimiter) {
|
|
44113
|
+
await rateLimiter.acquireSlot();
|
|
44114
|
+
}
|
|
43905
44115
|
if (toolOverride?.http_request) {
|
|
43906
44116
|
return toolOverride.http_request({
|
|
43907
44117
|
url: url2,
|
|
@@ -43917,7 +44127,10 @@ COMMON TESTING PATTERNS:
|
|
|
43917
44127
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
43918
44128
|
const response = await fetch(url2, {
|
|
43919
44129
|
method,
|
|
43920
|
-
headers:
|
|
44130
|
+
headers: {
|
|
44131
|
+
...offensiveHeaders || {},
|
|
44132
|
+
...headers || {}
|
|
44133
|
+
},
|
|
43921
44134
|
body: body || undefined,
|
|
43922
44135
|
redirect: followRedirects ? "follow" : "manual",
|
|
43923
44136
|
signal: controller.signal
|
|
@@ -43976,82 +44189,6 @@ COMMON TESTING PATTERNS:
|
|
|
43976
44189
|
};
|
|
43977
44190
|
}
|
|
43978
44191
|
|
|
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
44192
|
// src/core/agent/utils.ts
|
|
44056
44193
|
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
44057
44194
|
import { execSync } from "child_process";
|
|
@@ -44866,9 +45003,10 @@ function runAgent(opts) {
|
|
|
44866
45003
|
silent,
|
|
44867
45004
|
authConfig,
|
|
44868
45005
|
toolOverride,
|
|
44869
|
-
messages
|
|
45006
|
+
messages,
|
|
45007
|
+
sessionConfig
|
|
44870
45008
|
} = opts;
|
|
44871
|
-
const session = opts.session || createSession(target, objective);
|
|
45009
|
+
const session = opts.session || createSession(target, objective, undefined, sessionConfig);
|
|
44872
45010
|
const pocsPath = join4(session.rootPath, "pocs");
|
|
44873
45011
|
if (!existsSync6(pocsPath)) {
|
|
44874
45012
|
mkdirSync4(pocsPath, { recursive: true });
|
|
@@ -45032,19 +45170,45 @@ You are only authorized to perform testing against the specific target endpoint
|
|
|
45032
45170
|
|
|
45033
45171
|
// scripts/quicktest.ts
|
|
45034
45172
|
async function runQuicktest(options) {
|
|
45035
|
-
const {
|
|
45173
|
+
const {
|
|
45174
|
+
target,
|
|
45175
|
+
objective,
|
|
45176
|
+
model = "claude-sonnet-4-5",
|
|
45177
|
+
headerMode = "default",
|
|
45178
|
+
customHeaders,
|
|
45179
|
+
rps
|
|
45180
|
+
} = options;
|
|
45036
45181
|
console.log("=".repeat(80));
|
|
45037
45182
|
console.log("PENSAR QUICK PENTEST");
|
|
45038
45183
|
console.log("=".repeat(80));
|
|
45039
45184
|
console.log(`Target: ${target}`);
|
|
45040
45185
|
console.log(`Objective: ${objective}`);
|
|
45041
45186
|
console.log(`Model: ${model}`);
|
|
45187
|
+
console.log(`Rate Limit: ${rps ? `${rps} req/s` : "Unlimited"}`);
|
|
45188
|
+
console.log(`Headers: ${headerMode === "none" ? "None" : headerMode === "default" ? "Default (pensar-apex)" : "Custom"}`);
|
|
45189
|
+
if (headerMode === "custom" && customHeaders) {
|
|
45190
|
+
for (const [key, value] of Object.entries(customHeaders)) {
|
|
45191
|
+
console.log(` ${key}: ${value}`);
|
|
45192
|
+
}
|
|
45193
|
+
}
|
|
45042
45194
|
console.log();
|
|
45043
45195
|
try {
|
|
45196
|
+
const sessionConfig = {
|
|
45197
|
+
offensiveHeaders: {
|
|
45198
|
+
mode: headerMode,
|
|
45199
|
+
headers: headerMode === "custom" ? customHeaders : undefined
|
|
45200
|
+
},
|
|
45201
|
+
...rps && {
|
|
45202
|
+
rateLimiter: {
|
|
45203
|
+
requestsPerSecond: rps
|
|
45204
|
+
}
|
|
45205
|
+
}
|
|
45206
|
+
};
|
|
45044
45207
|
const { streamResult, session } = runAgent({
|
|
45045
45208
|
target,
|
|
45046
45209
|
objective,
|
|
45047
|
-
model
|
|
45210
|
+
model,
|
|
45211
|
+
sessionConfig
|
|
45048
45212
|
});
|
|
45049
45213
|
console.log(`Session ID: ${session.id}`);
|
|
45050
45214
|
console.log(`Session Path: ${session.rootPath}`);
|
|
@@ -45085,17 +45249,28 @@ async function runQuicktest(options) {
|
|
|
45085
45249
|
async function main() {
|
|
45086
45250
|
const args = process.argv.slice(2);
|
|
45087
45251
|
if (args.length === 0) {
|
|
45088
|
-
console.error("Usage: pensar quicktest --target <target> --objective <objective> [
|
|
45252
|
+
console.error("Usage: pensar quicktest --target <target> --objective <objective> [options]");
|
|
45253
|
+
console.error();
|
|
45254
|
+
console.error("Required:");
|
|
45255
|
+
console.error(" --target <target> Target URL or IP address to test");
|
|
45256
|
+
console.error(" --objective <objective> Objective or goal of the pentest");
|
|
45089
45257
|
console.error();
|
|
45090
45258
|
console.error("Options:");
|
|
45091
|
-
console.error(" --target <target> Target URL or IP address to test (required)");
|
|
45092
|
-
console.error(" --objective <objective> Objective or goal of the pentest (required)");
|
|
45093
45259
|
console.error(" --model <model> AI model to use (default: claude-sonnet-4-5)");
|
|
45260
|
+
console.error(" --rps <number> Rate limit: requests per second (default: unlimited)");
|
|
45261
|
+
console.error(" --headers <mode> Header mode: none, default, or custom (default: default)");
|
|
45262
|
+
console.error(" --header <name:value> Add custom header (requires --headers custom, can be repeated)");
|
|
45263
|
+
console.error();
|
|
45264
|
+
console.error("Header Modes:");
|
|
45265
|
+
console.error(" none No custom headers added to requests");
|
|
45266
|
+
console.error(" default Add 'User-Agent: pensar-apex' to all offensive requests");
|
|
45267
|
+
console.error(" custom Use custom headers defined with --header flag");
|
|
45094
45268
|
console.error();
|
|
45095
45269
|
console.error("Examples:");
|
|
45096
|
-
console.error(" pensar quicktest --target http://localhost:3000 --objective 'Find SQL injection
|
|
45097
|
-
console.error(" pensar quicktest --target 192.168.1.100 --objective 'Test
|
|
45098
|
-
console.error(" pensar quicktest --target
|
|
45270
|
+
console.error(" pensar quicktest --target http://localhost:3000 --objective 'Find SQL injection'");
|
|
45271
|
+
console.error(" pensar quicktest --target 192.168.1.100 --objective 'Test auth bypass' --headers none");
|
|
45272
|
+
console.error(" pensar quicktest --target api.example.com --objective 'API testing' --rps 5 \\");
|
|
45273
|
+
console.error(" --headers custom --header 'User-Agent: pensar_client123' --header 'X-Bug-Bounty: researcher'");
|
|
45099
45274
|
console.error();
|
|
45100
45275
|
process.exit(1);
|
|
45101
45276
|
}
|
|
@@ -45129,11 +45304,69 @@ async function main() {
|
|
|
45129
45304
|
}
|
|
45130
45305
|
model = modelArg;
|
|
45131
45306
|
}
|
|
45307
|
+
const rpsIndex = args.indexOf("--rps");
|
|
45308
|
+
let rps;
|
|
45309
|
+
if (rpsIndex !== -1) {
|
|
45310
|
+
const rpsArg = args[rpsIndex + 1];
|
|
45311
|
+
if (!rpsArg) {
|
|
45312
|
+
console.error("Error: --rps must be followed by a number");
|
|
45313
|
+
process.exit(1);
|
|
45314
|
+
}
|
|
45315
|
+
const parsedRps = parseInt(rpsArg, 10);
|
|
45316
|
+
if (isNaN(parsedRps) || parsedRps <= 0) {
|
|
45317
|
+
console.error("Error: --rps must be a positive number");
|
|
45318
|
+
process.exit(1);
|
|
45319
|
+
}
|
|
45320
|
+
rps = parsedRps;
|
|
45321
|
+
}
|
|
45322
|
+
const headersIndex = args.indexOf("--headers");
|
|
45323
|
+
let headerMode = "default";
|
|
45324
|
+
if (headersIndex !== -1) {
|
|
45325
|
+
const headersArg = args[headersIndex + 1];
|
|
45326
|
+
if (!headersArg || !["none", "default", "custom"].includes(headersArg)) {
|
|
45327
|
+
console.error("Error: --headers must be 'none', 'default', or 'custom'");
|
|
45328
|
+
process.exit(1);
|
|
45329
|
+
}
|
|
45330
|
+
headerMode = headersArg;
|
|
45331
|
+
}
|
|
45332
|
+
const customHeaders = {};
|
|
45333
|
+
for (let i = 0;i < args.length; i++) {
|
|
45334
|
+
if (args[i] === "--header") {
|
|
45335
|
+
const headerArg = args[i + 1];
|
|
45336
|
+
if (!headerArg) {
|
|
45337
|
+
console.error("Error: --header must be followed by 'Name: Value'");
|
|
45338
|
+
process.exit(1);
|
|
45339
|
+
}
|
|
45340
|
+
const colonIndex = headerArg.indexOf(":");
|
|
45341
|
+
if (colonIndex === -1) {
|
|
45342
|
+
console.error("Error: --header must be in format 'Name: Value'");
|
|
45343
|
+
process.exit(1);
|
|
45344
|
+
}
|
|
45345
|
+
const name18 = headerArg.substring(0, colonIndex).trim();
|
|
45346
|
+
const value = headerArg.substring(colonIndex + 1).trim();
|
|
45347
|
+
if (!name18) {
|
|
45348
|
+
console.error("Error: Header name cannot be empty");
|
|
45349
|
+
process.exit(1);
|
|
45350
|
+
}
|
|
45351
|
+
customHeaders[name18] = value;
|
|
45352
|
+
}
|
|
45353
|
+
}
|
|
45354
|
+
if (headerMode !== "custom" && Object.keys(customHeaders).length > 0) {
|
|
45355
|
+
console.error("Error: --header flag requires --headers custom");
|
|
45356
|
+
process.exit(1);
|
|
45357
|
+
}
|
|
45358
|
+
if (headerMode === "custom" && Object.keys(customHeaders).length === 0) {
|
|
45359
|
+
console.error("Error: --headers custom requires at least one --header flag");
|
|
45360
|
+
process.exit(1);
|
|
45361
|
+
}
|
|
45132
45362
|
try {
|
|
45133
45363
|
await runQuicktest({
|
|
45134
45364
|
target,
|
|
45135
45365
|
objective,
|
|
45136
|
-
...model && { model }
|
|
45366
|
+
...model && { model },
|
|
45367
|
+
headerMode,
|
|
45368
|
+
...headerMode === "custom" && { customHeaders },
|
|
45369
|
+
...rps && { rps }
|
|
45137
45370
|
});
|
|
45138
45371
|
} catch (error46) {
|
|
45139
45372
|
console.error("Fatal error:", error46.message);
|