Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Changed `curl/wget | bash/sh` handling to require approval by default and block only when hard indicators or multiple suspicious signals are present.

### Fixed
- Updated compatibility with DSH `0.1.1-rc.2`: profile installs now authorize AgentGuard package builds, tarball acceptance uses pnpm's exact local-file build key, and the persistent PowerShell `pwsh` tool is classified as shell execution for runtime policy evaluation.
- DSH protect hooks now exempt only the four exact AgentGuard-owned tool names, preventing third-party `agentguard_*` tools from bypassing runtime policy evaluation.
- DSH runtime scanning now includes referenced generated `dist/` and `build/` files, while protect mode requires explicit decisions for MCP, skill-install, and unknown tools.
- DSH scans now fail closed with `DSH_SCAN_INCOMPLETE` and `expert-review-required` when matching files are omitted by the file-count limit, exceed the per-file byte limit, or cannot be read, preventing incomplete scans from returning `safe-to-try`.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Reports include DSH identification confidence, plugin kind, explainable risk lev
Install AgentGuard as a native DSH tool plugin, then restart the profile:

```bash
dsh plugin --profile web add @goplus/agentguard
dsh plugin --profile web add --allow-build=@goplus/agentguard @goplus/agentguard
```

DSH will expose the read-only `agentguard_dsh_scan` tool for scanning local plugin directories and HTTPS GitHub repositories before installation.
Expand All @@ -162,11 +162,11 @@ Use `agentguard_dsh_compare` or the `agentguard dsh-compare` CLI command to iden

