@ellistevo/openclaw-secure 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/LICENSE +21 -0
- package/MOLTBOOK-POST.md +66 -0
- package/OPENCLAW-PR.md +136 -0
- package/README.md +222 -0
- package/bin/cli.js +399 -0
- package/package.json +43 -0
- package/src/index.js +57 -0
- package/src/schema.js +269 -0
- package/src/signing.js +193 -0
- package/src/trust.js +279 -0
- package/src/validator.js +128 -0
- package/test/all.test.js +529 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OpenClaw Secure CLI
|
|
5
|
+
* Command-line tool for managing skill security
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Command } = require('commander');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const yaml = require('yaml');
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
|
|
14
|
+
// Import our modules
|
|
15
|
+
const {
|
|
16
|
+
validateManifest,
|
|
17
|
+
generateKeyPair,
|
|
18
|
+
signManifest,
|
|
19
|
+
verifyManifest,
|
|
20
|
+
isSigned,
|
|
21
|
+
calculateTrustScore,
|
|
22
|
+
formatTrustScore,
|
|
23
|
+
getKeyFingerprint,
|
|
24
|
+
defaultPermissions,
|
|
25
|
+
defaultResources
|
|
26
|
+
} = require('../src');
|
|
27
|
+
|
|
28
|
+
const program = new Command();
|
|
29
|
+
|
|
30
|
+
// Colors for terminal output (basic ANSI)
|
|
31
|
+
const colors = {
|
|
32
|
+
reset: '\x1b[0m',
|
|
33
|
+
red: '\x1b[31m',
|
|
34
|
+
green: '\x1b[32m',
|
|
35
|
+
yellow: '\x1b[33m',
|
|
36
|
+
blue: '\x1b[34m',
|
|
37
|
+
cyan: '\x1b[36m',
|
|
38
|
+
gray: '\x1b[90m',
|
|
39
|
+
bold: '\x1b[1m'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function colorize(text, color) {
|
|
43
|
+
return `${colors[color] || ''}${text}${colors.reset}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function success(msg) {
|
|
47
|
+
console.log(colorize('✓ ' + msg, 'green'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function error(msg) {
|
|
51
|
+
console.error(colorize('✗ ' + msg, 'red'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function info(msg) {
|
|
55
|
+
console.log(colorize('ℹ ' + msg, 'cyan'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function warn(msg) {
|
|
59
|
+
console.log(colorize('⚠ ' + msg, 'yellow'));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
program
|
|
63
|
+
.name('openclaw-secure')
|
|
64
|
+
.description('Security toolkit for OpenClaw skills')
|
|
65
|
+
.version('1.0.0');
|
|
66
|
+
|
|
67
|
+
// === INIT COMMAND ===
|
|
68
|
+
program
|
|
69
|
+
.command('init')
|
|
70
|
+
.description('Initialize a new skill manifest (skill.yaml)')
|
|
71
|
+
.option('-n, --name <name>', 'Skill name')
|
|
72
|
+
.option('-a, --author <author>', 'Author name')
|
|
73
|
+
.option('-f, --force', 'Overwrite existing manifest')
|
|
74
|
+
.action((options) => {
|
|
75
|
+
const manifestPath = path.join(process.cwd(), 'skill.yaml');
|
|
76
|
+
|
|
77
|
+
if (fs.existsSync(manifestPath) && !options.force) {
|
|
78
|
+
error('skill.yaml already exists. Use --force to overwrite.');
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const skillName = options.name || path.basename(process.cwd());
|
|
83
|
+
const authorName = options.author || process.env.USER || 'unknown';
|
|
84
|
+
|
|
85
|
+
const manifest = {
|
|
86
|
+
name: skillName.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
87
|
+
version: '1.0.0',
|
|
88
|
+
display_name: skillName,
|
|
89
|
+
description: 'A secure OpenClaw skill',
|
|
90
|
+
author: {
|
|
91
|
+
name: authorName
|
|
92
|
+
},
|
|
93
|
+
license: 'MIT',
|
|
94
|
+
permissions: {
|
|
95
|
+
network: {
|
|
96
|
+
allow: [],
|
|
97
|
+
deny: []
|
|
98
|
+
},
|
|
99
|
+
filesystem: {
|
|
100
|
+
read: [],
|
|
101
|
+
write: [],
|
|
102
|
+
deny: ['~/.ssh', '~/.gnupg', '~/.config/openclaw']
|
|
103
|
+
},
|
|
104
|
+
shell: {
|
|
105
|
+
allowed: false,
|
|
106
|
+
commands: []
|
|
107
|
+
},
|
|
108
|
+
credentials: [],
|
|
109
|
+
capabilities: {
|
|
110
|
+
browser: false,
|
|
111
|
+
messaging: false,
|
|
112
|
+
cron: false,
|
|
113
|
+
spawn_agents: false
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
resources: {
|
|
117
|
+
max_memory_mb: 256,
|
|
118
|
+
max_cpu_percent: 25,
|
|
119
|
+
max_runtime_seconds: 60,
|
|
120
|
+
max_network_mb: 10
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const yamlContent = yaml.stringify(manifest);
|
|
125
|
+
fs.writeFileSync(manifestPath, yamlContent);
|
|
126
|
+
|
|
127
|
+
success(`Created skill.yaml for "${manifest.name}"`);
|
|
128
|
+
info('Edit the manifest to declare your skill\'s permissions');
|
|
129
|
+
info('Then run: openclaw-secure sign');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// === VALIDATE COMMAND ===
|
|
133
|
+
program
|
|
134
|
+
.command('validate')
|
|
135
|
+
.description('Validate a skill manifest')
|
|
136
|
+
.argument('[path]', 'Path to skill.yaml', 'skill.yaml')
|
|
137
|
+
.action((manifestPath) => {
|
|
138
|
+
if (!fs.existsSync(manifestPath)) {
|
|
139
|
+
error(`File not found: ${manifestPath}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const content = fs.readFileSync(manifestPath, 'utf8');
|
|
145
|
+
const manifest = yaml.parse(content);
|
|
146
|
+
|
|
147
|
+
const result = validateManifest(manifest);
|
|
148
|
+
|
|
149
|
+
if (result.valid) {
|
|
150
|
+
success('Manifest is valid');
|
|
151
|
+
|
|
152
|
+
// Show trust score
|
|
153
|
+
const trust = calculateTrustScore(result.manifest, {
|
|
154
|
+
signed: isSigned(result.manifest),
|
|
155
|
+
verified: false
|
|
156
|
+
});
|
|
157
|
+
console.log(formatTrustScore(trust));
|
|
158
|
+
} else {
|
|
159
|
+
error('Manifest validation failed:');
|
|
160
|
+
result.errors.forEach(err => {
|
|
161
|
+
console.log(` - ${err}`);
|
|
162
|
+
});
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
} catch (err) {
|
|
166
|
+
error(`Failed to parse manifest: ${err.message}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// === KEYGEN COMMAND ===
|
|
172
|
+
program
|
|
173
|
+
.command('keygen')
|
|
174
|
+
.description('Generate a new signing keypair')
|
|
175
|
+
.option('-o, --output <dir>', 'Output directory', path.join(process.env.HOME || process.env.USERPROFILE, '.openclaw-secure'))
|
|
176
|
+
.option('-n, --name <name>', 'Key name', 'default')
|
|
177
|
+
.action((options) => {
|
|
178
|
+
const outputDir = options.output;
|
|
179
|
+
const keyName = options.name;
|
|
180
|
+
|
|
181
|
+
// Create output directory if needed
|
|
182
|
+
if (!fs.existsSync(outputDir)) {
|
|
183
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const secretKeyPath = path.join(outputDir, `${keyName}.key`);
|
|
187
|
+
const publicKeyPath = path.join(outputDir, `${keyName}.pub`);
|
|
188
|
+
|
|
189
|
+
if (fs.existsSync(secretKeyPath)) {
|
|
190
|
+
error(`Key already exists: ${secretKeyPath}`);
|
|
191
|
+
error('Use a different --name or delete the existing key');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Generate keypair
|
|
196
|
+
const keyPair = generateKeyPair();
|
|
197
|
+
|
|
198
|
+
// Save keys
|
|
199
|
+
fs.writeFileSync(secretKeyPath, keyPair.secretKey, { mode: 0o600 });
|
|
200
|
+
fs.writeFileSync(publicKeyPath, keyPair.publicKey, { mode: 0o644 });
|
|
201
|
+
|
|
202
|
+
const fingerprint = getKeyFingerprint(keyPair.publicKey);
|
|
203
|
+
|
|
204
|
+
success('Generated new signing keypair');
|
|
205
|
+
info(`Secret key: ${secretKeyPath}`);
|
|
206
|
+
info(`Public key: ${publicKeyPath}`);
|
|
207
|
+
info(`Fingerprint: ${fingerprint}`);
|
|
208
|
+
warn('Keep your secret key safe! Anyone with it can sign skills as you.');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// === SIGN COMMAND ===
|
|
212
|
+
program
|
|
213
|
+
.command('sign')
|
|
214
|
+
.description('Sign a skill manifest')
|
|
215
|
+
.argument('[path]', 'Path to skill.yaml', 'skill.yaml')
|
|
216
|
+
.option('-k, --key <path>', 'Path to secret key')
|
|
217
|
+
.option('-n, --name <name>', 'Signer name (for signature block)')
|
|
218
|
+
.action((manifestPath, options) => {
|
|
219
|
+
if (!fs.existsSync(manifestPath)) {
|
|
220
|
+
error(`File not found: ${manifestPath}`);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Find secret key
|
|
225
|
+
let secretKeyPath = options.key;
|
|
226
|
+
if (!secretKeyPath) {
|
|
227
|
+
const defaultKeyDir = path.join(process.env.HOME || process.env.USERPROFILE, '.openclaw-secure');
|
|
228
|
+
secretKeyPath = path.join(defaultKeyDir, 'default.key');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!fs.existsSync(secretKeyPath)) {
|
|
232
|
+
error(`Secret key not found: ${secretKeyPath}`);
|
|
233
|
+
error('Run: openclaw-secure keygen');
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
// Read manifest
|
|
239
|
+
const content = fs.readFileSync(manifestPath, 'utf8');
|
|
240
|
+
const manifest = yaml.parse(content);
|
|
241
|
+
|
|
242
|
+
// Validate first
|
|
243
|
+
const validation = validateManifest(manifest);
|
|
244
|
+
if (!validation.valid) {
|
|
245
|
+
error('Manifest validation failed. Fix errors before signing:');
|
|
246
|
+
validation.errors.forEach(err => console.log(` - ${err}`));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Read secret key
|
|
251
|
+
const secretKey = fs.readFileSync(secretKeyPath, 'utf8').trim();
|
|
252
|
+
|
|
253
|
+
// Determine signer name
|
|
254
|
+
const signerName = options.name || manifest.author?.name || 'unknown';
|
|
255
|
+
|
|
256
|
+
// Sign manifest
|
|
257
|
+
const signedManifest = signManifest(validation.manifest, secretKey, signerName);
|
|
258
|
+
|
|
259
|
+
// Write back
|
|
260
|
+
const yamlContent = yaml.stringify(signedManifest);
|
|
261
|
+
fs.writeFileSync(manifestPath, yamlContent);
|
|
262
|
+
|
|
263
|
+
success(`Signed manifest as "${signerName}"`);
|
|
264
|
+
info(`Hash: ${signedManifest.signature.content_hash}`);
|
|
265
|
+
info(`Signed at: ${signedManifest.signature.signed_at}`);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
error(`Signing failed: ${err.message}`);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// === VERIFY COMMAND ===
|
|
273
|
+
program
|
|
274
|
+
.command('verify')
|
|
275
|
+
.description('Verify a signed skill manifest')
|
|
276
|
+
.argument('[path]', 'Path to skill.yaml', 'skill.yaml')
|
|
277
|
+
.action((manifestPath) => {
|
|
278
|
+
if (!fs.existsSync(manifestPath)) {
|
|
279
|
+
error(`File not found: ${manifestPath}`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const content = fs.readFileSync(manifestPath, 'utf8');
|
|
285
|
+
const manifest = yaml.parse(content);
|
|
286
|
+
|
|
287
|
+
if (!isSigned(manifest)) {
|
|
288
|
+
error('Manifest is not signed');
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const result = verifyManifest(manifest);
|
|
293
|
+
|
|
294
|
+
if (result.valid) {
|
|
295
|
+
success('Signature is valid');
|
|
296
|
+
info(`Signer: ${result.signer}`);
|
|
297
|
+
info(`Signed at: ${result.signed_at}`);
|
|
298
|
+
info(`Public key: ${result.public_key.substring(0, 16)}...`);
|
|
299
|
+
|
|
300
|
+
// Show trust score
|
|
301
|
+
const trust = calculateTrustScore(manifest, { signed: true, verified: true });
|
|
302
|
+
console.log(formatTrustScore(trust));
|
|
303
|
+
} else {
|
|
304
|
+
error(`Verification failed: ${result.error}`);
|
|
305
|
+
if (result.expected && result.computed) {
|
|
306
|
+
info(`Expected hash: ${result.expected}`);
|
|
307
|
+
info(`Computed hash: ${result.computed}`);
|
|
308
|
+
}
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
} catch (err) {
|
|
312
|
+
error(`Verification failed: ${err.message}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// === AUDIT COMMAND ===
|
|
318
|
+
program
|
|
319
|
+
.command('audit')
|
|
320
|
+
.description('Audit a skill and show trust score')
|
|
321
|
+
.argument('[path]', 'Path to skill.yaml', 'skill.yaml')
|
|
322
|
+
.option('-v, --verbose', 'Show detailed breakdown')
|
|
323
|
+
.action((manifestPath, options) => {
|
|
324
|
+
if (!fs.existsSync(manifestPath)) {
|
|
325
|
+
error(`File not found: ${manifestPath}`);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const content = fs.readFileSync(manifestPath, 'utf8');
|
|
331
|
+
const manifest = yaml.parse(content);
|
|
332
|
+
|
|
333
|
+
// Validate
|
|
334
|
+
const validation = validateManifest(manifest);
|
|
335
|
+
if (!validation.valid) {
|
|
336
|
+
warn('Manifest has validation errors:');
|
|
337
|
+
validation.errors.forEach(err => console.log(` - ${err}`));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Check signature
|
|
341
|
+
let signed = false;
|
|
342
|
+
let verified = false;
|
|
343
|
+
|
|
344
|
+
if (isSigned(manifest)) {
|
|
345
|
+
const verifyResult = verifyManifest(manifest);
|
|
346
|
+
signed = true;
|
|
347
|
+
verified = verifyResult.valid;
|
|
348
|
+
|
|
349
|
+
if (verified) {
|
|
350
|
+
success(`Signed by: ${verifyResult.signer}`);
|
|
351
|
+
} else {
|
|
352
|
+
warn(`Signature present but invalid: ${verifyResult.error}`);
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
warn('Manifest is not signed');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Calculate trust score
|
|
359
|
+
const trust = calculateTrustScore(validation.manifest, { signed, verified });
|
|
360
|
+
console.log(formatTrustScore(trust));
|
|
361
|
+
|
|
362
|
+
// Verbose output
|
|
363
|
+
if (options.verbose) {
|
|
364
|
+
console.log('\n' + colorize('Permissions Detail:', 'bold'));
|
|
365
|
+
console.log(yaml.stringify(validation.manifest.permissions));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
} catch (err) {
|
|
369
|
+
error(`Audit failed: ${err.message}`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// === SHOW-PUBLIC-KEY COMMAND ===
|
|
375
|
+
program
|
|
376
|
+
.command('show-key')
|
|
377
|
+
.description('Show public key for sharing')
|
|
378
|
+
.option('-n, --name <name>', 'Key name', 'default')
|
|
379
|
+
.action((options) => {
|
|
380
|
+
const keyDir = path.join(process.env.HOME || process.env.USERPROFILE, '.openclaw-secure');
|
|
381
|
+
const publicKeyPath = path.join(keyDir, `${options.name}.pub`);
|
|
382
|
+
|
|
383
|
+
if (!fs.existsSync(publicKeyPath)) {
|
|
384
|
+
error(`Public key not found: ${publicKeyPath}`);
|
|
385
|
+
error('Run: openclaw-secure keygen');
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const publicKey = fs.readFileSync(publicKeyPath, 'utf8').trim();
|
|
390
|
+
const fingerprint = getKeyFingerprint(publicKey);
|
|
391
|
+
|
|
392
|
+
console.log('\n' + colorize('Your Public Key:', 'bold'));
|
|
393
|
+
console.log(publicKey);
|
|
394
|
+
console.log('\n' + colorize('Fingerprint:', 'bold'));
|
|
395
|
+
console.log(fingerprint);
|
|
396
|
+
console.log('\n' + colorize('Share this key so others can verify your signatures.', 'gray'));
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ellistevo/openclaw-secure",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Security toolkit for OpenClaw skills - signing, manifests, and verification",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"openclaw-secure": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test test/*.test.js",
|
|
11
|
+
"lint": "eslint src/ bin/",
|
|
12
|
+
"build": "echo 'No build step needed'",
|
|
13
|
+
"prepublishOnly": "npm test"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"openclaw",
|
|
17
|
+
"ai",
|
|
18
|
+
"agent",
|
|
19
|
+
"security",
|
|
20
|
+
"signing",
|
|
21
|
+
"manifest"
|
|
22
|
+
],
|
|
23
|
+
"author": "Sociable Inc <hello@sociable.social>",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/sociable-inc/openclaw-secure"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=18.0.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"yaml": "^2.3.4",
|
|
34
|
+
"ajv": "^8.12.0",
|
|
35
|
+
"ajv-formats": "^2.1.1",
|
|
36
|
+
"commander": "^11.1.0",
|
|
37
|
+
"tweetnacl": "^1.0.3",
|
|
38
|
+
"tweetnacl-util": "^0.15.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"eslint": "^8.56.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Secure
|
|
3
|
+
* Security toolkit for OpenClaw skills - signing, manifests, and verification
|
|
4
|
+
*
|
|
5
|
+
* @author Sociable Inc <hello@sociable.social>
|
|
6
|
+
* @license MIT
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { manifestSchema, defaultPermissions, defaultResources } = require('./schema');
|
|
10
|
+
const { validateManifest, isValidManifest, assertValidManifest, applyDefaults } = require('./validator');
|
|
11
|
+
const {
|
|
12
|
+
generateKeyPair,
|
|
13
|
+
computeHash,
|
|
14
|
+
signManifest,
|
|
15
|
+
verifyManifest,
|
|
16
|
+
isSigned,
|
|
17
|
+
getSignableContent,
|
|
18
|
+
getKeyFingerprint
|
|
19
|
+
} = require('./signing');
|
|
20
|
+
const {
|
|
21
|
+
calculateTrustScore,
|
|
22
|
+
scoreToGrade,
|
|
23
|
+
gradeColor,
|
|
24
|
+
gradeEmoji,
|
|
25
|
+
formatTrustScore,
|
|
26
|
+
RISK_WEIGHTS
|
|
27
|
+
} = require('./trust');
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
// Schema
|
|
31
|
+
manifestSchema,
|
|
32
|
+
defaultPermissions,
|
|
33
|
+
defaultResources,
|
|
34
|
+
|
|
35
|
+
// Validation
|
|
36
|
+
validateManifest,
|
|
37
|
+
isValidManifest,
|
|
38
|
+
assertValidManifest,
|
|
39
|
+
applyDefaults,
|
|
40
|
+
|
|
41
|
+
// Signing
|
|
42
|
+
generateKeyPair,
|
|
43
|
+
computeHash,
|
|
44
|
+
signManifest,
|
|
45
|
+
verifyManifest,
|
|
46
|
+
isSigned,
|
|
47
|
+
getSignableContent,
|
|
48
|
+
getKeyFingerprint,
|
|
49
|
+
|
|
50
|
+
// Trust scoring
|
|
51
|
+
calculateTrustScore,
|
|
52
|
+
scoreToGrade,
|
|
53
|
+
gradeColor,
|
|
54
|
+
gradeEmoji,
|
|
55
|
+
formatTrustScore,
|
|
56
|
+
RISK_WEIGHTS
|
|
57
|
+
};
|