@pensar/apex 2.6.1-canary.e936ec17 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/build/agent-6w7egcf1.js +13 -0
  2. package/build/agent-9zx75feh.js +13 -0
  3. package/build/{agent-t2g0e5v1.js → agent-ayreaj6z.js} +5 -5
  4. package/build/{agent-runs-mw4bp0n5.js → agent-runs-g37vkrjr.js} +16 -16
  5. package/build/{agent-x8wxd9ma.js → agent-yby11zaq.js} +6 -6
  6. package/build/{apps-b2x50tm7.js → apps-xn6km9yn.js} +13 -13
  7. package/build/{auth-0rfqyw3s.js → auth-d105wc64.js} +13 -13
  8. package/build/{blackboxAgent-7bapanbv.js → blackboxAgent-s56sgx9b.js} +6 -6
  9. package/build/{blackboxPentest-ngs99vv8.js → blackboxPentest-ps78v3w7.js} +11 -11
  10. package/build/{cli-tt7d70n9.js → cli-1ve18npr.js} +1 -1
  11. package/build/{cli-en2trtdx.js → cli-3sp6nd8d.js} +2 -2
  12. package/build/{cli-k594kwq8.js → cli-65g6rcat.js} +1 -1
  13. package/build/{cli-w9d2bnf3.js → cli-709rvekb.js} +1 -1
  14. package/build/{cli-rwnm0p5r.js → cli-7eea4e2y.js} +39 -7
  15. package/build/{cli-1b0pw0wp.js → cli-ae8jqxqr.js} +2 -2
  16. package/build/{cli-raavtvwt.js → cli-az3xq70v.js} +1 -1
  17. package/build/{cli-c2nb79ce.js → cli-c05y640n.js} +1 -1
  18. package/build/{cli-9d8m1qw2.js → cli-d37a42dc.js} +8 -8
  19. package/build/{cli-jpa3w5b4.js → cli-dpsw2fe6.js} +5 -5
  20. package/build/{cli-9a7b3x56.js → cli-fy6vh2a7.js} +1 -1
  21. package/build/{cli-72aenzba.js → cli-gx71jcke.js} +1 -1
  22. package/build/{cli-84qrqxjc.js → cli-k2nwxta0.js} +2 -2
  23. package/build/{cli-ay461rtr.js → cli-kb21qhvm.js} +2 -2
  24. package/build/{cli-sf6x1vjx.js → cli-n2en7j5b.js} +1 -1
  25. package/build/{cli-zahhskff.js → cli-ngq9dr8n.js} +3 -3
  26. package/build/{cli-8q41y2ss.js → cli-qgz1q9g5.js} +12 -12
  27. package/build/{cli-h68jfmth.js → cli-xtc2t6me.js} +3 -3
  28. package/build/cli.js +40 -36
  29. package/build/{config-8eg7v57g.js → config-zpqkd5cz.js} +1 -1
  30. package/build/{fastStrike-6pejawnd.js → fastStrike-mcjdvwez.js} +5 -5
  31. package/build/{fixes-94pbkpy6.js → fixes-kytx5dmj.js} +13 -13
  32. package/build/{index-xavch1zh.js → index-4vqdgb42.js} +19 -19
  33. package/build/{index-k3vaa38r.js → index-dr3tg1w3.js} +1 -1
  34. package/build/{index-g8z6kn6v.js → index-fv64eb32.js} +2 -2
  35. package/build/{index-bsdq9wd9.js → index-je6hcsqr.js} +1 -1
  36. package/build/{index-c6szs0vn.js → index-n65z6v7b.js} +6 -6
  37. package/build/{index-jvpmhhqm.js → index-ns4m4s01.js} +4 -4
  38. package/build/{index-9hnznpdk.js → index-qv4cc65p.js} +28 -14
  39. package/build/{index-2sc33n6h.js → index-r9yjkfrv.js} +4 -4
  40. package/build/{index-yt0n80v2.js → index-wqetp6gq.js} +5 -5
  41. package/build/{issues-hxcq3q0g.js → issues-eb3q9d3a.js} +13 -13
  42. package/build/knowledge-fqsvtx0f.js +139 -0
  43. package/build/lib/{chunk-nv32cpyc.js → chunk-1azdancq.js} +1 -1
  44. package/build/lib/{chunk-9yjz0ynw.js → chunk-1fy8azee.js} +1 -1
  45. package/build/lib/{chunk-zfp495pt.js → chunk-1jvwqnem.js} +3 -3
  46. package/build/lib/{chunk-dj3q3fxw.js → chunk-1yct6cra.js} +1 -1
  47. package/build/lib/{chunk-gt5gf7ps.js → chunk-2qsq76xq.js} +3 -3
  48. package/build/lib/{chunk-tjch07p0.js → chunk-3c00n0cy.js} +3 -3
  49. package/build/lib/{chunk-86z9yr32.js → chunk-4hwzqthr.js} +1 -1
  50. package/build/lib/{chunk-9jfybqs2.js → chunk-7pwa92rn.js} +1 -1
  51. package/build/lib/{chunk-pcpzbdnn.js → chunk-9tt2021m.js} +9 -9
  52. package/build/lib/{chunk-xqygd85b.js → chunk-9yksqgzr.js} +52 -13
  53. package/build/lib/{chunk-xw3w0xzz.js → chunk-acgbyf5h.js} +1 -1
  54. package/build/lib/{chunk-5wt0sejb.js → chunk-ba41jh41.js} +1 -1
  55. package/build/lib/{chunk-r18sc80z.js → chunk-bb0ekhkw.js} +8 -8
  56. package/build/lib/{chunk-shtrgm3j.js → chunk-e8yy2k73.js} +1 -1
  57. package/build/lib/{chunk-0z4r4r0x.js → chunk-mg03jdmt.js} +1 -1
  58. package/build/lib/{chunk-nhnhhnk1.js → chunk-nhfax4gb.js} +1 -1
  59. package/build/lib/{chunk-521eq8kw.js → chunk-r2dkp4tb.js} +1 -1
  60. package/build/lib/{chunk-mqftsw7s.js → chunk-r4j8ybn9.js} +3 -3
  61. package/build/lib/{chunk-v8p7j8br.js → chunk-v3dk9j7k.js} +11 -11
  62. package/build/lib/{chunk-afc4wybp.js → chunk-w66ce82a.js} +1 -1
  63. package/build/lib/{chunk-ydgsc09t.js → chunk-xhdpgpkm.js} +2 -2
  64. package/build/lib/{chunk-837ga30c.js → chunk-yrnrbjes.js} +1 -1
  65. package/build/lib/{chunk-m9anmxyq.js → chunk-z137x08b.js} +5 -5
  66. package/build/lib/{chunk-pnnj2k0n.js → chunk-z96pbjhd.js} +1 -1
  67. package/build/lib/core/agents/offSecAgent/index.js +7 -7
  68. package/build/lib/core/agents/offSecAgent/subagentSpawner.js +5 -5
  69. package/build/lib/core/agents/offSecAgent/tools/index.js +4 -4
  70. package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +9 -9
  71. package/build/lib/core/agents/specialized/attackSurface/schemas.js +9 -9
  72. package/build/lib/core/agents/specialized/authenticationAgent/index.js +10 -10
  73. package/build/lib/core/agents/specialized/codeAgent/agent.js +9 -9
  74. package/build/lib/core/agents/specialized/findingJudge/agent.js +8 -8
  75. package/build/lib/core/agents/specialized/findingJudge/index.js +3 -3
  76. package/build/lib/core/agents/specialized/patching/index.js +10 -10
  77. package/build/lib/core/agents/specialized/pentest/agent.js +9 -9
  78. package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +9 -9
  79. package/build/lib/core/ai/ai.js +2 -2
  80. package/build/lib/core/ai/index.js +2 -2
  81. package/build/lib/core/ai/utils.js +2 -2
  82. package/build/lib/core/api/index.d.ts +2 -0
  83. package/build/lib/core/api/index.js +34 -20
  84. package/build/lib/core/api/issues.d.ts +1 -0
  85. package/build/lib/core/api/knowledge.d.ts +33 -0
  86. package/build/lib/core/api/targetedPentest.js +10 -10
  87. package/build/lib/core/config/index.js +1 -1
  88. package/build/lib/core/findings/registry.js +3 -3
  89. package/build/lib/core/session/index.js +2 -2
  90. package/build/lib/core/session/persistence.js +3 -3
  91. package/build/lib/core/workflows/pentest.js +15 -15
  92. package/build/lib/core/workflows/seams.js +4 -4
  93. package/build/lib/core/workflows/whiteboxAttackSurface.js +11 -11
  94. package/build/lib/index.js +22 -22
  95. package/build/{logs-szgf3e9j.js → logs-x62b7hk1.js} +13 -13
  96. package/build/{native-rollout-evidence-zep91dya.js → native-rollout-evidence-c9yhfqcs.js} +3 -3
  97. package/build/{offesecAgent-g9a7jvmb.js → offesecAgent-5whnpeta.js} +4 -4
  98. package/build/pentest-e61hc38s.js +22 -0
  99. package/build/{pentests-pk66smvx.js → pentests-e0f32aqx.js} +21 -14
  100. package/build/recordedRunClient-dp032c32.js +11 -0
  101. package/build/{targetedPentest-t7f2vdpk.js → targetedPentest-rcbs4nvp.js} +6 -6
  102. package/build/{targets-5dp972y7.js → targets-xj9g3q0z.js} +13 -13
  103. package/build/threatModel-jkxfa40s.js +21 -0
  104. package/build/{uninstall-qshcasbp.js → uninstall-7s784q4a.js} +1 -1
  105. package/build/{upload-jy6hp68b.js → upload-4gvhwmvr.js} +4 -4
  106. package/build/{utils-6z5t7vq1.js → utils-cjrz64pc.js} +3 -3
  107. package/package.json +1 -1
  108. package/src/cli/knowledge.test.ts +140 -0
  109. package/src/cli/knowledge.ts +146 -0
  110. package/src/cli/pentests.test.ts +15 -0
  111. package/src/cli/pentests.ts +15 -1
  112. package/src/cli.ts +4 -0
  113. package/src/core/api/index.ts +15 -0
  114. package/src/core/api/issues.ts +1 -0
  115. package/src/core/api/knowledge.ts +70 -0
  116. package/build/agent-6pyd6vmv.js +0 -13
  117. package/build/agent-9wqvzg28.js +0 -13
  118. package/build/pentest-wx108ypq.js +0 -22
  119. package/build/recordedRunClient-4yx9czwe.js +0 -11
  120. package/build/threatModel-cxkg9kd3.js +0 -21
