Sensitive Data Guard (dsh-sensitive-data-guard): Adding a backup-and-confirm checkpoint for DSH delete operations

Introduction

DeepSeek Harness (DSH) models can delete any file outside the workspace in danger-full-access mode. When an Agent runs batch jobs, cleans up old versions, or deletes temporary assets, a single typo in Remove-Item -Recurse could wipe out an entire directory. During these operations, when the deletion enters the model’s context, there is often no obvious confirmation step.

Below is the DSH plugin Sensitive Data Guard maintained by DreamInsight24. Its positioning is not for post-event recovery, but for intercepting before deletion occurs: it backs up first, then asks via a popup; if the backup fails, it blocks hard and does not downgrade to a warning.

What is it

The plugin package name is @local/dsh-sensitive-data-guard, version 0.2.6, compatible with DSH 0.2.2-rc.1. It does not modify DSH code or depend on @deepseek-ai/*, but wraps shell execution and common delete/overwrite tool calls via patch hooks (cordis.patch.yml), inserting judgment and backup logic. The maintainer is DreamInsight24, category admin-security. License MIT, see repo LICENSE.

Three Zones

Each deletion target is first categorized into a zone, which determines how it is handled:

Zone Judgment Handling
Workspace Inside the current session’s working directory No intervention at all
.dsh Inside the local machine’s .dsh directory Silent backup once
Sensitive Zone Any path outside the above two Backup + Popup confirmation

When an operation involves multiple targets, the strictest zone applies. The workspace itself is prioritized for judgment, meaning if the workspace is inside .dsh, it is handled as the workspace. If the session’s working directory cannot be obtained, it is treated as the Sensitive Zone.

The decision to leave the workspace completely defenseless is explicit: the plugin aims to prevent the model from wiping directories outside the workspace, not to prevent accidental deletion of your own project.

Confirmation Flow

When a deletion in the Sensitive Zone is hit, the Guard completes the backup first, then pops up a confirmation card on the interface, pausing the tool call:

Delete Guard · Sensitive Zone Deletion

Allow this deletion to proceed?

Content to be deleted:
  • D:\Project\Old Versions      1284 files, 3.7 GB
  • D:\Assets\Temp                56 files, 12.4 MB

Total: 1340 files, 3.71 GB
Backup already taken: 2026-11-05/<session>/0007
Restore command: node bin/delete-guard.mjs restore 2026-11-05/<session>/0007

[Allow Execution]  [Intercept]

Clicking “Allow Execution” executes the command immediately; clicking “Intercept” stops execution, preserving the backup. If no answer is given, it waits for policy.confirmTimeoutMs (default 5 minutes); on timeout, it blocks (fail-closed) and will not auto-allow. If the popup cannot be sent (browser closed, pure CLI, initiated from a sub-agent), it also blocks.

The answer is returned via DSH’s user Q&A channel (ctx.userQuestions, same card style as ask_user_question), only back to the Guard; the model cannot see or access it.

It is worth noting that versions before 0.2.4 used a 4-digit confirmation code, written in the tool return value, which the model could fill back in, so the above Q&A channel was adopted. If there is truly no interface scenario, you can switch back to text confirmation code: set policy.confirmVia: 'code', provide the 4 digits in the report, and you send them to the model; the model re-sends the same command with a prefix to reuse the backup. Since the model can see the code in this channel, it should only be used when there is no interface.

When Targets Cannot Be Resolved

Scenarios where targets are only determined at runtime, such as Remove-Item -LiteralPath $env:SOME_VAR -Recurse, default (policy.unresolvedAction: 'confirm') still pops up a dialog, but the body clearly states no backup was made and the target cannot be verified against protected directories. To change back to hard deny, set it to deny.

Most “unresolvable” cases can actually be resolved: $x = 'literal' enters the variable table, Get-ChildItem X | Remove-Item resolves back to X, [IO.File]::Delete($_.FullName) fills in the upstream path.

Unrecognized Tools

The plugin’s own tools (such as config2ds you installed yourself) are not in the Guard’s built-in list. Default (policy.unknownTool: 'prompt') pops up a card showing the tool name, registration info, parameter structure, and current call parameters, letting you choose to trust and add to whitelist, allow only this time, or intercept. The whitelist is written to data/settings.json. If the popup cannot be sent or times out, it blocks (fail-closed).

Installation and Activation

In DSH’s plugin management, install the plugin directory as a local directory path, or let the model call plugin_manager’s install_bundle with an absolute path. The installation result should be application: applied with no warnings.

Run a self-check after installation:

node bin/delete-guard.mjs status

You should see the backup root, health status, batch and usage, Sysmon status, and the ok for the hook file. The self-check path is resolved relative to the plugin directory; relative paths like backup/ or bin/... appearing later in the README are based on the directory containing package.json.

To disable, simply disable the bundle in DSH. Disabling takes effect immediately and restores the wrapped shell methods; it does not delete already generated backups.

Settings

Three entry points modify the same state, with priority: lib/defaults.js defaults < cordis.patch.yml config < data/settings.json. Changes take effect immediately without restart:

  1. Settings → Sensitive Data Guard
  2. Plugins → This Plugin Page
  3. File: data/settings.json

Five fields can be changed in the interface:

Field Description
triggerLevel soft (backup only) / medium (threshold-based) / hard (always confirm)
mediumMinFiles File count threshold for medium (default 10)
mediumMinBytes Size threshold for medium (default 64 MiB)
protectedPaths Protected directories, one per line, ! prefix means exclude
safeTools Trusted read-only tool names, one per line

Tuning towards a looser direction (hard → medium → soft) won’t be blocked, but will show red warnings and log an audit. Full configuration items (backup quotas, retention policies, hook switches, etc.) have Chinese comments in cordis.patch.yml and are the authoritative source. config.schema.json is a machine-readable version of the same information; DSH does not read it.

Protected Paths

policy.protectedPaths is a list of .gitignore style patterns. Modification is prohibited at any level; judgment occurs before “workspace direct allow”:

D:\Company\Finance
C:\Windows\System32
!D:\Company\Finance\PublicDocs    ← ! prefix means allow

Rules are separated by /, case-insensitive; can include drive letters (without them matches any drive); * matches a segment, ** spans segments, ? matches one character; ! allows; later rules override earlier ones; paths without / match any level, paths with / anchor from the drive root. The declaration in cordis.patch.yml is the deployment floor; the settings page can only append upwards, not delete.

The plugin directory itself, backup root, audit logs, and profile config files (cordis.patch.yml / cordis.yml / package.json) are automatically added to protection; no need to write them manually.

Backup and Restore

Backup directory structure:

backup\
  _store\<aa>\<bb>\<sha256>          Content-addressed object store (same content stored once)
  2026-11-05\<sessionId>\0001\       Batch bucket (manifest.jsonl + hard-linked mirror tree\)
  _audit\2026-11-05.jsonl            Audit (allow/backup/block/confirm/restore/exception)

Retention policy: .dsh keeps the latest 10 batches and no more than 30 days; Sensitive Zone 30 days; Workspace does not generate backups. Audit logs are not deleted with batches. When exceeding the total quota (default 50 GiB), new backups are directly blocked, preventing the disk from filling up.

Restore will overwrite current files with old content. By default, it writes back to the original path file-by-file; if the target already exists, overwrite is rejected; use --force to overwrite. Restore validates SHA-256:

node bin/delete-guard.mjs restore latest --dry-run
node bin/delete-guard.mjs restore 2026-11-05/<session>/0007
node bin/delete-guard.mjs restore latest --to D:\restore-here --force

Scope and Boundaries

Intercepts:
* All model tools (overwrites by write/edit, pwsh/bash commands, delete/move tools, plugin_manager modifications to this plugin itself).
* Plugin tools not in the built-in list, defaults to asking the user if they trust them.
* All shell commands complete judgment and backup before spawn.
* Python/Node child processes derived from commands are validated via runtime hooks for os.remove/shutil.rmtree/fs.rmSync/fs.rename.
* Sysmon status detection (read-only, alerts only, does not modify security config).

Cannot intercept:
1. Third-party processes: Cleanup software unrelated to DSH, scheduled tasks deleting files — the plugin has no control.
2. cmd.exe sub-processes: cmd /c del does not go through Python/Node hooks, only at the command text parsing layer.
3. Dynamic or obfuscated code: Clearing environment, python -E/-I, native binaries directly unlinking — hooks fail.
4. Plugin not loaded or crashed: There is no access control; use status self-check.
5. Workspace is defenseless.
6. Persistent shell tools: If tool-pwsh-persistent is installed, it uses ctx.terminals instead of ctx.shell; deep shell layers and runtime hooks do not cover it, only command text parsing. This profile does not have it installed by default.

Other cases are blocked (fail-closed): Single file > 256 MiB, single batch > 10 GiB or > 50k files, total > 50 GiB, hit protected directory, target falls in plugin directory itself, internal plugin exception.

Command Line

node bin/delete-guard.mjs status [--root DIR] [--json]
node bin/delete-guard.mjs restore <batch|latest> [--to DIR] [--force] [--dry-run]
node bin/delete-guard.mjs dry-run -- Remove-Item -Recurse -Force D:\tmp\victim
node bin/delete-guard.mjs gc [--root DIR]

dry-run only reports what will be backed up, the size, and if it exceeds limits; it does not delete or write backups, suitable for verifying target paths fall within protected areas before deployment.

Use Cases and Notes

Suitable for enabling in DSH sessions running long tasks, batch cleaning, or migrating old directories, especially when the model permission is danger-full-access and the directory structure has clear old version or temp asset partitions. If you just want to confirm if a deletion will be blocked or if a backup will be generated, using dry-run is the most direct way.

A few points to note. The plugin runs with the permissions of the current DSH process, protecting paths it can see and intercept; the six boundary categories above that cannot be intercepted do not become safe boundaries just because the plugin is installed. It does not modify DSH code, but intercepts all destructive operations initiated by DSH. Before installing on someone else’s machine, read docs/DEPLOY-CHECKLIST.md in the repo. Before installation, you should also check the plugin source code and the MIT license to ensure the scope of patch hooks fits your usage environment. Backups consume disk space; if the quota is exceeded, new backups are blocked, requiring periodic cleanup of old batches using gc.

Conclusion

The value of the Sensitive Data Guard lies in making “backup before delete, ask during delete, don’t delete if backup fails” the default behavior, with clear zone division and consistent failure direction towards blocking.

Directory page and source code at:

  • https://www.skillhub.cn/plugins/DreamInsight24/dsh-sensitive-data-guard
  • https://github.com/DreamInsight24/dsh-sensitive-data-guard
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye