DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Maestro Guard — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
M

@ddtcorex/dsh-maestro-guard

Maestro Guard

Maestro Guard — pre-execute safety gate: native DSH approval prompts, parsed command-surface rules, decision journal

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:ddtcorex/dsh-maestro-guard#418128e7f55cf0952c7dd69c490f3cdd923092bf
READMECompatibilityVersions

Compatibility and provenance

Maestro Guard is published as @ddtcorex/dsh-maestro-guard and currently resolves to version 0.3.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
any
Release source
github
Registry updated
9/14/2026

Versions

0.3.0stable
9/14/2026
0.2.3stable
9/3/2026
0.2.2stable
9/1/2026
Show 2 more versionsCollapse versions
0.2.0stable
9/1/2026
0.1.0stable
8/26/2026

Related plugins

Loading related plugins…

Latest
0.3.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
any
License
Not declared
Source
github
GitHub
★ 1
Weekly downloads
0
Last push
9/18/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in security-access.

Doctor@linxin666/dsh-doctorTransactional rescue mode for DSH profiles with a supervised launcher, isolated recovery capsule, deterministic repairs, health monitoring, and a local Web recovery consoleMobiledsh-mobileDeepSeek Harness mobile adaptation and secure access plugin, supporting LAN, remote connections, Android App, and mobile browsers.DSCODE@toddzheng024/dscode-bundleA complete DeepSeek coding agent with persistent shell, Ultra collaboration and automatic permission review.Auto Reviewdsh-auto-reviewSecond-model AI auto-review for DeepSeek Harness approval requests: a read-only reviewer subagent decides allow/deny on the approval answerer chain, with fail-closed fallback and full session-log audit.

README

@ddtcorex/dsh-maestro-guard

Host-only safety gate for the DeepSeek Harness: one Cordis row (dsh-maestro-guard) listens on the tools/pre-execute waterfall and decides every tool call before it runs.

Part of the Maestro Harness suite (dsh-maestro-*). Cordis patch row id: dsh-maestro-guard.

Opt-in and intentionally not part of the meta-bundle one-liner until published: add it explicitly with dsh plugin add @ddtcorex/dsh-maestro-guard.

What it provides

Every call runs the same five-step pipeline:

