@lievclouds/dsh-cortex 0.3.4 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -41,14 +41,16 @@ export function apply(ctx, config) {
41
41
  name: 'dsh-cortex.tools',
42
42
  order: 500,
43
43
  text: [
44
- '## Cortex (dsh-cortex plugin, no MCP needed)',
45
- 'The local Cortex database (schema v5) is available through native tools. The old `mcp__cortex__*` servers/skills are not required; prefer the `cortex_*` tools whenever the user mentions Cortex, 漏洞库/漏洞, 资产, 快照, 内网作战 or 记忆星云.',
46
- '- Cortex findings: `cortex_list_vulns` / `cortex_get_vuln`; save with `cortex_save_vuln` (dedupe by host first).',
44
+ '## Cortex (dsh-cortex plugin, always active)',
45
+ 'This plugin is installed and injects this guide automatically: the cortex_* tools below are available in every conversation without a skill or an MCP server.',
46
+ 'When the user asks for ANY authorized penetration-testing work (渗透测试 / SRC / 漏洞挖掘 / 安全评估 / 资产与接口侦察 / 内网横向 / 漏洞复测), use the Cortex tools proactively instead of relying on memory:',
47
+ '- Reuse memory first: `cortex_list_snapshots` + `cortex_get_snapshot` for task progress, `cortex_list_vulns` + `cortex_get_asset` for existing findings/assets; `cortex_list_assets` before repeating recon.',
48
+ '- Persist confirmed findings: dedupe with `cortex_list_vulns`, then `cortex_save_vuln` (reproduction_steps in Markdown).',
49
+ '- Persist recon context: `cortex_sync_asset_info` (mode=merge) for fingerprints, interfaces (raw HTTP), leaks and test parameters.',
47
50
  '- Status / retest: `cortex_update_vuln_status`, `cortex_append_retest` (append-only, never overwrite original evidence).',
48
- '- Assets: `cortex_list_assets`, `cortex_get_asset`, `cortex_sync_asset_info` (fingerprints, interfaces, leaks, test parameters).',
49
- '- Snapshots / internal / nebula: `cortex_list_snapshots`, `cortex_get_snapshot`, `cortex_internal_overview`, `cortex_graph_summary`.',
50
- 'Examples that should call the tools: “列出 Cortex 里的高危漏洞”, “把刚才确认的漏洞写入 Cortex”, “把 127.0.0.1:8080 的接口同步到资产库”, “把 VULN-… 标记为已确认并追加复测结论”.',
51
- 'If a tools call fails, report the failure; never claim a record was stored without a successful result.',
51
+ '- Summaries: `cortex_internal_overview`, `cortex_graph_summary`.',
52
+ 'Do not skip these calls merely because no skill was loaded. If a tool call fails, report the failure; never claim a record was saved without a successful result.',
53
+ 'Examples: “列出 Cortex 里的高危漏洞”, “把刚才确认的漏洞写入 Cortex”, “把 127.0.0.1:8080 的接口同步到资产库”, “把 VULN-… 标记为已确认并追加复测结论”.',
52
54
  ].join('\n'),
53
55
  }),
54
56
  'dsh-cortex: usage prompt',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lievclouds/dsh-cortex",
3
- "version": "0.3.4",
3
+ "version": "0.3.9",
4
4
  "description": "Cortex workbench for DeepSeek Harness: the original Cortex UI (vulnerabilities, assets, internal operations, Memory Nebula, snapshots, retest) as a native DSH panel, plus 12 native agent tools so DSH conversations use the local Cortex SQLite without MCP.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -99,6 +99,14 @@
99
99
  border: 3px solid transparent;
100
100
  }
101
101
 
