livedesk 0.1.210 → 0.1.211
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/hub/src/agents/agent-audit-store.js +93 -93
- package/hub/src/agents/agent-manager.js +175 -175
- package/hub/src/agents/agent-permissions.js +194 -185
- package/hub/src/agents/codex-agent-runtime.js +604 -604
- package/hub/src/remote-hub.js +11 -7
- package/hub/src/server.js +2549 -2538
- package/package.json +45 -45
|
@@ -1,200 +1,209 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { getAgentToolDefinition } from './agent-tool-registry.js';
|
|
3
|
-
|
|
4
|
-
export const AGENT_PERMISSION_MODES = Object.freeze(['ask', 'safe-auto', 'full-access', 'custom']);
|
|
5
|
-
export const AGENT_PERMISSION_DECISIONS = Object.freeze(['allow', 'ask', 'deny']);
|
|
6
|
-
export const AGENT_PERMISSION_CATEGORIES = Object.freeze([
|
|
7
|
-
'read',
|
|
8
|
-
'processControl',
|
|
9
|
-
'serviceControl',
|
|
10
|
-
'applicationControl',
|
|
11
|
-
'fileRead',
|
|
12
|
-
'fileWrite',
|
|
13
|
-
'fileDelete',
|
|
14
|
-
'shell',
|
|
15
|
-
'script',
|
|
16
|
-
'softwareInstall',
|
|
17
|
-
'network',
|
|
18
|
-
'systemPower',
|
|
19
|
-
'systemConfiguration',
|
|
20
|
-
'userAccount'
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
const DEFAULT_CUSTOM_CATEGORIES = Object.freeze({
|
|
24
|
-
read: 'allow',
|
|
25
|
-
processControl: 'ask',
|
|
26
|
-
serviceControl: 'ask',
|
|
27
|
-
applicationControl: 'ask',
|
|
28
|
-
fileRead: 'ask',
|
|
29
|
-
fileWrite: 'ask',
|
|
30
|
-
fileDelete: 'ask',
|
|
31
|
-
shell: 'ask',
|
|
32
|
-
script: 'ask',
|
|
33
|
-
softwareInstall: 'ask',
|
|
34
|
-
network: 'ask',
|
|
35
|
-
systemPower: 'ask',
|
|
36
|
-
systemConfiguration: 'ask',
|
|
37
|
-
userAccount: 'deny'
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
const PRESET_CATEGORIES = Object.freeze({
|
|
41
|
-
ask: {
|
|
42
|
-
...DEFAULT_CUSTOM_CATEGORIES,
|
|
43
|
-
userAccount: 'ask'
|
|
44
|
-
},
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { getAgentToolDefinition } from './agent-tool-registry.js';
|
|
3
|
+
|
|
4
|
+
export const AGENT_PERMISSION_MODES = Object.freeze(['ask', 'safe-auto', 'full-access', 'custom']);
|
|
5
|
+
export const AGENT_PERMISSION_DECISIONS = Object.freeze(['allow', 'ask', 'deny']);
|
|
6
|
+
export const AGENT_PERMISSION_CATEGORIES = Object.freeze([
|
|
7
|
+
'read',
|
|
8
|
+
'processControl',
|
|
9
|
+
'serviceControl',
|
|
10
|
+
'applicationControl',
|
|
11
|
+
'fileRead',
|
|
12
|
+
'fileWrite',
|
|
13
|
+
'fileDelete',
|
|
14
|
+
'shell',
|
|
15
|
+
'script',
|
|
16
|
+
'softwareInstall',
|
|
17
|
+
'network',
|
|
18
|
+
'systemPower',
|
|
19
|
+
'systemConfiguration',
|
|
20
|
+
'userAccount'
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const DEFAULT_CUSTOM_CATEGORIES = Object.freeze({
|
|
24
|
+
read: 'allow',
|
|
25
|
+
processControl: 'ask',
|
|
26
|
+
serviceControl: 'ask',
|
|
27
|
+
applicationControl: 'ask',
|
|
28
|
+
fileRead: 'ask',
|
|
29
|
+
fileWrite: 'ask',
|
|
30
|
+
fileDelete: 'ask',
|
|
31
|
+
shell: 'ask',
|
|
32
|
+
script: 'ask',
|
|
33
|
+
softwareInstall: 'ask',
|
|
34
|
+
network: 'ask',
|
|
35
|
+
systemPower: 'ask',
|
|
36
|
+
systemConfiguration: 'ask',
|
|
37
|
+
userAccount: 'deny'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const PRESET_CATEGORIES = Object.freeze({
|
|
41
|
+
ask: {
|
|
42
|
+
...DEFAULT_CUSTOM_CATEGORIES,
|
|
43
|
+
userAccount: 'ask'
|
|
44
|
+
},
|
|
45
45
|
'safe-auto': {
|
|
46
46
|
read: 'allow',
|
|
47
47
|
processControl: 'allow',
|
|
48
48
|
serviceControl: 'allow',
|
|
49
|
-
|
|
49
|
+
// launch_application accepts an executable path, so allowing it would
|
|
50
|
+
// provide a shell-equivalent escape hatch (for example powershell.exe).
|
|
51
|
+
applicationControl: 'ask',
|
|
50
52
|
fileRead: 'allow',
|
|
51
53
|
fileWrite: 'allow',
|
|
52
54
|
fileDelete: 'ask',
|
|
53
55
|
shell: 'ask',
|
|
54
|
-
script
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
56
|
+
// A writable script directory must never become an auto-execution path.
|
|
57
|
+
script: 'ask',
|
|
58
|
+
softwareInstall: 'ask',
|
|
59
|
+
network: 'allow',
|
|
60
|
+
systemPower: 'ask',
|
|
61
|
+
systemConfiguration: 'ask',
|
|
62
|
+
userAccount: 'deny'
|
|
63
|
+
},
|
|
64
|
+
'full-access': {
|
|
65
|
+
read: 'allow',
|
|
66
|
+
processControl: 'allow',
|
|
67
|
+
serviceControl: 'allow',
|
|
68
|
+
applicationControl: 'allow',
|
|
69
|
+
fileRead: 'allow',
|
|
70
|
+
fileWrite: 'allow',
|
|
71
|
+
fileDelete: 'allow',
|
|
72
|
+
shell: 'allow',
|
|
73
|
+
script: 'allow',
|
|
74
|
+
softwareInstall: 'allow',
|
|
75
|
+
network: 'allow',
|
|
76
|
+
systemPower: 'allow',
|
|
77
|
+
systemConfiguration: 'allow',
|
|
78
|
+
userAccount: 'ask'
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const TOOL_CATEGORY_ALIASES = Object.freeze({
|
|
83
|
+
'process.list': 'read',
|
|
84
|
+
'service.status': 'read',
|
|
85
|
+
'system.health': 'read',
|
|
86
|
+
'gpu.status': 'read',
|
|
87
|
+
'disk.status': 'read',
|
|
88
|
+
'diagnostics.collect': 'read'
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
function normalizeIds(value) {
|
|
92
|
+
return [...new Set((Array.isArray(value) ? value : [])
|
|
93
|
+
.map(item => String(item || '').trim())
|
|
94
|
+
.filter(Boolean))].sort().slice(0, 500);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeDecision(value, fallback = 'ask') {
|
|
98
|
+
return AGENT_PERMISSION_DECISIONS.includes(String(value || '').toLowerCase())
|
|
99
|
+
? String(value).toLowerCase()
|
|
100
|
+
: fallback;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function normalizeAgentPermissionCategories(value, fallback = DEFAULT_CUSTOM_CATEGORIES) {
|
|
104
|
+
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
105
|
+
return Object.fromEntries(AGENT_PERMISSION_CATEGORIES.map(category => [
|
|
106
|
+
category,
|
|
107
|
+
normalizeDecision(source[category], fallback[category] || 'ask')
|
|
108
|
+
]));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getPresetAgentPermissionCategories(mode, customCategories) {
|
|
112
|
+
const normalizedMode = AGENT_PERMISSION_MODES.includes(mode) ? mode : 'ask';
|
|
113
|
+
if (normalizedMode === 'custom') return normalizeAgentPermissionCategories(customCategories);
|
|
114
|
+
return normalizeAgentPermissionCategories(PRESET_CATEGORIES[normalizedMode] || PRESET_CATEGORIES.ask);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function createAgentPermissionPolicy({ mode = 'ask', deviceIds = [], customCategories, maxToolCalls, now = new Date() } = {}) {
|
|
118
|
+
const normalizedMode = AGENT_PERMISSION_MODES.includes(String(mode)) ? String(mode) : 'ask';
|
|
119
|
+
const categories = getPresetAgentPermissionCategories(normalizedMode, customCategories);
|
|
120
|
+
const defaultMax = normalizedMode === 'ask' ? 30 : normalizedMode === 'safe-auto' ? 50 : normalizedMode === 'full-access' ? 100 : 50;
|
|
121
|
+
const requestedMax = Number(maxToolCalls);
|
|
122
|
+
const boundedMax = Number.isFinite(requestedMax) ? Math.max(1, Math.min(100, Math.floor(requestedMax))) : defaultMax;
|
|
123
|
+
const createdAt = new Date(now).toISOString();
|
|
124
|
+
const expiresAt = new Date(new Date(now).getTime() + 60 * 60 * 1000).toISOString();
|
|
125
|
+
return {
|
|
126
|
+
policyVersion: 1,
|
|
127
|
+
mode: normalizedMode,
|
|
128
|
+
deviceIds: normalizeIds(deviceIds),
|
|
129
|
+
categories,
|
|
130
|
+
maxToolCalls: boundedMax,
|
|
131
|
+
expiresAt,
|
|
132
|
+
createdAt
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function hashAgentPermissionPolicy(policy) {
|
|
137
|
+
const canonical = JSON.stringify({
|
|
138
|
+
policyVersion: policy?.policyVersion,
|
|
139
|
+
mode: policy?.mode,
|
|
140
|
+
deviceIds: normalizeIds(policy?.deviceIds),
|
|
141
|
+
categories: normalizeAgentPermissionCategories(policy?.categories),
|
|
142
|
+
allowedAppIds: normalizeIds(policy?.allowedAppIds),
|
|
143
|
+
allowedServiceNames: normalizeIds(policy?.allowedServiceNames),
|
|
144
|
+
allowedFileRoots: normalizeIds(policy?.allowedFileRoots),
|
|
145
|
+
allowedDomains: normalizeIds(policy?.allowedDomains),
|
|
146
|
+
allowedExecutables: normalizeIds(policy?.allowedExecutables),
|
|
147
|
+
maxToolCalls: Number(policy?.maxToolCalls) || 0,
|
|
148
|
+
expiresAt: String(policy?.expiresAt || '')
|
|
149
|
+
});
|
|
150
|
+
return crypto.createHash('sha256').update(canonical).digest('hex');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function targetIdsFromArgs(args, fallback = []) {
|
|
154
|
+
return normalizeIds(args?.deviceIds?.length ? args.deviceIds : fallback);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function isSafeAutoRelativePath(value) {
|
|
158
|
+
const text = String(value || '').trim();
|
|
159
|
+
if (!text || text.length > 600 || text.startsWith('/') || /^[A-Za-z]:[\\/]/.test(text) || text.startsWith('\\\\')) return false;
|
|
160
|
+
const segments = text.replaceAll('\\', '/').split('/').filter(Boolean);
|
|
161
|
+
return segments.length > 0 && !segments.includes('..');
|
|
162
|
+
}
|
|
163
|
+
|
|
161
164
|
function safeAutoConstraint(tool, args, mode) {
|
|
162
165
|
if (mode !== 'safe-auto') return '';
|
|
163
166
|
if (tool.category === 'fileWrite' && !isSafeAutoRelativePath(args?.path)) {
|
|
164
167
|
return 'Safe Auto file writes are limited to relative paths inside the LiveDesk files directory.';
|
|
165
168
|
}
|
|
166
|
-
if (tool.category === '
|
|
167
|
-
const
|
|
168
|
-
if (
|
|
169
|
-
return 'Safe Auto
|
|
169
|
+
if (tool.category === 'fileWrite') {
|
|
170
|
+
const filePath = String(args?.path || '').trim().replaceAll('\\', '/').toLowerCase();
|
|
171
|
+
if (filePath === 'scripts' || filePath.startsWith('scripts/')) {
|
|
172
|
+
return 'Safe Auto cannot write files inside the script directory.';
|
|
170
173
|
}
|
|
171
174
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
risk: tool.risk,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
175
|
+
if (tool.category === 'script') {
|
|
176
|
+
const scriptPath = String(args?.path || '').trim().toLowerCase();
|
|
177
|
+
if (!isSafeAutoRelativePath(scriptPath) || !scriptPath.replaceAll('\\', '/').startsWith('scripts/') || !/\.(ps1|psm1|sh|bash|py|js|mjs|cmd|bat)$/.test(scriptPath)) {
|
|
178
|
+
return 'Safe Auto scripts must be registered relative script files inside the LiveDesk files directory.';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function evaluateAgentToolPermission({ policy, toolName, arguments: args = {}, deviceIds = [] } = {}) {
|
|
185
|
+
const tool = getAgentToolDefinition(toolName);
|
|
186
|
+
if (!tool) return { decision: 'deny', category: 'unknown', risk: 'critical', reason: 'Unknown or unregistered Agent tool.' };
|
|
187
|
+
const allowed = new Set(normalizeIds(policy?.deviceIds));
|
|
188
|
+
const requested = targetIdsFromArgs(args, deviceIds);
|
|
189
|
+
if (requested.some(deviceId => !allowed.has(deviceId))) {
|
|
190
|
+
return { decision: 'deny', category: tool.category, risk: tool.risk, reason: 'The tool requested a device outside the selected Agent scope.' };
|
|
191
|
+
}
|
|
192
|
+
if (policy?.expiresAt && Date.parse(policy.expiresAt) <= Date.now()) {
|
|
193
|
+
return { decision: 'deny', category: tool.category, risk: 'high', reason: 'The Agent permission policy has expired.' };
|
|
194
|
+
}
|
|
195
|
+
const category = TOOL_CATEGORY_ALIASES[tool.name.replace(/^livedesk\./, '')] || tool.category;
|
|
196
|
+
const constraintError = safeAutoConstraint(tool, args, policy?.mode);
|
|
197
|
+
if (constraintError) {
|
|
198
|
+
return { decision: 'deny', category, risk: tool.risk, reason: constraintError };
|
|
199
|
+
}
|
|
200
|
+
const decision = normalizeDecision(policy?.categories?.[category], 'deny');
|
|
201
|
+
return {
|
|
202
|
+
decision,
|
|
203
|
+
category,
|
|
204
|
+
risk: tool.risk,
|
|
205
|
+
reason: decision === 'allow' ? `Allowed by ${policy?.mode || 'current'} policy.` : decision === 'ask' ? 'This operation requires approval in the current policy.' : 'This operation is denied by the current policy.'
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export const DEFAULT_CUSTOM_AGENT_PERMISSION_CATEGORIES = Object.freeze({ ...DEFAULT_CUSTOM_CATEGORIES });
|