@@ -0,0 +1,22 @@
1
+ import {
2
+ DEFAULT_CONCURRENCY2,
3
+ runPentestSwarm2,
4
+ runPentestWorkflow2
5
+ } from "./cli-d37a42dc.js";
6
+ import"./cli-n2en7j5b.js";
7
+ import"./cli-ngq9dr8n.js";
8
+ import"./cli-k2nwxta0.js";
9
+ import"./cli-xtc2t6me.js";
10
+ import"./cli-kb21qhvm.js";
11
+ import"./cli-1ve18npr.js";
12
+ import"./cli-qba2x69h.js";
13
+ import"./cli-qgz1q9g5.js";
14
+ import"./cli-dpsw2fe6.js";
15
+ import"./cli-c05y640n.js";
16
+ import"./cli-c2k4xxaw.js";
17
+ import"./cli-fy6vh2a7.js";
18
+ export {
19
+ DEFAULT_CONCURRENCY2 as DEFAULT_CONCURRENCY,
20
+ runPentestSwarm2 as runPentestSwarm,
21
+ runPentestWorkflow2 as runPentestWorkflow
22
+ };
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env bun
2
- import"./cli-9a7b3x56.js";
3
- import"./cli-c2nb79ce.js";
2
+ import"./cli-fy6vh2a7.js";
3
+ import"./cli-c05y640n.js";
4
4
  import {
5
5
  listScans2,
6
6
  getScan2,
7
7
  dispatchPentest2
8
- } from "./cli-rwnm0p5r.js";
9
- import"./cli-8q41y2ss.js";
10
- import"./cli-84qrqxjc.js";
11
- import"./cli-ay461rtr.js";
12
- import"./cli-zahhskff.js";
13
- import"./cli-9d8m1qw2.js";
14
- import"./cli-en2trtdx.js";
15
- import"./cli-h68jfmth.js";
16
- import"./cli-sf6x1vjx.js";
8
+ } from "./cli-7eea4e2y.js";
9
+ import"./cli-qgz1q9g5.js";
10
+ import"./cli-k2nwxta0.js";
11
+ import"./cli-kb21qhvm.js";
12
+ import"./cli-ngq9dr8n.js";
13
+ import"./cli-d37a42dc.js";
14
+ import"./cli-3sp6nd8d.js";
15
+ import"./cli-xtc2t6me.js";
16
+ import"./cli-n2en7j5b.js";
17
17
  import"./cli-xcy2vxj2.js";
