@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/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
- existsSync
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 = join(session.findingsPath, filename);
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
- writeFileSync(filepath, markdown);
42623
- const summaryPath = join(session.rootPath, "findings-summary.md");
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
- writeFileSync(summaryPath, header + summaryEntry);
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 = join(session.scratchpadPath, "notes.md");
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
- writeFileSync(scratchpadFile, header + entry);
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 (existsSync(session.findingsPath)) {
42823
- const findingFiles = readdirSync(session.findingsPath).filter((f) => f.endsWith(".json"));
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 = join(session.findingsPath, file2);
42826
- const content = readFileSync(filePath, "utf-8");
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 = join(session.scratchpadPath, "notes.md");
42852
- if (existsSync(scratchpadFile)) {
42853
- scratchpadNotes = readFileSync(scratchpadFile, "utf-8");
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 = join(session.scratchpadPath, "test-results.jsonl");
42857
- if (existsSync(testResultsFile)) {
42858
- const testLines = readFileSync(testResultsFile, "utf-8").split(`
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 = join(session.rootPath, "pentest-report.md");
43033
- writeFileSync(reportPath, report);
43034
- const readmePath = join(session.rootPath, "README.md");
43035
- if (existsSync(readmePath)) {
43036
- let readme = readFileSync(readmePath, "utf-8");
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
- writeFileSync(readmePath, readme);
43198
+ writeFileSync2(readmePath, readme);
43041
43199
  }
43042
- const metadataPath = join(session.rootPath, "session.json");
43043
- if (existsSync(metadataPath)) {
43044
- const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
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
- writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
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 = join(session.scratchpadPath, "test-results.jsonl");
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 = join(session.scratchpadPath, "test-results.jsonl");
43503
- if (!existsSync(testResultsPath)) {
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 = readFileSync(testResultsPath, "utf-8");
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 = join(session.scratchpadPath, "notes.md");
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
- writeFileSync(scratchpadFile, header + entry);
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 { stdout, stderr } = await execAsync(command, {
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: 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> JSON string or path to JSON file");
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> Specify the AI model to use (default: claude-sonnet-4-5)");
45291
- console.error(" --silent Suppress all output");
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 --silent");
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.27",
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",