guardlink 1.4.1 → 1.4.3

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 (138) hide show
  1. package/CHANGELOG.md +111 -7
  2. package/README.md +53 -5
  3. package/dist/agents/config.d.ts +7 -0
  4. package/dist/agents/config.d.ts.map +1 -1
  5. package/dist/agents/config.js.map +1 -1
  6. package/dist/agents/index.d.ts +9 -1
  7. package/dist/agents/index.d.ts.map +1 -1
  8. package/dist/agents/index.js +36 -1
  9. package/dist/agents/index.js.map +1 -1
  10. package/dist/agents/launcher.d.ts.map +1 -1
  11. package/dist/agents/launcher.js +5 -0
  12. package/dist/agents/launcher.js.map +1 -1
  13. package/dist/agents/prompts.d.ts +16 -1
  14. package/dist/agents/prompts.d.ts.map +1 -1
  15. package/dist/agents/prompts.js +511 -16
  16. package/dist/agents/prompts.js.map +1 -1
  17. package/dist/analyze/format.d.ts +72 -0
  18. package/dist/analyze/format.d.ts.map +1 -0
  19. package/dist/analyze/format.js +176 -0
  20. package/dist/analyze/format.js.map +1 -0
  21. package/dist/analyze/index.d.ts +76 -0
  22. package/dist/analyze/index.d.ts.map +1 -1
  23. package/dist/analyze/index.js +165 -2
  24. package/dist/analyze/index.js.map +1 -1
  25. package/dist/analyze/prompts.d.ts +3 -2
  26. package/dist/analyze/prompts.d.ts.map +1 -1
  27. package/dist/analyze/prompts.js +17 -3
  28. package/dist/analyze/prompts.js.map +1 -1
  29. package/dist/analyzer/sarif.d.ts +3 -2
  30. package/dist/analyzer/sarif.d.ts.map +1 -1
  31. package/dist/analyzer/sarif.js +29 -3
  32. package/dist/analyzer/sarif.js.map +1 -1
  33. package/dist/cli/index.d.ts +2 -0
  34. package/dist/cli/index.d.ts.map +1 -1
  35. package/dist/cli/index.js +408 -37
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/dashboard/data.d.ts +11 -0
  38. package/dist/dashboard/data.d.ts.map +1 -1
  39. package/dist/dashboard/data.js +12 -0
  40. package/dist/dashboard/data.js.map +1 -1
  41. package/dist/dashboard/diagrams.d.ts +81 -12
  42. package/dist/dashboard/diagrams.d.ts.map +1 -1
  43. package/dist/dashboard/diagrams.js +750 -362
  44. package/dist/dashboard/diagrams.js.map +1 -1
  45. package/dist/dashboard/generate.d.ts +5 -2
  46. package/dist/dashboard/generate.d.ts.map +1 -1
  47. package/dist/dashboard/generate.js +2516 -244
  48. package/dist/dashboard/generate.js.map +1 -1
  49. package/dist/diff/engine.d.ts +2 -1
  50. package/dist/diff/engine.d.ts.map +1 -1
  51. package/dist/diff/engine.js +3 -2
  52. package/dist/diff/engine.js.map +1 -1
  53. package/dist/diff/git.js +3 -3
  54. package/dist/diff/git.js.map +1 -1
  55. package/dist/init/index.d.ts +7 -0
  56. package/dist/init/index.d.ts.map +1 -1
  57. package/dist/init/index.js +82 -27
  58. package/dist/init/index.js.map +1 -1
  59. package/dist/init/migrate.d.ts +39 -0
  60. package/dist/init/migrate.d.ts.map +1 -0
  61. package/dist/init/migrate.js +45 -0
  62. package/dist/init/migrate.js.map +1 -0
  63. package/dist/init/templates.d.ts +8 -0
  64. package/dist/init/templates.d.ts.map +1 -1
  65. package/dist/init/templates.js +68 -6
  66. package/dist/init/templates.js.map +1 -1
  67. package/dist/mcp/lookup.d.ts +1 -0
  68. package/dist/mcp/lookup.d.ts.map +1 -1
  69. package/dist/mcp/lookup.js +138 -10
  70. package/dist/mcp/lookup.js.map +1 -1
  71. package/dist/mcp/server.d.ts +2 -1
  72. package/dist/mcp/server.d.ts.map +1 -1
  73. package/dist/mcp/server.js +32 -15
  74. package/dist/mcp/server.js.map +1 -1
  75. package/dist/parser/clear.d.ts +2 -1
  76. package/dist/parser/clear.d.ts.map +1 -1
  77. package/dist/parser/clear.js +19 -29
  78. package/dist/parser/clear.js.map +1 -1
  79. package/dist/parser/comment-strip.d.ts +5 -0
  80. package/dist/parser/comment-strip.d.ts.map +1 -1
  81. package/dist/parser/comment-strip.js +8 -0
  82. package/dist/parser/comment-strip.js.map +1 -1
  83. package/dist/parser/feature-filter.d.ts +42 -0
  84. package/dist/parser/feature-filter.d.ts.map +1 -0
  85. package/dist/parser/feature-filter.js +109 -0
  86. package/dist/parser/feature-filter.js.map +1 -0
  87. package/dist/parser/format.d.ts +24 -0
  88. package/dist/parser/format.d.ts.map +1 -0
  89. package/dist/parser/format.js +29 -0
  90. package/dist/parser/format.js.map +1 -0
  91. package/dist/parser/index.d.ts +2 -0
  92. package/dist/parser/index.d.ts.map +1 -1
  93. package/dist/parser/index.js +1 -0
  94. package/dist/parser/index.js.map +1 -1
  95. package/dist/parser/parse-file.d.ts +1 -0
  96. package/dist/parser/parse-file.d.ts.map +1 -1
  97. package/dist/parser/parse-file.js +34 -9
  98. package/dist/parser/parse-file.js.map +1 -1
  99. package/dist/parser/parse-line.d.ts +9 -0
  100. package/dist/parser/parse-line.d.ts.map +1 -1
  101. package/dist/parser/parse-line.js +100 -26
  102. package/dist/parser/parse-line.js.map +1 -1
  103. package/dist/parser/parse-project.d.ts +1 -0
  104. package/dist/parser/parse-project.d.ts.map +1 -1
  105. package/dist/parser/parse-project.js +36 -2
  106. package/dist/parser/parse-project.js.map +1 -1
  107. package/dist/parser/validate.d.ts +3 -0
  108. package/dist/parser/validate.d.ts.map +1 -1
  109. package/dist/parser/validate.js +7 -0
  110. package/dist/parser/validate.js.map +1 -1
  111. package/dist/report/index.d.ts +1 -0
  112. package/dist/report/index.d.ts.map +1 -1
  113. package/dist/report/index.js +1 -0
  114. package/dist/report/index.js.map +1 -1
  115. package/dist/report/report.d.ts.map +1 -1
  116. package/dist/report/report.js +924 -24
  117. package/dist/report/report.js.map +1 -1
  118. package/dist/report/sequence.d.ts +11 -0
  119. package/dist/report/sequence.d.ts.map +1 -0
  120. package/dist/report/sequence.js +140 -0
  121. package/dist/report/sequence.js.map +1 -0
  122. package/dist/review/index.d.ts +3 -1
  123. package/dist/review/index.d.ts.map +1 -1
  124. package/dist/review/index.js +77 -35
  125. package/dist/review/index.js.map +1 -1
  126. package/dist/tui/commands.d.ts +1 -0
  127. package/dist/tui/commands.d.ts.map +1 -1
  128. package/dist/tui/commands.js +98 -12
  129. package/dist/tui/commands.js.map +1 -1
  130. package/dist/tui/index.d.ts.map +1 -1
  131. package/dist/tui/index.js +7 -2
  132. package/dist/tui/index.js.map +1 -1
  133. package/dist/types/index.d.ts +59 -3
  134. package/dist/types/index.d.ts.map +1 -1
  135. package/dist/workspace/merge.d.ts.map +1 -1
  136. package/dist/workspace/merge.js +6 -2
  137. package/dist/workspace/merge.js.map +1 -1
  138. package/package.json +1 -1