18
18
  import {
19
19
  markCommandFailed
20
20
  } from "./cli-z82n3qk1.js";
21
- import"./cli-tt7d70n9.js";
21
+ import"./cli-1ve18npr.js";
22
22
  import"./cli-qba2x69h.js";
23
- import"./cli-jpa3w5b4.js";
23
+ import"./cli-dpsw2fe6.js";
24
24
  import"./cli-c2k4xxaw.js";
25
25
  import {
26
26
  init_zod
@@ -51,6 +51,8 @@ dispatch options:
51
51
  --endpoint-id <UUIDs> Select endpoints; repeat or separate IDs with commas (full only)
52
52
  --endpoint-ids-file <file|-> Read endpoint UUIDs, one per line; - reads stdin
53
53
  May combine with --endpoint-id; blank lines ignored
54
+ --knowledge-id <UUIDs> Attach active knowledge base entries as run context;
55
+ repeat or separate IDs with commas (maximum 20)
54
56
 
55
57
  Endpoint selection requires 1–1000 unique UUIDs and full depth.
56
58
 
@@ -85,6 +87,7 @@ async function main() {
85
87
  level: { type: "string" },
86
88
  "endpoint-ids-file": { type: "string" },
87
89
  "endpoint-id": { type: "string", multiple: true },
90
+ "knowledge-id": { type: "string", multiple: true },
88
91
  help: { type: "boolean", short: "h" }
89
92
  },
90
93
  strict: true,
@@ -117,7 +120,11 @@ async function main() {
117
120
  endpointIds: array(uuid()).min(1).max(1000).parse([...new Set(ids.map((id) => id.trim().toLowerCase()))]),
118
121
  scanLevel: scanLevel ?? "full"
119
122
  } : { branch: values.branch, scanLevel };
120
- const result = await dispatchPentest2(input);
123
+ const knowledgeIds = (values["knowledge-id"] ?? []).flatMap((value) => value.split(","));
124
+ const knowledgeEntryIds = knowledgeIds.length ? array(uuid()).max(20).parse([
125
+ ...new Set(knowledgeIds.map((id) => id.trim().toLowerCase()))
126
+ ]) : undefined;
127
+ const result = await dispatchPentest2({ ...input, knowledgeEntryIds });
121
128
  console.log(JSON.stringify(result, null, 2));
122
129
  } else if (!sub || sub === "list") {
123
130
  const scans = await listScans2();
@@ -0,0 +1,11 @@
1
+ import {
2
+ openRecordedRunClient2
3
+ } from "./cli-ae8jqxqr.js";
4
+ import"./cli-gx71jcke.js";
5
+ import"./cli-dpsw2fe6.js";
6
+ import"./cli-c05y640n.js";
7
+ import"./cli-c2k4xxaw.js";
8
+ import"./cli-fy6vh2a7.js";
9
+ export {
10
+ openRecordedRunClient2 as openRecordedRunClient
11
+ };
@@ -1,15 +1,15 @@
1
- import"./cli-9a7b3x56.js";
2
- import"./cli-c2nb79ce.js";
3
- import"./cli-8q41y2ss.js";
1
+ import"./cli-fy6vh2a7.js";
2
+ import"./cli-c05y640n.js";
3
+ import"./cli-qgz1q9g5.js";
4
4
  import {
5
5
  TargetedPentestAgent2
6
- } from "./cli-h68jfmth.js";
6
+ } from "./cli-xtc2t6me.js";
7
7
  import {
8
8
  startAgentExecution
9
9
  } from "./cli-g06s7edq.js";