102
+ /* DSH draws its native window buttons above the page in the top-right corner.
103
+ Right-hand inspector sheets start at the very top edge, so their header
104
+ actions (export / close) ended up underneath those buttons. Move every sheet
105
+ below that band; the overlay still dims the area behind it. */
106
+ .dshc-cortex-root .sheet-content {
107
+ top: calc(0.875rem + 2.25rem);
108
+ }
109
+
102
110
  /* ------------------------------------------------------------------------- */
103
111
  /* DSH shell adaptations */
104
112
  /* ------------------------------------------------------------------------- */
@@ -2,6 +2,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'
2
2
  import { Loader2, Trash2 } from 'lucide-react'
3
3
  import { Button } from '@renderer/components/ui/button'
4
4
  import { useI18n } from '@renderer/i18n'
5
+ import { getPortalContainer } from '../../../dsh/portal'
5
6
 
6
7
  interface DeleteAssetDialogProps {
7
8
  open: boolean
@@ -27,8 +28,8 @@ export function DeleteAssetDialog({
27
28
  const { t } = useI18n()
28
29
 
29
30
  return (
30
- <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
31
- <DialogPrimitive.Portal>
31
+ <DialogPrimitive.Root open={open} onOpenChange={onOpenChange} modal={false}>
32
+ <DialogPrimitive.Portal container={getPortalContainer()}>
32
33
  <DialogPrimitive.Overlay className="sheet-overlay fixed inset-0 z-[70] bg-[hsl(224_18%_10%/0.3)]" />
33
34
  <DialogPrimitive.Content className="fixed left-1/2 top-1/2 z-[70] w-[min(420px,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-background p-5 shadow-panel focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
34
35
  <DialogPrimitive.Title className="text-[16px] font-medium tracking-tight text-foreground">
@@ -37,6 +37,7 @@ import { usePocErrorMessage } from '@renderer/components/poc/usePocErrorMessage'
37
37
  import { getApi } from '@renderer/lib/api'
38
38
  import { useI18n } from '@renderer/i18n'
39
39
  import { cn } from '@renderer/lib/utils'
40
+ import { getPortalContainer } from '../../../dsh/portal'
40
41
 
41
42
  interface PocConfigPanelProps {
42
43
  open: boolean
@@ -122,8 +123,8 @@ export function PocConfigPanel({
122
123
  : null
123
124
 
124
125
  return (
125
- <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
126
- <DialogPrimitive.Portal>
126
+ <DialogPrimitive.Root open={open} onOpenChange={onOpenChange} modal={false}>
127
+ <DialogPrimitive.Portal container={getPortalContainer()}>
127
128
  <DialogPrimitive.Overlay className="sheet-overlay fixed inset-0 z-[85] bg-[hsl(224_18%_10%/0.3)]" />
128
129
  <DialogPrimitive.Content
129
130
  data-testid="retest-config-panel"
@@ -5,6 +5,7 @@ import type { PocConfigView } from '@shared/poc'
5
5
  import { ProxySection } from '@renderer/components/poc/PocConfigPanel'
6
6
  import { useI18n } from '@renderer/i18n'
7
7
  import { getApi } from '@renderer/lib/api'
8
+ import { getPortalContainer } from '../../../dsh/portal'
8
9
 
9
10
  interface RetestProxyDialogProps {
10
11
  open: boolean
@@ -43,8 +44,8 @@ export function RetestProxyDialog({ open, onOpenChange }: RetestProxyDialogProps
43
44
  }, [open, t])
44
45
 
45
46
  return (
46
- <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
47
- <DialogPrimitive.Portal>
47
+ <DialogPrimitive.Root open={open} onOpenChange={onOpenChange} modal={false}>
48
+ <DialogPrimitive.Portal container={getPortalContainer()}>
48
49
  <DialogPrimitive.Overlay className="sheet-overlay fixed inset-0 z-[85] bg-[hsl(224_18%_10%/0.3)]" />
49
50
  <DialogPrimitive.Content className="sheet-content fixed left-1/2 top-1/2 z-[90] flex max-h-[88vh] w-[calc(100vw-32px)] max-w-[560px] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-xl border border-border bg-background">
50
51
  <div className="flex shrink-0 items-start justify-between gap-3 border-b border-border px-5 py-4">
@@ -6,6 +6,7 @@ import type { SnapshotSession } from '@shared/types'
6
6
  import { Button } from '@renderer/components/ui/button'
7
7
  import { apiUnavailableMessage, getApi, toErrorMessage } from '@renderer/lib/api'
8
8
  import { useI18n } from '@renderer/i18n'
9
+ import { getPortalContainer } from '../../../dsh/portal'
9
10
 
10
11
  interface DeleteSnapshotDialogProps {
11
12
  open: boolean
@@ -56,13 +57,14 @@ export function DeleteSnapshotDialog({
56
57
  }
57
58
 
58
59
  return (
59
- <DialogPrimitive.Root
60
+ <DialogPrimitive.Root
61
+ modal={false}
60
62
  open={open}
61
63
  onOpenChange={(next) => {
62
64
  if (!pending) onOpenChange(next)
63
65
  }}
64
66
  >
65
- <DialogPrimitive.Portal>
67
+ <DialogPrimitive.Portal container={getPortalContainer()}>
66
68
  <DialogPrimitive.Overlay className="sheet-overlay fixed inset-0 z-[70] bg-[hsl(224_18%_10%/0.3)]" />
67
69
  <DialogPrimitive.Content className="fixed left-1/2 top-1/2 z-[70] w-[min(440px,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-background p-5 shadow-panel focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
68
70
  <DialogPrimitive.Title className="text-[16px] font-medium tracking-tight text-foreground">
@@ -2,6 +2,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'
2
2
  import { Loader2, Trash2 } from 'lucide-react'
3
3
  import { Button } from '@renderer/components/ui/button'
4
4
  import { useI18n } from '@renderer/i18n'
5
+ import { getPortalContainer } from '../../../dsh/portal'
5
6
 
6
7
  interface BulkDeleteDialogProps {
7
8
  open: boolean
@@ -22,8 +23,8 @@ export function BulkDeleteDialog({
22
23
  const { t } = useI18n()
23
24
 
24
25
  return (
25
- <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
26
- <DialogPrimitive.Portal>
26
+ <DialogPrimitive.Root open={open} onOpenChange={onOpenChange} modal={false}>
27
+ <DialogPrimitive.Portal container={getPortalContainer()}>
27
28
  <DialogPrimitive.Overlay className="sheet-overlay fixed inset-0 z-[70] bg-[hsl(224_18%_10%/0.3)]" />
28
29
  <DialogPrimitive.Content className="fixed left-1/2 top-1/2 z-[70] w-[min(420px,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-background p-5 shadow-panel focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
29
30
  <DialogPrimitive.Title className="text-[16px] font-medium tracking-tight text-foreground">
@@ -2,6 +2,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'
2
2
  import { Loader2, Trash2 } from 'lucide-react'
3
3
  import { Button } from '@renderer/components/ui/button'
4
4
  import { useI18n } from '@renderer/i18n'
5
+ import { getPortalContainer } from '../../../dsh/portal'
5
6
 
6
7
  interface ConfirmDeleteDialogProps {
7
8
  open: boolean
@@ -22,8 +23,8 @@ export function ConfirmDeleteDialog({
22
23
  const { t } = useI18n()
23
24
 
24
25
  return (
25
- <DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
26
- <DialogPrimitive.Portal>
26
+ <DialogPrimitive.Root open={open} onOpenChange={onOpenChange} modal={false}>
27
+ <DialogPrimitive.Portal container={getPortalContainer()}>
27
28
  <DialogPrimitive.Overlay className="sheet-overlay fixed inset-0 z-[70] bg-[hsl(224_18%_10%/0.3)]" />
28
29
  <DialogPrimitive.Content className="fixed left-1/2 top-1/2 z-[70] w-[min(420px,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 rounded-xl border border-border bg-background p-5 shadow-panel focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring">
29
30
  <DialogPrimitive.Title className="text-[16px] font-medium tracking-tight text-foreground">
@@ -85,6 +85,9 @@ const ROUTES = [
85
85
 
86
86
  { pattern: /^\/snapshots$/, methods: { GET: snapshots.handleList } },
87
87
  { pattern: /^\/snapshots\/([^/]+)\/archive$/, methods: { POST: snapshots.handleArchive } },
88
+ // Both spellings exist: the vendored renderer's original Web API uses
89
+ // `/restore`, while the desktop contract names it `restore-previous`.
90
+ { pattern: /^\/snapshots\/([^/]+)\/restore$/, methods: { POST: snapshots.handleRestorePrevious } },
88
91
  {
89
92
  pattern: /^\/snapshots\/([^/]+)\/restore-previous$/,
90
93
  methods: { POST: snapshots.handleRestorePrevious },
@@ -132,19 +135,21 @@ function assertWriteFence(req, method) {
132
135
  }
133
136
  const headers = (req && req.headers) || {}
134
137
  const origin = headers.origin
138
+ const fetchSite = String(headers['sec-fetch-site'] || '').trim().toLowerCase()
135
139
  if (origin !== undefined && origin !== null && String(origin).trim().length > 0) {
136
140
  if (!isLoopbackOrigin(String(origin))) {
137
- throw forbidden('FORBIDDEN_ORIGIN', 'write request origin is not allowed')
141
+ // DSH renders the plugin from the custom `dsh-app://app` origin; its
142
+ // same-origin DELETE carries `Sec-Fetch-Site: same-origin`, which is the
143
+ // signal we trust (a cross-site page would report cross-site).
144
+ if (fetchSite !== 'same-origin' && fetchSite !== 'none') {
145
+ throw forbidden('FORBIDDEN_ORIGIN', 'write request origin is not allowed')
146
+ }
138
147
  }
139
148
  }
140
- const fetchSite = headers['sec-fetch-site']
141
- if (fetchSite !== undefined && fetchSite !== null && String(fetchSite).trim().length > 0) {
142
- const site = String(fetchSite).trim().toLowerCase()
143
- if (site !== 'same-origin' && site !== 'none') {
144
- throw forbidden('FORBIDDEN_ORIGIN', 'cross-site write requests are not allowed', {
145
- header: 'Sec-Fetch-Site',
146
- })
147
- }
149
+ if (fetchSite.length > 0 && fetchSite !== 'same-origin' && fetchSite !== 'none') {
150
+ throw forbidden('FORBIDDEN_ORIGIN', 'cross-site write requests are not allowed', {
151
+ header: 'Sec-Fetch-Site',
152
+ })
148
153
  }
149
154
  }
150
155
 
package/src/host/http.js CHANGED
@@ -135,7 +135,14 @@ export function assertSameOriginWrite(req) {
135
135
  const origin = headers.origin
136
136
  if (origin !== undefined && origin !== null && String(origin).trim().length > 0) {
137
137
  if (!isLoopbackOrigin(String(origin))) {
138
- throw forbidden('FORBIDDEN_ORIGIN', 'write request origin is not allowed')
138
+ // The DSH page origin is the custom `dsh-app://app` scheme, not a
139
+ // loopback http(s) origin. Its same-origin fetch still carries
140
+ // `Sec-Fetch-Site: same-origin`; trust that rather than the scheme, so
141
+ // UI writes are not rejected while cross-site pages stay blocked.
142
+ const site = String(headers['sec-fetch-site'] || '').trim().toLowerCase()
143
+ if (site !== 'same-origin' && site !== 'none') {
144
+ throw forbidden('FORBIDDEN_ORIGIN', 'write request origin is not allowed')
145
+ }
139
146
  }
140
147
  }
141
148