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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Changelog

## [1.1.6] - 2026-05-18
## [1.1.7] - 2026-05-18

### Fixed
- Added the missing `agentguard policy pull` command used by AgentGuard Cloud policy refresh instructions.
- OpenClaw installs now enable the AgentGuard plugin when installing the skill through `setup.sh` or running `agentguard init --agent openclaw`.
- Added a dedicated OpenClaw package entry so OpenClaw loads the runtime plugin instead of the generic SDK entrypoint.

## [1.1.5] - 2026-05-18

Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ agentguard subscribe --json
# Or run a one-off self-check against a single advisory id
agentguard checkup --against-advisory AGS-2026-0042

# Optional: write host-specific hook templates
# Optional: write host-specific hook templates.
# OpenClaw also installs and enables the AgentGuard plugin.
agentguard init --agent claude-code
agentguard init --agent codex
agentguard init --agent openclaw
Expand Down Expand Up @@ -135,7 +136,13 @@ cp -r agentguard/skills/agentguard ~/.claude/skills/agentguard
npm install @goplus/agentguard
```

Register in your OpenClaw plugin config:
Then enable it:

```bash
agentguard init --agent openclaw
```

Or register manually in your OpenClaw plugin config:

```typescript
import register from '@goplus/agentguard/openclaw';
Expand Down
4 changes: 2 additions & 2 deletions docs/openclaw.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ OpenClaw can use AgentGuard as a local runtime guard and optional Cloud-connecte

## Plugin usage

To write a starter plugin file in the current project:
To install and enable the AgentGuard OpenClaw plugin:

```bash
agentguard init --agent openclaw
```

This creates `openclaw.agentguard.plugin.ts`.
This creates a local plugin under `~/.openclaw/plugins/agentguard` and enables it in `~/.openclaw/openclaw.json`.

```ts
import { registerOpenClawPlugin } from '@goplus/agentguard';
Expand Down
6 changes: 6 additions & 0 deletions openclaw.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default } from './dist/openclaw.js';
export {
getPluginIdFromTool,
getPluginScanResult,
registerOpenClawPlugin,
} from './dist/openclaw.js';
1 change: 1 addition & 0 deletions openclaw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/openclaw.js');
1 change: 1 addition & 0 deletions openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": "agentguard",
"name": "GoPlus AgentGuard",
"description": "AI agent security framework — blocks dangerous commands, prevents data leaks, and protects secrets",
"skills": ["./skills"],
"configSchema": {
"type": "object",
"properties": {
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"types": "dist/index.d.ts",
"openclaw": {
"extensions": [
"./dist/index.js"
"./dist/openclaw.js"
]
},
"bin": {
Expand Down Expand Up @@ -66,6 +66,8 @@
"examples/openclaw-docker",
"README.md",
"LICENSE",
"openclaw.d.ts",
"openclaw.js",
"openclaw.plugin.json",
"skills"
]
Expand Down
74 changes: 74 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_SRC="$SCRIPT_DIR/skills/agentguard"
AGENTGUARD_DIR="$HOME/.agentguard"
MIN_NODE_VERSION=18
OPENCLAW_ROOT="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
OPENCLAW_PLUGIN_DIR="$OPENCLAW_ROOT/plugins/agentguard"
OPENCLAW_CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-$OPENCLAW_ROOT/openclaw.json}"

echo ""
echo " GoPlus AgentGuard — AI Agent Security Guard"
Expand Down Expand Up @@ -76,6 +79,7 @@ if [ "${1:-}" = "--uninstall" ] || [ "${1:-}" = "uninstall" ]; then
rm -rf "$HOME/.claude/skills/agentguard" 2>/dev/null || true
rm -rf "$HOME/.openclaw/skills/agentguard" 2>/dev/null || true
rm -rf "$HOME/.openclaw/workspace/skills/agentguard" 2>/dev/null || true
rm -rf "$OPENCLAW_PLUGIN_DIR" 2>/dev/null || true
rm -rf "$AGENTGUARD_DIR" 2>/dev/null && echo " Removed config from $AGENTGUARD_DIR" || true
echo ""
echo " GoPlus AgentGuard has been uninstalled."
Expand Down Expand Up @@ -145,6 +149,76 @@ else
echo " OK: Config already exists (keeping current settings)"
fi

if [ "$PLATFORM" = "openclaw-workspace" ] || [ "$PLATFORM" = "openclaw-managed" ]; then
echo " Enabling OpenClaw plugin..."
mkdir -p "$OPENCLAW_PLUGIN_DIR"
AGENTGUARD_DIST_INDEX="$SCRIPT_DIR/dist/index.js" node - "$OPENCLAW_PLUGIN_DIR/index.js" <<'NODE'
const { writeFileSync } = require('node:fs');
const pluginPath = process.argv[2];
const distIndex = process.env.AGENTGUARD_DIST_INDEX;
writeFileSync(pluginPath, `const { registerOpenClawPlugin } = require(${JSON.stringify(distIndex)});