10
- import"./cli-tt7d70n9.js";
10
+ import"./cli-1ve18npr.js";
11
11
  import"./cli-qba2x69h.js";
12
- import"./cli-jpa3w5b4.js";
12
+ import"./cli-dpsw2fe6.js";
13
13
  import"./cli-c2k4xxaw.js";
14
14
 
15
15
  // src/core/api/targetedPentest.ts
@@ -1,26 +1,26 @@
1
1
  #!/usr/bin/env bun
2
- import"./cli-9a7b3x56.js";
3
- import"./cli-c2nb79ce.js";
2
+ import"./cli-fy6vh2a7.js";
3
+ import"./cli-c05y640n.js";
4
4
  import {
5
5
  listPentestTargets2,
6
6
  listTargetLogs2,
7
7
  searchTargetLogs2
8
- } from "./cli-rwnm0p5r.js";
9
- import"./cli-8q41y2ss.js";
10
- import"./cli-84qrqxjc.js";
11
- import"./cli-ay461rtr.js";
12
- import"./cli-zahhskff.js";
13
- import"./cli-9d8m1qw2.js";
14
- import"./cli-en2trtdx.js";
15
- import"./cli-h68jfmth.js";
16
- import"./cli-sf6x1vjx.js";
8
+ } from "./cli-7eea4e2y.js";
9
+ import"./cli-qgz1q9g5.js";
10
+ import"./cli-k2nwxta0.js";
11
+ import"./cli-kb21qhvm.js";
12
+ import"./cli-ngq9dr8n.js";
13
+ import"./cli-d37a42dc.js";
14
+ import"./cli-3sp6nd8d.js";
15
+ import"./cli-xtc2t6me.js";
16
+ import"./cli-n2en7j5b.js";
17
17
  import"./cli-xcy2vxj2.js";
18
18
  import {
19
19
  markCommandFailed
20
20
  } from "./cli-z82n3qk1.js";