parse → classify → decide → journal → act
  1. parse — read the executed surface of the call: a shell tool's command text, a file tool's path field. The command is parsed, not regex-matched: a shell-aware tokenizer/segmenter splits it on operators that sit outside quotes, unwraps the wrappers that only change who runs it (env VAR=…, sudo, nohup, time, command, busybox) and replaces a shell wrapper (bash -c <script>, bash -s, a bash <<EOF body) with the script it runs. Anything unresolvable is marked ambiguous, and an ambiguous segment is escalated whenever its unresolved text names a rule verb the guard can act on (git push, gh pr merge, a package publish, curl … | sh) — so an unreadable wrapper around a protected operation is still asked about, never allowed. When the unresolved text names no such verb it stays an allow, and quoted or inline program text is deliberately data: a protected operation spelled inside python3 -c "…" or an --body "…" is not a command.

    Two hard cases are decided by SHAPE rather than by a longer verb list. A segment whose first token cannot be a command (a VAR=value assignment, a (/{ group opener, a shell keyword) or whose LATER tokens name a rule verb while argv[0] does not (pkexec …, perf …, /usr/bin/git …, my-custom-runner …) is ambiguous, so the operation written behind it is asked about instead of silently allowed — compared by the command BASE name, so a qualified path cannot hide it. A segment LED by a mention verb (echo, printf, grep, rg, sed, awk, ls, find, …) opts out of that later-token rule, because those verbs cannot execute their arguments: rg git push docs/ and echo pnpm publish are a search and a print, not commands. find is the one exception, and through any of its action flags (-exec, -execdir, -ok, -okdir) the command it runs is still judged like any other — so find . -execdir git push origin master + asks, while find . -execdir ls -la + stays an allow. A shell handed a -c script by a verb the guard cannot name (my-custom-runner bash -c "git push origin master") is unwrapped like any other shell wrapper, so that script is judged as the command that really runs. And a quoted protected path is not erased: secret.access reads the parsed segment argv, where the tokenizer has already dropped the quotes and kept the content, so cat "<path>", cp "<path>" /tmp/x, curl -T "<path>" … and the write-into cp /tmp/x "<path>" all ask just like their unquoted forms.

    The corpus rows carried: interpreter inline program pushing a protected branch, carried: node -e inline program tagging a release, carried: quoted data mentioning a release and the protected path: quoted … rows pin those decisions — including the deliberate fail-closed trade-off that an interpreter inline program which names a protected path asks again. guard.tamper is the one rule that judges the raw segment text rather than a resolved shape, and even there only an EDIT counts. A verb that always writes its target (rm, mv, cp, ln, truncate, shred, dd, tee, install, chmod, chown, patch, sponge, unlink, rmdir, ed, plus sed -i / perl -i) naming a guard path denies, and so does a write redirection (>, >>, 2>, &>, >|) whose TARGET is one — in the absolute, ~, $HOME or ${HOME} spelling. The dual-use verbs are judged on their WRITE SHAPE, not on their name, or their read form would become an unappealable deny: curl -o <journal> …, wget -O <journal> …, a rsync/scp DESTINATION, vi/vim/nano without a read-only switch and patch deny, while curl -I <journal>, wget -O - <journal>, rsync --list-only <journal> /tmp/, scp -r host:<journal> /tmp/, vi -R <journal> and nano -v <journal> fall through. The mutation does not have to be the segment's own verb either: what a wrapper the parser does not unwrap runs (nice, timeout, flock, ssh, watch), what a find action flag runs, what a -c script contains, and what an xargs takes from the segment PIPED into it all count, because each is the same edit arriving one level down. A mutation WORD is only read where a command can be — the whole argv for that wrapper class, the tokens after a find action flag for a mention-led segment — so less -p rm <journal>, and stay the reads and non-edits they are, and cannot borrow the path across an , while still follows the pipe to the deleting command. A READ of the same path (, , , , ) is not tampering and falls through to the ordinary rules, which is what makes the guard's own deny text ("see the guard journal") followable. Three limits are recorded rather than implied: an interpreter inline program () is data, an UNKNOWN runner around a mutating verb () is not read because nothing distinguishes it from a tool whose argument merely spells , and a script chain is read two levels deep (a third level is not read).

  2. classify — map the call to a stable rule id (below), resolving the branch of the repo the command targets through its cd / git -C.

  3. decide — resolve the tier: the classified tier, unless domains.guard.rules carries an entry that DIFFERS from the rule's built-in default. An entry that merely echoes the default is the table repeating itself, not a user choice — honouring those made every classify-level refinement (a --dry-run publish is a journal, not an ask) unreachable.

  4. journal — append one redacted record per decision.

  5. act — run the call or return a deny decision.

Rule ids

Rule ids are the contract — config overrides, journal entries and approval reasons all key on them. There are 11:

Rule idDefault tierFires on
git.push.protectedaska push targeting a protected branch: an explicit refspec naming one, HEAD/no refspec resolved through the target repo's checked-out branch, --all/--mirror (they push every local branch), or a branch that cannot be resolved (fail-closed)
git.merge.protectedjournala gh pr merge (recorded rather than gated)
git.tag.releaseaska release/semver tag push (a refs/tags/* refspec, a bare vX.Y.Z, or --tags)
git.push.forceaska force push (--force, --force-with-lease, -f, +refspec)
gh.release.createaskgh release create / gh release publish
gh.protection.deleteaska gh api … DELETE against branch protection
pkg.publishaska package-manager publish (npm / pnpm / yarn)
secret.accessaskaccess to a protected credential path — any file tool (read or write) whose path field is the protected path, or a parsed segment whose COMMAND is an access verb and whose argv holds the path (a mention-only verb such as grep/ls/printf is never an access, an access verb inside a message or body is not the command, and a heredoc body is never argv)
fs.write.outsideaska file-tool write outside the session working directory (the OS temp dir is exempt, and so is the runtime spill dir while spillReads is on)
net.exec.remoteaskpiping a remote script into a shell (curl … | sh, source <(curl …))

guard.tamper is an unappealable deny floor — it cannot be downgraded by configuration. Every other default tier can be overridden per rule id in domains.guard.rules.

Decision tiers

TierEffect
allowrun the call; nothing recorded
journalrun the call and record it (e.g. gh pr merge is no longer gated)
askDSH's native approval prompt: the guard calls the approval service itself, only allowed-once proceeds, and the outcome plus the time the human took is recorded
denyrefuse and tell the agent why (unappealable self-protection: guard.tamper)

A deny also fires when the tool policy (PermissionPolicy) rejects the call. The guard registers no approval tool, so no agent can grant itself a protected operation.

Fail-closed by construction: a session whose approval policy never prompts, an agent-less execution, or a missing/unreachable approval service all resolve to a denial that names the cause and the fix — the shipped unavailable message reads no approval channel is available for this session (start a session under a preset that prompts — this deployment's danger-full-access now asks). Nothing is ever allowed silently because the prompt could not be raised.

A request() that throws is denied with the approval request failed (see the guard journal), and the thrown message is stored in that journal entry's note, so the pointer is honest. DSH resolves a never approval policy to rejected inside the approval service, before any answerer is dispatched, so the guard cannot tell a policy rejection from a human one — the reject text names both causes and the preset fix rather than claiming a human decided.

The handler also refuses to run on an unknown tools/pre-execute payload (spec §8): a payload carrying neither name/tool nor args/arguments is denied and journaled as contract-mismatch. Without that guard a DSH upgrade that renames args would make every command rule read undefined and silently allow everything.

What an ask looks like

git push origin master classifies as git.push.protected, so the guard journals the decision and raises DSH's own prompt instead of running the call. The prompt carries the rule id and the exact command the guard classified (the reason string is <rule id> :: <redacted command>):

Approval required
git.push.protected :: git push origin master
  Allow once   /   Reject

Allow runs that one call (outcome: granted in the journal) and returns next(), so any other pre-execute listener still runs; the next git push origin master asks again — there is no standing grant. Reject returns a deny to the agent and records outcome: rejected, which is what maestro_guard_status then shows. A prompt that cannot be raised at all is the fail-closed unavailable outcome above, never a silent allow.

Journal

~/.dsh/dsh-maestro-guard/journal.jsonl — one JSON line per decision, mode 0600. Secret families (registry tokens, env assignments, auth headers, private keys) are redacted at the Journal.append choke point, so every string field of a persisted entry is redacted and no call site can write an unredacted value; the executed call is never rewritten. A legacy pending.json ticket file is retired to legacy-pending.json on first boot.

Ordinary (allow) decisions never reach the journal individually: they are counted in memory and persisted as one periodic counters aggregate line, so they stay off the decision path.

Rotation and retention run on their own: at boot the guard rolls the live file when its last write predates today, then rolls once a day, archiving it as journal-YYYY-MM-DD.jsonl and pruning archived files that fall outside both retention windows (retainFiles and retainDays). So the live file stays bounded and those knobs actually apply on a host that never restarts — rotation is time-based, not a size trigger, and Journal.rotate() remains callable on demand.

Tools

The guard registers three host tools. All are read-only with respect to the guard:

  • maestro_guard_status — the ~20 most recent decisions, the effective rule tiers, and the journal path/state.
  • maestro_guard_stats — the folded counters over the last ~1000 journal entries: byRule, byTier, byOutcome, and the ask approval-latency percentiles (p50 / p90 / max). byRule/byTier fold only rows whose rule is one of the closed rule ids, so the guard's own bookkeeping rows (counters, config-legacy, guard.migration, policy.deny) do not appear as decisions; byOutcome counts every row.
  • maestro_full_scan — the on-demand full scan.

There is no approval tool: ask is answered by DSH's own prompt, so no agent can grant itself a protected operation.

Configuration — domains.guard (schema v2)

In the shared Maestro settings store:

KeyMeaning
rulesper-rule-id tier override (merges per id onto the defaults). An entry that equals the rule's built-in default is not an override — the effective tier is the classified one unless an entry DIFFERS from the default
protectedBranchesbranch names treated as protected (default master, main)
protectedPathscredential paths that fire secret.access when any file tool targets them
guardPathsthe guard's own config/secret paths (guard.tamper): the settings file, the profile row patch, the profile package.json that mounts the guard, and the journal + legacy-pending.json
journalenabled, allowCounters, and the retention window retainFiles (14) / retainDays (30); a window that is not a positive integer falls back to the built-in one (a fractional value used to floor to 0 and prune every archive)
workingDirContainmentenabled (default true) switches the fs.write.outside rule on/off; spillReads (default true) keeps the runtime spill dir exempt from that rule — set false to gate spill-dir writes too

The journal block is read once at boot (it decides whether the journal writes at all and which retention defaults rotation uses), so changing it needs a host restart. Every other key, workingDirContainment included, is part of the per-call config read and takes effect on the next tool call. A missing, unreadable or half-written config falls back to the built-in defaults — protection degrades in precision, never in coverage.

Legacy keys written for schema v1 are translated forward on read and the translated key is journalled once per boot:

Legacy keyMaps to
gitProtection.enabled: falsethe three git rules (git.push.protected, git.tag.release, git.push.force) become journal
gitProtection.branchesprotectedBranches
publishBlocked: falsepkg.publish becomes journal
cwdContainment: falsefs.write.outside becomes journal
credentialPathsadded to protectedPaths (never replaces it)

An explicit v2 rules entry wins over the legacy boolean that produced the same rule id.

Deployment files that must stay in sync

An ask tier only bites if the session actually prompts, and two deployment files carry that (neither ships in this package):

  • the permission row in the web profile composition (~/.dsh/profiles/web/cordis.patch.yml) — its presets table must define danger-full-access with approval: ask (the prompting full-access mode rides the existing preset value so it keeps the picker's built-in shield glyph, product label and risk confirmation);
  • permission.defaultPreset in ~/.dsh/settings.yaml — must name danger-full-access, so new sessions prompt instead of running under an approval: never preset.

Change one without the other and every ask denies (fail-closed, but unusable). Rollback: set permission.defaultPreset back to danger-full-access, remove the permission row patch, restart.

Live validation is a recorded, deliberate deferral — not an oversight. The spec marks a CDP probe on :3080 (prompt appears with the rendered reason; Allow runs; Reject blocks; a never session is denied with the actionable message; a background subagent fails closed) as required before "done". The human deferred the dsh web restart that would put this build into the running host, so the deployed host is still 0.2.3. The profile installs this package with link: and lib/ is already built, so any unrelated dsh web restart deploys 0.3.0 unvalidated against a live approval prompt — until this validation runs, the build is unverified in production.

Host-only: no client bundle; DSH types come from local structural declarations (src/host/augment.d.ts).

Development

pnpm install
pnpm verify   # tsc --noEmit
pnpm test     # vitest run
pnpm build    # tsc -> lib/

License

MIT

ag rm <journal>
docker rm <journal>
tail <journal> && xargs rm -rf /tmp/junk
&&
cat <journal> | grep x | xargs rm -f
cat
tail
head
grep
less
python3 -c "… open(p,'w') …"
my-custom-runner rm -f <journal>
rm
-c
guard.tamper
deny
EDITING the guard's own config paths — a writer naming one (an always-writing verb, or the write SHAPE of a dual-use one such as curl -o/rsync DESTINATION/vi), or a write redirection targeting one, in the absolute / ~ / $HOME / ${HOME} spelling, whether the writer leads the segment or arrives behind an exec wrapper, a find action flag, a -c script or a piped-in xargs (a read of the same path is not tampering)