dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Ships DeepSeek Harness session activity to your SIEM as OCSF 1.9.0 records
A read-side SIEM forwarder for DeepSeek Harness. It observes the
session event firehose, normalises every event to OCSF 1.9.0 with the native ai_operation
profile, and writes newline-delimited OCSF JSON to a local append-only spool — optionally shipping
it to Splunk HTTP Event Collector or an OTLP/HTTP collector.
📖 Full documentation — including the complete event → OCSF mapping table for all 59 session event types.
session/event, session/created and session/disposed, and sweeps
ctx.sessions.list() at mount.tool/call ↔ tool/result and approval/asked ↔ approval/decided, emitting
approval decision latency — the approval-fatigue signal.mcp__<server>__<tool> call.load_workspace_dependencies
unpacks Desktop's bundled Python, Node and pnpm under $DSH_HOME and appends no event of its
own, so the tool call is the only trace. It is mapped to Application Lifecycle / Install
carrying the interpreter paths and the bundled distribution inventory, not to a generic API
read.session-log-deepseek
row attaches the log's own event envelopes to every model request, and each accepted upload
becomes a record naming the destination service and counting the events that went with it. That
row's enabled default is false through the 0.1.5 line and true from 0.1.7, so on a
current harness this record is what a host looks like normally, and its absence is what an
operator who set enabled: false should be checking for.severity_id: 5, with the count of what it dropped.record_integrity profile, and ships
dsh-ocsf-verify to check the chain.Session.append() cannot set the envelope's ignorable
flag, so a plugin-owned event type makes the next resume throw SessionFormatUnsupportedError
and refuse the entire session. All durable output goes to our own sink, and the plugin registers
no waterfall listener, so it cannot change a tool call, an approval decision or a model request.inspect/lock/unlock verb with
no version in it. A plugin therefore cannot tell a SOC that this host's agent binary changed
version. The same is true of the embedded browser: partitions, storage clearing and document
destruction are all main-process, and none of it reaches the session log.
The evidence, and what is observable →bash can delete or rewrite the spool — and can recompute the hash chain over what
it wrote, because the algorithm is published. What it buys you is that records leave the host
promptly and that a gap is visible — the chain's entry numbering, metadata.sequence holes per
session, and a shipper cursor that stopped advancing.The profile must already compose a runnable agent — a profile carrying only
@deepseek-ai/dsh-base has no agent loop and this plugin would observe nothing:
dsh plugin --profile <name> add @deepseek-ai/dsh-headless@0.1.1-rc.2
dsh plugin --profile <name> add dsh-ocsf-forwarder
dsh --profile <name> --dump-config # verify the row is mounted
Pin @deepseek-ai/dsh-headless explicitly — the @deepseek-ai/dsh-* libraries' npm latest tag
still points at 0.0.1-rc.1. Install from the registry or a packed tarball, not from a git
spec: lib/ is a build output git does not carry.
Runs on dsh 0.1.0-rc.6 through the 0.1.2 line and on the 0.1.5 and 0.1.7 lines; CI runs the
end-to-end suite against every line the peer range admits. The 0.1.3 and 0.1.6 lines are
deliberately outside the range: nothing here has been run against them.
- id: dsh-ocsf-forwarder
config:
spoolPath: /var/log/dsh/ocsf.jsonl # absolute; created 0640
splunk:
endpoint: https://splunk.example:8088
token: { source: env, variable: SPLUNK_HEC_TOKEN }
privacy:
hmacKey: { source: env, variable: DSH_OCSF_KEY }
Every numeric key that is resolved must be a positive finite number, and those counting records or
files must be whole numbers — statsIntervalMs is the one exception, where 0 means "only at
unload". A value outside those ranges fails at load, because the alternative is worse than a
refused mount: batchSize: 0 makes the shipper loop without ever advancing its cursor. A shipper
block with no endpoint configures no shipper and is not resolved, so nothing in it is checked.
The default privacy posture keeps raw values out of the SOC lane — argument values and command lines are digested, URLs reduced to their host. A second restricted lane carries verbatim payloads and must be explicitly acknowledged before it will open.
Every configuration key → · Record format and the mapping table →
Splunk HEC and OTLP/HTTP are both supported; configure exactly one per spool. Delivery is cursor-based off the spool, so a collector outage costs nothing but disk, and the spool refuses to delete an un-drained generation rather than silently discarding unacknowledged evidence.
Splunk and OTLP setup → · Delivery and failure modes →
Every record carries an OCSF 1.9.0 record_integrity attestation: the SHA-256 fingerprint of the
record, plus the uid and fingerprint of the record before it. Editing, reordering, or deleting a
record from the middle of a spool breaks the chain at that record and at the one after it.
dsh-ocsf-verify /var/log/dsh/ocsf.jsonl # 0 intact, 1 broken, 2 unreadable
dsh-ocsf-verify --anchor shipped.jsonl /var/log/dsh/ocsf.jsonl
Deleting from the end breaks nothing — the shorter chain still verifies — so that check needs a
reference the writer cannot reach. Every shipped record is one: --anchor takes records back from
the SIEM and reports a spool that stops short of them. Without anchors the report says no anchor
rather than implying it checked.
The fingerprints are unkeyed, so anyone can recompute them — which is the point, and which also
means the chain does not resist the agent it observes. A spool replaced wholesale under a fresh
chain_uid contradicts no anchor; it fails to overlap one, and since 0.8.0 an anchor naming a chain
that is absent is a finding (--no-strict-anchors makes it a count again, for a host whose shipper
legitimately drained that chain). What remains open is direction: anchors bound a chain from below
and never from above, so records added past the last one delivered are not caught.
The canonicalisation, the threat model, and the cost →
On Linux the complementary move is chattr +a on the spool, which makes truncation and rewriting
fail rather than merely detectable. The plugin tolerates the chmod that attribute refuses — but
it also refuses the rename, so rotation stops permanently and the file's size becomes a manual
job. Harden the live spool file only; hardening its directory breaks the mount.
What chattr +a buys, costs, and breaks →
dsh-netguardBoth packages emit OCSF into one index and share the correlation_uid scheme
<session>:<callId>, so a Network Activity record from netguard joins to this package's Process
Activity record for the same tool call — answering which tool call opened this connection.
metadata.uid is deliberately not shared: this package's key is <session>:<seq> over the
session log's event sequence, and netguard namespaces its own as <session>:netguard:<seq> so a
SIEM deduplicating on that field cannot mistake one package's records for the other's.
nvm use 22 # Node ^22.19.0 || >=24, and pnpm 11
pnpm install
pnpm run typecheck
pnpm run test:coverage
pnpm run test:e2e # boots a real dsh against a mock model; no API key
Design decisions and their rationale live in ADR.md. Security policy is in SECURITY.md.
MIT