21
- import"./cli-tt7d70n9.js";
21
+ import"./cli-1ve18npr.js";
22
22
  import"./cli-qba2x69h.js";
23
- import"./cli-jpa3w5b4.js";
23
+ import"./cli-dpsw2fe6.js";
24
24
  import"./cli-c2k4xxaw.js";
25
25
 
26
26
  // src/cli/targets.ts
@@ -0,0 +1,21 @@
1
+ import"./cli-qgz1q9g5.js";
2
+ import"./cli-fy6vh2a7.js";
3
+ import"./cli-c05y640n.js";
4
+ import {
5
+ runThreatModelWorkflow2
6
+ } from "./cli-7eea4e2y.js";
7
+ import"./cli-k2nwxta0.js";
8
+ import"./cli-kb21qhvm.js";
9
+ import"./cli-ngq9dr8n.js";
10
+ import"./cli-d37a42dc.js";
11
+ import"./cli-3sp6nd8d.js";
12
+ import"./cli-xtc2t6me.js";
13
+ import"./cli-n2en7j5b.js";
14
+ import"./cli-xcy2vxj2.js";
15
+ import"./cli-1ve18npr.js";
16
+ import"./cli-qba2x69h.js";
17
+ import"./cli-dpsw2fe6.js";
18
+ import"./cli-c2k4xxaw.js";
19
+ export {
20
+ runThreatModelWorkflow2 as runThreatModelWorkflow
21
+ };
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  detectInstallMethod,
7
7
  init_installation
8
- } from "./cli-k594kwq8.js";
8
+ } from "./cli-65g6rcat.js";
9
9
  import {
10
10
  __require
11
11
  } from "./cli-h6y3bt60.js";
@@ -1,10 +1,10 @@
1
- import"./cli-9a7b3x56.js";
2
- import"./cli-c2nb79ce.js";
1
+ import"./cli-fy6vh2a7.js";
2
+ import"./cli-c05y640n.js";
3
3
  import {
4
4
  resolveConfig,
5
5
  createWeaveTracer
6
- } from "./cli-raavtvwt.js";
7
- import"./cli-jpa3w5b4.js";
6
+ } from "./cli-az3xq70v.js";
7
+ import"./cli-dpsw2fe6.js";
8
8
  import"./cli-c2k4xxaw.js";
9
9
 
10
10
  // src/core/integrations/wandb/upload.ts
@@ -1,12 +1,12 @@
1
- import"./cli-9a7b3x56.js";
2
- import"./cli-c2nb79ce.js";
1
+ import"./cli-fy6vh2a7.js";
2
+ import"./cli-c05y640n.js";
3
3
  import {
4
4
  init_types
5
5
  } from "./cli-3gt761d2.js";
6
6
  import {
7
7
  AVAILABLE_MODELS,
8
8
  init_ai
9
- } from "./cli-jpa3w5b4.js";
9
+ } from "./cli-dpsw2fe6.js";
10
10
  import"./cli-c2k4xxaw.js";
11
11
 
12
12
  // src/core/providers/utils.ts
package/package.json CHANGED
@@ -344,5 +344,5 @@
344
344
  "tsc": "tsc --noEmit"
345
345
  },
346
346
  "type": "module",
347
- "version": "2.6.1-canary.e936ec17"
347
+ "version": "2.6.1"
348
348
  }