module.exports = function setup(api) {
registerOpenClawPlugin(api, {
skipAutoScan: false,
});
};
module.exports.default = module.exports;
`);
NODE
cat > "$OPENCLAW_PLUGIN_DIR/openclaw.plugin.json" <<'JSON'
{
"id": "agentguard",
"name": "GoPlus AgentGuard",
"description": "AI agent security framework — blocks dangerous commands, prevents data leaks, and protects secrets",
"configSchema": {
"type": "object",
"properties": {
"level": {
"type": "string",
"enum": ["strict", "balanced", "permissive"],
"default": "balanced",
"description": "Protection level: strict (block all risky), balanced (block dangerous, confirm risky), permissive (only block critical)"
}
}
}
}
JSON
node - "$OPENCLAW_CONFIG_PATH" "$OPENCLAW_PLUGIN_DIR" <<'NODE'
const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('node:fs');
const { dirname } = require('node:path');
const [configPath, pluginDir] = process.argv.slice(2);
const ensureRecord = (parent, key) => {
const existing = parent[key];
if (existing && typeof existing === 'object' && !Array.isArray(existing)) return existing;
const next = {};
parent[key] = next;
return next;
};
let config = {};
if (existsSync(configPath)) {
const raw = readFileSync(configPath, 'utf8').trim();
config = raw ? JSON.parse(raw) : {};
}
const plugins = ensureRecord(config, 'plugins');
const load = ensureRecord(plugins, 'load');
const entries = ensureRecord(plugins, 'entries');
const agentguard = ensureRecord(entries, 'agentguard');
agentguard.enabled = true;
const paths = Array.isArray(load.paths) ? load.paths.filter((p) => typeof p === 'string') : [];
if (!paths.includes(pluginDir)) paths.push(pluginDir);
load.paths = paths;
if (Array.isArray(plugins.allow)) {
const allow = plugins.allow.filter((id) => typeof id === 'string');
if (!allow.includes('agentguard')) allow.push('agentguard');
plugins.allow = allow;
}
mkdirSync(dirname(configPath), { recursive: true });
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
NODE
echo " OK: OpenClaw plugin enabled in $OPENCLAW_CONFIG_PATH"
fi

# ---- Done ----
echo ""
echo " ✅ GoPlus AgentGuard is installed!"
Expand Down
41 changes: 39 additions & 2 deletions skills/agentguard/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ filesystem-access:
reason: "Read/write audit log (audit.jsonl) and protection level config (config.json)"
user-invocable: true
allowed-tools: Read, Write, Grep, Glob, Bash(node *trust-cli.ts *) Bash(node *action-cli.ts *) Bash(*checkup-report.js) Bash(echo *checkup-report.js) Bash(cat *checkup-report.js) Bash(agentguard *) Bash(openclaw *) Bash(ss *) Bash(lsof *) Bash(ufw *) Bash(iptables *) Bash(crontab *) Bash(systemctl list-timers *) Bash(find *) Bash(stat *) Bash(env) Bash(sha256sum *) Bash(node *) Bash(cd *)
argument-hint: "[scan|action|patrol|subscribe|trust|report|config|checkup] [args...]"
argument-hint: "[scan|action|patrol|subscribe|trust|report|config|checkup|cli] [args...]"
---

# GoPlus AgentGuard — AI Agent Security Framework
Expand Down Expand Up @@ -64,9 +64,32 @@ Parse `$ARGUMENTS` to determine the subcommand:
- **`config <strict|balanced|permissive>`** — Set protection level
- **`checkup`** — Run a comprehensive agent health checkup and generate a visual HTML report
- **`hermes-hooks`** — Show or install Hermes shell-hook configuration for runtime protection
- **`cli <args...>`** — Run the installed `agentguard` CLI directly for supported commands not otherwise routed by this skill

If no subcommand is given, or the first argument is a path, default to **scan**.

### CLI Passthrough

This skill is allowed to run `agentguard *`, so CLI commands and flags are available even when the skill has a higher-level workflow for the same area.

Use CLI passthrough when the user explicitly asks for a concrete `agentguard ...` command, when the command is one of the CLI-only commands below, or when a CLI flag changes semantics that this skill's high-level workflow does not implement.

Supported CLI commands and options:

| CLI command | Options | Notes |
|---|---|---|
| `agentguard init` | `--level <level>`, `--agent <agent>`, `--cloud <url>`, `--force` | Creates local config and optionally installs agent templates |
| `agentguard connect` | `--key <key>`, `--api-key <key>`, `--url <url>`, `--cloud <url>` | Prefer `AGENTGUARD_API_KEY` over passing secrets in flags |
| `agentguard status` | none | Shows local config, Cloud URL/API key status, policy cache, audit path |
| `agentguard policy pull` | `--json` | Pulls Cloud effective runtime policy into the local cache |
| `agentguard doctor` | none | Checks local setup and Cloud reachability when connected |
| `agentguard scan <path>` | `--json` | Runs the packaged scanner against a local path |
| `agentguard protect` | `--agent <agent>`, `--action-type <type>`, `--tool-name <name>`, `--session-id <id>`, `--decision-mode <local-first|cloud>`, `--json` | Evaluates one runtime action from stdin or hook environment |
| `agentguard subscribe` | `--since <iso>`, `--json`, `--no-report`, `--install-cron`, `--cron-name <name>`, `--interval-minutes <minutes>`, `--force`, `--cron-run` | Pulls Cloud threat advisories and self-checks local skills |
| `agentguard checkup` | `--against-advisory <id>`, `--json` | CLI threat-feed self-check; without `--against-advisory`, it only prints a tip in the current CLI build |

If the user writes `/agentguard cli <args...>`, execute `agentguard <args...>` directly. If the user writes `/agentguard checkup --against-advisory <id>`, use the CLI command `agentguard checkup --against-advisory <id>` instead of the comprehensive HTML health-report workflow.

## Subcommand: hermes-hooks

Help the user configure AgentGuard runtime protection for Hermes Agent.
Expand Down Expand Up @@ -152,15 +175,20 @@ Examples:
```bash
agentguard subscribe
agentguard subscribe --json
agentguard subscribe --since 2026-05-01T00:00:00.000Z
agentguard subscribe --no-report
agentguard subscribe --install-cron
agentguard subscribe --install-cron --cron-name agentguard-threat-feed
agentguard subscribe --install-cron --interval-minutes 5
agentguard subscribe --install-cron --force
```

When `--install-cron` is used, the CLI registers an OpenClaw isolated cron job through the local OpenClaw Gateway at `127.0.0.1:18789`. It runs every 15 minutes by default. Pass `--interval-minutes <n>` to override the cadence. If a job with the same name already exists, the CLI leaves it untouched unless `--force` is passed. The cron delivery is intentionally silent (`delivery.mode = "none"`); the isolated turn executes `agentguard subscribe --json --cron-run` and only sends the configured notification when `shouldNotify` is `true`.
When `--install-cron` is used, the CLI registers an OpenClaw isolated cron job through the local OpenClaw Gateway at `127.0.0.1:18789`. It runs every 15 minutes by default. Pass `--interval-minutes <n>` to override the cadence and `--cron-name <name>` to choose the job name. If a job with the same name already exists, the CLI leaves it untouched unless `--force` is passed. The cron delivery is intentionally silent (`delivery.mode = "none"`); the isolated turn executes `agentguard subscribe --json --cron-run` and only sends the configured notification when `shouldNotify` is `true`.

`agentguard subscribe --json` always includes a stable `cron` object with `requested`, `installed`, and optional `result` fields. If cron installation fails, the command exits non-zero instead of printing a misleading success summary.

`--since <iso>` overrides the persisted feed cursor for one run. `--no-report` skips uploading local matches back to Cloud. `--cron-run` is internal and should only be used by the OpenClaw cron prompt unless the user explicitly asks to reproduce cron behavior.

---

# Security Operations
Expand Down Expand Up @@ -726,6 +754,15 @@ If the log file doesn't exist, inform the user that no security events have been

Run a comprehensive agent health checkup across 6 security dimensions. Generates a visual HTML report with a lobster mascot and opens it in the browser. The lobster's appearance reflects the agent's health: muscular bodybuilder (score 90+), healthy with shield (70–89), tired with coffee (50–69), or sick with bandages (0–49).

If the arguments include `--against-advisory <id>`, do not run this comprehensive HTML workflow. Instead execute the CLI threat-feed self-check:

```bash
agentguard checkup --against-advisory <id>
agentguard checkup --against-advisory <id> --json
```

That CLI path fetches the current Cloud advisory feed and checks local skills against the single advisory. It is separate from the full health report below.

### Step 1: Data Collection

**IMPORTANT: You MUST run ALL 7 checks below — not just the skill scan. The checkup covers 5 security dimensions, not just code scanning. Do NOT skip checks 2–7.**
Expand Down
83 changes: 78 additions & 5 deletions src/installers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';

export type AgentInstaller = 'claude-code' | 'codex' | 'openclaw';
Expand All @@ -12,7 +13,7 @@ export function installAgentTemplates(agent: AgentInstaller, options: { cwd?: st
const root = options.cwd || process.cwd();
if (agent === 'claude-code') return installClaudeCode(root, Boolean(options.force));
if (agent === 'codex') return installCodex(root, Boolean(options.force));
if (agent === 'openclaw') return installOpenClaw(root, Boolean(options.force));
if (agent === 'openclaw') return installOpenClaw(options.cwd, Boolean(options.force));
throw new Error(`Unsupported agent installer: ${agent}`);
}

Expand All @@ -36,10 +37,22 @@ function installCodex(root: string, force: boolean): InstallResult {
return { agent: 'codex', files: [skillPath, hookPath] };
}

function installOpenClaw(root: string, force: boolean): InstallResult {
const pluginPath = join(root, 'openclaw.agentguard.plugin.ts');
function installOpenClaw(cwd: string | undefined, force: boolean): InstallResult {
const openClawRoot = cwd
? join(cwd, '.openclaw')
: process.env.OPENCLAW_STATE_DIR || join(homedir(), '.openclaw');
const pluginDir = join(openClawRoot, 'plugins', 'agentguard');
const pluginPath = join(pluginDir, 'index.ts');
const manifestPath = join(pluginDir, 'openclaw.plugin.json');
const configPath = cwd
? join(openClawRoot, 'openclaw.json')
: process.env.OPENCLAW_CONFIG_PATH || join(openClawRoot, 'openclaw.json');

writeIfAllowed(pluginPath, openClawPluginTemplate(), force);
return { agent: 'openclaw', files: [pluginPath] };
writeIfAllowed(manifestPath, JSON.stringify(openClawPluginManifest(), null, 2) + '\n', force);
enableOpenClawPlugin(configPath, pluginDir);

return { agent: 'openclaw', files: [pluginPath, manifestPath, configPath] };
}

function writeIfAllowed(path: string, content: string, force: boolean): void {
Expand Down Expand Up @@ -147,3 +160,63 @@ export default function setup(api) {
}
`;
}