Update or remove it from the same profile with `dsh plugin --profile web update @goplus/agentguard` or `dsh plugin --profile web remove @goplus/agentguard`. The [DSH operations and report guide](docs/dsh.md#operate-the-dsh-installation) includes verification and troubleshooting steps.

> **DSH runtime guard:** the packaged composition uses non-disruptive `observe` mode. Startup logs and the input-redacted `agentguard_dsh_runtime_summary` tool explicitly show the current configured mode and whether pre-execute enforcement is active. An explicit `runtime.mode: protect` applies AgentGuard's shared allow/warn/require-approval/block policy before DSH dispatches a tool, using DSH's native one-shot approval service and monotonic composition with other policies. Optional `runtime.postResponseMode: block-malicious` suppresses only block-class malicious network results; approval-class post results remain audit-only because DSH has no resumable post-result approval protocol. Exact operator-configured `runtime.attribution.toolOwners` bindings add source ownership without guessing, and `runtime.ownerPolicies` can impose per-owner minimum decisions without weakening shared security policy. Unmapped tools remain `unknown` until DSH exposes a reliable native owner field. See the [DSH runtime guide](docs/dsh-runtime.md).
> **DSH runtime guard:** the packaged composition enables `protect` mode by default, applying AgentGuard's shared allow/warn/require-approval/block policy before DSH dispatches a tool. Startup logs and the input-redacted `agentguard_dsh_runtime_summary` tool explicitly show the current configured mode and whether pre-execute enforcement is active. Set `runtime.mode: observe` only when audit-only shadow evaluation is required. DSH protection uses DSH's native one-shot approval service and monotonic composition with other policies. Optional `runtime.postResponseMode: block-malicious` suppresses only block-class malicious network results; approval-class post results remain audit-only because DSH has no resumable post-result approval protocol. Exact operator-configured `runtime.attribution.toolOwners` bindings add source ownership without guessing, and `runtime.ownerPolicies` can impose per-owner minimum decisions without weakening shared security policy. Unmapped tools remain `unknown` until DSH exposes a reliable native owner field. See the [DSH runtime guide](docs/dsh-runtime.md).

The complete candidate scope, activation override, acceptance gates, and intentional boundaries are collected in [AgentGuard for DSH complete candidate](docs/dsh-complete-candidate.md).

The enforcing adapter maps approval decisions to DSH's native `ask` contract, emits bounded evidence-free reasons, preserves stronger downstream policies, fails closed on unexpected evaluator errors by default, and is registered only when `protect` is explicitly selected.
The enforcing adapter maps approval decisions to DSH's native `ask` contract, emits bounded evidence-free reasons, preserves stronger downstream policies, fails closed on unexpected evaluator errors by default, and is registered whenever `protect` is configured, including by the packaged default composition.

Native contract gates cover the full pre-execute approval outcome matrix, concurrent and nested calls, failures, unload, and post-execute result containment. Block-class malicious responses can be suppressed explicitly; approval-class post results remain audit-only because DSH currently exposes no native post-approval resume primitive. The complete candidate passed all 11 guided DSH UAT cases, including native approval/rejection, pre-execute blocking, response containment, redaction, and service stability.

Expand Down
12 changes: 6 additions & 6 deletions docs/dsh-complete-candidate.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AgentGuard for DSH complete candidate

This candidate completes the agreed installation-time scanner and DSH-native pre-execute runtime guard without changing the bundle's non-disruptive installation default.
This candidate completes the agreed installation-time scanner and DSH-native pre-execute runtime guard with `protect` as the packaged installation default.

## Included

Expand All @@ -11,7 +11,7 @@ This candidate completes the agreed installation-time scanner and DSH-native pre
- Bounded batch scanning and version/report comparison.
- DSH-native tools: `agentguard_dsh_scan`, `agentguard_dsh_scan_batch`, `agentguard_dsh_compare`, and `agentguard_dsh_runtime_summary`.
- Native pre/post lifecycle observation with shared AgentGuard policy semantics.
- Opt-in `protect` mode for pre-execute allow, warn, DSH-native approval, and block.
- Default `protect` mode for pre-execute allow, warn, DSH-native approval, and block.
- Optional block-class malicious network-response containment without returning untrusted response content.
- Exact operator-configured tool ownership attribution and monotonic per-owner policy floors.
- Fail-closed unexpected evaluator errors by default, with an explicit compatibility override.
Expand All @@ -21,7 +21,7 @@ This candidate completes the agreed installation-time scanner and DSH-native pre

## Installation posture

The packaged `dsh.cordis.patch.yml` remains on `observe`. This avoids turning an ordinary plugin update into an unexpected behavior-changing policy rollout.
The packaged `dsh.cordis.patch.yml` enables `protect` by default, so a standard DSH installation enforces the configured runtime policy immediately. Operators can explicitly switch to `observe` for audit-only shadow evaluation.

To confirm protection in a profile, add this complete config override to that profile's `cordis.patch.yml`:

Expand All @@ -34,7 +34,7 @@ To confirm protection in a profile, add this complete config override to that pr
postResponseMode: block-malicious
```

DSH profile patches replace the row's entire `config`, so the complete runtime configuration is restated. Optional `attribution.toolOwners` entries must contain only exact owner bindings trusted by the operator; a corresponding `ownerPolicies.<owner>.minimumDecision` can raise that owner's calls to `warn`, `require_approval`, or `block`, but cannot weaken the shared policy. Removing the override returns the bundle to its packaged `observe` configuration after recomposition/restart.
DSH profile patches replace the row's entire `config`, so the complete runtime configuration is restated. Optional `attribution.toolOwners` entries must contain only exact owner bindings trusted by the operator; a corresponding `ownerPolicies.<owner>.minimumDecision` can raise that owner's calls to `warn`, `require_approval`, or `block`, but cannot weaken the shared policy. Removing the override returns the bundle to its packaged `protect` configuration after recomposition/restart.

## Acceptance status

Expand Down Expand Up @@ -78,8 +78,8 @@ npm run benchmark:dsh

Confirm this candidate if the following product contract is acceptable:

1. installation remains observation-first;
2. protection is explicit and fails closed on unexpected evaluator errors;
1. installation enables pre-execute protection by default, with `observe` available as an explicit audit-only mode;
2. protection fails closed on unexpected evaluator errors;
3. approval is owned by DSH rather than a duplicate AgentGuard queue;
4. pre-execute protection is real, and optional post-response containment applies only to block-class results;
5. unattributed calls never receive plugin-specific trust automatically.
6 changes: 3 additions & 3 deletions docs/dsh-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ The runtime integration accepts three explicit modes:
| Mode | Pre-execute | Post-execute | Intended use |
|---|---|---|---|
| `off` | no listener | no listener | scanner tools only |
| `observe` | evaluate and audit; preserve downstream decision | evaluate network responses and audit | packaged default and rollout baseline |
| `protect` | apply `allow`, `warn`, native `ask`, or `deny` | audit by default; optionally suppress block-class malicious results | explicit real-time protection |
| `observe` | evaluate and audit; preserve downstream decision | evaluate network responses and audit | explicit audit-only evaluation |
| `protect` | apply `allow`, `warn`, native `ask`, or `deny` | audit by default; optionally suppress block-class malicious results | packaged default and real-time protection |

The npm bundle continues to compose `observe` by default so installing an update does not silently change tool execution. Enable protection in a custom DSH composition:
The npm bundle composes `protect` by default. A custom DSH composition can restate the protected runtime settings and add optional attribution or policy controls:

```yaml
- insert:
Expand Down
18 changes: 11 additions & 7 deletions docs/dsh.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

AgentGuard for DeepSeek Harness (DSH) is an installation-time trust layer for the DSH plugin ecosystem. It identifies DSH bundles, profiles, client extensions, and Cordis configuration, then combines that context with AgentGuard's existing static rules to produce an explainable security report.

The Phase 1 scanner is intentionally read-only: it scans source, classifies capabilities, and recommends an installation posture. It never installs the target, executes package lifecycle scripts, evaluates Cordis `!!js` expressions, or starts DSH. The separate runtime integration can observe DSH tool calls or, when explicitly configured as `protect`, enforce pre-execute policy through DSH's native approval protocol.
The Phase 1 scanner is intentionally read-only: it scans source, classifies capabilities, and recommends an installation posture. It never installs the target, executes package lifecycle scripts, evaluates Cordis `!!js` expressions, or starts DSH. The separately installed runtime integration enforces pre-execute policy through DSH's native approval protocol in its packaged default `protect` mode; operators can switch it to `observe` for audit-only evaluation.

## Install in DSH

Expand All @@ -18,9 +18,13 @@ When invoked inside DSH, bare `agentguard init` auto-detects DSH and performs th
The equivalent low-level DSH command is:

```bash
dsh plugin --profile web add @goplus/agentguard
dsh plugin --profile web add --allow-build=@goplus/agentguard @goplus/agentguard
```

The scoped `--allow-build` approval lets pnpm run AgentGuard's reviewed npm
postinstall lifecycle and records that approval in this DSH profile. The
high-level `agentguard init` commands add it automatically.

For local development, link the checkout instead:

```bash
Expand All @@ -29,7 +33,7 @@ dsh plugin --profile web add link:/absolute/path/to/agentguard

The profile then exposes `agentguard_dsh_scan`, which accepts a local directory or HTTPS GitHub repository URL, an optional GitHub `ref`, and a Markdown or JSON format. It also exposes `agentguard_dsh_scan_batch` for sequentially scanning up to 10 targets, `agentguard_dsh_compare` for comparing an approved version with a candidate, and `agentguard_dsh_runtime_summary` for input-redacted runtime audit aggregates. For example, ask DSH: “Use AgentGuard to compare tags `v1.2.3` and `v1.3.0` of `https://github.com/owner/plugin` before I update.”

The three static AgentGuard DSH tools preserve the Phase 1 boundary: they do not install or execute the target plugin. The fourth tool only summarizes local runtime audit events and never returns raw tool input. The installed bundle enables `observe` by default; [DSH runtime guard](dsh-runtime.md) documents explicit `protect` configuration.
The three static AgentGuard DSH tools preserve the Phase 1 boundary: they do not install or execute the target plugin. The fourth tool only summarizes local runtime audit events and never returns raw tool input. The installed bundle enables `protect` by default; the [DSH runtime guard](dsh-runtime.md) documents audit-only `observe` mode and the available protection settings.

### Operate the DSH installation

Expand All @@ -53,7 +57,7 @@ Verification checklist:
1. `dsh web --dump-config` contains `id: agentguard-dsh-plugin` and the `@goplus/agentguard/dist/dsh/plugin.js` entry.
2. DSH exposes the `agentguard_dsh_scan`, `agentguard_dsh_scan_batch`, `agentguard_dsh_compare`, and `agentguard_dsh_runtime_summary` tools.
3. A JSON scan contains `scanner.version`, `scanner.phase`, and `scanner.rulesBaseline`. Keep these fields with a saved report so later rescans can be compared to the same implementation.
4. `~/.agentguard/audit.jsonl` receives DSH events with `agentHost: "dsh"`. The default composition records `runtimeMode: "observe"`; an explicit protected composition records pre-execute events with `runtimeMode: "protect"` and `enforcementApplied: true`.
4. `~/.agentguard/audit.jsonl` receives DSH events with `agentHost: "dsh"`. The default composition records pre-execute events with `runtimeMode: "protect"` and `enforcementApplied: true`; an explicit audit-only composition records `runtimeMode: "observe"` and does not apply pre-execute enforcement.
5. After removal and restart, the AgentGuard composition row, tools, and runtime listener are absent.

If `http://127.0.0.1:3080/` returns `ERR_CONNECTION_REFUSED`, the DSH web process is not listening; it is not evidence of a scanner failure. Start or restart DSH and inspect its terminal output. If the tool is missing while DSH is running, check the explicit profile with `--dump-config`, then confirm the package appears in that profile's dependencies.
Expand All @@ -71,10 +75,10 @@ If `http://127.0.0.1:3080/` returns `ERR_CONNECTION_REFUSED`, the DSH web proces
| Preserve workspace and request context | Runtime | Uses the DSH session cwd plus shell workdir and network method/header/body fields supported by the shared evaluator. |
| Observe network responses | Runtime | Uses native `tools/post-execute`; status, content type, headers, bounded text preview, and explicit byte counts feed shared anomaly detection. |
| Summarize recent runtime decisions | Runtime | Bounded local aggregation; raw tool input and reason evidence are omitted. |
| Apply allow, warn, approve, or block decisions inside DSH | Opt-in `protect` | Pre-execute decisions use DSH native `allow`/`ask`/`deny`; optional `postResponseMode: block-malicious` suppresses block-class malicious network results while approval-class post results remain audit-only. |
| Apply allow, warn, approve, or block decisions inside DSH | Default `protect` | Pre-execute decisions use DSH native `allow`/`ask`/`deny`; optional `postResponseMode: block-malicious` suppresses block-class malicious network results while approval-class post results remain audit-only. |
| Attribute a call to its source plugin | Partial | Exact operator-configured tool-owner bindings are recorded; unmapped tools remain `unknown` and AgentGuard does not guess. |

Installing the bundle is non-disruptive because its packaged composition uses `observe`. Changing the runtime row to `protect` is the explicit opt-in for real-time pre-execute enforcement.
Installing the bundle enables real-time pre-execute enforcement because its packaged composition uses `protect`. Change the runtime row to `observe` only for audit-only shadow evaluation.

## Why this exists

Expand Down Expand Up @@ -105,7 +109,7 @@ Phase 1 does not include:
- Installing a plugin or resolving its lifecycle scripts.
- Fetching a package by npm name or comparing an npm tarball with its source repository.
- Resolving every layer of an already-installed DSH profile into one effective runtime tree.
- Automatically enabling runtime enforcement when the scanner is installed. Runtime protection is an explicit composition choice documented separately.
- Using a static scan to enable, disable, or alter runtime enforcement. The separately installed DSH bundle defaults to `protect` and can be switched explicitly to `observe`.
- Persisting scan history or integrating with a DSH marketplace.

## Command line
Expand Down
2 changes: 1 addition & 1 deletion dsh.cordis.patch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
name: '@goplus/agentguard/dist/dsh/plugin.js'
config:
runtime:
mode: observe
mode: protect
10 changes: 6 additions & 4 deletions scripts/test-dsh-package.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import assert from 'node:assert/strict';
import { execFile } from 'node:child_process';
import { access, mkdtemp, readFile, rm } from 'node:fs/promises';
import { access, mkdtemp, readFile, realpath, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { dirname, join, relative, resolve } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { promisify } from 'node:util';

Expand Down Expand Up @@ -71,14 +71,16 @@ try {
for (const path of required) assert.ok(archiveFiles.has(path), `tarball is missing ${path}`);
assert.ok(![...archiveFiles].some(path => path.startsWith('package/dist/tests/')), 'tarball contains compiled tests');

await dsh(['plugin', '--profile', 'web', 'add', tarball]);
await dsh(['plugin', '--profile', 'web', 'install', '--lockfile-only']);
const tarballBuildKey = `@goplus/agentguard@file:${relative(await realpath(profileDir), tarball).replaceAll('\\', '/')}`;
await dsh(['plugin', '--profile', 'web', 'add', `--allow-build=${tarballBuildKey}`, tarball]);
const installedManifest = JSON.parse(await readFile(join(profileDir, 'package.json'), 'utf8'));
assert.ok(installedManifest.dependencies?.['@goplus/agentguard']);
assert.ok(installedManifest.dsh?.profile?.bundles?.includes('@goplus/agentguard'));

const { stdout: composed } = await dsh(['web', '--dump-config']);
assert.match(composed, /id:\s*agentguard-dsh-plugin/);
assert.match(composed, /runtime:\s*\n\s+mode:\s*observe/);
assert.match(composed, /runtime:\s*\n\s+mode:\s*protect/);
const installedPlugin = join(profileDir, 'node_modules/@goplus/agentguard/dist/dsh/plugin.js');
const plugin = await import(`${pathToFileURL(installedPlugin).href}?package=${Date.now()}`);
const registeredTools = [];
Expand Down
2 changes: 1 addition & 1 deletion scripts/test-dsh-plugin-lifecycle.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ try {
const { stdout: composed } = await dsh(['web', '--dump-config']);
assert.match(composed, /id:\s*agentguard-dsh-plugin/);
assert.match(composed, /@goplus\/agentguard\/dist\/dsh\/plugin\.js/);
assert.match(composed, /runtime:\s*\n\s+mode:\s*observe/);
assert.match(composed, /runtime:\s*\n\s+mode:\s*protect/);

const installedPlugin = join(profileDir, 'node_modules/@goplus/agentguard/dist/dsh/plugin.js');
await access(installedPlugin);
Expand Down
2 changes: 1 addition & 1 deletion src/dsh/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export interface DshRuntimeObservation {
}

const SHELL_TOOLS = new Set([
'bash', 'terminal', 'shell', 'exec', 'exec_command', 'execute_command', 'execute_code',
'bash', 'pwsh', 'terminal', 'shell', 'exec', 'exec_command', 'execute_command', 'execute_code',
'run_command', 'run_shell_command', 'spawn_process',
]);
const READ_TOOLS = new Set([
Expand Down
Loading
Loading