@@ -0,0 +1,140 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { createServer } from "node:http";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { describe, expect, it } from "vitest";
7
+
8
+ const CLI = join(import.meta.dirname, "knowledge.ts");
9
+ const ID = "33333333-3333-4333-8333-333333333333";
10
+
11
+ function run(args: string[]) {
12
+ const result = spawnSync("bun", [CLI, ...args], { encoding: "utf8" });
13
+ return {
14
+ status: result.status,
15
+ stdout: result.stdout,
16
+ stderr: result.stderr,
17
+ };
18
+ }
19
+
20
+ async function captureRequest(args: string[]) {
21
+ let request: { method?: string; url?: string; body: unknown } | undefined;
22
+ const server = createServer((req, res) => {
23
+ const chunks: Buffer[] = [];
24
+ req.on("data", (c: Buffer) => chunks.push(c));
25
+ req.on("end", () => {
26
+ const raw = Buffer.concat(chunks).toString("utf-8");
27
+ request = {
28
+ method: req.method,
29
+ url: req.url,
30
+ body: raw ? JSON.parse(raw) : undefined,
31
+ };
32
+ res.writeHead(200, { "Content-Type": "application/json" });
33
+ res.end(JSON.stringify({ id: ID }));
34
+ });
35
+ });
36
+ await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
37
+ const address = server.address();
38
+ if (address === null || typeof address === "string")
39
+ throw new Error("no port");
40
+ const home = mkdtempSync(join(tmpdir(), "pensar-knowledge-"));
41
+ try {
42
+ const status = await new Promise<number | null>((resolve) => {
43
+ const child = spawn("bun", [CLI, ...args], {
44
+ env: {
45
+ ...process.env,
46
+ HOME: home,
47
+ PENSAR_API_KEY: "test-key",
48
+ PENSAR_API_URL: `http://127.0.0.1:${address.port}`,
49
+ },
50
+ stdio: "ignore",
51
+ });
52
+ child.on("exit", resolve);
53
+ });
54
+ expect(status).toBe(0);
55
+ } finally {
56
+ server.close();
57
+ rmSync(home, { recursive: true, force: true });
58
+ }
59
+ if (!request) throw new Error("CLI sent no request");
60
+ return request;
61
+ }
62
+
63
+ describe("pensar knowledge CLI", () => {
64
+ it("documents that writes need a user login", () => {
65
+ const { status, stdout } = run(["--help"]);
66
+ expect(status).toBe(0);
67
+ expect(stdout).toContain("require a user login");
68
+ });
69
+
70
+ it("lists with filters", async () => {
71
+ const req = await captureRequest([
72
+ "--category",
73
+ "user_note",
74
+ "--status",
75
+ "active",
76
+ "--tag",
77
+ "auth,api",
78
+ ]);
79
+ expect(req.method).toBe("GET");
80
+ expect(req.url).toBe(
81
+ "/knowledge?category=user_note&status=active&tags=auth%2Capi",
82
+ );
83
+ });
84
+
85
+ it("adds an entry", async () => {
86
+ const req = await captureRequest([
87
+ "add",
88
+ "--title",
89
+ "Stack",
90
+ "--content",
91
+ "Next.js on Lambda",
92
+ "--category",
93
+ "technology_context",
94
+ "--tag",
95
+ "infra",
96
+ ]);
97
+ expect(req).toMatchObject({
98
+ method: "POST",
99
+ url: "/knowledge",
100
+ body: {
101
+ title: "Stack",
102
+ content: "Next.js on Lambda",
103
+ category: "technology_context",
104
+ tags: ["infra"],
105
+ },
106
+ });
107
+ });
108
+
109
+ it("updates an entry", async () => {
110
+ const req = await captureRequest(["update", ID, "--status", "stale"]);
111
+ expect(req).toMatchObject({
112
+ method: "PATCH",
113
+ url: `/knowledge/${ID}`,
114
+ body: { status: "stale" },
115
+ });
116
+ });
117
+
118
+ it("archives an entry", async () => {
119
+ const req = await captureRequest(["archive", ID]);
120
+ expect(req).toMatchObject({ method: "DELETE", url: `/knowledge/${ID}` });
121
+ });
122
+
123
+ it("requires title, content and category to add", () => {
124
+ const { status, stderr } = run(["add", "--title", "x"]);
125
+ expect(status).toBe(1);
126
+ expect(stderr).toContain("--title, --content and --category are required");
127
+ });
128
+
129
+ it("rejects an unknown category before calling the API", () => {
130
+ const { status, stderr } = run(["add", "--category", "nope"]);
131
+ expect(status).toBe(1);
132
+ expect(stderr).toContain('invalid --category "nope"');
133
+ });
134
+
135
+ it("requires an id for get", () => {
136
+ const { status, stderr } = run(["get"]);
137
+ expect(status).toBe(1);
138
+ expect(stderr).toContain("entry ID is required");
139
+ });
140
+ });
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * pensar knowledge — Read and edit the workspace knowledge base
5
+ *
6
+ * Usage:
7
+ * pensar knowledge [filters] List entries
8
+ * pensar knowledge get <id> Get one entry
9
+ * pensar knowledge add [options] Add an entry
10
+ * pensar knowledge update <id> [options] Edit an entry
11
+ * pensar knowledge archive <id> Archive an entry
12
+ */
13
+
14
+ import { parseArgs } from "node:util";
15
+ import {
16
+ archiveKnowledge,
17
+ createKnowledge,
18
+ getKnowledge,
19
+ KNOWLEDGE_CATEGORIES,
20
+ KNOWLEDGE_STATUSES,
21
+ type KnowledgeCategory,
22
+ type KnowledgeInput,
23
+ type KnowledgeStatus,
24
+ listKnowledge,
25
+ updateKnowledge,
26
+ } from "../core/api";
27
+ import { markCommandFailed } from "./command-exit";
28
+
29
+ function showHelp(): void {
30
+ console.log(`pensar knowledge — Read and edit the workspace knowledge base
31
+
32
+ Entries are context that agents use during pentests. A false_positive_pattern
33
+ entry suppresses matching findings.
34
+
35
+ Usage:
36
+ pensar knowledge [filters] List entries
37
+ pensar knowledge get <id> Get one entry
38
+ pensar knowledge add [options] Add an entry
39
+ pensar knowledge update <id> [options] Edit an entry
40
+ pensar knowledge archive <id> Archive an entry
41
+
42
+ Filters and options:
43
+ --title <text> Entry title (required for add)
44
+ --content <text> Entry body (required for add)
45
+ --category <category> ${KNOWLEDGE_CATEGORIES.join(", ")}
46
+ (required for add)
47
+ --status <status> ${KNOWLEDGE_STATUSES.join(", ")}
48
+ --tag <tags> Repeat or separate with commas; a list filter
49
+ matches entries carrying every tag
50
+
51
+ Adding, editing and archiving require a user login; a workspace API key can
52
+ only read.
53
+
54
+ Options:
55
+ -h, --help Show this help message`);
56
+ }
57
+
58
+ function parseCategory(value: string | undefined) {
59
+ if (value === undefined) return undefined;
60
+ if (!(KNOWLEDGE_CATEGORIES as readonly string[]).includes(value)) {
61
+ throw new Error(
62
+ `invalid --category "${value}". Accepted: ${KNOWLEDGE_CATEGORIES.join(", ")}`,
63
+ );
64
+ }
65
+ return value as KnowledgeCategory;
66
+ }
67
+
68
+ function parseStatus(value: string | undefined) {
69
+ if (value === undefined) return undefined;
70
+ if (!(KNOWLEDGE_STATUSES as readonly string[]).includes(value)) {
71
+ throw new Error(
72
+ `invalid --status "${value}". Accepted: ${KNOWLEDGE_STATUSES.join(", ")}`,
73
+ );
74
+ }
75
+ return value as KnowledgeStatus;
76
+ }
77
+
78
+ async function main(): Promise<void> {
79
+ const args = process.argv.slice(2);
80
+ const sub = args[0] && !args[0].startsWith("-") ? args[0] : "list";
81
+
82
+ if (sub === "help" || args.includes("--help") || args.includes("-h")) {
83
+ showHelp();
84
+ return;
85
+ }
86
+
87
+ try {
88
+ const takesId = sub === "get" || sub === "update" || sub === "archive";
89
+ const id = takesId ? args[1] : undefined;
90
+ if (takesId && (!id || id.startsWith("-"))) {
91
+ throw new Error(`entry ID is required: pensar knowledge ${sub} <id>`);
92
+ }
93
+ const { values } = parseArgs({
94
+ args: args.slice(args[0] === sub ? (takesId ? 2 : 1) : 0),
95
+ options: {
96
+ title: { type: "string" },
97
+ content: { type: "string" },
98
+ category: { type: "string" },
99
+ status: { type: "string" },
100
+ tag: { type: "string", multiple: true },
101
+ },
102
+ strict: true,
103
+ allowPositionals: false,
104
+ });
105
+ const tags = values.tag?.flatMap((t) => t.split(",")).filter(Boolean);
106
+ const input: KnowledgeInput = {
107
+ title: values.title,
108
+ content: values.content,
109
+ category: parseCategory(values.category),
110
+ status: parseStatus(values.status),
111
+ tags,
112
+ };
113
+
114
+ let result: unknown;
115
+ if (sub === "list") {
116
+ result = await listKnowledge({
117
+ category: input.category,
118
+ status: input.status,
119
+ tags,
120
+ });
121
+ } else if (sub === "get") {
122
+ result = await getKnowledge(id!);
123
+ } else if (sub === "add") {
124
+ if (!input.title || !input.content || !input.category) {
125
+ throw new Error("--title, --content and --category are required");
126
+ }
127
+ result = await createKnowledge(input);
128
+ } else if (sub === "update") {
129
+ result = await updateKnowledge(id!, input);
130
+ } else if (sub === "archive") {
131
+ result = await archiveKnowledge(id!);
132
+ } else {
133
+ console.error(`Error: Unknown subcommand "${sub}"`);
134
+ showHelp();
135
+ return markCommandFailed();
136
+ }
137
+ console.log(JSON.stringify(result, null, 2));
138
+ } catch (err) {
139
+ console.error(
140
+ `\nError: ${err instanceof Error ? err.message : String(err)}`,
141
+ );
142
+ return markCommandFailed();
143
+ }
144
+ }
145
+
146
+ await main();
@@ -70,6 +70,21 @@ describe("hosted pentest dispatch", () => {
70
70
  });