@@ -7,20 +7,66 @@
7
7
  * @audit #agent-launcher -- "Prompt injection mitigated by agent's own safety measures; GuardLink prompt is read-only context"
8
8
  * @exposes #agent-launcher to #path-traversal [medium] cwe:CWE-22 -- "Reads reference docs from root-relative paths"
9
9
  * @mitigates #agent-launcher against #path-traversal using #path-validation -- "resolve() with root constrains file access"
10
+ * @exposes #agent-launcher to #config-tamper [medium] cwe:CWE-15 -- "Translate prompt may read CXG reference paths from environment overrides"
11
+ * @audit #agent-launcher -- "Environment override paths are optional convenience; verify trusted local paths in CI"
10
12
  * @flows UserPrompt -> #agent-launcher via buildAnnotatePrompt -- "User instruction input"
13
+ * @flows UserPrompt -> #agent-launcher via buildTranslatePrompt -- "Template translation instruction input"
14
+ * @flows UserPrompt -> #agent-launcher via buildAskPrompt -- "Threat model question input"
11
15
  * @flows ThreatModel -> #agent-launcher via model -- "Model context injection"
12
16
  * @flows #agent-launcher -> AgentPrompt via return -- "Assembled prompt output"
13
17
  * @handles internal on #agent-launcher -- "Serializes threat model IDs and flows into prompt"
14
18
  */
15
19
  import { existsSync, readFileSync } from 'node:fs';
16
20
  import { resolve } from 'node:path';
21
+ import { homedir } from 'node:os';
22
+ // CXG canonical install layout — matches what 'cargo install cert-x-gen'
23
+ // produces and what 'cxg template fetch' writes. The 'official' subdirectory
24
+ // is CXG's name for the Bugb-Technologies/cert-x-gen-templates remote;
25
+ // see PathResolver::user_template_dir() in cert-x-gen/src/template/paths.rs.
26
+ // Override with GUARDLINK_CXG_ROOT for forks or non-standard layouts.
27
+ const DEFAULT_CXG_ROOT = resolve(homedir(), '.cert-x-gen', 'templates', 'official');
28
+ const DEFAULT_CXG_SKELETON_DIR = resolve(DEFAULT_CXG_ROOT, 'templates', 'skeleton');
29
+ function readIfExists(path, maxChars = 5000) {
30
+ if (!existsSync(path))
31
+ return '';
32
+ try {
33
+ return readFileSync(path, 'utf-8').slice(0, maxChars);
34
+ }
35
+ catch {
36
+ return '';
37
+ }
38
+ }
39
+ function annotationModeLabel(mode) {
40
+ return mode === 'external' ? 'externalized .gal files' : 'inline source comments';
41
+ }
42
+ function annotationModeInstructions(mode) {
43
+ if (mode === 'external') {
44
+ return `## Annotation Placement Mode
45
+ You MUST write annotations into associated standalone \`.gal\` files, not inline in the source code.
46
+
47
+ - Keep definitions in \`.guardlink/definitions.*\`
48
+ - For each annotated source file, create or update an associated file under \`.guardlink/annotations/\`
49
+ - Mirror the source path in the annotation file path (example: \`src/auth/login.ts\` -> \`.guardlink/annotations/src/auth/login.ts.gal\`)
50
+ - Group annotations under \`@source file:<path> line:<n> [symbol:<name>]\` so each block points at the real code location
51
+ - In \`.gal\` files, write raw GAL lines without \`//\` or \`#\` prefixes
52
+ - Do NOT modify source files just to add comments when this mode is selected
53
+ `;
54
+ }
55
+ return `## Annotation Placement Mode
56
+ You MUST write annotations inline in the source code comments.
57
+
58
+ - Place annotations in the file doc-block or directly above the security-relevant code
59
+ - Use the host language comment syntax (\`//\`, \`#\`, \`--\`, etc.)
60
+ - Do NOT externalize annotations into \`.gal\` files when this mode is selected
61
+ `;
62
+ }
17
63
  /**
18
64
  * Build a prompt for annotation agents.
19
65
  *
20
66
  * Includes the GuardLink reference doc, current model summary with flows and exposures,
21
67
  * flow-first threat modeling methodology, and precise GAL syntax rules.
22
68
  */
