@lievclouds/dsh-cortex 0.4.4 → 1.0.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.

Potentially problematic release.


This version of @lievclouds/dsh-cortex might be problematic. Click here for more details.

Files changed (58) hide show
  1. package/README.md +1 -1
  2. package/client.js +428 -406
  3. package/index.js +7 -0
  4. package/locale/en.json +6 -6
  5. package/locale/zh.json +6 -6
  6. package/package.json +115 -106
  7. package/src/cortex-ui/dsh/api.ts +5 -2
  8. package/src/cortex-ui/dsh/css-overrides.css +8 -0
  9. package/src/cortex-ui/renderer/App.tsx +39 -0
  10. package/src/cortex-ui/renderer/components/asset/AssetDetail.tsx +9 -1
  11. package/src/cortex-ui/renderer/components/miniapp/FileTree.tsx +161 -0
  12. package/src/cortex-ui/renderer/components/miniapp/MiniappCard.tsx +263 -0
  13. package/src/cortex-ui/renderer/components/miniapp/MiniappDetail.tsx +1493 -0
  14. package/src/cortex-ui/renderer/components/retest/CurlImportDialog.tsx +158 -0
  15. package/src/cortex-ui/renderer/components/retest/RepeaterCollectionsDialog.tsx +327 -0
  16. package/src/cortex-ui/renderer/components/retest/RepeaterHistoryDialog.tsx +242 -0
  17. package/src/cortex-ui/renderer/components/retest/RepeaterPanel.tsx +333 -148
  18. package/src/cortex-ui/renderer/components/retest/YakitSendPanel.tsx +411 -0
  19. package/src/cortex-ui/renderer/components/vuln/DirectoryPickerDialog.tsx +187 -0
  20. package/src/cortex-ui/renderer/components/vuln/ExportReportDialog.tsx +173 -59
  21. package/src/cortex-ui/renderer/components/vuln/VulnDetail.tsx +16 -4
  22. package/src/cortex-ui/renderer/i18n/messages/common.ts +2 -0
  23. package/src/cortex-ui/renderer/i18n/messages/fuzzer.ts +32 -0
  24. package/src/cortex-ui/renderer/i18n/messages/index.ts +5 -1
  25. package/src/cortex-ui/renderer/i18n/messages/miniapp.ts +392 -0
  26. package/src/cortex-ui/renderer/i18n/messages/retest.ts +146 -2
  27. package/src/cortex-ui/renderer/i18n/messages/vuln.ts +26 -6
  28. package/src/cortex-ui/renderer/lib/curl.ts +635 -0
  29. package/src/cortex-ui/renderer/lib/miniappApi.ts +459 -0
  30. package/src/cortex-ui/renderer/lib/navigation.ts +13 -1
  31. package/src/cortex-ui/renderer/lib/repeaterRaw.ts +102 -0
  32. package/src/cortex-ui/renderer/lib/repeaterStoreApi.ts +163 -0
  33. package/src/cortex-ui/renderer/lib/retestResponse.ts +64 -0
  34. package/src/cortex-ui/renderer/lib/transport/httpClient.ts +1 -1
  35. package/src/cortex-ui/renderer/pages/DashboardPage.tsx +3 -3
  36. package/src/cortex-ui/renderer/pages/FuzzerPage.tsx +212 -0
  37. package/src/cortex-ui/renderer/pages/MiniappPage.tsx +664 -0
  38. package/src/cortex-ui/shared/ipc.ts +3 -4
  39. package/src/cortex-ui/shared/types.ts +9 -0
  40. package/src/host/cortex/fs.js +72 -0
  41. package/src/host/cortex/index.js +49 -1
  42. package/src/host/cortex/repeater-store.js +411 -0
  43. package/src/host/cortex/repeater.js +15 -2
  44. package/src/host/cortex/settings.js +54 -0
  45. package/src/host/cortex/tools.js +2 -0
  46. package/src/host/cortex/vulns.js +36 -13
  47. package/src/host/miniapp/analyze.js +1078 -0
  48. package/src/host/miniapp/assets-import.js +294 -0
  49. package/src/host/miniapp/detect.js +426 -0
  50. package/src/host/miniapp/format.js +263 -0
  51. package/src/host/miniapp/roots.js +360 -0
  52. package/src/host/miniapp/routes.js +304 -0
  53. package/src/host/miniapp/service.js +559 -0
  54. package/src/host/miniapp/store.js +756 -0
  55. package/src/host/miniapp/tools.js +335 -0
  56. package/src/host/miniapp/unpack.js +1010 -0
  57. package/src/host/miniapp/wxapkg.js +416 -0
  58. package/src/host/reports-office.js +341 -0
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Office-format report generation for the ported Cortex UI.
3
+ *
4
+ * The Markdown report built by `reports.js` is the single source of truth; this
5
+ * module renders the same content as a Word document (docx) or a PDF. PDF text
6
+ * is Chinese, so a CJK TTF font from the host OS is embedded (PDFKit cannot
7
+ * embed the system .ttc collections, hence the TTF candidate list).
8
+ *
9
+ * Both renderers are pure Node (no browser APIs) and used by
10
+ * `POST /vulns/export` when `format` is `docx` or `pdf`.
11
+ */
12
+
13
+ import { existsSync } from 'node:fs'
14
+ import PDFDocument from 'pdfkit'
15
+ import {
16
+ BorderStyle,
17
+ Document,
18
+ HeadingLevel,
19
+ Packer,
20
+ Paragraph,
21
+ TextRun,
22
+ } from 'docx'
23
+
24
+ /** Common single-face CJK TrueType fonts; PDFKit rejects .ttc collections. */
25
+ const CJK_FONT_CANDIDATES = [
26
+ 'C:\\Windows\\Fonts\\simhei.ttf',
27
+ 'C:\\Windows\\Fonts\\simkai.ttf',
28
+ 'C:\\Windows\\Fonts\\simfang.ttf',
29
+ 'C:\\Windows\\Fonts\\Deng.ttf',
30
+ 'C:\\Windows\\Fonts\\msyh.ttf',
31
+ '/System/Library/Fonts/Supplemental/Arial Unicode.ttf',
32
+ '/Library/Fonts/Arial Unicode.ttf',
33
+ '/usr/share/fonts/truetype/arphic/uming.ttc',
34
+ ]
35
+
36
+ const DOCX_BODY_FONT = 'Microsoft YaHei'
37
+ const DOCX_MONO_FONT = 'Consolas'
38
+ const PDF_PAGE_MARGIN = 56
39
+
40
+ export function findCjkFont() {
41
+ const explicit = process.env.CORTEX_PDF_FONT
42
+ if (explicit && existsSync(explicit)) return explicit
43
+ for (const candidate of CJK_FONT_CANDIDATES) {
44
+ if (existsSync(candidate)) return candidate
45
+ }
46
+ return null
47
+ }
48
+
49
+ /* ------------------------------------------------------------ markdown -- */
50
+
51
+ /**
52
+ * Parse the report Markdown into simple blocks. Covers exactly what
53
+ * `buildVulnReport` emits plus fenced HTTP/JSON code inside reproduction steps.
54
+ * @param {string} markdown
55
+ * @returns {Array<{type:'heading'|'paragraph'|'list'|'code'|'hr', level?:number, text?:string, lines?:string[]}>}
56
+ */
57
+ export function parseReportMarkdown(markdown) {
58
+ const lines = String(markdown ?? '')
59
+ .replace(/\r\n/g, '\n')
60
+ .split('\n')
61
+ const blocks = []
62
+ let index = 0
63
+ while (index < lines.length) {
64
+ const line = lines[index]
65
+ if (line.trim() === '') {
66
+ index += 1
67
+ continue
68
+ }
69
+ if (/^```/.test(line)) {
70
+ const code = []
71
+ index += 1
72
+ while (index < lines.length && !/^```/.test(lines[index])) {
73
+ code.push(lines[index])
74
+ index += 1
75
+ }
76
+ index += 1
77
+ blocks.push({ type: 'code', text: code.join('\n') })
78
+ continue
79
+ }
80
+ const heading = /^(#{1,6})\s+(.*)$/.exec(line)
81
+ if (heading) {
82
+ blocks.push({ type: 'heading', level: heading[1].length, text: heading[2].trim() })
83
+ index += 1
84
+ continue
85
+ }
86
+ if (/^\s*[-*]\s+/.test(line)) {
87
+ const items = []
88
+ while (index < lines.length && /^\s*[-*]\s+/.test(lines[index])) {
89
+ items.push(lines[index].replace(/^\s*[-*]\s+/, '').trim())
90
+ index += 1
91
+ }
92
+ blocks.push({ type: 'list', lines: items })
93
+ continue
94
+ }
95
+ if (/^---+$/.test(line.trim())) {
96
+ blocks.push({ type: 'hr' })
97
+ index += 1
98
+ continue
99
+ }
100
+ const paragraph = []
101
+ while (index < lines.length) {
102
+ const next = lines[index]
103
+ if (
104
+ next.trim() === '' ||
105
+ /^```/.test(next) ||
106
+ /^#{1,6}\s+/.test(next) ||
107
+ /^\s*[-*]\s+/.test(next) ||
108
+ /^---+$/.test(next.trim())
109
+ ) {
110
+ break
111
+ }
112
+ paragraph.push(next.trim())
113
+ index += 1
114
+ }
115
+ blocks.push({ type: 'paragraph', text: paragraph.join(' ') })
116
+ }
117
+ return blocks
118
+ }
119
+
120
+ /** Strip inline Markdown markers for plain-text renderers. */
121
+ export function plainInline(text) {
122
+ return String(text ?? '')
123
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
124
+ .replace(/`([^`]+)`/g, '$1')
125
+ .replace(/\*([^*]+)\*/g, '$1')
126
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
127
+ }
128
+
129
+ /* ---------------------------------------------------------------- docx -- */
130
+
131
+ function docxInlineRuns(text) {
132
+ const runs = []
133
+ const pattern = /(\*\*[^*]+\*\*|`[^`]+`)/g
134
+ let last = 0
135
+ let match
136
+ while ((match = pattern.exec(String(text ?? ''))) !== null) {
137
+ if (match.index > last) {
138
+ runs.push(new TextRun({ text: String(text).slice(last, match.index) }))
139
+ }
140
+ const token = match[0]
141
+ if (token.startsWith('**')) {
142
+ runs.push(new TextRun({ text: token.slice(2, -2), bold: true }))
143
+ } else {
144
+ runs.push(new TextRun({ text: token.slice(1, -1), font: DOCX_MONO_FONT, shading: { fill: 'F3F4F6' } }))
145
+ }
146
+ last = match.index + token.length
147
+ }
148
+ if (last < String(text ?? '').length) runs.push(new TextRun({ text: String(text).slice(last) }))
149
+ return runs.length > 0 ? runs : [new TextRun({ text: '' })]
150
+ }
151
+
152
+ function headingLevel(level) {
153
+ if (level <= 1) return HeadingLevel.HEADING_1
154
+ if (level === 2) return HeadingLevel.HEADING_2
155
+ return HeadingLevel.HEADING_3
156
+ }
157
+
158
+ /**
159
+ * Render the Markdown report as a Word document.
160
+ * @param {string} markdown
161
+ * @param {{ title?: string, author?: string }} [meta]
162
+ * @returns {Promise<Buffer>}
163
+ */
164
+ export async function renderDocxReport(markdown, meta = {}) {
165
+ const children = []
166
+ for (const block of parseReportMarkdown(markdown)) {
167
+ if (block.type === 'heading') {
168
+ children.push(
169
+ new Paragraph({
170
+ heading: headingLevel(block.level),
171
+ spacing: { before: block.level <= 1 ? 0 : 220, after: 120 },
172
+ children: docxInlineRuns(plainInline(block.text)),
173
+ }),
174
+ )
175
+ } else if (block.type === 'paragraph') {
176
+ children.push(
177
+ new Paragraph({
178
+ spacing: { after: 140, line: 320 },
179
+ children: docxInlineRuns(block.text),
180
+ }),
181
+ )
182
+ } else if (block.type === 'list') {
183
+ for (const item of block.lines) {
184
+ children.push(
185
+ new Paragraph({
186
+ bullet: { level: 0 },
187
+ spacing: { after: 80, line: 300 },
188
+ children: docxInlineRuns(item),
189
+ }),
190
+ )
191
+ }
192
+ } else if (block.type === 'code') {
193
+ for (const line of block.text.split('\n')) {
194
+ children.push(
195
+ new Paragraph({
196
+ spacing: { after: 0 },
197
+ shading: { fill: 'F4F5F7' },
198
+ children: [new TextRun({ text: line === '' ? ' ' : line, font: DOCX_MONO_FONT, size: 18 })],
199
+ }),
200
+ )
201
+ }
202
+ children.push(new Paragraph({ spacing: { after: 120 }, children: [] }))
203
+ } else if (block.type === 'hr') {
204
+ children.push(
205
+ new Paragraph({
206
+ border: { bottom: { style: BorderStyle.SINGLE, size: 6, color: 'D1D5DB' } },
207
+ spacing: { after: 120 },
208
+ children: [],
209
+ }),
210
+ )
211
+ }
212
+ }
213
+
214
+ const document = new Document({
215
+ creator: 'Cortex / DSH',
216
+ title: meta.title || 'Cortex vulnerability report',
217
+ styles: {
218
+ default: {
219
+ document: {
220
+ run: { font: DOCX_BODY_FONT, size: 21, color: '1F2937' },
221
+ paragraph: { spacing: { line: 320 } },
222
+ },
223
+ },
224
+ },
225
+ sections: [{ properties: {}, children }],
226
+ })
227
+ return Packer.toBuffer(document)
228
+ }
229
+
230
+ /* ----------------------------------------------------------------- pdf -- */
231
+
232
+ function pdfCodeHeight(doc, text, width) {
233
+ return doc.heightOfString(text, { width, lineGap: 1, fontSize: 8.5 })
234
+ }
235
+
236
+ /**
237
+ * Render the Markdown report as a PDF with an embedded CJK font.
238
+ * @param {string} markdown
239
+ * @param {{ title?: string, author?: string }} [meta]
240
+ * @returns {Promise<Buffer>}
241
+ */
242
+ export async function renderPdfReport(markdown, meta = {}) {
243
+ const fontPath = findCjkFont()
244
+ if (fontPath === null) {
245
+ throw new Error(
246
+ 'PDF 导出需要系统中文字体(TTF):Windows 请安装“黑体/等线/楷体”,或设置环境变量 CORTEX_PDF_FONT 指向一个中文字体文件',
247
+ )
248
+ }
249
+
250
+ const doc = new PDFDocument({
251
+ size: 'A4',
252
+ margins: { top: PDF_PAGE_MARGIN, left: PDF_PAGE_MARGIN, right: PDF_PAGE_MARGIN, bottom: PDF_PAGE_MARGIN + 18 },
253
+ bufferPages: true,
254
+ info: {
255
+ Title: meta.title || 'Cortex vulnerability report',
256
+ Author: meta.author || 'Cortex / DSH',
257
+ },
258
+ })
259
+ const chunks = []
260
+ doc.on('data', (chunk) => chunks.push(chunk))
261
+ const finished = new Promise((resolve, reject) => {
262
+ doc.on('end', () => resolve(Buffer.concat(chunks)))
263
+ doc.on('error', reject)
264
+ })
265
+
266
+ doc.registerFont('body', fontPath)
267
+ const contentWidth = doc.page.width - PDF_PAGE_MARGIN * 2
268
+ const bottom = doc.page.height - PDF_PAGE_MARGIN - 18
269
+
270
+ const ensureSpace = (height) => {
271
+ if (doc.y + height > bottom) doc.addPage()
272
+ }
273
+
274
+ for (const block of parseReportMarkdown(markdown)) {
275
+ if (block.type === 'heading') {
276
+ const size = block.level <= 1 ? 19 : block.level === 2 ? 13.5 : 11.5
277
+ ensureSpace(size + 24)
278
+ doc.moveDown(block.level <= 1 ? 0 : 0.4)
279
+ doc.font('body').fontSize(size).fillColor('#111111').text(plainInline(block.text), {
280
+ width: contentWidth,
281
+ lineGap: 2,
282
+ })
283
+ doc.moveDown(0.35)
284
+ } else if (block.type === 'paragraph') {
285
+ const text = plainInline(block.text)
286
+ ensureSpace(20)
287
+ doc.font('body').fontSize(10).fillColor('#26303C').text(text, {
288
+ width: contentWidth,
289
+ lineGap: 2.5,
290
+ align: 'left',
291
+ })
292
+ doc.moveDown(0.55)
293
+ } else if (block.type === 'list') {
294
+ for (const item of block.lines) {
295
+ ensureSpace(16)
296
+ doc.font('body').fontSize(10).fillColor('#26303C').text(`• ${plainInline(item)}`, {
297
+ width: contentWidth,
298
+ indent: 10,
299
+ lineGap: 2,
300
+ })
301
+ doc.moveDown(0.2)
302
+ }
303
+ doc.moveDown(0.25)
304
+ } else if (block.type === 'code') {
305
+ const text = block.text.length > 0 ? block.text : ' '
306
+ const height = pdfCodeHeight(doc, text, contentWidth - 16) + 10
307
+ ensureSpace(height + 6)
308
+ const top = doc.y
309
+ doc.save().roundedRect(PDF_PAGE_MARGIN, top, contentWidth, height, 4).fill('#F3F4F6').restore()
310
+ doc
311
+ .font('body')
312
+ .fontSize(8.5)
313
+ .fillColor('#1F2937')
314
+ .text(text, PDF_PAGE_MARGIN + 8, top + 5, { width: contentWidth - 16, lineGap: 1 })
315
+ doc.moveDown(0.5)
316
+ } else if (block.type === 'hr') {
317
+ ensureSpace(14)
318
+ doc.moveDown(0.2)
319
+ const y = doc.y
320
+ doc.save().moveTo(PDF_PAGE_MARGIN, y).lineTo(PDF_PAGE_MARGIN + contentWidth, y)
321
+ .lineWidth(0.6).strokeColor('#D1D5DB').stroke().restore()
322
+ doc.moveDown(0.6)
323
+ }
324
+ }
325
+
326
+ const pageCount = doc.bufferedPageRange().count
327
+ for (let index = 0; index < pageCount; index += 1) {
328
+ doc.switchToPage(index)
329
+ doc
330
+ .font('body')
331
+ .fontSize(8)
332
+ .fillColor('#94A3B8')
333
+ .text(`第 ${index + 1} / ${pageCount} 页`, PDF_PAGE_MARGIN, doc.page.height - PDF_PAGE_MARGIN - 8, {
334
+ width: contentWidth,
335
+ align: 'right',
336
+ lineBreak: false,
337
+ })
338
+ }
339
+ doc.end()
340
+ return finished
341
+ }