guardlink 1.2.0 → 1.3.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.
Files changed (142) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +11 -2
  3. package/dist/agents/config.d.ts +11 -0
  4. package/dist/agents/config.d.ts.map +1 -1
  5. package/dist/agents/config.js +11 -0
  6. package/dist/agents/config.js.map +1 -1
  7. package/dist/agents/index.d.ts +3 -0
  8. package/dist/agents/index.d.ts.map +1 -1
  9. package/dist/agents/index.js +3 -0
  10. package/dist/agents/index.js.map +1 -1
  11. package/dist/agents/launcher.d.ts +11 -8
  12. package/dist/agents/launcher.d.ts.map +1 -1
  13. package/dist/agents/launcher.js +11 -8
  14. package/dist/agents/launcher.js.map +1 -1
  15. package/dist/agents/prompts.d.ts +9 -0
  16. package/dist/agents/prompts.d.ts.map +1 -1
  17. package/dist/agents/prompts.js +9 -0
  18. package/dist/agents/prompts.js.map +1 -1
  19. package/dist/analyze/index.d.ts +10 -7
  20. package/dist/analyze/index.d.ts.map +1 -1
  21. package/dist/analyze/index.js +10 -7
  22. package/dist/analyze/index.js.map +1 -1
  23. package/dist/analyze/llm.d.ts +11 -10
  24. package/dist/analyze/llm.d.ts.map +1 -1
  25. package/dist/analyze/llm.js +11 -10
  26. package/dist/analyze/llm.js.map +1 -1
  27. package/dist/analyze/prompts.d.ts +3 -0
  28. package/dist/analyze/prompts.d.ts.map +1 -1
  29. package/dist/analyze/prompts.js +3 -0
  30. package/dist/analyze/prompts.js.map +1 -1
  31. package/dist/analyze/tools.d.ts +10 -4
  32. package/dist/analyze/tools.d.ts.map +1 -1
  33. package/dist/analyze/tools.js +10 -4
  34. package/dist/analyze/tools.js.map +1 -1
  35. package/dist/analyzer/index.d.ts +3 -0
  36. package/dist/analyzer/index.d.ts.map +1 -1
  37. package/dist/analyzer/index.js +3 -0
  38. package/dist/analyzer/index.js.map +1 -1
  39. package/dist/analyzer/sarif.d.ts +5 -6
  40. package/dist/analyzer/sarif.d.ts.map +1 -1
  41. package/dist/analyzer/sarif.js +5 -6
  42. package/dist/analyzer/sarif.js.map +1 -1
  43. package/dist/cli/index.d.ts +12 -9
  44. package/dist/cli/index.d.ts.map +1 -1
  45. package/dist/cli/index.js +236 -11
  46. package/dist/cli/index.js.map +1 -1
  47. package/dist/dashboard/generate.d.ts +8 -5
  48. package/dist/dashboard/generate.d.ts.map +1 -1
  49. package/dist/dashboard/generate.js +30 -7
  50. package/dist/dashboard/generate.js.map +1 -1
  51. package/dist/dashboard/index.d.ts +5 -0
  52. package/dist/dashboard/index.d.ts.map +1 -1
  53. package/dist/dashboard/index.js +5 -0
  54. package/dist/dashboard/index.js.map +1 -1
  55. package/dist/diff/git.d.ts +10 -7
  56. package/dist/diff/git.d.ts.map +1 -1
  57. package/dist/diff/git.js +10 -7
  58. package/dist/diff/git.js.map +1 -1
  59. package/dist/diff/index.d.ts +4 -0
  60. package/dist/diff/index.d.ts.map +1 -1
  61. package/dist/diff/index.js +4 -0
  62. package/dist/diff/index.js.map +1 -1
  63. package/dist/init/detect.d.ts +5 -0
  64. package/dist/init/detect.d.ts.map +1 -1
  65. package/dist/init/detect.js +5 -0
  66. package/dist/init/detect.js.map +1 -1
  67. package/dist/init/index.d.ts +26 -6
  68. package/dist/init/index.d.ts.map +1 -1
  69. package/dist/init/index.js +91 -11
  70. package/dist/init/index.js.map +1 -1
  71. package/dist/init/picker.d.ts.map +1 -1
  72. package/dist/init/picker.js +17 -6
  73. package/dist/init/picker.js.map +1 -1
  74. package/dist/init/templates.d.ts +20 -0
  75. package/dist/init/templates.d.ts.map +1 -1
  76. package/dist/init/templates.js +132 -21
  77. package/dist/init/templates.js.map +1 -1
  78. package/dist/mcp/index.d.ts +5 -0
  79. package/dist/mcp/index.d.ts.map +1 -1
  80. package/dist/mcp/index.js +5 -0
  81. package/dist/mcp/index.js.map +1 -1
  82. package/dist/mcp/lookup.d.ts +5 -0
  83. package/dist/mcp/lookup.d.ts.map +1 -1
  84. package/dist/mcp/lookup.js +5 -0
  85. package/dist/mcp/lookup.js.map +1 -1
  86. package/dist/mcp/server.d.ts +16 -13
  87. package/dist/mcp/server.d.ts.map +1 -1
  88. package/dist/mcp/server.js +134 -15
  89. package/dist/mcp/server.js.map +1 -1
  90. package/dist/mcp/suggest.d.ts +8 -6
  91. package/dist/mcp/suggest.d.ts.map +1 -1
  92. package/dist/mcp/suggest.js +8 -6
  93. package/dist/mcp/suggest.js.map +1 -1
  94. package/dist/parser/clear.d.ts +36 -0
  95. package/dist/parser/clear.d.ts.map +1 -0
  96. package/dist/parser/clear.js +148 -0
  97. package/dist/parser/clear.js.map +1 -0
  98. package/dist/parser/index.d.ts +2 -0
  99. package/dist/parser/index.d.ts.map +1 -1
  100. package/dist/parser/index.js +1 -0
  101. package/dist/parser/index.js.map +1 -1
  102. package/dist/parser/parse-file.d.ts +5 -2
  103. package/dist/parser/parse-file.d.ts.map +1 -1
  104. package/dist/parser/parse-file.js +29 -2
  105. package/dist/parser/parse-file.js.map +1 -1
  106. package/dist/parser/parse-line.d.ts +3 -3
  107. package/dist/parser/parse-line.js +3 -3
  108. package/dist/parser/parse-project.d.ts +7 -7
  109. package/dist/parser/parse-project.d.ts.map +1 -1
  110. package/dist/parser/parse-project.js +24 -11
  111. package/dist/parser/parse-project.js.map +1 -1
  112. package/dist/report/index.d.ts +3 -0
  113. package/dist/report/index.d.ts.map +1 -1
  114. package/dist/report/index.js +3 -0
  115. package/dist/report/index.js.map +1 -1
  116. package/dist/report/report.d.ts +4 -7
  117. package/dist/report/report.d.ts.map +1 -1
  118. package/dist/report/report.js +4 -7
  119. package/dist/report/report.js.map +1 -1
  120. package/dist/review/index.d.ts +62 -0
  121. package/dist/review/index.d.ts.map +1 -0
  122. package/dist/review/index.js +226 -0
  123. package/dist/review/index.js.map +1 -0
  124. package/dist/tui/commands.d.ts +20 -0
  125. package/dist/tui/commands.d.ts.map +1 -1
  126. package/dist/tui/commands.js +200 -2
  127. package/dist/tui/commands.js.map +1 -1
  128. package/dist/tui/config.d.ts +6 -0
  129. package/dist/tui/config.d.ts.map +1 -1
  130. package/dist/tui/config.js +6 -0
  131. package/dist/tui/config.js.map +1 -1
  132. package/dist/tui/index.d.ts +8 -8
  133. package/dist/tui/index.d.ts.map +1 -1
  134. package/dist/tui/index.js +29 -9
  135. package/dist/tui/index.js.map +1 -1
  136. package/dist/tui/input.d.ts +6 -0
  137. package/dist/tui/input.d.ts.map +1 -1
  138. package/dist/tui/input.js +6 -0
  139. package/dist/tui/input.js.map +1 -1
  140. package/dist/types/index.d.ts +2 -0
  141. package/dist/types/index.d.ts.map +1 -1
  142. package/package.json +1 -1
