mecord-connect 1.0.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/DISCLOSURE +47 -0
- package/LICENSE +64 -0
- package/README.md +44 -0
- package/bin/operator.mjs +10 -0
- package/package.json +58 -0
- package/runtime/app/apps/local-agent/src/approval-store.js +342 -0
- package/runtime/app/apps/local-agent/src/bootstrap-config.js +255 -0
- package/runtime/app/apps/local-agent/src/cli.js +237 -0
- package/runtime/app/apps/local-agent/src/device-reset.js +260 -0
- package/runtime/app/apps/local-agent/src/emergency-stop.js +141 -0
- package/runtime/app/apps/local-agent/src/main.js +311 -0
- package/runtime/app/apps/local-agent/src/privacy-data.js +129 -0
- package/runtime/app/apps/local-agent/src/relay-agent.js +333 -0
- package/runtime/app/apps/local-agent/src/relay-enrollment.js +156 -0
- package/runtime/app/apps/local-agent/src/relay-session-credentials.js +436 -0
- package/runtime/app/apps/local-agent/src/remote.js +130 -0
- package/runtime/app/apps/local-agent/src/runtime-factory.js +66 -0
- package/runtime/app/apps/local-agent/src/server.js +578 -0
- package/runtime/app/package.json +4 -0
- package/runtime/app/src/capabilities/browser-cdp-connection.js +199 -0
- package/runtime/app/src/capabilities/browser-cdp-frames.js +288 -0
- package/runtime/app/src/capabilities/browser-cdp-page.js +430 -0
- package/runtime/app/src/capabilities/browser-cdp.js +384 -0
- package/runtime/app/src/capabilities/browser-managed.js +345 -0
- package/runtime/app/src/capabilities/docker.js +448 -0
- package/runtime/app/src/capabilities/filesystem.js +264 -0
- package/runtime/app/src/capabilities/git-checkpoint.js +415 -0
- package/runtime/app/src/capabilities/git-write.js +321 -0
- package/runtime/app/src/capabilities/git.js +252 -0
- package/runtime/app/src/capabilities/index.js +16 -0
- package/runtime/app/src/capabilities/path-scope.js +86 -0
- package/runtime/app/src/capabilities/postgres.js +566 -0
- package/runtime/app/src/capabilities/process.js +258 -0
- package/runtime/app/src/capabilities/project-command.js +599 -0
- package/runtime/app/src/capabilities/project-transaction.js +215 -0
- package/runtime/app/src/capabilities/project.js +67 -0
- package/runtime/app/src/capabilities/system.js +40 -0
- package/runtime/app/src/capabilities/vscode.js +312 -0
- package/runtime/app/src/capabilities/windows-uia.js +331 -0
- package/runtime/app/src/core/account-device-registry.js +604 -0
- package/runtime/app/src/core/action-identity.js +44 -0
- package/runtime/app/src/core/audit.js +295 -0
- package/runtime/app/src/core/capability-policy.js +58 -0
- package/runtime/app/src/core/child-environment.js +29 -0
- package/runtime/app/src/core/device-enrollment.js +417 -0
- package/runtime/app/src/core/device-identity.js +436 -0
- package/runtime/app/src/core/device-registry.js +477 -0
- package/runtime/app/src/core/device-reset.js +178 -0
- package/runtime/app/src/core/device-routing.js +305 -0
- package/runtime/app/src/core/durable-state.js +306 -0
- package/runtime/app/src/core/errors.js +20 -0
- package/runtime/app/src/core/evidence.js +9 -0
- package/runtime/app/src/core/index.js +17 -0
- package/runtime/app/src/core/machine-state.js +160 -0
- package/runtime/app/src/core/network-authority.js +20 -0
- package/runtime/app/src/core/policy.js +67 -0
- package/runtime/app/src/core/product-identity.js +7 -0
- package/runtime/app/src/core/provenance.js +18 -0
- package/runtime/app/src/core/provider-learning.js +274 -0
- package/runtime/app/src/core/public-plugin-surface.js +25 -0
- package/runtime/app/src/core/public-restricted-data.js +105 -0
- package/runtime/app/src/core/rate-limit.js +118 -0
- package/runtime/app/src/core/relay-client.js +568 -0
- package/runtime/app/src/core/relay-delivery-store.js +512 -0
- package/runtime/app/src/core/relay-result-store.js +321 -0
- package/runtime/app/src/core/release-update.js +253 -0
- package/runtime/app/src/core/router.js +96 -0
- package/runtime/app/src/core/runtime.js +136 -0
- package/runtime/app/src/core/scoped-path-syntax.js +16 -0
- package/runtime/app/src/core/session-token.js +498 -0
- package/runtime/app/src/core/task-orchestrator.js +1148 -0
- package/runtime/app/src/core/task-store.js +517 -0
- package/runtime/app/src/core/task.js +142 -0
- package/runtime/app/src/core/trusted-executable.js +124 -0
- package/runtime/app/src/core/types.js +83 -0
- package/runtime/app/src/core/windows-path-lease.js +138 -0
- package/runtime/native/operator-windows-dpapi.exe +0 -0
- package/runtime/native/operator-windows-path-lease.exe +0 -0
- package/runtime/native/operator-windows-uia.exe +0 -0
- package/runtime/runtime-manifest.json +380 -0
- package/src/cli.mjs +345 -0
- package/src/publish-check.mjs +22 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import { OperatorError } from './errors.js';
|
|
4
|
+
import { appendDurableStateText, readDurableStateText, writeDurableStateText } from './durable-state.js';
|
|
5
|
+
|
|
6
|
+
const SECRET_KEY = /(token|password|secret|authorization|cookie|private.?key|api.?key)/i;
|
|
7
|
+
const AUDIT_CHAIN_VERSION = 1 ;
|
|
8
|
+
const HASH_RE = /^[0-9a-f]{64}$/;
|
|
9
|
+
const MAX_AUDIT_BYTES = 256 * 1024 * 1024;
|
|
10
|
+
const MAX_AUDIT_HEAD_BYTES = 64 * 1024;
|
|
11
|
+
const MAX_AUDIT_EVENT_BYTES = 256 * 1024;
|
|
12
|
+
const MAX_REDACT_COLLECTION_ITEMS = 1000;
|
|
13
|
+
const MAX_TAIL_EVENTS = 1000;
|
|
14
|
+
const AUDIT_STATE_OPTIONS = {
|
|
15
|
+
maxBytes: MAX_AUDIT_BYTES,
|
|
16
|
+
errorCode: 'AUDIT_INTEGRITY_FAILED',
|
|
17
|
+
invalidMessage: 'Audit log file is invalid.'
|
|
18
|
+
} ;
|
|
19
|
+
const AUDIT_HEAD_OPTIONS = {
|
|
20
|
+
maxBytes: MAX_AUDIT_HEAD_BYTES,
|
|
21
|
+
errorCode: 'AUDIT_INTEGRITY_FAILED',
|
|
22
|
+
invalidMessage: 'Audit head file is invalid.'
|
|
23
|
+
} ;
|
|
24
|
+
|
|
25
|
+
function redact(value , depth = 0) {
|
|
26
|
+
if (depth > 8) return '[TRUNCATED_DEPTH]';
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
const output = value.slice(0, MAX_REDACT_COLLECTION_ITEMS).map((item) => redact(item, depth + 1));
|
|
29
|
+
if (value.length > MAX_REDACT_COLLECTION_ITEMS) output.push(`[TRUNCATED_${value.length - MAX_REDACT_COLLECTION_ITEMS}_ITEMS]`);
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
32
|
+
if (value && typeof value === 'object') {
|
|
33
|
+
const output = {};
|
|
34
|
+
const entries = Object.entries(value );
|
|
35
|
+
for (const [key, child] of entries.slice(0, MAX_REDACT_COLLECTION_ITEMS)) {
|
|
36
|
+
output[key] = SECRET_KEY.test(key) ? '[REDACTED]' : redact(child, depth + 1);
|
|
37
|
+
}
|
|
38
|
+
if (entries.length > MAX_REDACT_COLLECTION_ITEMS) output.__operatorTruncatedEntries = entries.length - MAX_REDACT_COLLECTION_ITEMS;
|
|
39
|
+
return output;
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === 'string' && value.length > 16_384) return `${value.slice(0, 16_384)}…[TRUNCATED]`;
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
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
|
+
export class AuditLog {
|
|
83
|
+
#file ;
|
|
84
|
+
#headFile ;
|
|
85
|
+
#head = null;
|
|
86
|
+
#queue = Promise.resolve();
|
|
87
|
+
|
|
88
|
+
constructor(stateDir ) {
|
|
89
|
+
const root = path.resolve(stateDir);
|
|
90
|
+
this.#file = path.join(root, 'audit.ndjson');
|
|
91
|
+
this.#headFile = path.join(root, 'audit-head.json');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async append(event ) {
|
|
95
|
+
let appended ;
|
|
96
|
+
const operation = this.#queue.then(async () => {
|
|
97
|
+
const head = await this.#loadHead();
|
|
98
|
+
const base = stripChain(redact({
|
|
99
|
+
...event,
|
|
100
|
+
id: event.id ?? crypto.randomUUID(),
|
|
101
|
+
timestamp: event.timestamp ?? new Date().toISOString()
|
|
102
|
+
}) );
|
|
103
|
+
appended = chainEvent(base, head.headHash);
|
|
104
|
+
const line = `${JSON.stringify(appended)}\n`;
|
|
105
|
+
if (Buffer.byteLength(line, 'utf8') > MAX_AUDIT_EVENT_BYTES) {
|
|
106
|
+
throw new OperatorError('AUDIT_EVENT_TOO_LARGE', `Audit event exceeds ${MAX_AUDIT_EVENT_BYTES} UTF-8 bytes after redaction.`);
|
|
107
|
+
}
|
|
108
|
+
await appendDurableStateText(this.#file, line, AUDIT_STATE_OPTIONS);
|
|
109
|
+
const next = { count: head.count + 1, headHash: appended.hash };
|
|
110
|
+
try {
|
|
111
|
+
await this.#writeHead(next);
|
|
112
|
+
this.#head = next;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.#head = null;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
this.#queue = operation.then(() => undefined, () => undefined);
|
|
119
|
+
await operation;
|
|
120
|
+
return appended;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async tail(limit = 100) {
|
|
124
|
+
await this.#queue;
|
|
125
|
+
const verified = await this.#readAndVerify(true);
|
|
126
|
+
const parsed = Number(limit);
|
|
127
|
+
const bounded = Number.isFinite(parsed) ? Math.min(Math.max(Math.trunc(parsed), 1), MAX_TAIL_EVENTS) : 100;
|
|
128
|
+
return verified.events.slice(-bounded);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async verifyIntegrity() {
|
|
132
|
+
await this.#queue;
|
|
133
|
+
const verified = await this.#readAndVerify(true);
|
|
134
|
+
return { valid: true, count: verified.count, headHash: verified.headHash };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async #loadHead() {
|
|
138
|
+
if (this.#head) return this.#head;
|
|
139
|
+
const verified = await this.#readAndVerify(true);
|
|
140
|
+
this.#head = { count: verified.count, headHash: verified.headHash };
|
|
141
|
+
return this.#head;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async #readAndVerify(reconcileAnchor ) {
|
|
145
|
+
let text ;
|
|
146
|
+
try {
|
|
147
|
+
text = await readDurableStateText(this.#file, AUDIT_STATE_OPTIONS);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
if ((error ).code === 'ENOENT') {
|
|
150
|
+
const empty = { events: [], count: 0, headHash: null };
|
|
151
|
+
if (reconcileAnchor) await this.#reconcileAnchor(empty);
|
|
152
|
+
return empty;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const lines = text.split('\n').filter((line) => line.length > 0);
|
|
158
|
+
if (lines.length === 0) {
|
|
159
|
+
const empty = { events: [], count: 0, headHash: null };
|
|
160
|
+
if (reconcileAnchor) await this.#reconcileAnchor(empty);
|
|
161
|
+
return empty;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let parsed ;
|
|
165
|
+
try {
|
|
166
|
+
parsed = lines.map((line) => JSON.parse(line) );
|
|
167
|
+
} catch {
|
|
168
|
+
throw integrityError('Audit log contains invalid or partial JSON.');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const chained = parsed.filter((event) => event.chainVersion === AUDIT_CHAIN_VERSION || event.hash !== undefined || event.previousHash !== undefined);
|
|
172
|
+
if (chained.length === 0) {
|
|
173
|
+
parsed = await this.#migrateLegacy(parsed);
|
|
174
|
+
} else if (chained.length !== parsed.length) {
|
|
175
|
+
throw integrityError('Audit log mixes chained and unchained records.');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let previousHash = null;
|
|
179
|
+
for (let index = 0; index < parsed.length; index += 1) {
|
|
180
|
+
const event = parsed[index] ;
|
|
181
|
+
if (event.chainVersion !== AUDIT_CHAIN_VERSION || typeof event.hash !== 'string' || !HASH_RE.test(event.hash)) {
|
|
182
|
+
throw integrityError(`Audit record ${index} has invalid chain metadata.`);
|
|
183
|
+
}
|
|
184
|
+
if (event.previousHash !== previousHash) {
|
|
185
|
+
throw integrityError(`Audit record ${index} does not link to the preceding record.`);
|
|
186
|
+
}
|
|
187
|
+
const expected = eventHash(stripChain(event), previousHash);
|
|
188
|
+
if (!safeHashEqual(event.hash, expected)) {
|
|
189
|
+
throw integrityError(`Audit record ${index} hash verification failed.`);
|
|
190
|
+
}
|
|
191
|
+
previousHash = event.hash;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const verified = { events: parsed, count: parsed.length, headHash: previousHash };
|
|
195
|
+
if (reconcileAnchor) await this.#reconcileAnchor(verified);
|
|
196
|
+
return verified;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async #migrateLegacy(events ) {
|
|
200
|
+
let previousHash = null;
|
|
201
|
+
const migrated = events.map((event) => {
|
|
202
|
+
const chained = chainEvent(stripChain(event), previousHash);
|
|
203
|
+
previousHash = chained.hash ;
|
|
204
|
+
return chained;
|
|
205
|
+
});
|
|
206
|
+
await writeDurableStateText(this.#file, `${migrated.map((event) => JSON.stringify(event)).join('\n')}\n`, AUDIT_STATE_OPTIONS);
|
|
207
|
+
await this.#writeHead({ count: migrated.length, headHash: previousHash });
|
|
208
|
+
return migrated;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async #reconcileAnchor(verified ) {
|
|
212
|
+
let anchor = null;
|
|
213
|
+
try {
|
|
214
|
+
anchor = JSON.parse(await readDurableStateText(this.#headFile, AUDIT_HEAD_OPTIONS)) ;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if ((error ).code !== 'ENOENT') throw integrityError('Audit head metadata is unreadable.');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!anchor) {
|
|
220
|
+
if (verified.count > 0) await this.#writeHead({ count: verified.count, headHash: verified.headHash });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (anchor.version !== 1 || !Number.isSafeInteger(anchor.count) || anchor.count < 0 ||
|
|
224
|
+
(anchor.headHash !== null && (typeof anchor.headHash !== 'string' || !HASH_RE.test(anchor.headHash)))) {
|
|
225
|
+
throw integrityError('Audit head metadata is invalid.');
|
|
226
|
+
}
|
|
227
|
+
if (anchor.count === verified.count && anchor.headHash === verified.headHash) return;
|
|
228
|
+
|
|
229
|
+
// Recover only the narrow crash window where one fully chained line reached disk
|
|
230
|
+
// but its head-file update did not. Anything else fails closed.
|
|
231
|
+
if (verified.count === anchor.count + 1) {
|
|
232
|
+
const last = verified.events.at(-1) ;
|
|
233
|
+
if (last.previousHash === anchor.headHash) {
|
|
234
|
+
await this.#writeHead({ count: verified.count, headHash: verified.headHash });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
throw integrityError('Audit log and persisted head metadata disagree.');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async #writeHead(head ) {
|
|
242
|
+
const record = {
|
|
243
|
+
version: 1,
|
|
244
|
+
count: head.count,
|
|
245
|
+
headHash: head.headHash,
|
|
246
|
+
updatedAt: new Date().toISOString()
|
|
247
|
+
};
|
|
248
|
+
await writeDurableStateText(this.#headFile, `${JSON.stringify(record, null, 2)}\n`, AUDIT_HEAD_OPTIONS);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function stripChain(event ) {
|
|
253
|
+
const { chainVersion: _chainVersion, previousHash: _previousHash, hash: _hash, ...base } = event;
|
|
254
|
+
return base;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function chainEvent(base , previousHash ) {
|
|
258
|
+
return {
|
|
259
|
+
...base,
|
|
260
|
+
chainVersion: AUDIT_CHAIN_VERSION,
|
|
261
|
+
previousHash,
|
|
262
|
+
hash: eventHash(base, previousHash)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function eventHash(base , previousHash ) {
|
|
267
|
+
return crypto.createHash('sha256')
|
|
268
|
+
.update('operator-audit-chain-v1\n')
|
|
269
|
+
.update(previousHash ?? 'GENESIS')
|
|
270
|
+
.update('\n')
|
|
271
|
+
.update(canonicalJson(base))
|
|
272
|
+
.digest('hex');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function canonicalJson(value ) {
|
|
276
|
+
if (value === null || value === undefined) return 'null';
|
|
277
|
+
if (typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value);
|
|
278
|
+
if (typeof value === 'number') return Number.isFinite(value) ? JSON.stringify(value) : 'null';
|
|
279
|
+
if (Array.isArray(value)) return `[${value.map((item) => canonicalJson(item)).join(',')}]`;
|
|
280
|
+
if (typeof value === 'object') {
|
|
281
|
+
const object = value ;
|
|
282
|
+
const keys = Object.keys(object).filter((key) => object[key] !== undefined).sort();
|
|
283
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(object[key])}`).join(',')}}`;
|
|
284
|
+
}
|
|
285
|
+
return 'null';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function safeHashEqual(actual , expected ) {
|
|
289
|
+
if (!HASH_RE.test(actual) || !HASH_RE.test(expected)) return false;
|
|
290
|
+
return crypto.timingSafeEqual(Buffer.from(actual, 'hex'), Buffer.from(expected, 'hex'));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function integrityError(message ) {
|
|
294
|
+
return new OperatorError('AUDIT_INTEGRITY_FAILED', message);
|
|
295
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
|
|
2
|
+
import { PolicyError } from './errors.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export const CAPABILITY_RISK_RULES = Object.freeze ({
|
|
7
|
+
'computer.inspect': 'read',
|
|
8
|
+
'project.inspect': 'read',
|
|
9
|
+
'project.command.inspect': 'read',
|
|
10
|
+
'project.command.run': 'dynamic',
|
|
11
|
+
'project.transaction.run': 'destructive',
|
|
12
|
+
'file.read': 'read',
|
|
13
|
+
'file.list': 'read',
|
|
14
|
+
'file.write': 'write',
|
|
15
|
+
'file.create': 'write',
|
|
16
|
+
'file.replace': 'destructive',
|
|
17
|
+
'git.status': 'read',
|
|
18
|
+
'git.diff': 'read',
|
|
19
|
+
'git.rev-parse': 'read',
|
|
20
|
+
'git.checkpoint.inspect': 'read',
|
|
21
|
+
'git.checkpoint.create': 'write',
|
|
22
|
+
'git.checkpoint.restore': 'destructive',
|
|
23
|
+
'git.write': 'write',
|
|
24
|
+
'docker.inspect': 'read',
|
|
25
|
+
'docker.manage': 'system',
|
|
26
|
+
'postgres.inspect': 'read',
|
|
27
|
+
'postgres.select': 'read',
|
|
28
|
+
'vscode.inspect': 'read',
|
|
29
|
+
'vscode.open': 'system',
|
|
30
|
+
'terminal.execute': 'destructive',
|
|
31
|
+
'browser.inspect': 'read',
|
|
32
|
+
'browser.navigate': 'write',
|
|
33
|
+
'browser.interact': 'external',
|
|
34
|
+
'browser.tab.focus': 'write',
|
|
35
|
+
'browser.tab.close': 'destructive',
|
|
36
|
+
'app.inspect': 'read',
|
|
37
|
+
'app.operate': 'external'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export function capabilityRiskRule(capability ) {
|
|
41
|
+
const rule = CAPABILITY_RISK_RULES[capability];
|
|
42
|
+
if (!rule) {
|
|
43
|
+
throw new PolicyError(
|
|
44
|
+
'CAPABILITY_RISK_UNREGISTERED',
|
|
45
|
+
`Capability ${capability} has no canonical risk policy.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return rule;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function assertCanonicalRisk(action , canonicalRisk ) {
|
|
52
|
+
if (action.risk === canonicalRisk) return;
|
|
53
|
+
throw new PolicyError(
|
|
54
|
+
'ACTION_RISK_MISMATCH',
|
|
55
|
+
`Capability ${action.capability} must be authorized as ${canonicalRisk}.`,
|
|
56
|
+
{ suppliedRisk: action.risk, canonicalRisk }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
const DESKTOP_ENV_KEYS = [
|
|
4
|
+
'PATH', 'Path', 'PATHEXT', 'SYSTEMROOT', 'WINDIR',
|
|
5
|
+
'HOME', 'USERPROFILE', 'HOMEDRIVE', 'HOMEPATH',
|
|
6
|
+
'USER', 'USERNAME', 'LOGNAME', 'TMP', 'TEMP', 'TMPDIR',
|
|
7
|
+
'LANG', 'LC_ALL', 'LC_CTYPE', 'TERM', 'COLORTERM', 'SHELL',
|
|
8
|
+
'PROGRAMFILES', 'PROGRAMFILES(X86)', 'PROGRAMW6432', 'PROGRAMDATA',
|
|
9
|
+
'LOCALAPPDATA', 'APPDATA', 'DISPLAY', 'WAYLAND_DISPLAY',
|
|
10
|
+
'XDG_RUNTIME_DIR', 'XDG_DATA_HOME', 'XDG_CONFIG_HOME',
|
|
11
|
+
'DBUS_SESSION_BUS_ADDRESS'
|
|
12
|
+
] ;
|
|
13
|
+
|
|
14
|
+
const WINDOWS_NATIVE_ENV_KEYS = [
|
|
15
|
+
'SYSTEMROOT', 'WINDIR', 'USERPROFILE', 'LOCALAPPDATA', 'APPDATA',
|
|
16
|
+
'TMP', 'TEMP'
|
|
17
|
+
] ;
|
|
18
|
+
|
|
19
|
+
export function safeChildEnvironment(
|
|
20
|
+
profile ,
|
|
21
|
+
source = process.env
|
|
22
|
+
) {
|
|
23
|
+
const keys = profile === 'desktop' ? DESKTOP_ENV_KEYS : WINDOWS_NATIVE_ENV_KEYS;
|
|
24
|
+
const env = {};
|
|
25
|
+
for (const key of keys) {
|
|
26
|
+
if (source[key] !== undefined) env[key] = source[key];
|
|
27
|
+
}
|
|
28
|
+
return env;
|
|
29
|
+
}
|