71
71
  });
72
72
 
73
+ it("attaches knowledge entries to a launch", async () => {
74
+ await run([
75
+ "dispatch",
76
+ "--knowledge-id",
77
+ `${endpointA.toUpperCase()},${endpointB}`,
78
+ "--knowledge-id",
79
+ endpointA,
80
+ ]);
81
+ expect(api.dispatchPentest).toHaveBeenCalledWith({
82
+ branch: undefined,
83
+ scanLevel: undefined,
84
+ knowledgeEntryIds: [endpointA, endpointB],
85
+ });
86
+ });
87
+
73
88
  it("combines repeated, comma-separated, and file IDs without duplicates", async () => {
74
89
  const file = join(directory, "endpoints.txt");
75
90
  await writeFile(file, `${endpointB.toUpperCase()}\n${endpointA}\n`);
@@ -33,6 +33,8 @@ dispatch options:
33
33
  --endpoint-id <UUIDs> Select endpoints; repeat or separate IDs with commas (full only)
34
34
  --endpoint-ids-file <file|-> Read endpoint UUIDs, one per line; - reads stdin
35
35
  May combine with --endpoint-id; blank lines ignored
36
+ --knowledge-id <UUIDs> Attach active knowledge base entries as run context;
37
+ repeat or separate IDs with commas (maximum 20)
36
38
 
37
39
  Endpoint selection requires 1–1000 unique UUIDs and full depth.
38
40
 
@@ -70,6 +72,7 @@ async function main(): Promise<void> {
70
72
  level: { type: "string" },
71
73
  "endpoint-ids-file": { type: "string" },
72
74
  "endpoint-id": { type: "string", multiple: true },
75
+ "knowledge-id": { type: "string", multiple: true },
73
76
  help: { type: "boolean", short: "h" },
74
77
  },
75
78
  strict: true,
@@ -124,7 +127,18 @@ async function main(): Promise<void> {
124
127
  scanLevel: scanLevel ?? "full",
125
128
  }
126
129
  : { branch: values.branch, scanLevel };
127
- const result = await dispatchPentest(input);
130
+ const knowledgeIds = (values["knowledge-id"] ?? []).flatMap((value) =>
131
+ value.split(","),
132
+ );
133
+ const knowledgeEntryIds = knowledgeIds.length
134
+ ? z
135
+ .array(z.uuid())
136
+ .max(20)
137
+ .parse([
138
+ ...new Set(knowledgeIds.map((id) => id.trim().toLowerCase())),
139
+ ])
140
+ : undefined;
141
+ const result = await dispatchPentest({ ...input, knowledgeEntryIds });
128
142
  console.log(JSON.stringify(result, null, 2));
129
143
  } else if (!sub || sub === "list") {
130
144
  const scans = await listScans();
package/src/cli.ts CHANGED
@@ -269,6 +269,7 @@ Usage:
269
269
  pensar targets List pentest targets and view their agent logs
270
270
  pensar issues List and manage security issues
271
271
  pensar fixes View security fixes
272
+ pensar knowledge Read and edit the workspace knowledge base
272
273
  pensar logs View agent execution logs
273
274
  pensar config headers Manage global default HTTP headers
274
275
  pensar export-trajectory Convert saved rollout evidence to ATIF
@@ -828,6 +829,9 @@ try {
828
829
  } else if (command === "issues") {
829
830
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
830
831
  await import("./cli/issues");
832
+ } else if (command === "knowledge") {
833
+ process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
834
+ await import("./cli/knowledge");
831
835
  } else if (command === "fixes") {
832
836
  process.argv = [process.argv[0], process.argv[1], ...args.slice(1)];
833
837
  await import("./cli/fixes");
@@ -151,6 +151,21 @@ export {
151
151
  searchTargetLogs,
152
152
  updateIssue,
153
153
  } from "./issues";
154
+ export type {
155
+ KnowledgeCategory,
156
+ KnowledgeEntry,
157
+ KnowledgeInput,
158
+ KnowledgeStatus,
159
+ } from "./knowledge";
160
+ export {
161
+ archiveKnowledge,
162
+ createKnowledge,
163
+ getKnowledge,
164
+ KNOWLEDGE_CATEGORIES,
165
+ KNOWLEDGE_STATUSES,
166
+ listKnowledge,
167
+ updateKnowledge,
168
+ } from "./knowledge";
154
169
  export { serveLocalRunWorker } from "./localWorker";
155
170
  export type {
156
171
  OffensiveSecurityAgentClientInput,
@@ -257,6 +257,7 @@ export interface DispatchPentestInput {
257
257
  branch?: string;
258
258
  scanLevel?: "priority" | "full";
259
259
  endpointIds?: string[];
260
+ knowledgeEntryIds?: string[];
260
261
  }
261
262
 
262
263
  export async function dispatchPentest(