function openClawPluginManifest(): unknown {
return {
id: 'agentguard',
name: 'GoPlus AgentGuard',
description: 'AI agent security framework - blocks dangerous commands, prevents data leaks, and protects secrets',
configSchema: {
type: 'object',
properties: {
level: {
type: 'string',
enum: ['strict', 'balanced', 'permissive'],
default: 'balanced',
description: 'Protection level: strict (block all risky), balanced (block dangerous, confirm risky), permissive (only block critical)',
},
},
},
};
}

function enableOpenClawPlugin(configPath: string, pluginDir: string): void {
let config: Record<string, unknown> = {};
if (existsSync(configPath)) {
const raw = readFileSync(configPath, 'utf8').trim();
config = raw ? JSON.parse(raw) as Record<string, unknown> : {};
}

const plugins = ensureRecord(config, 'plugins');
const load = ensureRecord(plugins, 'load');
const entries = ensureRecord(plugins, 'entries');
const agentguard = ensureRecord(entries, 'agentguard');
agentguard.enabled = true;

const paths = Array.isArray(load.paths) ? load.paths.filter((p): p is string => typeof p === 'string') : [];
if (!paths.includes(pluginDir)) {
paths.push(pluginDir);
}
load.paths = paths;

if (Array.isArray(plugins.allow)) {
const allow = plugins.allow.filter((id): id is string => typeof id === 'string');
if (!allow.includes('agentguard')) {
allow.push('agentguard');
}
plugins.allow = allow;
}

mkdirSync(dirname(configPath), { recursive: true });
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
}

function ensureRecord(parent: Record<string, unknown>, key: string): Record<string, unknown> {
const existing = parent[key];
if (existing && typeof existing === 'object' && !Array.isArray(existing)) {
return existing as Record<string, unknown>;
}
const next: Record<string, unknown> = {};
parent[key] = next;
return next;
}
6 changes: 6 additions & 0 deletions src/openclaw.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { default } from './adapters/openclaw-plugin.js';
export {
getPluginIdFromTool,
getPluginScanResult,
registerOpenClawPlugin,
} from './adapters/openclaw-plugin.js';
Loading
Loading