@@ -0,0 +1,226 @@
1
+ /**
2
+ * GuardLink — Review module.
3
+ *
4
+ * Interactive governance workflow for unmitigated exposures.
5
+ * Users walk through the GAL (Governance Acceptance List) and decide:
6
+ * accept — write @accepts + @audit (risk acknowledged, intentional)
7
+ * remediate — write @audit with planned-fix note
8
+ * skip — leave open for now
9
+ *
10
+ * @exposes #cli to #arbitrary-write [medium] cwe:CWE-73 -- "Writes @accepts/@audit annotations into source files"
11
+ * @mitigates #cli against #arbitrary-write using #path-validation -- "Only modifies files already in the parsed project"
12
+ * @audit #cli -- "Review decisions require human justification; no empty accepts allowed"
13
+ * @flows ThreatModel -> #cli via getReviewableExposures -- "Exposure list input"
14
+ * @flows #cli -> SourceFiles via writeFile -- "Annotation insertion output"
15
+ * @handles internal on #cli -- "Processes exposure metadata and user justification text"
16
+ */
17
+ import { readFile, writeFile } from 'node:fs/promises';
18
+ import { resolve } from 'node:path';
19
+ import { stripCommentPrefix } from '../parser/comment-strip.js';
20
+ import { findUnmitigatedExposures } from '../parser/validate.js';
21
+ // ─── Severity ordering ──────────────────────────────────────────────
22
+ const SEVERITY_ORDER = {
23
+ critical: 0, high: 1, medium: 2, low: 3,
24
+ };
25
+ // ─── Core logic ─────────────────────────────────────────────────────
26
+ /**
27
+ * Get all unmitigated exposures eligible for review, sorted by severity.
28
+ * Excludes test fixtures and files outside the src/ tree.
29
+ */
30
+ export function getReviewableExposures(model) {
31
+ const unmitigated = findUnmitigatedExposures(model);
32
+ // Filter out test fixtures and non-source files
33
+ const filtered = unmitigated.filter(e => {
34
+ const f = e.location.file;
35
+ return !f.startsWith('tests/') && !f.startsWith('test/') && !f.includes('__tests__/') && !f.includes('fixtures/');
36
+ });
37
+ // Sort: critical → high → medium → low, then by file
38
+ filtered.sort((a, b) => {
39
+ const sa = SEVERITY_ORDER[a.severity || 'low'] ?? 3;
40
+ const sb = SEVERITY_ORDER[b.severity || 'low'] ?? 3;
41
+ if (sa !== sb)
42
+ return sa - sb;
43
+ return a.location.file.localeCompare(b.location.file);
44
+ });
45
+ return filtered.map((exposure, i) => ({
46
+ index: i + 1,
47
+ exposure,
48
+ id: `${exposure.location.file}:${exposure.location.line}`,
49
+ }));
50
+ }
51
+ /**
52
+ * Format a severity tag with color hint for display.
53
+ */
54
+ export function severityLabel(s) {
55
+ if (!s)
56
+ return '[?]';
57
+ return `[${s}]`;
58
+ }
59
+ /**
60
+ * Detect the comment style and indentation from the @exposes source line.
61
+ * Supports JSDoc ( * @...), single-line (// @...), and hash (# @...) styles.
62
+ */
63
+ function detectCommentStyle(rawLine) {
64
+ const indent = rawLine.match(/^(\s*)/)?.[1] || '';
65
+ const trimmed = rawLine.trimStart();
66
+ if (trimmed.startsWith('* @') || trimmed.startsWith('* @')) {
67
+ return { prefix: '* ', indent };
68
+ }
69
+ if (trimmed.startsWith('// @')) {
70
+ return { prefix: '// ', indent };
71
+ }
72
+ if (trimmed.startsWith('# @')) {
73
+ return { prefix: '# ', indent };
74
+ }
75
+ if (trimmed.startsWith('-- @')) {
76
+ return { prefix: '-- ', indent };
77
+ }
78
+ // Fallback: single-line JS style
79
+ return { prefix: '// ', indent };
80
+ }
81
+ /**
82
+ * Check if a source line is a GuardLink annotation (used to walk past coupled blocks).
83
+ */
84
+ function isAnnotationLine(line) {
85
+ const inner = stripCommentPrefix(line);
86
+ if (inner === null)
87
+ return false;
88
+ const trimmed = inner.trim();
89
+ // Annotation line: starts with @verb
90
+ if (trimmed.startsWith('@'))
91
+ return true;
92
+ // Continuation line: -- "..."
93
+ if (/^--\s*"/.test(trimmed))
94
+ return true;
95
+ return false;
96
+ }
97
+ /**
98
+ * Find the insertion point after the coupled annotation block that contains
99
+ * the @exposes line at `exposureLine` (1-indexed).
100
+ *
101
+ * Walks forward from the exposure line past consecutive annotation lines
102
+ * to find the end of the block, then returns the 0-indexed line to insert after.
103
+ */
104
+ function findInsertionIndex(lines, exposureLine) {
105
+ // exposureLine is 1-indexed, convert to 0-indexed
106
+ let idx = exposureLine - 1;
107
+ // Walk forward past consecutive annotation lines
108
+ while (idx + 1 < lines.length && isAnnotationLine(lines[idx + 1])) {
109
+ idx++;
110
+ }
111
+ // Insert after the last annotation line in the block
112
+ return idx + 1;
113
+ }
114
+ // ─── Annotation builders ────────────────────────────────────────────
115
+ function todayISO() {
116
+ return new Date().toISOString().slice(0, 10);
117
+ }
118
+ /**
119
+ * Build the annotation lines to insert for an "accept" decision.
120
+ * Returns lines WITHOUT trailing newline.
121
+ */
122
+ function buildAcceptLines(style, exposure, justification) {
123
+ const { prefix, indent } = style;
124
+ const date = todayISO();
125
+ return [
126
+ `${indent}${prefix}@accepts ${exposure.threat} on ${exposure.asset} -- "${escapeDesc(justification)}"`,
127
+ `${indent}${prefix}@audit ${exposure.asset} -- "Accepted via guardlink review on ${date}"`,
128
+ ];
129
+ }
130
+ /**
131
+ * Build the annotation line to insert for a "remediate" decision.
132
+ */
133
+ function buildRemediateLines(style, exposure, note) {
134
+ const { prefix, indent } = style;
135
+ const date = todayISO();
136
+ return [
137
+ `${indent}${prefix}@audit ${exposure.asset} -- "Planned remediation: ${escapeDesc(note)} — flagged via guardlink review on ${date}"`,
138
+ ];
139
+ }
140
+ /** Escape double quotes in description strings */
141
+ function escapeDesc(s) {
142
+ return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
143
+ }
144
+ // ─── File modification ──────────────────────────────────────────────
145
+ /**
146
+ * Insert annotation lines into a source file after the coupled block
147
+ * containing the given @exposes annotation.
148
+ *
149
+ * Returns the number of lines inserted.
150
+ */
151
+ async function insertAnnotations(root, exposure, newLines) {
152
+ const filePath = resolve(root, exposure.location.file);
153
+ const content = await readFile(filePath, 'utf-8');
154
+ const lines = content.split('\n');
155
+ // Validate that the exposure line exists and looks right
156
+ const exposureIdx = exposure.location.line - 1; // 0-indexed
157
+ if (exposureIdx < 0 || exposureIdx >= lines.length) {
158
+ throw new Error(`Line ${exposure.location.line} out of range in ${exposure.location.file}`);
159
+ }
160
+ const insertIdx = findInsertionIndex(lines, exposure.location.line);
161
+ // Splice in the new lines
162
+ lines.splice(insertIdx, 0, ...newLines);
163
+ await writeFile(filePath, lines.join('\n'));
164
+ return newLines.length;
165
+ }
166
+ // ─── Public API ─────────────────────────────────────────────────────
167
+ /**
168
+ * Apply a review decision to an exposure.
169
+ * For 'accept': inserts @accepts + @audit after the coupled block.
170
+ * For 'remediate': inserts @audit with planned-fix note.
171
+ * For 'skip': does nothing.
172
+ *
173
+ * Returns the result including lines inserted.
174
+ */
175
+ export async function applyReviewAction(root, reviewable, action) {
176
+ if (action.decision === 'skip') {
177
+ return { exposure: reviewable, action, linesInserted: 0 };
178
+ }
179
+ const { exposure } = reviewable;
180
+ const filePath = resolve(root, exposure.location.file);
181
+ const content = await readFile(filePath, 'utf-8');
182
+ const lines = content.split('\n');
183
+ // Detect comment style from the @exposes line
184
+ const exposureIdx = exposure.location.line - 1;
185
+ const style = detectCommentStyle(lines[exposureIdx]);
186
+ let newLines;
187
+ if (action.decision === 'accept') {
188
+ newLines = buildAcceptLines(style, exposure, action.justification);
189
+ }
190
+ else {
191
+ newLines = buildRemediateLines(style, exposure, action.justification);
192
+ }
193
+ const linesInserted = await insertAnnotations(root, exposure, newLines);
194
+ return { exposure: reviewable, action, linesInserted };
195
+ }
196
+ /**
197
+ * Format an exposure for display in CLI/TUI review UI.
198
+ */
199
+ export function formatExposureForReview(r, total) {
200
+ const e = r.exposure;
201
+ const sev = e.severity || 'unknown';
202
+ const desc = e.description || '(no description)';
203
+ return [
204
+ `[${r.index}/${total}] ${e.asset} → ${e.threat} [${sev}]`,
205
+ ` File: ${e.location.file}:${e.location.line}`,
206
+ ` Exposure: "${desc}"`,
207
+ ].join('\n');
208
+ }
209
+ /**
210
+ * Summarize review session results.
211
+ */
212
+ export function summarizeReview(results) {
213
+ const accepted = results.filter(r => r.action.decision === 'accept').length;
214
+ const remediated = results.filter(r => r.action.decision === 'remediate').length;
215
+ const skipped = results.filter(r => r.action.decision === 'skip').length;
216
+ const totalLines = results.reduce((sum, r) => sum + r.linesInserted, 0);
217
+ const parts = [];
218
+ if (accepted > 0)
219
+ parts.push(`${accepted} accepted`);
220
+ if (remediated > 0)
221
+ parts.push(`${remediated} marked for remediation`);
222
+ if (skipped > 0)
223
+ parts.push(`${skipped} skipped`);
224
+ return `Review complete: ${parts.join(', ')}. ${totalLines} annotation line(s) written.`;
225
+ }
226
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/review/index.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,22 @@
3
3
  *
4
4
  * Each command function takes (args, ctx) and prints output directly.
5
5
  * Returns void. Throws on fatal errors.
6
+ *
7
+ * @exposes #tui to #path-traversal [high] cwe:CWE-22 -- "File paths from user args in /view, /sarif -o"
8
+ * @mitigates #tui against #path-traversal using #path-validation -- "resolve() with ctx.root constrains file access"
9
+ * @exposes #tui to #arbitrary-write [high] cwe:CWE-73 -- "/report, /sarif, /dashboard write files"
10
+ * @mitigates #tui against #arbitrary-write using #path-validation -- "Output paths resolved relative to project root"
11
+ * @exposes #tui to #cmd-injection [high] cwe:CWE-78 -- "/annotate and /threat-report spawn child processes"
12
+ * @audit #tui -- "Child process spawning delegated to agents/launcher.ts"
13
+ * @exposes #tui to #api-key-exposure [high] cwe:CWE-798 -- "/model handles API key input and storage"
14
+ * @mitigates #tui against #api-key-exposure using #key-redaction -- "API keys masked in /model show output"
15
+ * @exposes #tui to #prompt-injection [medium] cwe:CWE-77 -- "Freeform chat sends user text to LLM"
16
+ * @audit #tui -- "User freeform text passed to LLM via cmdChat; model context is read-only"
17
+ * @flows UserArgs -> #tui via args -- "Command argument input"
18
+ * @flows #tui -> FileSystem via writeFile -- "Report/config output"
19
+ * @flows #tui -> #agent-launcher via launchAgent -- "Agent spawn path"
20
+ * @flows #tui -> #llm-client via chatCompletion -- "LLM API call path"
21
+ * @handles secrets on #tui -- "Processes and stores API keys via /model"
6
22
  */
7
23
  import type { ThreatModel, ThreatModelExposure } from '../types/index.js';
8
24
  export interface TuiContext {
@@ -37,6 +53,10 @@ export declare function cmdThreatReport(args: string, ctx: TuiContext): Promise<
37
53
  export declare function cmdThreatReports(ctx: TuiContext): void;
38
54
  export declare function cmdAnnotate(args: string, ctx: TuiContext): Promise<void>;
39
55
  export declare function cmdChat(text: string, ctx: TuiContext): Promise<void>;
56
+ export declare function cmdClear(args: string, ctx: TuiContext): Promise<void>;
57
+ export declare function cmdSync(ctx: TuiContext): Promise<void>;
58
+ export declare function cmdUnannotated(ctx: TuiContext): void;
59
+ export declare function cmdReview(args: string, ctx: TuiContext): Promise<void>;
40
60
  export declare function cmdReport(ctx: TuiContext): Promise<void>;
41
61
  export declare function cmdDashboard(ctx: TuiContext): Promise<void>;
42
62
  //# sourceMappingURL=commands.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/tui/commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,KAAK,EAAE,WAAW,EAAmB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAuB3F,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,EAAE,EAAE,OAAO,eAAe,EAAE,SAAS,CAAC;IACtC,8DAA8D;IAC9D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qEAAqE;IACrE,aAAa,EAAE,mBAAmB,EAAE,CAAC;CACtC;AAED,8CAA8C;AAC9C,wBAAsB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjE;AAoBD,wBAAgB,OAAO,IAAI,IAAI,CA0C9B;AAID,wBAAgB,MAAM,IAAI,IAAI,CAoJ7B;AAID,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CA4D/C;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CA8DhE;AAID,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAyB3D;AAID,wBAAgB,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CA0B7C;AAID,wBAAgB,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CA8C/C;AAID,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CA6E9C;AAID,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAuH3D;AAID,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAoC1E;AAID,wBAAsB,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B7D;AAID,wBAAsB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkEhE;AAID,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1E;AAID,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAwC3E;AA6GD,wBAAsB,QAAQ,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkI7D;AAkFD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAwIlF;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAsBtD;AAID,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D9E;AAID,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA8E1E;AAID,wBAAsB,SAAS,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB9D;AAID,wBAAsB,YAAY,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBjE"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/tui/commands.ts"],"names":[],"mappings":"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"}
@@ -3,11 +3,27 @@
3
3
  *
4
4
  * Each command function takes (args, ctx) and prints output directly.
5
5
  * Returns void. Throws on fatal errors.
6
+ *
7
+ * @exposes #tui to #path-traversal [high] cwe:CWE-22 -- "File paths from user args in /view, /sarif -o"
8
+ * @mitigates #tui against #path-traversal using #path-validation -- "resolve() with ctx.root constrains file access"
9
+ * @exposes #tui to #arbitrary-write [high] cwe:CWE-73 -- "/report, /sarif, /dashboard write files"
10
+ * @mitigates #tui against #arbitrary-write using #path-validation -- "Output paths resolved relative to project root"
11
+ * @exposes #tui to #cmd-injection [high] cwe:CWE-78 -- "/annotate and /threat-report spawn child processes"
12
+ * @audit #tui -- "Child process spawning delegated to agents/launcher.ts"
13
+ * @exposes #tui to #api-key-exposure [high] cwe:CWE-798 -- "/model handles API key input and storage"
14
+ * @mitigates #tui against #api-key-exposure using #key-redaction -- "API keys masked in /model show output"
15
+ * @exposes #tui to #prompt-injection [medium] cwe:CWE-77 -- "Freeform chat sends user text to LLM"
16
+ * @audit #tui -- "User freeform text passed to LLM via cmdChat; model context is read-only"
17
+ * @flows UserArgs -> #tui via args -- "Command argument input"
18
+ * @flows #tui -> FileSystem via writeFile -- "Report/config output"
19
+ * @flows #tui -> #agent-launcher via launchAgent -- "Agent spawn path"
20
+ * @flows #tui -> #llm-client via chatCompletion -- "LLM API call path"
21
+ * @handles secrets on #tui -- "Processes and stores API keys via /model"
6
22
  */
7
23
  import { resolve, basename } from 'node:path';
8
24
  import { existsSync, writeFileSync, mkdirSync } from 'node:fs';
9
- import { parseProject, findDanglingRefs, findUnmitigatedExposures, findAcceptedWithoutAudit, findAcceptedExposures } from '../parser/index.js';
10
- import { initProject, detectProject, promptAgentSelection } from '../init/index.js';
25
+ import { parseProject, findDanglingRefs, findUnmitigatedExposures, findAcceptedWithoutAudit, findAcceptedExposures, clearAnnotations } from '../parser/index.js';
26
+ import { initProject, detectProject, promptAgentSelection, syncAgentFiles } from '../init/index.js';
11
27
  import { generateReport } from '../report/index.js';
12
28
  import { generateDashboardHTML } from '../dashboard/index.js';
13
29
  import { computeStats, computeSeverity, computeExposures } from '../dashboard/data.js';
@@ -18,6 +34,7 @@ import { C, severityBadge, severityText, severityTextPad, severityOrder, compute
18
34
  import { resolveLLMConfig, saveTuiConfig, loadTuiConfig } from './config.js';
19
35
  import { AGENTS, parseAgentFlag, launchAgent, launchAgentInline, copyToClipboard, buildAnnotatePrompt } from '../agents/index.js';
20
36
  import { describeConfigSource } from '../agents/config.js';
37
+ import { getReviewableExposures, applyReviewAction, summarizeReview } from '../review/index.js';
21
38
  // ─── Shared context ──────────────────────────────────────────────────
22
39
  /** Prompt user to pick an agent interactively (TUI only) */
23
40
  async function pickAgent(ctx) {
@@ -74,6 +91,9 @@ export function cmdHelp() {
74
91
  ['/threat-reports', 'List saved AI threat reports'],
75
92
  ['/annotate <prompt>', 'Launch coding agent to annotate codebase'],
76
93
  ['/model', 'Set AI provider (API or CLI agent: Claude Code, Codex, Gemini)'],
94
+ ['/clear', 'Remove all annotations from source files (start fresh)'],
95
+ ['/sync', 'Sync agent instruction files with current threat model'],
96
+ ['/review [severity]', 'Interactive governance review of unmitigated exposures'],
77
97
  ['(freeform text)', 'Chat about your threat model with AI'],
78
98
  ['', ''],
79
99
  ['/report', 'Generate markdown + JSON report'],
@@ -253,6 +273,10 @@ export function cmdStatus(ctx) {
253
273
  console.log(` ${C.dim('Assets:')} ${stats.assets} ${C.dim('Threats:')} ${stats.threats} ${C.dim('Controls:')} ${stats.controls}`);
254
274
  console.log(` ${C.dim('Flows:')} ${stats.flows} ${C.dim('Boundaries:')} ${stats.boundaries} ${C.dim('Annotations:')} ${stats.annotations}`);
255
275
  console.log(` ${C.dim('Coverage:')} ${stats.coverageAnnotated}/${stats.coverageTotal} symbols (${stats.coveragePercent}%)`);
276
+ console.log(` ${C.dim('Files:')} ${m.annotated_files.length} annotated, ${m.unannotated_files.length} not annotated of ${m.source_files} scanned`);
277
+ if (m.unannotated_files.length > 0) {
278
+ console.log(` ${C.dim('Run')} /unannotated ${C.dim('to list files without annotations')}`);
279
+ }
256
280
  // Top threats
257
281
  if (m.exposures.length > 0) {
258
282
  const threatCounts = new Map();
@@ -690,6 +714,13 @@ export async function cmdParse(ctx) {
690
714
  console.log(` ${C.success('✓')} Parsed ${C.bold(String(model.annotations_parsed))} annotations from ${model.source_files} files`);
691
715
  console.log(` ${model.assets.length} assets · ${model.threats.length} threats · ${model.controls.length} controls`);
692
716
  console.log(` ${model.exposures.length} exposures · ${model.mitigations.length} mitigations · Grade: ${gradeColored(grade)}`);
717
+ // Auto-sync agent instruction files with updated model
718
+ if (model.annotations_parsed > 0) {
719
+ const syncResult = syncAgentFiles({ root: ctx.root, model });
720
+ if (syncResult.updated.length > 0) {
721
+ console.log(C.dim(` ↻ Synced ${syncResult.updated.length} agent instruction file(s)`));
722
+ }
723
+ }
693
724
  console.log('');
694
725
  }
695
726
  catch (err) {
@@ -1356,6 +1387,173 @@ Keep responses under 500 words unless the user asks for detail.`;
1356
1387
  }
1357
1388
  }
1358
1389
  }
1390
+ // ─── /clear ──────────────────────────────────────────────────────
1391
+ export async function cmdClear(args, ctx) {
1392
+ const includeDefinitions = args.includes('--include-definitions');
1393
+ const isDryRun = args.includes('--dry-run');
1394
+ console.log(C.dim(' Scanning for annotations...'));
1395
+ const preview = await clearAnnotations({
1396
+ root: ctx.root,
1397
+ dryRun: true,
1398
+ includeDefinitions,
1399
+ });
1400
+ if (preview.totalRemoved === 0) {
1401
+ console.log('');
1402
+ console.log(C.dim(' No GuardLink annotations found in source files.'));
1403
+ console.log('');
1404
+ return;
1405
+ }
1406
+ console.log('');
1407
+ console.log(` Found ${C.bold(String(preview.totalRemoved))} annotation line(s) across ${C.bold(String(preview.modifiedFiles.length))} file(s):`);
1408
+ console.log('');
1409
+ for (const [file, count] of preview.perFile) {
1410
+ console.log(` ${file} ${C.dim(`(${count} line${count > 1 ? 's' : ''})`)}`);
1411
+ }
1412
+ console.log('');
1413
+ if (isDryRun) {
1414
+ console.log(C.dim(' (dry run) No files were modified.'));
1415
+ console.log('');
1416
+ return;
1417
+ }
1418
+ const answer = await ask(ctx, ` ${C.warn('⚠')} Remove all annotations? This cannot be undone. (y/N): `);
1419
+ if (answer.trim().toLowerCase() !== 'y') {
1420
+ console.log(C.dim(' Cancelled.'));
1421
+ console.log('');
1422
+ return;
1423
+ }
1424
+ const result = await clearAnnotations({
1425
+ root: ctx.root,
1426
+ dryRun: false,
1427
+ includeDefinitions,
1428
+ });
1429
+ console.log('');
1430
+ console.log(` ${C.success('✓')} Removed ${C.bold(String(result.totalRemoved))} annotation line(s) from ${result.modifiedFiles.length} file(s).`);
1431
+ console.log(C.dim(' Run /annotate to re-annotate from scratch, or /parse to update the model.'));
1432
+ ctx.model = null;
1433
+ ctx.lastExposures = [];
1434
+ console.log('');
1435
+ }
1436
+ // ─── /sync ───────────────────────────────────────────────────────
1437
+ export async function cmdSync(ctx) {
1438
+ if (!ctx.model) {
1439
+ console.log(C.warn(' No threat model. Run /parse first.'));
1440
+ return;
1441
+ }
1442
+ console.log(C.dim(' Syncing agent instruction files with current threat model...'));
1443
+ console.log('');
1444
+ const result = syncAgentFiles({ root: ctx.root, model: ctx.model });
1445
+ if (result.updated.length > 0) {
1446
+ console.log(` ${C.success('✓')} Updated ${C.bold(String(result.updated.length))} agent instruction file(s):`);
1447
+ console.log('');
1448
+ for (const f of result.updated) {
1449
+ console.log(` ${f}`);
1450
+ }
1451
+ }
1452
+ if (result.skipped.length > 0) {
1453
+ console.log('');
1454
+ console.log(C.dim(` Skipped: ${result.skipped.join(', ')}`));
1455
+ }
1456
+ console.log('');
1457
+ console.log(` ${C.dim(`${ctx.model.assets.length} assets, ${ctx.model.threats.length} threats, ${ctx.model.controls.length} controls, ${ctx.model.exposures.length} exposures synced.`)}`);
1458
+ console.log(C.dim(' Any coding agent (Cursor, Claude, Copilot, Windsurf, etc.) will see these IDs.'));
1459
+ console.log('');
1460
+ }
1461
+ // ─── /unannotated ────────────────────────────────────────────────────
1462
+ export function cmdUnannotated(ctx) {
1463
+ if (!ctx.model) {
1464
+ console.log(C.warn(' No threat model. Run /parse first.'));
1465
+ return;
1466
+ }
1467
+ const files = ctx.model.unannotated_files;
1468
+ if (files.length === 0) {
1469
+ console.log(`\n ${C.success('✓')} All source files have GuardLink annotations.\n`);
1470
+ return;
1471
+ }
1472
+ console.log(`\n ${C.warn('⚠')} ${C.bold(String(files.length))} source file(s) with no annotations:\n`);
1473
+ for (const f of files) {
1474
+ console.log(` ${f}`);
1475
+ }
1476
+ console.log(`\n ${C.dim('Not all files need annotations — only those that touch security boundaries.')}`);
1477
+ console.log('');
1478
+ }
1479
+ // ─── /review ─────────────────────────────────────────────────────────
1480
+ export async function cmdReview(args, ctx) {
1481
+ if (!ctx.model) {
1482
+ console.log(C.warn(' No threat model. Run /parse first.'));
1483
+ return;
1484
+ }
1485
+ let exposures = getReviewableExposures(ctx.model);
1486
+ // Parse severity filter from args (e.g., "/review critical,high")
1487
+ if (args) {
1488
+ const allowed = new Set(args.split(',').map(s => s.trim().toLowerCase()));
1489
+ exposures = exposures.filter(e => allowed.has(e.exposure.severity || 'low'));
1490
+ exposures = exposures.map((e, i) => ({ ...e, index: i + 1 }));
1491
+ }
1492
+ if (exposures.length === 0) {
1493
+ console.log(`\n ${C.success('✓')} No unmitigated exposures to review.\n`);
1494
+ return;
1495
+ }
1496
+ console.log(`\n ${C.bold('guardlink review')} — ${exposures.length} unmitigated exposure(s)\n`);
1497
+ const results = [];
1498
+ for (const reviewable of exposures) {
1499
+ const e = reviewable.exposure;
1500
+ const sev = severityText(e.severity || 'low');
1501
+ console.log(` ${C.bold(`[${reviewable.index}/${exposures.length}]`)} ${e.asset} → ${e.threat} ${sev}`);
1502
+ console.log(` File: ${fileLink(e.location.file, e.location.line)}`);
1503
+ console.log(` Exposure: ${C.dim('"' + (e.description || 'no description') + '"')}`);
1504
+ console.log('');
1505
+ console.log(` ${C.bold('a')} Accept ${C.dim('— risk acknowledged and intentional')}`);
1506
+ console.log(` ${C.bold('r')} Remediate ${C.dim('— mark as planned fix')}`);
1507
+ console.log(` ${C.bold('s')} Skip ${C.dim('— leave open for now')}`);
1508
+ console.log(` ${C.bold('q')} Quit`);
1509
+ console.log('');
1510
+ const choice = (await ask(ctx, ' Choice [a/r/s/q]: ')).toLowerCase();
1511
+ if (choice === 'q') {
1512
+ console.log(`\n ${C.dim('Review ended.')}\n`);
1513
+ break;
1514
+ }
1515
+ if (choice === 'a') {
1516
+ let justification = '';
1517
+ while (!justification) {
1518
+ justification = await ask(ctx, ' Justification (required): ');
1519
+ if (!justification)
1520
+ console.log(C.warn(' ⚠ Justification is mandatory for acceptance.'));
1521
+ }
1522
+ const result = await applyReviewAction(ctx.root, reviewable, { decision: 'accept', justification });
1523
+ results.push(result);
1524
+ console.log(` ${C.success('✓')} Accepted — ${result.linesInserted} line(s) written\n`);
1525
+ }
1526
+ else if (choice === 'r') {
1527
+ let note = '';
1528
+ while (!note) {
1529
+ note = await ask(ctx, ' Remediation note (required): ');
1530
+ if (!note)
1531
+ console.log(C.warn(' ⚠ Remediation note is mandatory.'));
1532
+ }
1533
+ const result = await applyReviewAction(ctx.root, reviewable, { decision: 'remediate', justification: note });
1534
+ results.push(result);
1535
+ console.log(` ${C.success('✓')} Marked for remediation — ${result.linesInserted} line(s) written\n`);
1536
+ }
1537
+ else {
1538
+ results.push({ exposure: reviewable, action: { decision: 'skip', justification: '' }, linesInserted: 0 });
1539
+ console.log(` ${C.dim('— Skipped')}\n`);
1540
+ }
1541
+ }
1542
+ if (results.length > 0) {
1543
+ console.log(`\n ${summarizeReview(results)}`);
1544
+ // Re-parse and sync if annotations were written
1545
+ if (results.some(r => r.linesInserted > 0)) {
1546
+ await refreshModel(ctx);
1547
+ try {
1548
+ const syncResult = syncAgentFiles({ root: ctx.root, model: ctx.model });
1549
+ if (syncResult.updated.length > 0)
1550
+ console.log(` ${C.dim('↻ Synced')} ${syncResult.updated.length} agent instruction file(s)`);
1551
+ }
1552
+ catch { }
1553
+ }
1554
+ }
1555
+ console.log('');
1556
+ }
1359
1557
  // ─── /report ─────────────────────────────────────────────────────────
1360
1558
  export async function cmdReport(ctx) {
1361
1559
  if (!ctx.model) {