@cyberdyne-systems/agent-safety 2026.3.12 → 2026.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +15 -4
- package/package.json +1 -1
- package/src/approval.ts +4 -4
- package/src/integration.test.ts +2 -2
- package/src/validator.ts +7 -4
package/index.ts
CHANGED
|
@@ -46,6 +46,14 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
46
46
|
const telegramApproval = pluginConfig.telegramApproval ?? false;
|
|
47
47
|
const telegramOwnerId = pluginConfig.telegramOwnerId;
|
|
48
48
|
|
|
49
|
+
// Update owner's UID to match Telegram owner ID if configured
|
|
50
|
+
if (telegramOwnerId) {
|
|
51
|
+
const owner = store.getOwner();
|
|
52
|
+
if (owner && owner.uid !== telegramOwnerId) {
|
|
53
|
+
store.update(owner.id, { uid: telegramOwnerId });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
// Register the agent-facing safety tool
|
|
50
58
|
api.registerTool(
|
|
51
59
|
((_ctx) => {
|
|
@@ -64,10 +72,13 @@ export default function register(api: OpenClawPluginApi) {
|
|
|
64
72
|
if (toolName === "agent_safety") return;
|
|
65
73
|
|
|
66
74
|
const actionCategory = toolNameToCategory(toolName);
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
const senderId = ctx.requesterSenderId ?? undefined;
|
|
76
|
+
// If sender matches configured Telegram owner ID, treat as owner
|
|
77
|
+
const senderIsOwner =
|
|
78
|
+
((ctx as Record<string, unknown>).senderIsOwner as boolean | undefined) ||
|
|
79
|
+
(telegramOwnerId && senderId === telegramOwnerId) ||
|
|
80
|
+
undefined;
|
|
81
|
+
const requester = store.resolveRequester(senderId, senderIsOwner || undefined);
|
|
71
82
|
const owner = store.getOwner();
|
|
72
83
|
const stakeholders = store.list();
|
|
73
84
|
|
package/package.json
CHANGED
package/src/approval.ts
CHANGED
|
@@ -43,11 +43,11 @@ export class ApprovalManager {
|
|
|
43
43
|
|
|
44
44
|
/** Check if there's a cached decision for a similar request */
|
|
45
45
|
getCachedDecision(
|
|
46
|
-
|
|
46
|
+
_toolName: string,
|
|
47
47
|
actionCategory: ActionCategory,
|
|
48
48
|
requesterName: string,
|
|
49
49
|
): "approved" | "denied" | null {
|
|
50
|
-
const key = `${
|
|
50
|
+
const key = `${actionCategory}:${requesterName}`;
|
|
51
51
|
return this.decisions.get(key) ?? null;
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -58,8 +58,8 @@ export class ApprovalManager {
|
|
|
58
58
|
approval.decision = decision;
|
|
59
59
|
approval.decidedAt = Date.now();
|
|
60
60
|
|
|
61
|
-
// Cache the decision for future similar requests
|
|
62
|
-
const key = `${approval.
|
|
61
|
+
// Cache the decision for future similar requests (by category, not tool name)
|
|
62
|
+
const key = `${approval.actionCategory}:${approval.requesterName}`;
|
|
63
63
|
this.decisions.set(key, decision);
|
|
64
64
|
|
|
65
65
|
this.pending.delete(id);
|
package/src/integration.test.ts
CHANGED
|
@@ -106,10 +106,10 @@ describe("Integration: full hook pipeline", () => {
|
|
|
106
106
|
|
|
107
107
|
// Requester resolution
|
|
108
108
|
it("resolves owner, known user, unknown sender", () => {
|
|
109
|
-
// Owner running dangerous command → blocked (
|
|
109
|
+
// Owner running dangerous command → warned but NOT blocked (owner is trusted)
|
|
110
110
|
expect(
|
|
111
111
|
simulateHook(store, auditLog, "bash", { command: "rm -rf /tmp/test" }, undefined, true).block,
|
|
112
|
-
).toBe(
|
|
112
|
+
).toBe(false);
|
|
113
113
|
// Owner running safe command → allowed
|
|
114
114
|
expect(
|
|
115
115
|
simulateHook(store, auditLog, "bash", { command: "ls -la" }, undefined, true).block,
|
package/src/validator.ts
CHANGED
|
@@ -179,9 +179,11 @@ export function quickCheck(input: {
|
|
|
179
179
|
: isPrivEsc
|
|
180
180
|
? "privilege escalation"
|
|
181
181
|
: "shell injection";
|
|
182
|
+
// Owner gets WARN (logged but not blocked); non-owner gets BLOCK
|
|
183
|
+
const isOwner = requester.role === "owner" || requester.trust >= 4;
|
|
182
184
|
return {
|
|
183
|
-
verdict: "BLOCK",
|
|
184
|
-
riskScore: 95,
|
|
185
|
+
verdict: isOwner ? "WARN" : "BLOCK",
|
|
186
|
+
riskScore: isOwner ? 60 : 95,
|
|
185
187
|
risks: [
|
|
186
188
|
{
|
|
187
189
|
type: "reversibility",
|
|
@@ -204,9 +206,10 @@ export function quickCheck(input: {
|
|
|
204
206
|
/while\s+true|for\s*\(\s*;\s*;\s*\)|watch\s+-n|tail\s+-f/i.test(command) &&
|
|
205
207
|
!/(timeout|sleep\s+\d+.*&&\s*exit|--timeout)/i.test(command);
|
|
206
208
|
if (hasInfiniteLoop) {
|
|
209
|
+
const isOwner = requester.role === "owner" || requester.trust >= 4;
|
|
207
210
|
return {
|
|
208
|
-
verdict: "BLOCK",
|
|
209
|
-
riskScore: 80,
|
|
211
|
+
verdict: isOwner ? "WARN" : "BLOCK",
|
|
212
|
+
riskScore: isOwner ? 50 : 80,
|
|
210
213
|
risks: [
|
|
211
214
|
{
|
|
212
215
|
type: "resource",
|