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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [1.1.20] - 2026-05-27

### Changed
- `agentguard init --agent openclaw` now enables the AgentGuard plugin in both the main OpenClaw config and companion workspace state when either layout is detected.
- OpenClaw Gateway fallback requests now reuse configured bearer tokens from `AGENTGUARD_OPENCLAW_GATEWAY_TOKEN`, `OPENCLAW_GATEWAY_TOKEN`, or the local OpenClaw config for HTTP and WebSocket paths.
- `agentguard status` now refreshes Agent JWT account binding state and clears stale activation links once the saved Agent JWT is accepted by Cloud.

### Fixed
- Fixed Agent JWT activation messaging to describe account binding instead of email binding.
- Fixed subscribe cron runs so Agent JWT 401 responses prompt for a manual `agentguard connect` instead of automatically re-registering the local agent.
- Fixed native OpenClaw cron replacement and removal to delete existing jobs by job ID before reinstalling, avoiding reliance on unsupported `openclaw cron add --force` behavior.

## [1.1.18] - 2026-05-26

### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@goplus/agentguard",
"version": "1.1.18",
"version": "1.1.20",
"description": "GoPlus AgentGuard — Security guard for AI agents. Blocks dangerous commands, prevents data leaks, protects secrets. 20 detection rules, runtime action evaluation, trust registry.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
128 changes: 102 additions & 26 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
connectCloud,
connectAgentJwt,
clearAgentJwt,
clearAgentRegisterUrl,
disconnectCloud,
ensureConfig,
getAgentGuardPaths,
Expand Down Expand Up @@ -141,10 +142,11 @@ async function main() {
agentRegisterUrl: config.agentRegisterUrl,
cloudUrl,
});
saveCachedPolicy(savedConfig.policyCachePath, policy);
console.log(`Connected to AgentGuard Cloud (${savedConfig.cloudUrl}).`);
console.log(`Agent JWT is active for local agent ${savedConfig.agentId}.`);
console.log(`Cached policy ${policy.policyVersion} at ${savedConfig.policyCachePath}.`);
const activeConfig = clearAgentRegisterUrl(savedConfig);
saveCachedPolicy(activeConfig.policyCachePath, policy);
console.log(`Connected to AgentGuard Cloud (${activeConfig.cloudUrl}).`);
console.log(`Agent JWT is active for local agent ${activeConfig.agentId}.`);
console.log(`Cached policy ${policy.policyVersion} at ${activeConfig.policyCachePath}.`);
return;
} catch (err) {
if (!(err instanceof CloudRequestError && err.status === 401)) {
Expand All @@ -154,14 +156,19 @@ async function main() {
}
}
}
const registration = await registerAgentCredential({
cloudUrl,
reason: 'connect',
notifyOpenClaw: true,
resetExistingJwt: true,
});
let registration: AgentCredentialRegistration;
try {
registration = await registerAgentCredential({
cloudUrl,
reason: 'connect',
notifyOpenClaw: true,
resetExistingJwt: true,
});
} catch (err) {
throw new Error(`Could not register AgentGuard agent: ${err instanceof Error ? err.message : String(err)}`);
}
console.log(`Registered local AgentGuard agent (${registration.config.agentId}).`);
console.log('Open this link to bind AgentGuard Cloud to your email:');
console.log('Open this link to bind this agent to your account:');
console.log(registration.registerUrl);
if (registration.openClawNotification.notified) {
console.log('Sent the activation link to the last OpenClaw channel.');
Expand Down Expand Up @@ -204,8 +211,8 @@ async function main() {
program
.command('status')
.description('Show local and Cloud connection status')
.action(() => {
const config = ensureConfig();
.action(async () => {
const config = await refreshAgentAccountBinding(ensureConfig());
const paths = getAgentGuardPaths();
console.log(`Config: ${paths.configPath}`);
console.log(`Protection level: ${config.level}`);
Expand Down Expand Up @@ -399,6 +406,7 @@ async function main() {
const since = options.since as string | undefined;
const quiet = Boolean(options.quiet);
const cronNotifyRun = Boolean(options.cronNotifyRun);
const cronInternalRun = Boolean(options.cronRun || options.cronNotifyRun);
const cronTarget = validateCronTarget(options.cronTarget);
const cronRunSendsToOpenClaw = Boolean(options.cronRun) && cronAgentHost === 'openclaw';
const cronExpression = options.cron && !options.cronRun
Expand Down Expand Up @@ -443,6 +451,11 @@ async function main() {
await client.subscribeFeed();
} catch (err) {
if (err instanceof CloudRequestError && err.status === 401) {
if (cronInternalRun) {
await printSubscribeConnectRequired(options, cronRunSendsToOpenClaw);
process.exitCode = 1;
return;
}
if (!isOpenClawAgentConfigured(config)) {
console.error('! AgentGuard Cloud credential was rejected. Run `agentguard connect --key <key>` again.');
process.exitCode = 1;
Expand Down Expand Up @@ -489,6 +502,11 @@ async function main() {
advisories = await client.pullAdvisories(since);
} catch (err) {
if (err instanceof CloudRequestError && err.status === 401) {
if (cronInternalRun) {
await printSubscribeConnectRequired(options, cronRunSendsToOpenClaw);
process.exitCode = 1;
return;
}
if (!isOpenClawAgentConfigured(config)) {
console.error('! AgentGuard Cloud credential was rejected. Run `agentguard connect --key <key>` again.');
process.exitCode = 1;
Expand Down Expand Up @@ -569,20 +587,32 @@ async function main() {
// match, we must NOT mark the advisory seen, otherwise a
// transient network blip silently buries a real hit.
try {
const reportResult = await runCloudRequestWithAgentJwtReauth({
config,
client,
reason: 'reauth',
notifyOpenClaw: resolveCronAgentHost(config) === 'openclaw',
operation: (activeClient) => activeClient.reportSelfCheck(advisory.id, result.matchedArtifacts, {
if (cronInternalRun) {
await client.reportSelfCheck(advisory.id, result.matchedArtifacts, {
elapsedMs: result.elapsedMs,
warnings: result.warnings,
}),
});
config = reportResult.config;
client = reportResult.client;
if (reportResult.registration) registration = reportResult.registration;
});
} else {
const reportResult = await runCloudRequestWithAgentJwtReauth({
config,
client,
reason: 'reauth',
notifyOpenClaw: resolveCronAgentHost(config) === 'openclaw',
operation: (activeClient) => activeClient.reportSelfCheck(advisory.id, result.matchedArtifacts, {
elapsedMs: result.elapsedMs,
warnings: result.warnings,
}),
});
config = reportResult.config;
client = reportResult.client;
if (reportResult.registration) registration = reportResult.registration;
}
} catch (err) {
if (cronInternalRun && err instanceof CloudRequestError && err.status === 401) {
await printSubscribeConnectRequired(options, cronRunSendsToOpenClaw);
process.exitCode = 1;
return;
}
console.error(`! Failed to report self-check for ${advisory.id}: ${(err as Error).message}`);
processed = false;
hardFailures += 1;
Expand Down Expand Up @@ -894,7 +924,13 @@ function printCloudAuthStatus(config: AgentGuardConfig): void {
console.log('API key: not used for this connection');
console.log(`Agent ID: ${config.agentId || 'configured'}`);
console.log('Agent JWT: configured');
console.log(`Agent activation URL: ${config.agentRegisterUrl || 'not configured'}`);
if (config.agentRegisterUrl) {
console.log('Agent account: not bound (activation required)');
console.log(`Agent activation URL: ${config.agentRegisterUrl}`);
} else {
console.log('Agent account: bound');
console.log('Agent activation URL: not required');
}
return;
}
if (config.apiKey) {
Expand All @@ -909,6 +945,44 @@ function printCloudAuthStatus(config: AgentGuardConfig): void {
console.log('Agent JWT: not configured');
}

async function printSubscribeConnectRequired(
options: { json?: boolean; cronNotifyRun?: boolean },
notifyOpenClaw: boolean
): Promise<void> {
const message = 'AgentGuard Cloud credential was rejected. Run `agentguard connect` again before the next subscribe cron run.';
if (notifyOpenClaw) {
const notification = await notifyOpenClawMessage(message, resolveOpenClawGatewayOptionsFromEnv(), {
idempotencyKeyPrefix: 'agentguard-subscribe-auth',
});
if (notification.notified) {
console.log('NO_REPLY');
return;
}
console.error(`! Could not send OpenClaw cron auth notification: ${notification.reason ?? 'Unknown error'}`);
return;
}
if (options.cronNotifyRun) {
console.log(message);
} else if (options.json) {
console.log(JSON.stringify({ success: false, error: message }, null, 2));
} else {
console.error(`! ${message}`);
}
}

async function refreshAgentAccountBinding(config: AgentGuardConfig): Promise<AgentGuardConfig> {
if (!config.agentJwt || !config.agentRegisterUrl) return config;
const client = new AgentGuardCloudClient(config);
try {
const policy = await client.fetchEffectivePolicy();
const activeConfig = clearAgentRegisterUrl(config);
saveCachedPolicy(activeConfig.policyCachePath, policy);
return activeConfig;
} catch {
return config;
}
}

function printCronRemovalSummary(results: ThreatFeedCronRemovalResult[]): void {
const removed = results.filter((result) => result.removed);
if (removed.length > 0) {
Expand Down Expand Up @@ -1343,7 +1417,7 @@ function printAgentActivationRequired(
console.error(`! AgentGuard Cloud authorization is not active yet. ${message}`);
const registerUrl = registration?.registerUrl || ensureConfig().agentRegisterUrl;
if (registerUrl) {
console.error('Open this link to bind this agent to your email, then rerun the command:');
console.error('Open this link to bind this agent to your account, then rerun the command:');
console.error(registerUrl);
}
}
Expand Down Expand Up @@ -1376,13 +1450,15 @@ function detectOpenClawRuntime(): boolean {
function resolveOpenClawGatewayOptionsFromEnv(): OpenClawGatewayOptions {
const url = process.env.AGENTGUARD_OPENCLAW_GATEWAY_URL?.trim();
const host = process.env.AGENTGUARD_OPENCLAW_GATEWAY_HOST?.trim();
const token = process.env.AGENTGUARD_OPENCLAW_GATEWAY_TOKEN?.trim();
const portRaw = process.env.AGENTGUARD_OPENCLAW_GATEWAY_PORT?.trim();
const timeoutRaw = process.env.AGENTGUARD_OPENCLAW_GATEWAY_TIMEOUT_MS?.trim();
const port = portRaw ? Number(portRaw) : undefined;
const timeoutMs = timeoutRaw ? Number(timeoutRaw) : undefined;
return {
...(url ? { url } : {}),
...(host ? { host } : {}),
...(token ? { token } : {}),
...(Number.isFinite(port) ? { port } : {}),
...(Number.isFinite(timeoutMs) ? { timeoutMs } : {}),
};
Expand Down
7 changes: 7 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,13 @@ export function clearAgentJwt(config: AgentGuardConfig = ensureConfig()): AgentG
return next;
}

export function clearAgentRegisterUrl(config: AgentGuardConfig = ensureConfig()): AgentGuardConfig {
const next: AgentGuardConfig = { ...config };
delete next.agentRegisterUrl;
saveConfig(next);
return next;
}

export function disconnectCloud(): AgentGuardConfig {
const current = ensureConfig();
const next: AgentGuardConfig = { ...current };
Expand Down
Loading
Loading