thumbgate 1.27.12 → 1.27.13
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/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +2 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +2 -4
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
- package/bin/cli.js +78 -259
- package/config/gate-templates.json +0 -228
- package/config/gates/claim-verification.json +0 -18
- package/package.json +35 -25
- package/public/assets/brand/thumbgate-logo-transparent.svg +22 -0
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +19 -0
- package/public/assets/brand/thumbgate-mark.svg +11 -5
- package/public/blog.html +0 -30
- package/public/brand/thumbgate-mark.svg +9 -5
- package/public/chatgpt-app.html +2 -2
- package/public/compare.html +2 -1
- package/public/dashboard.html +1 -1
- package/public/federal.html +1 -1
- package/public/index.html +95 -216
- package/public/learn.html +59 -35
- package/public/lessons.html +1 -1
- package/public/numbers.html +2 -2
- package/public/pro.html +7 -7
- package/scripts/aws-blocks-guardrails.js +228 -0
- package/scripts/cli-schema.js +22 -10
- package/scripts/dashboard-chat.js +2 -1
- package/scripts/document-intake.js +1 -49
- package/scripts/durability/step.js +3 -3
- package/scripts/gate-stats.js +5 -11
- package/scripts/gates-engine.js +0 -49
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-stop-anti-claim.js +116 -184
- package/scripts/hosted-config.js +0 -12
- package/scripts/lesson-search.js +1 -15
- package/scripts/llm-client.js +187 -5
- package/scripts/plausible-domain-config.js +3 -1
- package/scripts/seo-gsd.js +240 -1
- package/scripts/tool-registry.js +2 -2
- package/scripts/vector-store.js +44 -0
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +340 -131
- package/public/assets/brand/thumbgate-mark-inline.svg +0 -15
- package/public/compare/adopt-ai.html +0 -219
- package/public/compare/agentix-labs.html +0 -197
- package/public/compare/ai-experience-orchestration.html +0 -216
- package/public/compare/anthropic-claude-for-legal.html +0 -260
- package/public/compare/anthropic-containment.html +0 -280
- package/public/compare/arcade.html +0 -175
- package/public/compare/arcjet.html +0 -239
- package/public/compare/bumblebee.html +0 -307
- package/public/compare/claude-code-hooks.html +0 -294
- package/public/compare/databricks-unity-ai-gateway.html +0 -215
- package/public/compare/fallow.html +0 -351
- package/public/compare/heidi.html +0 -233
- package/public/compare/mem0.html +0 -342
- package/public/compare/oak-and-sparrow-gatekeeper.html +0 -289
- package/public/compare/rein.html +0 -236
- package/public/compare/sigmashake.html +0 -256
- package/public/compare/speclock.html +0 -342
- package/public/guides/agent-harness-optimization.html +0 -342
- package/public/guides/agentic-web-governance.html +0 -406
- package/public/guides/ai-agent-governance-sprint.html +0 -415
- package/public/guides/ai-agent-pre-action-approval-gates.html +0 -401
- package/public/guides/ai-agent-workflow-migration-checklist.html +0 -392
- package/public/guides/ai-deployment-readiness.html +0 -415
- package/public/guides/ai-mode-ads-agent-governance.html +0 -401
- package/public/guides/ai-search-topical-presence.html +0 -342
- package/public/guides/autoresearch-agent-safety.html +0 -342
- package/public/guides/background-agent-governance.html +0 -358
- package/public/guides/best-tools-stop-ai-agents-breaking-production.html +0 -363
- package/public/guides/browser-automation-safety.html +0 -342
- package/public/guides/chatgpt-ads-trust.html +0 -353
- package/public/guides/claude-code-feedback.html +0 -339
- package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
- package/public/guides/claude-code-skills-guardrails.html +0 -343
- package/public/guides/claude-desktop.html +0 -356
- package/public/guides/code-knowledge-graph-guardrails.html +0 -365
- package/public/guides/codex-cli-guardrails.html +0 -339
- package/public/guides/cursor-agent-guardrails.html +0 -339
- package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
- package/public/guides/database-agent-safety.html +0 -406
- package/public/guides/deepseek-v4-runtime-guardrails.html +0 -346
- package/public/guides/developer-machine-supply-chain-guardrails.html +0 -358
- package/public/guides/gcp-mcp-guardrails.html +0 -147
- package/public/guides/gemini-cli-feedback-memory.html +0 -339
- package/public/guides/gpt-5-5-model-evaluation.html +0 -358
- package/public/guides/internal-ai-engineering-stack-guardrails.html +0 -348
- package/public/guides/long-running-agent-context-management.html +0 -346
- package/public/guides/mcp-tool-governance.html +0 -401
- package/public/guides/multica-thumbgate-setup.html +0 -134
- package/public/guides/native-messaging-host-security.html +0 -342
- package/public/guides/policy-engine-pre-action-gates.html +0 -346
- package/public/guides/pre-action-checks.html +0 -342
- package/public/guides/pretooluse-hooks-vs-advisory-prompt-rules.html +0 -342
- package/public/guides/prompt-tricks-to-workflow-rules.html +0 -365
- package/public/guides/proxy-pointer-rag-guardrails.html +0 -352
- package/public/guides/rag-precision-tuning-guardrails.html +0 -352
- package/public/guides/reasoning-compression-guardrails.html +0 -346
- package/public/guides/relational-knowledge-ai-recommendations.html +0 -342
- package/public/guides/roo-code-alternative-cline.html +0 -339
- package/public/guides/semantic-programmatic-seo-guardrails.html +0 -352
- package/public/guides/seo-agent-skills-guardrails.html +0 -344
- package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -342
- package/public/learn/ac-dc-runtime-enforcement.html +0 -277
- package/public/learn/agent-harness-pattern.html +0 -181
- package/public/learn/agent-identity-connector-governance.html +0 -146
- package/public/learn/agent-swarms-shared-gates.html +0 -173
- package/public/learn/agentic-enterprise-context-brain.html +0 -117
- package/public/learn/agentic-os-team-governance.html +0 -146
- package/public/learn/ai-agent-governance.html +0 -158
- package/public/learn/ai-agent-persistent-memory.html +0 -211
- package/public/learn/anthropomorphic-claim-gates.html +0 -180
- package/public/learn/background-agent-control-layer.html +0 -184
- package/public/learn/claude-code-goal-with-rubrics.html +0 -205
- package/public/learn/codex-role-plugins-need-governance.html +0 -125
- package/public/learn/cost-aware-agent-gate-routing.html +0 -173
- package/public/learn/databricks-unity-ai-gateway-runtime-governance.html +0 -157
- package/public/learn/deterministic-agent-workflows.html +0 -185
- package/public/learn/feedback-loop-vs-decision-layer.html +0 -283
- package/public/learn/from-prototype-to-production.html +0 -223
- package/public/learn/learn.css +0 -51
- package/public/learn/mcp-pre-action-checks-explained.html +0 -172
- package/public/learn/pretix-stripe-connect-marketplaces.html +0 -161
- package/public/learn/regulated-agent-execution-boundary.html +0 -196
- package/public/learn/spec-driven-development.html +0 -168
- package/public/learn/stop-ai-agent-force-push.html +0 -134
- package/public/learn/vibe-coding-safety-net.html +0 -142
- package/scripts/reddit-browser-notification-watch.js +0 -230
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
3
|
"description": "One 👎 becomes a hard rule the agent cannot bypass. Captures thumbs-down feedback, distills it into PreToolUse Pre-Action Checks, enforced across every future Claude Code session.",
|
|
4
|
-
"version": "1.27.
|
|
4
|
+
"version": "1.27.8",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Igor Ganapolsky",
|
|
7
7
|
"email": "ig5973700@gmail.com",
|
package/.well-known/llms.txt
CHANGED
|
@@ -64,10 +64,11 @@ npx thumbgate init --agent claude-code
|
|
|
64
64
|
- Agent discovery: https://thumbgate.ai/.well-known/mcp.json
|
|
65
65
|
- Progressive tool index: https://thumbgate.ai/.well-known/mcp/tools.json
|
|
66
66
|
- Context footprint report: https://thumbgate.ai/.well-known/mcp/footprint.json
|
|
67
|
+
- Headroom context compression guardrails: https://thumbgate.ai/guides/headroom-context-compression-guardrails
|
|
68
|
+
- Sovereign coding model guardrails: https://thumbgate.ai/guides/sovereign-coding-model-guardrails
|
|
67
69
|
- Agentic web governance: https://thumbgate.ai/guides/agentic-web-governance
|
|
68
70
|
- AI Mode ads and agent governance: https://thumbgate.ai/guides/ai-mode-ads-agent-governance
|
|
69
71
|
- MCP tool governance: https://thumbgate.ai/guides/mcp-tool-governance
|
|
70
|
-
- Policy engines need pre-action gates: https://thumbgate.ai/guides/policy-engine-pre-action-gates
|
|
71
72
|
- AI agent pre-action approval gates: https://thumbgate.ai/guides/ai-agent-pre-action-approval-gates
|
|
72
73
|
- Agent skills: https://thumbgate.ai/.well-known/mcp/skills.json
|
|
73
74
|
- MCP applications: https://thumbgate.ai/.well-known/mcp/applications.json
|
package/README.md
CHANGED
|
@@ -16,10 +16,6 @@ The product is a self-improving enforcement layer: thumbs-down feedback, prompt
|
|
|
16
16
|
<img src="docs/media/thumbgate-demo.gif" alt="ThumbGate blocking an AI agent's dangerous commands (rm -rf, force-push, chmod 777) in real time, while letting safe commands through" width="820" />
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
<p align="center">
|
|
20
|
-
<img src="docs/diagrams/pre-action-gate-loop.svg" alt="ThumbGate pre-action gate loop: agent intent, PreToolUse gate, block or allow with audit receipt" width="920" />
|
|
21
|
-
</p>
|
|
22
|
-
|
|
23
19
|
```
|
|
24
20
|
Agent tries: rm -rf tests/
|
|
25
21
|
ThumbGate: ⛔ BLOCKED — "Never delete test directories"
|
|
@@ -388,6 +384,8 @@ npx thumbgate model-candidates --workload=dashboard-analysis --provider=openai -
|
|
|
388
384
|
npx thumbgate native-messaging-audit # inspect local browser bridges and extension hosts
|
|
389
385
|
npx thumbgate dashboard --open # open local project-scoped dashboard in browser
|
|
390
386
|
thumbgate-dashboard # standalone browser dashboard shortcut (run '/project:thumbgate-dashboard' in Claude/Grok)
|
|
387
|
+
npx thumbgate check-update # check if a new version is available on npm/GitHub
|
|
388
|
+
npx thumbgate self-update # update ThumbGate to the latest version globally
|
|
391
389
|
npx thumbgate serve # start MCP server on stdio
|
|
392
390
|
npx thumbgate bench # run reliability benchmark
|
|
393
391
|
npx thumbgate bench --programbench-smoke # include cleanroom whole-repo proof lane
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"thumbgate": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["--yes", "--package", "thumbgate@1.27.
|
|
5
|
+
"args": ["--yes", "--package", "thumbgate@1.27.8", "thumbgate", "serve"]
|
|
6
6
|
}
|
|
7
7
|
},
|
|
8
8
|
"hooks": {
|
|
9
9
|
"preToolUse": {
|
|
10
10
|
"command": "npx",
|
|
11
|
-
"args": ["--yes", "--package", "thumbgate@1.27.
|
|
11
|
+
"args": ["--yes", "--package", "thumbgate@1.27.8", "thumbgate", "gate-check"]
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -231,7 +231,7 @@ const {
|
|
|
231
231
|
finalizeSession: finalizeFeedbackSession,
|
|
232
232
|
} = require('../../scripts/feedback-session');
|
|
233
233
|
|
|
234
|
-
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.27.
|
|
234
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.27.8' };
|
|
235
235
|
const COMMERCE_CATEGORIES = [
|
|
236
236
|
'product_recommendation',
|
|
237
237
|
'brand_compliance',
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_ENDPOINT = 'https://api.oraclestechnologies.com/v1/guardian/analyze';
|
|
5
|
+
|
|
6
|
+
function requireApiKey(env = process.env) {
|
|
7
|
+
const key = env.ETHICORE_API_KEY || env.GUARDIAN_API_KEY || env.ORACLES_GUARDIAN_API_KEY;
|
|
8
|
+
if (!key) {
|
|
9
|
+
throw new Error('ETHICORE_API_KEY env var is required');
|
|
10
|
+
}
|
|
11
|
+
return key;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function analyzeText(text, options = {}) {
|
|
15
|
+
if (!String(text || '').trim()) {
|
|
16
|
+
throw new Error('analyzeText requires text');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const env = options.env || process.env;
|
|
20
|
+
const endpoint = options.endpoint || env.ETHICORE_GUARDIAN_ENDPOINT || DEFAULT_ENDPOINT;
|
|
21
|
+
const apiKey = options.apiKey || requireApiKey(env);
|
|
22
|
+
const fetchImpl = options.fetch || fetch;
|
|
23
|
+
|
|
24
|
+
const response = await fetchImpl(endpoint, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${apiKey}`,
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({ text }),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const bodyText = await response.text();
|
|
34
|
+
let body = bodyText;
|
|
35
|
+
try {
|
|
36
|
+
body = bodyText ? JSON.parse(bodyText) : {};
|
|
37
|
+
} catch {
|
|
38
|
+
// Keep non-JSON body for diagnostics.
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error(`Ethicore Guardian API ${response.status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function createEthicorePolicyCheck(options = {}) {
|
|
49
|
+
return async function ethicorePolicyCheck(action = {}) {
|
|
50
|
+
const toolText = [
|
|
51
|
+
action.toolName,
|
|
52
|
+
action.actionType,
|
|
53
|
+
action.command,
|
|
54
|
+
action.path,
|
|
55
|
+
action.url,
|
|
56
|
+
action.input ? JSON.stringify(action.input) : '',
|
|
57
|
+
].filter(Boolean).join('\n');
|
|
58
|
+
|
|
59
|
+
return analyzeText(toolText || JSON.stringify(action), options);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
DEFAULT_ENDPOINT,
|
|
65
|
+
analyzeText,
|
|
66
|
+
createEthicorePolicyCheck,
|
|
67
|
+
requireApiKey,
|
|
68
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { normalizeProviderAction } = require('../../scripts/provider-action-normalizer');
|
|
5
|
+
|
|
6
|
+
const BLOCK_DECISIONS = new Set([
|
|
7
|
+
'block',
|
|
8
|
+
'blocked',
|
|
9
|
+
'deny',
|
|
10
|
+
'denied',
|
|
11
|
+
'disallow',
|
|
12
|
+
'disallowed',
|
|
13
|
+
'fail',
|
|
14
|
+
'failed',
|
|
15
|
+
'forbid',
|
|
16
|
+
'forbidden',
|
|
17
|
+
'reject',
|
|
18
|
+
'rejected',
|
|
19
|
+
'unsafe',
|
|
20
|
+
'violation',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const REVIEW_DECISIONS = new Set([
|
|
24
|
+
'approval',
|
|
25
|
+
'approval-required',
|
|
26
|
+
'approval_required',
|
|
27
|
+
'approve',
|
|
28
|
+
'human-review',
|
|
29
|
+
'human_review',
|
|
30
|
+
'manual-review',
|
|
31
|
+
'manual_review',
|
|
32
|
+
'review',
|
|
33
|
+
'requires-approval',
|
|
34
|
+
'requires_approval',
|
|
35
|
+
'requires-review',
|
|
36
|
+
'requires_review',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
const ALLOW_DECISIONS = new Set([
|
|
40
|
+
'accept',
|
|
41
|
+
'accepted',
|
|
42
|
+
'allow',
|
|
43
|
+
'allowed',
|
|
44
|
+
'ok',
|
|
45
|
+
'pass',
|
|
46
|
+
'passed',
|
|
47
|
+
'permit',
|
|
48
|
+
'permitted',
|
|
49
|
+
'safe',
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
function asObject(value) {
|
|
53
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function asArray(value) {
|
|
57
|
+
return Array.isArray(value) ? value : [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function firstString(...values) {
|
|
61
|
+
for (const value of values) {
|
|
62
|
+
const text = String(value || '').trim();
|
|
63
|
+
if (text) return text;
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeDecisionToken(value) {
|
|
69
|
+
return String(value || '')
|
|
70
|
+
.trim()
|
|
71
|
+
.toLowerCase()
|
|
72
|
+
.replace(/\s+/g, '-');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeEvidence(value) {
|
|
76
|
+
const direct = asArray(value.evidence);
|
|
77
|
+
const citations = asArray(value.citations);
|
|
78
|
+
const violations = asArray(value.violations);
|
|
79
|
+
const reasons = asArray(value.reasons);
|
|
80
|
+
const reasoning = asArray(value.reasoning);
|
|
81
|
+
const threatTypes = asArray(value.threat_types || value.threatTypes)
|
|
82
|
+
.map((threatType) => ({
|
|
83
|
+
code: String(threatType || '').trim(),
|
|
84
|
+
message: `Threat type: ${String(threatType || '').trim()}`,
|
|
85
|
+
source: 'guardian',
|
|
86
|
+
severity: firstString(value.threat_level, value.threatLevel),
|
|
87
|
+
}));
|
|
88
|
+
return [...direct, ...citations, ...violations, ...reasons, ...reasoning, ...threatTypes]
|
|
89
|
+
.map((entry) => {
|
|
90
|
+
if (typeof entry === 'string') return { text: entry };
|
|
91
|
+
const object = asObject(entry);
|
|
92
|
+
if (!Object.keys(object).length) return null;
|
|
93
|
+
return {
|
|
94
|
+
id: firstString(object.id, object.ruleId, object.rule_id, object.code),
|
|
95
|
+
text: firstString(object.text, object.reason, object.message, object.description, object.title),
|
|
96
|
+
source: firstString(object.source, object.provider, object.policy),
|
|
97
|
+
severity: firstString(object.severity, object.level),
|
|
98
|
+
raw: object,
|
|
99
|
+
};
|
|
100
|
+
})
|
|
101
|
+
.filter(Boolean);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractPolicyDecision(input = {}) {
|
|
105
|
+
const event = asObject(input);
|
|
106
|
+
for (const candidate of [
|
|
107
|
+
event.policyDecision,
|
|
108
|
+
event.policy_decision,
|
|
109
|
+
event.guardrailResult,
|
|
110
|
+
event.guardrail_result,
|
|
111
|
+
event.result,
|
|
112
|
+
]) {
|
|
113
|
+
const object = asObject(candidate);
|
|
114
|
+
if (Object.keys(object).length) return object;
|
|
115
|
+
}
|
|
116
|
+
return event;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function classifyPolicyDecision(input = {}) {
|
|
120
|
+
const value = extractPolicyDecision(input);
|
|
121
|
+
const token = normalizeDecisionToken(firstString(
|
|
122
|
+
value.decision,
|
|
123
|
+
value.action,
|
|
124
|
+
value.status,
|
|
125
|
+
value.result,
|
|
126
|
+
value.verdict,
|
|
127
|
+
value.outcome,
|
|
128
|
+
value.effect,
|
|
129
|
+
value.recommended_action,
|
|
130
|
+
value.recommendedAction,
|
|
131
|
+
));
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
value.allowed === false
|
|
135
|
+
|| value.is_safe === false
|
|
136
|
+
|| value.isSafe === false
|
|
137
|
+
|| value.accepted === false
|
|
138
|
+
|| value.blocked === true
|
|
139
|
+
|| value.denied === true
|
|
140
|
+
|| BLOCK_DECISIONS.has(token)
|
|
141
|
+
) {
|
|
142
|
+
return 'block';
|
|
143
|
+
}
|
|
144
|
+
if (
|
|
145
|
+
value.requiresApproval === true
|
|
146
|
+
|| value.requires_approval === true
|
|
147
|
+
|| value.reviewRequired === true
|
|
148
|
+
|| value.review_required === true
|
|
149
|
+
|| REVIEW_DECISIONS.has(token)
|
|
150
|
+
) {
|
|
151
|
+
return 'approval_required';
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
value.allowed === true
|
|
155
|
+
|| value.is_safe === true
|
|
156
|
+
|| value.isSafe === true
|
|
157
|
+
|| value.accepted === true
|
|
158
|
+
|| ALLOW_DECISIONS.has(token)
|
|
159
|
+
) {
|
|
160
|
+
return 'allow';
|
|
161
|
+
}
|
|
162
|
+
return 'unknown';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function normalizePolicyDecision(input = {}, options = {}) {
|
|
166
|
+
const value = extractPolicyDecision(input);
|
|
167
|
+
const decision = classifyPolicyDecision(value);
|
|
168
|
+
const source = firstString(
|
|
169
|
+
options.source,
|
|
170
|
+
value.source,
|
|
171
|
+
value.provider,
|
|
172
|
+
value.engine,
|
|
173
|
+
value.policyEngine,
|
|
174
|
+
value.policy_engine,
|
|
175
|
+
'policy-engine'
|
|
176
|
+
);
|
|
177
|
+
const reason = firstString(
|
|
178
|
+
value.reason,
|
|
179
|
+
value.message,
|
|
180
|
+
value.explanation,
|
|
181
|
+
value.summary,
|
|
182
|
+
asArray(value.reasoning).join('; '),
|
|
183
|
+
asArray(value.reasons).join('; '),
|
|
184
|
+
decision === 'unknown' ? 'Policy engine returned an unknown decision; approval required before execution.' : ''
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
allowed: decision === 'allow',
|
|
189
|
+
blocked: decision === 'block',
|
|
190
|
+
approvalRequired: decision === 'approval_required' || decision === 'unknown',
|
|
191
|
+
decision,
|
|
192
|
+
reason,
|
|
193
|
+
source,
|
|
194
|
+
confidence: Number.isFinite(Number(value.confidence)) ? Number(value.confidence) : null,
|
|
195
|
+
policyId: firstString(value.policyId, value.policy_id, value.ruleId, value.rule_id, value.id),
|
|
196
|
+
severity: firstString(value.severity, value.level, value.threat_level, value.threatLevel),
|
|
197
|
+
score: Number.isFinite(Number(value.score))
|
|
198
|
+
? Number(value.score)
|
|
199
|
+
: (Number.isFinite(Number(value.threat_score)) ? Number(value.threat_score) : null),
|
|
200
|
+
evidence: normalizeEvidence(value),
|
|
201
|
+
raw: value,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizePolicyAction(input = {}) {
|
|
206
|
+
const event = asObject(input);
|
|
207
|
+
return {
|
|
208
|
+
...normalizeProviderAction({
|
|
209
|
+
...event,
|
|
210
|
+
provider: firstString(event.provider, event.agentRuntime, event.runtime, 'policy-engine'),
|
|
211
|
+
toolName: firstString(event.toolName, event.tool_name, event.name),
|
|
212
|
+
input: asObject(event.toolInput || event.input || event.arguments || event.args),
|
|
213
|
+
}),
|
|
214
|
+
policyContext: asObject(event.policyContext || event.policy_context),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function createPolicyEngineGuard({
|
|
219
|
+
policyCheck,
|
|
220
|
+
executeTool,
|
|
221
|
+
gateCheck,
|
|
222
|
+
onDecision,
|
|
223
|
+
source = 'policy-engine',
|
|
224
|
+
} = {}) {
|
|
225
|
+
if (typeof policyCheck !== 'function') {
|
|
226
|
+
throw new TypeError('createPolicyEngineGuard requires a policyCheck function');
|
|
227
|
+
}
|
|
228
|
+
if (typeof executeTool !== 'function') {
|
|
229
|
+
throw new TypeError('createPolicyEngineGuard requires an executeTool function');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return async function guardedPolicyTool(input = {}) {
|
|
233
|
+
const normalizedAction = normalizePolicyAction(input);
|
|
234
|
+
const policyDecision = normalizePolicyDecision(await policyCheck(normalizedAction), { source });
|
|
235
|
+
const gateDecision = typeof gateCheck === 'function'
|
|
236
|
+
? normalizePolicyDecision(await gateCheck({ normalizedAction, policyDecision }), { source: 'thumbgate' })
|
|
237
|
+
: null;
|
|
238
|
+
const effectiveDecision = gateDecision && !gateDecision.allowed ? gateDecision : policyDecision;
|
|
239
|
+
|
|
240
|
+
if (typeof onDecision === 'function') {
|
|
241
|
+
await onDecision({ normalizedAction, policyDecision, gateDecision, effectiveDecision });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!effectiveDecision.allowed) {
|
|
245
|
+
const error = new Error(effectiveDecision.reason || 'ThumbGate blocked this action before execution.');
|
|
246
|
+
error.code = effectiveDecision.approvalRequired ? 'THUMBGATE_APPROVAL_REQUIRED' : 'THUMBGATE_BLOCKED';
|
|
247
|
+
error.thumbgate = { normalizedAction, policyDecision, gateDecision, effectiveDecision };
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return executeTool(input, { normalizedAction, policyDecision, gateDecision, effectiveDecision });
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = {
|
|
256
|
+
createPolicyEngineGuard,
|
|
257
|
+
extractPolicyDecision,
|
|
258
|
+
normalizePolicyAction,
|
|
259
|
+
normalizePolicyDecision,
|
|
260
|
+
};
|