23
- export function buildAnnotatePrompt(userPrompt, root, model) {
69
+ export function buildAnnotatePrompt(userPrompt, root, model, annotationMode = 'inline') {
24
70
  // Read the reference doc if available
25
71
  let refDoc = '';
26
72
  const refPath = resolve(root, '.guardlink', 'GUARDLINK_REFERENCE.md');
@@ -42,6 +88,7 @@ export function buildAnnotatePrompt(userPrompt, root, model) {
42
88
  const parts = [
43
89
  `${model.annotations_parsed} annotations`,
44
90
  `${model.exposures.length} exposures`,
91
+ ...((model.confirmed || []).length > 0 ? [`${model.confirmed.length} confirmed exploitable`] : []),
45
92
  `${model.assets.length} assets`,
46
93
  `${model.threats.length} threats`,
47
94
  `${model.controls.length} controls`,
@@ -85,6 +132,7 @@ export function buildAnnotatePrompt(userPrompt, root, model) {
85
132
  }
86
133
  return `You are an expert security engineer performing threat modeling as code.
87
134
  Your job is to read this codebase deeply, understand how code flows between components, and annotate it with GuardLink (GAL) security annotations that accurately represent the security posture.
135
+ This run MUST produce annotations as ${annotationModeLabel(annotationMode)}.
88
136
 
89
137
  This is NOT a vulnerability scanner. You are building a living threat model embedded in the code itself.
90
138
  Annotations capture what COULD go wrong, what controls exist, and how data moves — not just confirmed bugs.
@@ -95,6 +143,8 @@ ${modelSummary}${existingIds}${existingFlows}${existingExposures}
95
143
  ## Your Task
96
144
  ${userPrompt}
97
145
 
146
+ ${annotationModeInstructions(annotationMode)}
147
+
98
148
  ## HOW TO THINK — Flow-First Threat Modeling
99
149
 
100
150
  Before writing ANY annotation, you MUST understand the code deeply:
@@ -197,19 +247,35 @@ Place @boundary annotations where trust level changes between two components:
197
247
  \`\`\`
198
248
 
199
249
  ### Where to Place Annotations
200
- Annotations go in the file's top doc-block comment OR directly above the security-relevant code:
250
+ ${annotationMode === 'external'
251
+ ? 'Annotations go in associated `.gal` files, grouped by `@source` blocks that point at the real code location:'
252
+ : "Annotations go in the file's top doc-block comment OR directly above the security-relevant code:"}
201
253
 
202
254
  \`\`\`
203
- // @shield:begin -- "Placement examples, excluded from parsing"
204
- //
205
- // FILE-LEVEL (top doc-block) for module-wide security properties:
206
- // Place @exposes, @mitigates, @flows, @handles, @boundary that describe the module as a whole
207
- //
208
- // INLINE (above specific functions/methods) for function-specific concerns:
209
- // Place @exposes, @mitigates above the exact function where the risk or control lives
210
- // Place @comment above tricky security-relevant code to explain intent
211
- //
212
- // @shield:end
255
+ ${annotationMode === 'external'
256
+ ? [
257
+ '@shield:begin -- "Externalized annotation examples, excluded from parsing"',
258
+ '@source file:src/auth/login.ts line:42 symbol:authenticate',
259
+ '@exposes #auth-api to #sqli [P1] cwe:CWE-89 -- "User-supplied email reaches query builder"',
260
+ '@mitigates #auth-api against #sqli using #input-validation -- "Zod schema validates email before query"',
261
+ '@comment -- "Externalized annotations for src/auth/login.ts"',
262
+ '',
263
+ '@source file:src/auth/session.ts line:88 symbol:issueToken',
264
+ '@handles secrets on #auth-api -- "Issues session token"',
265
+ '@shield:end',
266
+ ].join('\n')
267
+ : [
268
+ '// @shield:begin -- "Placement examples, excluded from parsing"',
269
+ '//',
270
+ '// FILE-LEVEL (top doc-block) — for module-wide security properties:',
271
+ '// Place @exposes, @mitigates, @flows, @handles, @boundary that describe the module as a whole',
272
+ '//',
273
+ '// INLINE (above specific functions/methods) — for function-specific concerns:',
274
+ '// Place @exposes, @mitigates above the exact function where the risk or control lives',
275
+ '// Place @comment above tricky security-relevant code to explain intent',
276
+ '//',
277
+ '// @shield:end',
278
+ ].join('\n')}
213
279
  \`\`\`
214
280
 
215
281
  ### Severity — Be Honest, Not Alarmist
@@ -251,6 +317,16 @@ Example — what to do when no mitigation exists:
251
317
 
252
318
  Leaving exposures unmitigated is HONEST. The dashboard and reports will surface them as open risks for humans to triage.
253
319
 
320
+ ### Pentest-Confirmable vs Governance-Only Gaps
321
+ When documenting threats, distinguish between:
322
+ 1. **Pentest-confirmable findings**: testable with concrete I/O behavior (e.g., injection, auth bypass, IDOR, exposed service, unsafe deserialization). Document the risk with @exposes (hypothesis). After a pentest, CXG scan, or manual reproduction **proves** exploitability with evidence, add @confirmed #threat on Asset [severity] -- "evidence summary" — never use @confirmed for guesses or scanner noise without verification.
323
+ 2. **Governance/design gaps**: important risks that are not directly testable as a penetration test template (e.g., missing ownership process, policy-only controls, broad architectural assumptions with no direct exploit path).
324
+
325
+ For governance/design gaps:
326
+ - Do NOT force a fake exploit-style exposure.
327
+ - Add @audit on the relevant asset with precise reasoning.
328
+ - Add @comment suggesting concrete controls or follow-up review tasks.
329
+
254
330
  ### @shield — DO NOT USE Unless Explicitly Asked
255
331
  @shield and @shield:begin/@shield:end block AI coding assistants from reading the annotated code.
256
332
  This means any shielded code becomes invisible to AI tools — they cannot analyze, refactor, or annotate it.
@@ -259,7 +335,7 @@ Adding @shield on your own initiative would actively harm the threat model by cr
259
335
 
260
336
  ## PRECISE GAL Syntax
261
337
 
262
- Definitions go in .guardlink/definitions.{ts,js,py,rs}. Source files use only relationship verbs.
338
+ Definitions go in .guardlink/definitions.{ts,js,py,rs}. Relationship annotations can live in source comments or standalone .gal files.
263
339
 
264
340
  ### Definitions (in .guardlink/definitions file)
265
341
  \`\`\`
@@ -274,6 +350,7 @@ Definitions go in .guardlink/definitions.{ts,js,py,rs}. Source files use only re
274
350
  \`\`\`
275
351
  // @shield:begin -- "Relationship syntax examples, excluded from parsing"
276
352
  // @exposes #auth to #sqli [P0] cwe:CWE-89 owasp:A03:2021 -- "User input concatenated into query"
353
+ // @confirmed #sqli on #auth [critical] cwe:CWE-89 -- "Pentest 2026-04: time-based blind SQLi on /login confirmed"
277
354
  // @mitigates #auth against #sqli using #prepared-stmts -- "Uses parameterized queries via sqlx"
278
355
  // @audit #auth -- "Timing attack risk — needs human review to decide if bcrypt constant-time comparison is sufficient"
279
356
  // @transfers #ddos from #api to #cdn -- "Cloudflare handles L7 DDoS mitigation"
@@ -284,10 +361,19 @@ Definitions go in .guardlink/definitions.{ts,js,py,rs}. Source files use only re
284
361
  // @audit #auth -- "Session token rotation logic needs cryptographic review"
285
362
  // @assumes #auth -- "Upstream API gateway has already validated TLS and rate-limited requests"
286
363
  // @owns security-team for #auth -- "Security team reviews all auth PRs"
364
+ // @feature "SSO Login" -- "Single sign-on authentication flow"
287
365
  // @comment -- "Password hashing uses bcrypt with cost factor 12, migration from SHA256 completed in v2.1"
288
366
  // @shield:end
289
367
  \`\`\`
290
368
 
369
+ ### Relationships (in standalone .gal files)
370
+ \`\`\`
371
+ @source file:src/auth/login.ts line:42 symbol:authenticate
372
+ @exposes #auth to #sqli [P0] cwe:CWE-89 owasp:A03:2021 -- "User input concatenated into query"
373
+ @mitigates #auth against #sqli using #prepared-stmts -- "Uses parameterized queries via sqlx"
374
+ @audit #auth -- "Timing attack risk — needs human review"
375
+ \`\`\`
376
+
291
377
  ## CRITICAL SYNTAX RULES (violations cause parse errors)
292
378
 
293
379
  1. **@boundary requires TWO assets**: \`@boundary between #A and #B\` or \`@boundary #A | #B\`.
@@ -304,6 +390,7 @@ Definitions go in .guardlink/definitions.{ts,js,py,rs}. Source files use only re
304
390
 
305
391
  4. **Severity in square brackets**: \`[P0]\` \`[P1]\` \`[P2]\` \`[P3]\` or \`[critical]\` \`[high]\` \`[medium]\` \`[low]\`.
306
392
  Goes AFTER the threat ref in @exposes: \`@exposes #app to #sqli [P0] cwe:CWE-89\`
393
+ On @confirmed, severity is optional but recommended — it reflects **verified** impact: \`@confirmed #sqli on #app [critical] -- "evidence"\`
307
394
 
308
395
  5. **Descriptions in double quotes after --**: \`-- "description text here"\`
309
396
  WRONG: \`@comment "just a note"\` or \`@comment -- note without quotes\`
@@ -322,6 +409,7 @@ Definitions go in .guardlink/definitions.{ts,js,py,rs}. Source files use only re
322
409
  A bare \`@comment\` without description is valid but useless. Always include context.
323
410
 
324
411
  10. **One annotation per comment line.** Do NOT put two @verbs on the same line.
412
+ 11. **In external mode, use \`@source\` before each block** so the annotations point at the intended file and line.
325
413
 
326
414
  ## Workflow
327
415
 
@@ -339,11 +427,418 @@ Definitions go in .guardlink/definitions.{ts,js,py,rs}. Source files use only re
339
427
  Think: "what's the risk, what's the defense, how does data flow here, and what should the next developer know?"
340
428
  NEVER write @accepts — that is a human-only governance decision. Use @audit to flag unmitigated risks for review.
341
429
 
342
- 5. **Use the project's comment style** (// for JS/TS/Go/Rust, # for Python/Ruby/Shell, etc.)
430
+ 5. **Use the selected annotation mode consistently.** Inline mode writes source comments; external mode writes associated \`.gal\` files with \`@source\` blocks.
431
+
432
+ 6. **Generate project description.** If \`.guardlink/prompt.md\` exists and contains only the skeleton template
433
+ (HTML comments / placeholder headings with no real content), fill it in based on what you learned while
434
+ reading the codebase. Write a security-focused project overview covering:
435
+ - What the application does and who its users are
436
+ - Key components and services
437
+ - Trust boundaries (where trust changes between components)
438
+ - Data sensitivity (PII, credentials, financial data, etc.)
439
+ - Deployment context (cloud, containers, CI/CD, etc.)
440
+ This file feeds into \`guardlink report\` as the Application Overview section.
441
+ **Do NOT overwrite user-written content** — only fill in the template placeholders.
343
442
 
344
- 6. **Run validation** via guardlink_validate (MCP) or \`guardlink validate\` to check for errors.
443
+ 7. **Run validation** via guardlink_validate (MCP) or \`guardlink validate\` to check for errors.
345
444
 
346
- 7. **Fix any validation errors** before finishing — especially dangling refs and malformed syntax.
445
+ 8. **Fix any validation errors** before finishing — especially dangling refs and malformed syntax.
446
+ `;
447
+ }
448
+ /**
449
+ * Build a prompt for translating GuardLink threat model findings into
450
+ * CERT-X-GEN (CXG) pentest templates.
451
+ */
452
+ export function buildTranslatePrompt(userPrompt, root, model) {
453
+ const cxgRoot = process.env.GUARDLINK_CXG_ROOT || DEFAULT_CXG_ROOT;
454
+ const skeletonDir = process.env.GUARDLINK_CXG_SKELETON_DIR || DEFAULT_CXG_SKELETON_DIR;
455
+ const templateGuide = readIfExists(resolve(cxgRoot, 'docs', 'TEMPLATE_GUIDE.md'), 4000);
456
+ // prompt.rs only exists when GUARDLINK_CXG_ROOT points at the CXG source
457
+ // repo (developer use). For end users on the installed layout the path
458
+ // resolves under ~/.cert-x-gen/templates/official/src/ai/prompt.rs which
459
+ // does not exist; readIfExists() returns '' gracefully and the prompt
460
+ // still works — they just get a slightly leaner template-authoring guide.
461
+ const promptEngine = readIfExists(resolve(cxgRoot, 'src', 'ai', 'prompt.rs'), 4000);
462
+ const yamlSkeleton = readIfExists(resolve(skeletonDir, 'yaml-template-skeleton.yaml'), 5000);
463
+ const pythonSkeleton = readIfExists(resolve(skeletonDir, 'python-template-skeleton.py'), 3000);
464
+ let modelSummary = 'No threat model parsed yet.';
465
+ let candidateExposures = '';
466
+ if (model) {
467
+ const unmitigated = model.exposures.filter((e) => !model.mitigations.some((m) => m.asset === e.asset && m.threat === e.threat));
468
+ modelSummary = `Current model: ${model.annotations_parsed} annotations, ${model.exposures.length} exposures, ${(model.confirmed || []).length} confirmed, ${unmitigated.length} unmitigated exposures, ${model.assets.length} assets, ${model.threats.length} threats.`;
469
+ if (unmitigated.length > 0) {
470
+ const lines = unmitigated.slice(0, 40).map((e) => `- ${e.asset} -> ${e.threat} [${e.severity || 'unrated'}] (${e.location.file}:${e.location.line})`);
471
+ candidateExposures = `\n\nUnmitigated exposure candidates:\n${lines.join('\n')}`;
472
+ if (unmitigated.length > 40) {
473
+ candidateExposures += `\n- ... and ${unmitigated.length - 40} more`;
474
+ }
475
+ }
476
+ }
477
+ const instruction = userPrompt.trim()
478
+ ? userPrompt.trim()
479
+ : 'Generate CXG pentest templates for all pentest-confirmable high/critical threats first, then medium.';
480
+ return `You are a senior offensive security engineer translating GuardLink threat-model findings into CERT-X-GEN (CXG) templates.
481
+
482
+ ## Mission
483
+ Convert pentest-confirmable threats into runnable CXG templates. Do NOT execute templates. Only author template files.
484
+
485
+ ## Current Threat Model
486
+ ${modelSummary}${candidateExposures}
487
+
488
+ ## User Request
489
+ ${instruction}
490
+
491
+ ## Required CXG CLI Discovery (Do This First)
492
+ Before generating final user guidance, discover the actual CLI usage on this machine:
493
+ 1. Try: \`cxg --help\`
494
+ 2. Try: \`cxg scan --help\`
495
+ 3. Try: \`cxg template --help\`
496
+ 4. If \`cxg\` is not in PATH, try local binary from source checkout (if present):
497
+ - \`cxg --help\`
498
+ - \`cxg scan --help\`
499
+ - \`cxg template --help\`
500
+ 5. Base user instructions on the commands that actually work. If none work, clearly state the blocker and provide install/build steps first.
501
+
502
+ ## Required Decision Rule (Critical)
503
+ For every candidate threat/exposure:
504
+ 1. Decide if it is **pentest-confirmable** — meaning it can be validated via:
505
+ - Network request/response behavior (HTTP, TCP, etc.)
506
+ - Local CLI invocation with crafted inputs (command injection, path traversal, etc.)
507
+ - File system operations (symlink attacks, arbitrary writes, config tampering)
508
+ - MCP/stdio protocol interactions (JSON-RPC tool calls with malicious payloads)
509
+ - Process spawning behavior (canary file creation, shell metacharacter interpretation)
510
+ 2. If yes: create one or more CXG templates. For local CLI/codebase threats, templates should use \`subprocess.run()\` or \`subprocess.Popen()\` with \`cwd=target\` to invoke the tool under test.
511
+ 3. If no (pure governance/process/design gap): do NOT create a template. Instead document it as audit-only guidance:
512
+ - Include suggested GuardLink @audit text and @comment text for the relevant asset/file.
513
+ - Explain briefly why no pentest template is appropriate.
514
+
515
+ ## Output and File Operations
516
+ 1. Create templates under: \`.guardlink/cxg-templates/\`
517
+ 2. Use meaningful filenames like:
518
+ - \`.guardlink/cxg-templates/<threat-id-or-name>.yaml\`
519
+ - or language variants \`.py\`, \`.js\`, \`.go\`, etc. if needed.
520
+ 3. Write an index file at \`.guardlink/cxg-templates/README.md\` with:
521
+ - generated templates list
522
+ - mapping: GuardLink threat/exposure -> template file(s)
523
+ - "audit-only / no-template" items with suggested @audit annotations
524
+ 4. CXG scan output goes to: \`.guardlink/pentest-findings/\` (this is where \`guardlink dashboard\` and \`guardlink threat-report\` read pentest results from). Always tell users to output to this path.
525
+ 5. Do NOT run CXG CLI or execute generated templates.
526
+ 6. Keep checks non-destructive.
527
+ 7. You MAY run \`cxg --help\` and other help/listing commands only for usage discovery. Do not run active scans unless user explicitly asks.
528
+
529
+ ## CXG Format Contract (from source)
530
+ Use the project skeleton contract and examples; mirror field names and structure exactly.
531
+
532
+ ${templateGuide ? `### TEMPLATE_GUIDE excerpt\n${templateGuide}\n` : ''}
533
+ ${promptEngine ? `### prompt.rs excerpt\n${promptEngine}\n` : ''}
534
+ ${yamlSkeleton ? `### YAML skeleton excerpt\n${yamlSkeleton}\n` : ''}
535
+ ${pythonSkeleton ? `### Python skeleton excerpt\n${pythonSkeleton}\n` : ''}
536
+
537
+ ## Quality Bar
538
+ - Each template must include clear metadata: id/name/author/severity/description/tags/references.
539
+ - Detection logic must align to the threat and include concrete matchers/assertions.
540
+ - Prefer YAML templates for declarative checks; use code templates where procedural logic is required.
541
+ - Avoid placeholder TODO logic.
542
+ - Keep template logic scoped to the specific threat confirmation.
543
+
544
+ ## CXG Engine Contract (Critical — templates MUST follow this)
545
+ When CXG runs a Python template, it does NOT pass the target as a CLI argument.
546
+ Instead, it sets environment variables and expects JSON on stdout.
547
+
548
+ ### Target resolution (in main / entry point):
549
+ \`\`\`python
550
+ target = os.environ.get("CERT_X_GEN_PROJECT_ROOT") or args.target or os.environ.get("CERT_X_GEN_TARGET_HOST")
551
+ \`\`\`
552
+ - \`CERT_X_GEN_PROJECT_ROOT\`: set for local codebase/CLI targets (absolute path).
553
+ - \`CERT_X_GEN_TARGET_HOST\`: set for network targets (hostname/IP).
554
+ - The positional \`target\` arg MUST use \`nargs="?"\` (optional) since CXG engine passes no argv.
555
+
556
+ ### Output contract:
557
+ - When \`CERT_X_GEN_MODE == "engine"\` (always true under CXG), print ONLY a JSON array to stdout.
558
+ - Output \`[]\` (empty array) when no findings — never print plain text in engine mode.
559
+ - Use: \`print(json.dumps(findings, indent=2))\`
560
+
561
+ ### Environment variables available:
562
+ | Variable | Value |
563
+ |----------|-------|
564
+ | \`CERT_X_GEN_MODE\` | Always \`"engine"\` |
565
+ | \`CERT_X_GEN_TARGET_HOST\` | Target address (path for local, hostname for network) |
566
+ | \`CERT_X_GEN_TARGET_TYPE\` | \`"local"\` or \`"network"\` |
567
+ | \`CERT_X_GEN_PROJECT_ROOT\` | Absolute path (local targets only) |
568
+ | \`CERT_X_GEN_TARGET_PORT\` | Port number (network targets, default 80) |
569
+
570
+ ### Running templates with CXG local scope:
571
+ \`\`\`bash
572
+ cxg scan --scope local://. --template-dir .guardlink/cxg-templates/ --output .guardlink/pentest-findings/guardlink-pentest --output-format json,sarif,html
573
+ \`\`\`
574
+
575
+ ## CXG Evidence Contract (Critical — findings MUST include rich evidence)
576
+ CXG parses finding evidence using specific field names. If these fields are missing or empty,
577
+ the output report will show blank evidence — making findings impossible to verify.
578
+
579
+ ### Required evidence structure in every finding dict:
580
+ \`\`\`python
581
+ "evidence": {
582
+ "request": "<string: what was sent — payload, RPC call, CLI args, env vars, etc.>",
583
+ "response": "<string: what came back — stdout, stderr, HTTP response, RPC response, etc.>",
584
+ "matched_patterns": ["<string>", ...], # list of STRINGS (not dicts) — e.g. CWE IDs, indicators found, regex matches
585
+ "data": { # arbitrary key-value map for all raw evidence details
586
+ "key1": "<string value>",
587
+ "key2": "<string value>",
588
+ ...
589
+ }
590
+ }
591
+ \`\`\`
592
+
593
+ ### Rules for populating evidence:
594
+ 1. **\`request\`**: MUST contain the exact input that triggered the finding. Examples:
595
+ - For CLI injection: the full command with payload (e.g., \`npx guardlink annotate "; touch /tmp/canary"\`)
596
+ - For MCP tests: the JSON-RPC request body sent to the tool
597
+ - For path traversal: the malicious path used (e.g., \`../../etc/passwd\`)
598
+ - For config tamper: the environment variable name and injected value
599
+
600
+ 2. **\`response\`**: MUST contain the raw output that proves the vulnerability. Examples:
601
+ - stdout/stderr excerpt from the command execution (up to 2000 chars)
602
+ - The MCP JSON-RPC response content
603
+ - File contents read from an unexpected location
604
+ - Error messages that reveal injection
605
+
606
+ 3. **\`matched_patterns\`**: MUST be a list of **strings** (CXG drops non-strings). Include:
607
+ - Shell error indicators found (e.g., "sh: command not found")
608
+ - Sensitive data patterns matched (e.g., "absolute_paths: 5 found")
609
+ - CWE/OWASP identifiers relevant to the finding
610
+ - Canary strings that proved exploitation
611
+
612
+ 4. **\`data\`**: Store ALL evidence key-value pairs here. All values must be strings
613
+ (use \`json.dumps()\` to serialize complex objects). This is the catch-all for:
614
+ - \`canary_created\`: "true"
615
+ - \`exit_code\`: "0"
616
+ - \`symlink_path\`: "/path/to/symlink"
617
+ - \`traversal_root\`: "/etc"
618
+ - \`env_var\`: "GUARDLINK_CXG_ROOT"
619
+
620
+ ### Helper pattern for \`create_finding\`:
621
+ Always use a centralized helper that maps your raw evidence dict into the CXG structure:
622
+
623
+ \`\`\`python
624
+ def create_finding(self, title, description, evidence):
625
+ return {
626
+ "template_id": self.id,
627
+ "title": title,
628
+ "severity": self.severity,
629
+ "confidence": self.confidence,
630
+ "description": description,
631
+ "evidence": {
632
+ "request": evidence.get("request") or evidence.get("payload") or evidence.get("rpc_request") or
633
+ json.dumps({k: v for k, v in evidence.items()
634
+ if k not in ("response", "stdout_excerpt", "stderr_excerpt",
635
+ "output_excerpt", "response_snippet", "matched_patterns")}, default=str),
636
+ "response": evidence.get("response") or evidence.get("stdout_excerpt") or
637
+ evidence.get("stderr_excerpt") or evidence.get("output_excerpt") or
638
+ evidence.get("response_snippet") or evidence.get("content_snippet") or "",
639
+ "matched_patterns": [p if isinstance(p, str) else
640
+ (f"{p.get('type','')}: {p.get('count','?')}" if isinstance(p, dict) else str(p))
641
+ for p in (evidence.get("matched_patterns") or [])],
642
+ "data": {k: (v if isinstance(v, str) else json.dumps(v, default=str))
643
+ for k, v in evidence.items()},
644
+ },
645
+ "cwe": self.cwe,
646
+ "tags": self.tags,
647
+ "remediation": "...",
648
+ }
649
+ \`\`\`
650
+
651
+ ### What to capture as evidence for each template type:
652
+ | Template type | request | response | matched_patterns |
653
+ |---|---|---|---|
654
+ | CLI injection | Full CLI command with payload | stdout + stderr (first 2000 chars) | Shell indicators, canary proof |
655
+ | MCP tool call | JSON-RPC request body | JSON-RPC response body | Sensitive data types found |
656
+ | Path traversal | Traversal path used | File/dir content from outside project | Path indicators (/etc, /tmp) |
657
+ | Config tamper | Env var name + injected value | Command output with canary | Canary string match |
658
+ | Prompt injection | Injected prompt text | LLM/agent output text | Injection markers found |
659
+ | Arbitrary write | Symlink/path payload | guardlink clear output showing external files | External paths listed |
660
+
661
+ ### NEVER do this:
662
+ - Do NOT pass raw evidence dicts without the CXG structure — CXG will show empty evidence fields.
663
+ - Do NOT put dicts or lists in \`matched_patterns\` — CXG drops non-string entries silently.
664
+ - Do NOT skip evidence collection — a finding without evidence is unverifiable.
665
+
666
+ ## Python Template Boilerplate (MUST use this structure)
667
+ Every Python template you create MUST follow this exact \`main()\` structure:
668
+
669
+ \`\`\`python
670
+ def main():
671
+ parser = argparse.ArgumentParser(description="...")
672
+ parser.add_argument("target", nargs="?", help="Project root or target host")
673
+ parser.add_argument("--port", type=int, default=0)
674
+ parser.add_argument("--json", action="store_true")
675
+ args = parser.parse_args()
676
+
677
+ template = CertXGenTemplate()
678
+ target = os.environ.get("CERT_X_GEN_PROJECT_ROOT") or args.target or os.environ.get("CERT_X_GEN_TARGET_HOST")
679
+ if not target:
680
+ parser.error("target is required (positional, CERT_X_GEN_PROJECT_ROOT, or CERT_X_GEN_TARGET_HOST)")
681
+
682
+ findings = template.execute(target, args.port)
683
+ if args.json or os.environ.get("CERT_X_GEN_MODE") == "engine":
684
+ print(json.dumps(findings, indent=2))
685
+ elif findings:
686
+ for f in findings:
687
+ print(f"[{f['severity'].upper()}] {f['title']}")
688
+ print(f" {f['description']}")
689
+ print()
690
+ else:
691
+ print("No findings detected.")
692
+
693
+ if __name__ == "__main__":
694
+ main()
695
+ \`\`\`
696
+
697
+ Key rules:
698
+ - \`target\` positional arg uses \`nargs="?"\` — CXG engine does NOT pass target as argv.
699
+ - Target resolution order: \`CERT_X_GEN_PROJECT_ROOT\` > \`args.target\` > \`CERT_X_GEN_TARGET_HOST\`.
700
+ - When \`CERT_X_GEN_MODE == "engine"\`, ALWAYS output JSON (even if \`--json\` is not set).
701
+ - Output \`[]\` (empty JSON array) when no findings — never plain text in engine mode.
702
+ - For local/CLI templates, use \`target\` as \`cwd\` in \`subprocess.run()\` / \`subprocess.Popen()\` calls.
703
+
704
+ ## Final Response Format
705
+ After writing files, return:
706
+ 1. A short "Generated templates" list with file paths.
707
+ 2. A short "Audit-only (no template)" list with recommended GuardLink @audit/@comment text.
708
+ 3. A "How to run these templates with CXG" section with these **exact steps**:
709
+
710
+ **Step 1 — Prerequisites:**
711
+ \`\`\`bash
712
+ cxg --version # Verify CXG is installed (expect v1.1.0+)
713
+ python3 --version # Python 3.8+ required for template execution
714
+ ls .guardlink/cxg-templates/*.py # Verify templates were created
715
+ \`\`\`
716
+
717
+ **Step 2 — Validate templates:**
718
+ \`\`\`bash
719
+ cxg template validate .guardlink/cxg-templates/ --recursive
720
+ \`\`\`
721
+
722
+ **Step 3 — Create output directory and run scan using local scope (for CLI/codebase targets):**
723
+ \`\`\`bash
724
+ mkdir -p .guardlink/pentest-findings
725
+ cxg scan \\
726
+ --scope local://. \\
727
+ --template-dir .guardlink/cxg-templates/ \\
728
+ --template-language python \\
729
+ --output .guardlink/pentest-findings/guardlink-pentest \\
730
+ --output-format json,sarif,html
731
+ \`\`\`
732
+ The \`local://.\` scope tells CXG this is a local codebase target. CXG will set
733
+ \`CERT_X_GEN_PROJECT_ROOT\` to the absolute path of the current directory and
734
+ \`CERT_X_GEN_TARGET_TYPE=local\`, so templates receive the correct project root.
735
+
736
+ Output is stored in \`.guardlink/pentest-findings/\` so that \`guardlink dashboard\`
737
+ and \`guardlink threat-report\` automatically pick up the results.
738
+
739
+ **Step 3b — Run scan using network scope (for HTTP/API targets):**
740
+ \`\`\`bash
741
+ cxg scan \\
742
+ --scope https://api.example.com \\
743
+ --template-dir .guardlink/cxg-templates/ \\
744
+ --output .guardlink/pentest-findings/guardlink-pentest \\
745
+ --output-format json,sarif,html
746
+ \`\`\`
747
+
748
+ **Step 4 — Run with verbose output for debugging:**
749
+ \`\`\`bash
750
+ cxg -vv scan \\
751
+ --scope local://. \\
752
+ --template-dir .guardlink/cxg-templates/ \\
753
+ --output .guardlink/pentest-findings/guardlink-pentest \\
754
+ --output-format json,sarif,html
755
+ \`\`\`
756
+
757
+ **Step 5 — Run individual templates standalone (without CXG):**
758
+ \`\`\`bash
759
+ python3 .guardlink/cxg-templates/<template-name>.py . --json
760
+ \`\`\`
761
+
762
+ **Expected output artifacts (in \`.guardlink/pentest-findings/\`):**
763
+ - \`guardlink-pentest.json\` — JSON with scan_id, findings array, statistics
764
+ - \`guardlink-pentest.sarif\` — SARIF 2.1.0 for GitHub Advanced Security / CI integration
765
+ - \`guardlink-pentest.html\` — Human-readable HTML report
766
+ - Each finding includes: template_id, severity, title, description, evidence (with request, response, matched_patterns, data), remediation
767
+ - **Evidence must be populated** — a finding with empty evidence (null request, null response, empty data) is a template bug
768
+ - These files are automatically consumed by \`guardlink dashboard\` (Pentest Findings tab) and \`guardlink threat-report\` (pentest context)
769
+
770
+ **Troubleshooting:**
771
+ | Issue | Fix |
772
+ |---|---|
773
+ | \`target is required\` error | Template is missing \`nargs="?"\` on target arg — engine uses env vars, not argv |
774
+ | \`JSON parse error\` | Template prints non-JSON text to stdout in engine mode — wrap all output in \`json.dumps()\` |
775
+ | \`Operation timed out\` | Template takes >30s; add \`--timeout 60s\` to scan command |
776
+ | All templates show 0 findings | Run with \`-vv\` to check for WARN lines; ensure \`local://.\` scope is used for CLI templates |
777
+ | \`guardlink CLI not found\` | Run \`npm install\` in the project root first |
778
+ | Evidence fields are null/empty | Template is passing raw dict without CXG structure — use the \`create_finding\` helper pattern from the Evidence Contract section |
779
+
780
+ 4. A "What to expect" section that explains:
781
+ - what a positive finding looks like (JSON with template_id, severity, evidence)
782
+ - what a negative/no-finding run means (code is secure against those specific checks)
783
+ - false-positive caveats and manual verification guidance
784
+ 5. Any assumptions requiring human review.`;
785
+ }
786
+ /**
787
+ * Build a prompt for answering freeform user questions about the codebase
788
+ * and GuardLink threat model.
789
+ */
790
+ export function buildAskPrompt(userQuery, root, model) {
791
+ let modelSummary = 'No threat model parsed yet.';
792
+ let idSummary = '';
793
+ let exposureSummary = '';
794
+ if (model) {
795
+ modelSummary = `Current model: ${model.annotations_parsed} annotations, ${model.exposures.length} exposures, ${(model.confirmed || []).length} confirmed, ${model.mitigations.length} mitigations, ${model.assets.length} assets, ${model.threats.length} threats, ${model.flows.length} flows.`;
796
+ const assetIds = model.assets.filter(a => a.id).slice(0, 30).map(a => `#${a.id}`);
797
+ const threatIds = model.threats.filter(t => t.id).slice(0, 30).map(t => `#${t.id}`);
798
+ const controlIds = model.controls.filter(c => c.id).slice(0, 30).map(c => `#${c.id}`);
799
+ const idLines = [];
800
+ if (assetIds.length)
801
+ idLines.push(`Assets: ${assetIds.join(', ')}`);
802
+ if (threatIds.length)
803
+ idLines.push(`Threats: ${threatIds.join(', ')}`);
804
+ if (controlIds.length)
805
+ idLines.push(`Controls: ${controlIds.join(', ')}`);
806
+ if (idLines.length)
807
+ idSummary = `\n\nKnown IDs:\n${idLines.join('\n')}`;
808
+ const unmitigated = model.exposures.filter((e) => !model.mitigations.some((m) => m.asset === e.asset && m.threat === e.threat));
809
+ if (unmitigated.length > 0) {
810
+ const lines = unmitigated.slice(0, 25).map((e) => `- ${e.asset} -> ${e.threat} [${e.severity || 'unrated'}] (${e.location.file}:${e.location.line})`);
811
+ exposureSummary = `\n\nOpen unmitigated exposures:\n${lines.join('\n')}`;
812
+ if (unmitigated.length > 25) {
813
+ exposureSummary += `\n- ... and ${unmitigated.length - 25} more`;
814
+ }
815
+ }
816
+ }
817
+ return `You are a senior AppSec engineer answering questions about a GuardLink-instrumented codebase.
818
+
819
+ ## Project Root
820
+ ${root}
821
+
822
+ ## Current Threat Model Context
823
+ ${modelSummary}${idSummary}${exposureSummary}
824
+
825
+ ## User Question
826
+ ${userQuery}
827
+
828
+ ## Required Method
829
+ 1. Read relevant source files and configs before answering.
830
+ 2. Use GuardLink annotations as guidance, but verify with actual code.
831
+ 3. If the question asks about a specific area (e.g. admin portal, API, auth), trace entry points, data flows, and related threats.
832
+ 4. If information is missing or ambiguous, say so clearly and list what was checked.
833
+ 5. Never invent endpoints, threats, or controls.
834
+
835
+ ## Output Format
836
+ - Provide a direct answer first.
837
+ - Then include concise evidence:
838
+ - files/components examined
839
+ - relevant threats/exposures/controls
840
+ - important gaps or unknowns
841
+ - If asked "do we have X threats," include counts and examples with file paths.
347
842
  `;
348
843
  }
349
844
  //# sourceMappingURL=prompts.js.map