@gatewaystack/gatewaystack-governance 0.1.0
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/README.md +187 -0
- package/SKILL.md +81 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +64 -0
- package/policy.example.json +81 -0
- package/references/attack-patterns.md +45 -0
- package/references/policy-reference.md +141 -0
- package/scripts/governance/audit.d.ts +2 -0
- package/scripts/governance/audit.js +53 -0
- package/scripts/governance/check.d.ts +8 -0
- package/scripts/governance/check.js +172 -0
- package/scripts/governance/cli.d.ts +4 -0
- package/scripts/governance/cli.js +208 -0
- package/scripts/governance/constants.d.ts +7 -0
- package/scripts/governance/constants.js +99 -0
- package/scripts/governance/identity.d.ts +7 -0
- package/scripts/governance/identity.js +40 -0
- package/scripts/governance/injection.d.ts +6 -0
- package/scripts/governance/injection.js +59 -0
- package/scripts/governance/policy.d.ts +2 -0
- package/scripts/governance/policy.js +56 -0
- package/scripts/governance/rate-limit.d.ts +5 -0
- package/scripts/governance/rate-limit.js +156 -0
- package/scripts/governance/scope.d.ts +5 -0
- package/scripts/governance/scope.js +27 -0
- package/scripts/governance/types.d.ts +73 -0
- package/scripts/governance/types.js +2 -0
- package/scripts/governance/utils.d.ts +1 -0
- package/scripts/governance/utils.js +40 -0
- package/scripts/governance/validate-policy.d.ts +6 -0
- package/scripts/governance/validate-policy.js +104 -0
- package/scripts/governance-gateway.d.ts +11 -0
- package/scripts/governance-gateway.js +23 -0
- package/scripts/governance-gateway.ts +24 -0
- package/src/plugin.d.ts +17 -0
- package/src/plugin.js +98 -0
- package/src/plugin.ts +90 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkRateLimit = checkRateLimit;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const constants_js_1 = require("./constants.js");
|
|
39
|
+
const LOCK_PATH = constants_js_1.RATE_LIMIT_STATE_PATH + ".lock";
|
|
40
|
+
const LOCK_TIMEOUT_MS = 5000;
|
|
41
|
+
const LOCK_RETRY_MS = 50;
|
|
42
|
+
function isLockStale() {
|
|
43
|
+
try {
|
|
44
|
+
const pidStr = fs.readFileSync(LOCK_PATH, "utf-8").trim();
|
|
45
|
+
const pid = parseInt(pidStr, 10);
|
|
46
|
+
if (isNaN(pid))
|
|
47
|
+
return true;
|
|
48
|
+
// process.kill(pid, 0) throws if the process doesn't exist
|
|
49
|
+
process.kill(pid, 0);
|
|
50
|
+
return false; // process is alive, lock is valid
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return true; // can't read or process is dead — stale lock
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function acquireLock() {
|
|
57
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
58
|
+
while (Date.now() < deadline) {
|
|
59
|
+
try {
|
|
60
|
+
// O_EXCL: fail if file exists — atomic advisory lock
|
|
61
|
+
fs.writeFileSync(LOCK_PATH, String(process.pid), { flag: "wx" });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Lock file exists — check if the holding process is still alive
|
|
66
|
+
if (isLockStale()) {
|
|
67
|
+
try {
|
|
68
|
+
fs.unlinkSync(LOCK_PATH);
|
|
69
|
+
continue; // retry immediately after clearing stale lock
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// another process beat us to cleanup — retry normally
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Lock held by a live process — spin-wait
|
|
76
|
+
const start = Date.now();
|
|
77
|
+
while (Date.now() - start < LOCK_RETRY_MS) {
|
|
78
|
+
// busy wait
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
function releaseLock() {
|
|
85
|
+
try {
|
|
86
|
+
fs.unlinkSync(LOCK_PATH);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Lock already released or never acquired
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function loadRateLimitState() {
|
|
93
|
+
if (fs.existsSync(constants_js_1.RATE_LIMIT_STATE_PATH)) {
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(fs.readFileSync(constants_js_1.RATE_LIMIT_STATE_PATH, "utf-8"));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
function saveRateLimitState(state) {
|
|
104
|
+
fs.writeFileSync(constants_js_1.RATE_LIMIT_STATE_PATH, JSON.stringify(state, null, 2));
|
|
105
|
+
}
|
|
106
|
+
function _checkRateLimitInner(userId, session, policy) {
|
|
107
|
+
const state = loadRateLimitState();
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
// Per-user check
|
|
110
|
+
const userKey = `user:${userId}`;
|
|
111
|
+
const userState = state[userKey] || { calls: [] };
|
|
112
|
+
const userWindow = policy.rateLimits.perUser.windowSeconds * 1000;
|
|
113
|
+
userState.calls = userState.calls.filter((c) => now - c.timestamp < userWindow);
|
|
114
|
+
if (userState.calls.length >= policy.rateLimits.perUser.maxCalls) {
|
|
115
|
+
return {
|
|
116
|
+
allowed: false,
|
|
117
|
+
detail: `User ${userId} exceeded rate limit: ${policy.rateLimits.perUser.maxCalls} calls per ${policy.rateLimits.perUser.windowSeconds}s (current: ${userState.calls.length})`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
// Per-session check
|
|
121
|
+
if (session) {
|
|
122
|
+
const sessionKey = `session:${session}`;
|
|
123
|
+
const sessionState = state[sessionKey] || { calls: [] };
|
|
124
|
+
const sessionWindow = policy.rateLimits.perSession.windowSeconds * 1000;
|
|
125
|
+
sessionState.calls = sessionState.calls.filter((c) => now - c.timestamp < sessionWindow);
|
|
126
|
+
if (sessionState.calls.length >= policy.rateLimits.perSession.maxCalls) {
|
|
127
|
+
return {
|
|
128
|
+
allowed: false,
|
|
129
|
+
detail: `Session ${session} exceeded rate limit: ${policy.rateLimits.perSession.maxCalls} calls per ${policy.rateLimits.perSession.windowSeconds}s`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
sessionState.calls.push({ timestamp: now });
|
|
133
|
+
state[sessionKey] = sessionState;
|
|
134
|
+
}
|
|
135
|
+
userState.calls.push({ timestamp: now });
|
|
136
|
+
state[userKey] = userState;
|
|
137
|
+
saveRateLimitState(state);
|
|
138
|
+
return {
|
|
139
|
+
allowed: true,
|
|
140
|
+
detail: `Rate limit OK: ${userState.calls.length}/${policy.rateLimits.perUser.maxCalls} calls in window`,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function checkRateLimit(userId, session, policy) {
|
|
144
|
+
if (!acquireLock()) {
|
|
145
|
+
return {
|
|
146
|
+
allowed: false,
|
|
147
|
+
detail: "Rate limit state lock timeout — concurrent access. Try again.",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
return _checkRateLimitInner(userId, session, policy);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
releaseLock();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkScope = checkScope;
|
|
4
|
+
function checkScope(tool, roles, policy) {
|
|
5
|
+
const toolPolicy = policy.allowedTools[tool];
|
|
6
|
+
// Deny-by-default: tool must be explicitly allowlisted
|
|
7
|
+
if (!toolPolicy) {
|
|
8
|
+
return {
|
|
9
|
+
allowed: false,
|
|
10
|
+
detail: `Tool "${tool}" is not in the allowlist. Deny-by-default policy enforced.`,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// If tool has role restrictions, check them
|
|
14
|
+
if (toolPolicy.roles && toolPolicy.roles.length > 0) {
|
|
15
|
+
const hasRole = roles.some((r) => toolPolicy.roles.includes(r));
|
|
16
|
+
if (!hasRole) {
|
|
17
|
+
return {
|
|
18
|
+
allowed: false,
|
|
19
|
+
detail: `Tool "${tool}" requires roles [${toolPolicy.roles.join(", ")}] but user has [${roles.join(", ")}]`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
allowed: true,
|
|
25
|
+
detail: `Tool "${tool}" is allowlisted for roles [${roles.join(", ")}]`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export interface Policy {
|
|
2
|
+
allowedTools: Record<string, {
|
|
3
|
+
roles?: string[];
|
|
4
|
+
maxArgsLength?: number;
|
|
5
|
+
description?: string;
|
|
6
|
+
}>;
|
|
7
|
+
rateLimits: {
|
|
8
|
+
perUser: {
|
|
9
|
+
maxCalls: number;
|
|
10
|
+
windowSeconds: number;
|
|
11
|
+
};
|
|
12
|
+
perSession: {
|
|
13
|
+
maxCalls: number;
|
|
14
|
+
windowSeconds: number;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
identityMap: Record<string, {
|
|
18
|
+
userId: string;
|
|
19
|
+
roles: string[];
|
|
20
|
+
}>;
|
|
21
|
+
injectionDetection: {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
sensitivity: "low" | "medium" | "high";
|
|
24
|
+
customPatterns?: string[];
|
|
25
|
+
};
|
|
26
|
+
auditLog: {
|
|
27
|
+
path: string;
|
|
28
|
+
maxFileSizeMB: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface GovernanceRequest {
|
|
32
|
+
action: "check" | "log-result" | "self-test";
|
|
33
|
+
tool?: string;
|
|
34
|
+
args?: string;
|
|
35
|
+
user?: string;
|
|
36
|
+
channel?: string;
|
|
37
|
+
session?: string;
|
|
38
|
+
requestId?: string;
|
|
39
|
+
result?: string;
|
|
40
|
+
output?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface GovernanceCheckResult {
|
|
43
|
+
allowed: boolean;
|
|
44
|
+
reason?: string;
|
|
45
|
+
requestId: string;
|
|
46
|
+
identity?: string;
|
|
47
|
+
roles?: string[];
|
|
48
|
+
patterns?: string[];
|
|
49
|
+
}
|
|
50
|
+
export interface AuditEntry {
|
|
51
|
+
timestamp: string;
|
|
52
|
+
requestId: string;
|
|
53
|
+
action: string;
|
|
54
|
+
tool?: string;
|
|
55
|
+
user?: string;
|
|
56
|
+
resolvedIdentity?: string;
|
|
57
|
+
roles?: string[];
|
|
58
|
+
channel?: string;
|
|
59
|
+
session?: string;
|
|
60
|
+
allowed?: boolean;
|
|
61
|
+
reason?: string;
|
|
62
|
+
checks?: Record<string, {
|
|
63
|
+
passed: boolean;
|
|
64
|
+
detail: string;
|
|
65
|
+
}>;
|
|
66
|
+
result?: string;
|
|
67
|
+
outputSummary?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface RateLimitState {
|
|
70
|
+
calls: {
|
|
71
|
+
timestamp: number;
|
|
72
|
+
}[];
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateRequestId(): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generateRequestId = generateRequestId;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
function generateRequestId() {
|
|
39
|
+
return `gov-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
|
|
40
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validatePolicy = validatePolicy;
|
|
4
|
+
function validatePolicy(policy) {
|
|
5
|
+
const errors = [];
|
|
6
|
+
const warnings = [];
|
|
7
|
+
if (typeof policy !== "object" || policy === null) {
|
|
8
|
+
return { valid: false, errors: ["Policy must be a non-null object"], warnings };
|
|
9
|
+
}
|
|
10
|
+
const p = policy;
|
|
11
|
+
// --- Required top-level fields ---
|
|
12
|
+
if (!p.allowedTools || typeof p.allowedTools !== "object") {
|
|
13
|
+
errors.push("Missing or invalid 'allowedTools' (must be an object)");
|
|
14
|
+
}
|
|
15
|
+
if (!p.rateLimits || typeof p.rateLimits !== "object") {
|
|
16
|
+
errors.push("Missing or invalid 'rateLimits' (must be an object)");
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const rl = p.rateLimits;
|
|
20
|
+
for (const key of ["perUser", "perSession"]) {
|
|
21
|
+
if (!rl[key] || typeof rl[key] !== "object") {
|
|
22
|
+
errors.push(`Missing or invalid 'rateLimits.${key}' (must be an object)`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
const bucket = rl[key];
|
|
26
|
+
if (typeof bucket.maxCalls !== "number" || bucket.maxCalls < 0) {
|
|
27
|
+
errors.push(`'rateLimits.${key}.maxCalls' must be a non-negative number`);
|
|
28
|
+
}
|
|
29
|
+
if (typeof bucket.windowSeconds !== "number" || bucket.windowSeconds < 0) {
|
|
30
|
+
errors.push(`'rateLimits.${key}.windowSeconds' must be a non-negative number`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (!p.identityMap || typeof p.identityMap !== "object") {
|
|
36
|
+
errors.push("Missing or invalid 'identityMap' (must be an object)");
|
|
37
|
+
}
|
|
38
|
+
if (!p.injectionDetection || typeof p.injectionDetection !== "object") {
|
|
39
|
+
errors.push("Missing or invalid 'injectionDetection' (must be an object)");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const id = p.injectionDetection;
|
|
43
|
+
if (typeof id.enabled !== "boolean") {
|
|
44
|
+
errors.push("'injectionDetection.enabled' must be a boolean");
|
|
45
|
+
}
|
|
46
|
+
if (!["low", "medium", "high"].includes(id.sensitivity)) {
|
|
47
|
+
errors.push("'injectionDetection.sensitivity' must be 'low', 'medium', or 'high'");
|
|
48
|
+
}
|
|
49
|
+
// Validate custom patterns
|
|
50
|
+
if (id.customPatterns !== undefined) {
|
|
51
|
+
if (!Array.isArray(id.customPatterns)) {
|
|
52
|
+
errors.push("'injectionDetection.customPatterns' must be an array");
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
for (let i = 0; i < id.customPatterns.length; i++) {
|
|
56
|
+
const pat = id.customPatterns[i];
|
|
57
|
+
if (typeof pat !== "string") {
|
|
58
|
+
warnings.push(`customPatterns[${i}] is not a string`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
new RegExp(pat);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
warnings.push(`customPatterns[${i}] is not a valid regex: "${pat}"`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!p.auditLog || typeof p.auditLog !== "object") {
|
|
72
|
+
errors.push("Missing or invalid 'auditLog' (must be an object)");
|
|
73
|
+
}
|
|
74
|
+
// --- Warnings (non-fatal) ---
|
|
75
|
+
// Empty collections
|
|
76
|
+
if (p.allowedTools && typeof p.allowedTools === "object" && Object.keys(p.allowedTools).length === 0) {
|
|
77
|
+
warnings.push("'allowedTools' is empty — all tools will be denied");
|
|
78
|
+
}
|
|
79
|
+
if (p.identityMap && typeof p.identityMap === "object" && Object.keys(p.identityMap).length === 0) {
|
|
80
|
+
warnings.push("'identityMap' is empty — all users will be denied");
|
|
81
|
+
}
|
|
82
|
+
// Cross-reference: roles referenced by tools but not assigned to any identity
|
|
83
|
+
if (p.allowedTools && typeof p.allowedTools === "object" &&
|
|
84
|
+
p.identityMap && typeof p.identityMap === "object") {
|
|
85
|
+
const allIdentityRoles = new Set();
|
|
86
|
+
for (const entry of Object.values(p.identityMap)) {
|
|
87
|
+
if (entry && typeof entry === "object" && Array.isArray(entry.roles)) {
|
|
88
|
+
for (const r of entry.roles) {
|
|
89
|
+
allIdentityRoles.add(r);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const [toolName, toolConfig] of Object.entries(p.allowedTools)) {
|
|
94
|
+
if (toolConfig && typeof toolConfig === "object" && Array.isArray(toolConfig.roles)) {
|
|
95
|
+
for (const role of toolConfig.roles) {
|
|
96
|
+
if (!allIdentityRoles.has(role)) {
|
|
97
|
+
warnings.push(`Tool "${toolName}" requires role "${role}" but no identity has it`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
104
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GatewayStack Governance Gateway for OpenClaw
|
|
4
|
+
*
|
|
5
|
+
* Barrel module — re-exports the public API and serves as the CLI entry point.
|
|
6
|
+
* Implementation lives in ./governance/*.ts modules.
|
|
7
|
+
*/
|
|
8
|
+
export type { Policy, GovernanceCheckResult } from "./governance/types.js";
|
|
9
|
+
export { loadPolicy } from "./governance/policy.js";
|
|
10
|
+
export { validatePolicy } from "./governance/validate-policy.js";
|
|
11
|
+
export { checkGovernance } from "./governance/check.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* GatewayStack Governance Gateway for OpenClaw
|
|
5
|
+
*
|
|
6
|
+
* Barrel module — re-exports the public API and serves as the CLI entry point.
|
|
7
|
+
* Implementation lives in ./governance/*.ts modules.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.checkGovernance = exports.validatePolicy = exports.loadPolicy = void 0;
|
|
11
|
+
var policy_js_1 = require("./governance/policy.js");
|
|
12
|
+
Object.defineProperty(exports, "loadPolicy", { enumerable: true, get: function () { return policy_js_1.loadPolicy; } });
|
|
13
|
+
var validate_policy_js_1 = require("./governance/validate-policy.js");
|
|
14
|
+
Object.defineProperty(exports, "validatePolicy", { enumerable: true, get: function () { return validate_policy_js_1.validatePolicy; } });
|
|
15
|
+
var check_js_1 = require("./governance/check.js");
|
|
16
|
+
Object.defineProperty(exports, "checkGovernance", { enumerable: true, get: function () { return check_js_1.checkGovernance; } });
|
|
17
|
+
// CLI entry point
|
|
18
|
+
const cli_js_1 = require("./governance/cli.js");
|
|
19
|
+
const isDirectExecution = require.main === module ||
|
|
20
|
+
process.argv[1]?.endsWith("governance-gateway.js");
|
|
21
|
+
if (isDirectExecution) {
|
|
22
|
+
(0, cli_js_1.runGovernanceCheck)((0, cli_js_1.parseArgs)(process.argv));
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GatewayStack Governance Gateway for OpenClaw
|
|
4
|
+
*
|
|
5
|
+
* Barrel module — re-exports the public API and serves as the CLI entry point.
|
|
6
|
+
* Implementation lives in ./governance/*.ts modules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Re-export public API
|
|
10
|
+
export type { Policy, GovernanceCheckResult } from "./governance/types.js";
|
|
11
|
+
export { loadPolicy } from "./governance/policy.js";
|
|
12
|
+
export { validatePolicy } from "./governance/validate-policy.js";
|
|
13
|
+
export { checkGovernance } from "./governance/check.js";
|
|
14
|
+
|
|
15
|
+
// CLI entry point
|
|
16
|
+
import { parseArgs, runGovernanceCheck } from "./governance/cli.js";
|
|
17
|
+
|
|
18
|
+
const isDirectExecution =
|
|
19
|
+
require.main === module ||
|
|
20
|
+
process.argv[1]?.endsWith("governance-gateway.js");
|
|
21
|
+
|
|
22
|
+
if (isDirectExecution) {
|
|
23
|
+
runGovernanceCheck(parseArgs(process.argv));
|
|
24
|
+
}
|
package/src/plugin.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GatewayStack Governance — OpenClaw Plugin
|
|
3
|
+
*
|
|
4
|
+
* Registers a `before_tool_call` hook that automatically runs governance
|
|
5
|
+
* checks on every tool invocation. The agent cannot bypass this — it runs
|
|
6
|
+
* at the process level before any tool executes.
|
|
7
|
+
*
|
|
8
|
+
* Identity mapping uses OpenClaw agent IDs (e.g. "main", "ops", "dev")
|
|
9
|
+
* rather than human users, since OpenClaw is a single-user personal AI.
|
|
10
|
+
*/
|
|
11
|
+
declare const plugin: {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
register(api: any): void;
|
|
16
|
+
};
|
|
17
|
+
export default plugin;
|
package/src/plugin.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* GatewayStack Governance — OpenClaw Plugin
|
|
4
|
+
*
|
|
5
|
+
* Registers a `before_tool_call` hook that automatically runs governance
|
|
6
|
+
* checks on every tool invocation. The agent cannot bypass this — it runs
|
|
7
|
+
* at the process level before any tool executes.
|
|
8
|
+
*
|
|
9
|
+
* Identity mapping uses OpenClaw agent IDs (e.g. "main", "ops", "dev")
|
|
10
|
+
* rather than human users, since OpenClaw is a single-user personal AI.
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const governance_gateway_js_1 = require("../scripts/governance-gateway.js");
|
|
49
|
+
// Resolve policy path: check plugin directory first, then ~/.openclaw default
|
|
50
|
+
function resolvePolicyPath() {
|
|
51
|
+
const pluginDir = path.resolve(__dirname, "..");
|
|
52
|
+
const localPolicy = path.join(pluginDir, "policy.json");
|
|
53
|
+
// Also check OpenClaw skills directory (for backward compat with skill installs)
|
|
54
|
+
const openclawSkillPolicy = path.join(os.homedir(), ".openclaw", "skills", "gatewaystack-governance", "policy.json");
|
|
55
|
+
// Prefer local plugin directory policy
|
|
56
|
+
try {
|
|
57
|
+
require("fs").accessSync(localPolicy);
|
|
58
|
+
return localPolicy;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Fall through
|
|
62
|
+
}
|
|
63
|
+
// Try OpenClaw skills directory
|
|
64
|
+
try {
|
|
65
|
+
require("fs").accessSync(openclawSkillPolicy);
|
|
66
|
+
return openclawSkillPolicy;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Fall through
|
|
70
|
+
}
|
|
71
|
+
// Default to local — will produce a clear error from checkGovernance
|
|
72
|
+
return localPolicy;
|
|
73
|
+
}
|
|
74
|
+
const plugin = {
|
|
75
|
+
id: "gatewaystack-governance",
|
|
76
|
+
name: "GatewayStack Governance",
|
|
77
|
+
description: "Automatic governance for every tool call — identity, scope, rate limiting, injection detection, and audit logging",
|
|
78
|
+
register(api) {
|
|
79
|
+
const policyPath = resolvePolicyPath();
|
|
80
|
+
api.on("before_tool_call", async (event, ctx) => {
|
|
81
|
+
const result = await (0, governance_gateway_js_1.checkGovernance)({
|
|
82
|
+
toolName: event.toolName,
|
|
83
|
+
args: JSON.stringify(event.params),
|
|
84
|
+
userId: ctx.agentId ?? "unknown",
|
|
85
|
+
session: ctx.sessionKey,
|
|
86
|
+
policyPath,
|
|
87
|
+
});
|
|
88
|
+
if (!result.allowed) {
|
|
89
|
+
return { block: true, blockReason: result.reason };
|
|
90
|
+
}
|
|
91
|
+
return {};
|
|
92
|
+
}, { priority: 0 });
|
|
93
|
+
if (api.logger) {
|
|
94
|
+
api.logger.info(`GatewayStack Governance loaded (policy: ${policyPath})`);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
exports.default = plugin;
|