@ai-dossier/cli 0.3.0 → 0.4.2
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 +56 -25
- package/bin/dossier-verify +6 -432
- package/dist/cli.js +82 -21
- package/dist/cli.js.map +1 -1
- package/dist/commands/cache.d.ts.map +1 -1
- package/dist/commands/cache.js +5 -1
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/checksum.d.ts.map +1 -1
- package/dist/commands/checksum.js +6 -8
- package/dist/commands/checksum.js.map +1 -1
- package/dist/commands/commands.d.ts +3 -0
- package/dist/commands/commands.d.ts.map +1 -0
- package/dist/commands/commands.js +73 -0
- package/dist/commands/commands.js.map +1 -0
- package/dist/commands/config-cmd.d.ts.map +1 -1
- package/dist/commands/config-cmd.js +170 -7
- package/dist/commands/config-cmd.js.map +1 -1
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +25 -10
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +16 -6
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/format.d.ts.map +1 -1
- package/dist/commands/format.js.map +1 -1
- package/dist/commands/from-file.d.ts.map +1 -1
- package/dist/commands/from-file.js +4 -3
- package/dist/commands/from-file.js.map +1 -1
- package/dist/commands/get.d.ts.map +1 -1
- package/dist/commands/get.js +19 -6
- package/dist/commands/get.js.map +1 -1
- package/dist/commands/info.d.ts.map +1 -1
- package/dist/commands/info.js +28 -58
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/install-skill.d.ts.map +1 -1
- package/dist/commands/install-skill.js +98 -29
- package/dist/commands/install-skill.js.map +1 -1
- package/dist/commands/keys.js +1 -1
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +58 -41
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +32 -6
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +22 -5
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/prompt-hook.d.ts.map +1 -1
- package/dist/commands/prompt-hook.js +4 -13
- package/dist/commands/prompt-hook.js.map +1 -1
- package/dist/commands/publish.d.ts.map +1 -1
- package/dist/commands/publish.js +131 -51
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +23 -8
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +61 -11
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/reset-hooks.js +1 -1
- package/dist/commands/reset-hooks.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +88 -73
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +30 -28
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/sign.d.ts.map +1 -1
- package/dist/commands/sign.js +71 -45
- package/dist/commands/sign.js.map +1 -1
- package/dist/commands/skill-export.d.ts +3 -0
- package/dist/commands/skill-export.d.ts.map +1 -0
- package/dist/commands/skill-export.js +218 -0
- package/dist/commands/skill-export.js.map +1 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +12 -33
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +6 -8
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +115 -12
- package/dist/commands/whoami.js.map +1 -1
- package/dist/config.d.ts +38 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +128 -1
- package/dist/config.js.map +1 -1
- package/dist/credentials.d.ts +14 -8
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +119 -39
- package/dist/credentials.js.map +1 -1
- package/dist/help.d.ts +7 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +86 -0
- package/dist/help.js.map +1 -0
- package/dist/helpers.d.ts +4 -18
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +35 -147
- package/dist/helpers.js.map +1 -1
- package/dist/multi-registry.d.ts +71 -0
- package/dist/multi-registry.d.ts.map +1 -0
- package/dist/multi-registry.js +136 -0
- package/dist/multi-registry.js.map +1 -0
- package/dist/oauth.d.ts +1 -0
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +1 -0
- package/dist/oauth.js.map +1 -1
- package/dist/registry-client.d.ts +64 -8
- package/dist/registry-client.d.ts.map +1 -1
- package/dist/registry-client.js +13 -0
- package/dist/registry-client.js.map +1 -1
- package/dist/verify-dossier.d.ts +28 -0
- package/dist/verify-dossier.d.ts.map +1 -0
- package/dist/verify-dossier.js +329 -0
- package/dist/verify-dossier.js.map +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -36,22 +36,48 @@ cd cli
|
|
|
36
36
|
npm link # Links the CLI globally for development
|
|
37
37
|
|
|
38
38
|
# Or use directly
|
|
39
|
-
chmod +x bin/dossier
|
|
40
|
-
./bin/dossier
|
|
39
|
+
chmod +x bin/ai-dossier
|
|
40
|
+
./bin/ai-dossier verify <file-or-url>
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
+
## Authentication
|
|
46
|
+
|
|
47
|
+
### Interactive (Browser OAuth)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
dossier login
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Non-Interactive (CI/CD, Agents)
|
|
54
|
+
|
|
55
|
+
Set the `DOSSIER_REGISTRY_TOKEN` environment variable:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
export DOSSIER_REGISTRY_TOKEN=<your-token>
|
|
59
|
+
|
|
60
|
+
# Optional: set user/org context
|
|
61
|
+
export DOSSIER_REGISTRY_USER=<username>
|
|
62
|
+
export DOSSIER_REGISTRY_ORGS=org1,org2
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
When `DOSSIER_REGISTRY_TOKEN` is set, it takes precedence over stored credentials. This is recommended for CI/CD pipelines, Docker containers, and AI agent contexts where interactive login is not possible.
|
|
66
|
+
|
|
67
|
+
Commands that require confirmation (`publish`, `remove`, `cache clean`) will fail with a clear error in non-interactive sessions. Use `-y`/`--yes` to skip confirmation prompts.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
45
71
|
## Usage
|
|
46
72
|
|
|
47
73
|
### Basic Verification
|
|
48
74
|
|
|
49
75
|
```bash
|
|
50
76
|
# Verify local file
|
|
51
|
-
dossier
|
|
77
|
+
ai-dossier verify path/to/dossier.ds.md
|
|
52
78
|
|
|
53
79
|
# Verify remote dossier
|
|
54
|
-
dossier
|
|
80
|
+
ai-dossier verify https://example.com/dossier.ds.md
|
|
55
81
|
```
|
|
56
82
|
|
|
57
83
|
**Exit codes**:
|
|
@@ -62,7 +88,7 @@ dossier-verify https://example.com/dossier.ds.md
|
|
|
62
88
|
### Verbose Mode
|
|
63
89
|
|
|
64
90
|
```bash
|
|
65
|
-
dossier
|
|
91
|
+
ai-dossier verify --verbose path/to/dossier.ds.md
|
|
66
92
|
```
|
|
67
93
|
|
|
68
94
|
Shows:
|
|
@@ -77,7 +103,7 @@ Shows:
|
|
|
77
103
|
```bash
|
|
78
104
|
# Shell function wrapper
|
|
79
105
|
claude-run-dossier() {
|
|
80
|
-
if dossier
|
|
106
|
+
if ai-dossier verify "$1"; then
|
|
81
107
|
claude-code "The dossier at $1 has been verified. Please execute it."
|
|
82
108
|
else
|
|
83
109
|
echo "❌ Security verification failed. Not executing."
|
|
@@ -91,7 +117,7 @@ claude-run-dossier https://example.com/dossier.ds.md
|
|
|
91
117
|
**Cursor**:
|
|
92
118
|
```bash
|
|
93
119
|
cursor-run-dossier() {
|
|
94
|
-
if dossier
|
|
120
|
+
if ai-dossier verify "$1"; then
|
|
95
121
|
cursor "Execute the verified dossier at $1"
|
|
96
122
|
else
|
|
97
123
|
echo "❌ Verification failed"
|
|
@@ -106,7 +132,7 @@ safe-run-dossier() {
|
|
|
106
132
|
local url="$1"
|
|
107
133
|
local tool="${2:-claude-code}"
|
|
108
134
|
|
|
109
|
-
if dossier
|
|
135
|
+
if ai-dossier verify "$url"; then
|
|
110
136
|
echo "✅ Dossier verified. Passing to $tool..."
|
|
111
137
|
"$tool" "run $url"
|
|
112
138
|
else
|
|
@@ -173,7 +199,7 @@ safe-run-dossier https://example.com/dossier.ds.md cursor
|
|
|
173
199
|
### Example 1: Legitimate Dossier (Passes)
|
|
174
200
|
|
|
175
201
|
```bash
|
|
176
|
-
$ dossier
|
|
202
|
+
$ ai-dossier verify examples/data-science/train-ml-model.ds.md
|
|
177
203
|
|
|
178
204
|
🔐 Dossier Verification Tool
|
|
179
205
|
|
|
@@ -202,7 +228,7 @@ $ echo $?
|
|
|
202
228
|
### Example 2: Malicious Dossier (Blocked)
|
|
203
229
|
|
|
204
230
|
```bash
|
|
205
|
-
$ dossier
|
|
231
|
+
$ ai-dossier verify https://raw.githubusercontent.com/imboard-ai/ai-dossier/main/examples/security/validate-project-config.ds.md
|
|
206
232
|
|
|
207
233
|
🔐 Dossier Verification Tool
|
|
208
234
|
|
|
@@ -242,7 +268,7 @@ $ echo $?
|
|
|
242
268
|
# Wrapper function for Claude Code
|
|
243
269
|
claude-run-dossier() {
|
|
244
270
|
echo "Verifying dossier security..."
|
|
245
|
-
if
|
|
271
|
+
if ai-dossier verify "$1"; then
|
|
246
272
|
echo ""
|
|
247
273
|
echo "✅ Verification passed. Executing with Claude Code..."
|
|
248
274
|
claude-code "Execute the verified dossier at $1"
|
|
@@ -266,7 +292,7 @@ claude-run-dossier https://example.com/dossier.ds.md
|
|
|
266
292
|
|
|
267
293
|
```
|
|
268
294
|
User Command:
|
|
269
|
-
dossier
|
|
295
|
+
ai-dossier verify https://example.com/dossier.ds.md
|
|
270
296
|
↓
|
|
271
297
|
Download/Read File
|
|
272
298
|
↓
|
|
@@ -331,23 +357,28 @@ Exit 0 (safe) or 1 (unsafe)
|
|
|
331
357
|
|
|
332
358
|
## Roadmap
|
|
333
359
|
|
|
334
|
-
### v0.1.0
|
|
360
|
+
### v0.1.0
|
|
335
361
|
- ✅ Basic checksum verification
|
|
336
362
|
- ✅ Signature presence detection
|
|
337
363
|
- ✅ Exit code support
|
|
338
364
|
- ✅ URL download support
|
|
339
365
|
|
|
340
|
-
### v0.2.0
|
|
341
|
-
-
|
|
342
|
-
-
|
|
343
|
-
-
|
|
344
|
-
|
|
366
|
+
### v0.2.0
|
|
367
|
+
- ✅ Multi-command CLI structure (`ai-dossier <command>`)
|
|
368
|
+
- ✅ `dossier run` command with 5-stage verification pipeline
|
|
369
|
+
- ✅ LLM auto-detection and execution integration
|
|
370
|
+
|
|
371
|
+
### v0.3.0
|
|
372
|
+
- ✅ Modular TypeScript migration
|
|
373
|
+
- ✅ Comprehensive test suite (261+ tests)
|
|
374
|
+
- ✅ CLI parity with dossier-tools
|
|
375
|
+
- ✅ `@ai-dossier` npm scope and CI/CD publishing
|
|
345
376
|
|
|
346
|
-
### v0.
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
377
|
+
### v0.4.0 (Current)
|
|
378
|
+
- ✅ Unified dossier parser across core/cli/mcp
|
|
379
|
+
- ✅ JSON output mode (`--json` flag on commands)
|
|
380
|
+
- ✅ Registry integration (publish, remove, install-skill)
|
|
381
|
+
- ✅ Non-TTY stdin detection
|
|
351
382
|
|
|
352
383
|
### v1.0.0 (Stable)
|
|
353
384
|
- ⏳ Complete signature verification
|
|
@@ -366,10 +397,10 @@ cd cli
|
|
|
366
397
|
npm link # For local testing
|
|
367
398
|
|
|
368
399
|
# Test
|
|
369
|
-
dossier
|
|
400
|
+
ai-dossier verify ../examples/devops/deploy-to-aws.ds.md
|
|
370
401
|
|
|
371
402
|
# Test with malicious example
|
|
372
|
-
dossier
|
|
403
|
+
ai-dossier verify ../examples/security/validate-project-config.ds.md
|
|
373
404
|
```
|
|
374
405
|
|
|
375
406
|
### Adding Features
|
package/bin/dossier-verify
CHANGED
|
@@ -1,435 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* Enforces security verification of dossier files before execution.
|
|
7
|
-
* Provides command-line interface for checksum and signature validation.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* dossier-verify <file-or-url>
|
|
11
|
-
* dossier-verify --run <file-or-url>
|
|
12
|
-
* dossier-verify --verbose <file-or-url>
|
|
13
|
-
*/
|
|
3
|
+
// Thin shim — all logic lives in src/verify-dossier.ts
|
|
4
|
+
const { main } = require('../dist/verify-dossier');
|
|
14
5
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const http = require('http');
|
|
20
|
-
const { execSync } = require('child_process');
|
|
21
|
-
const {
|
|
22
|
-
parseDossierContent,
|
|
23
|
-
verifyIntegrity,
|
|
24
|
-
loadTrustedKeys,
|
|
25
|
-
verifySignature
|
|
26
|
-
} = require('@ai-dossier/core');
|
|
27
|
-
const { convertGitHubBlobToRaw } = require('../dist/github-url');
|
|
28
|
-
|
|
29
|
-
// Colors for terminal output
|
|
30
|
-
const colors = {
|
|
31
|
-
reset: '\x1b[0m',
|
|
32
|
-
bright: '\x1b[1m',
|
|
33
|
-
red: '\x1b[31m',
|
|
34
|
-
green: '\x1b[32m',
|
|
35
|
-
yellow: '\x1b[33m',
|
|
36
|
-
blue: '\x1b[34m',
|
|
37
|
-
cyan: '\x1b[36m',
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
function log(message, color = 'reset') {
|
|
41
|
-
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function error(message) {
|
|
45
|
-
log(`❌ ${message}`, 'red');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function success(message) {
|
|
49
|
-
log(`✅ ${message}`, 'green');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function warning(message) {
|
|
53
|
-
log(`⚠️ ${message}`, 'yellow');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function info(message) {
|
|
57
|
-
log(`ℹ️ ${message}`, 'cyan');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Parse command line arguments
|
|
61
|
-
function parseArgs() {
|
|
62
|
-
const args = process.argv.slice(2);
|
|
63
|
-
const options = {
|
|
64
|
-
verbose: false,
|
|
65
|
-
run: false,
|
|
66
|
-
outputPath: false,
|
|
67
|
-
help: false,
|
|
68
|
-
input: null,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
for (let i = 0; i < args.length; i++) {
|
|
72
|
-
const arg = args[i];
|
|
73
|
-
if (arg === '--verbose' || arg === '-v') {
|
|
74
|
-
options.verbose = true;
|
|
75
|
-
} else if (arg === '--run') {
|
|
76
|
-
options.run = true;
|
|
77
|
-
} else if (arg === '--output-path') {
|
|
78
|
-
options.outputPath = true;
|
|
79
|
-
} else if (arg === '--help' || arg === '-h') {
|
|
80
|
-
options.help = true;
|
|
81
|
-
} else if (!options.input) {
|
|
82
|
-
options.input = arg;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return options;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function showHelp() {
|
|
90
|
-
console.log(`
|
|
91
|
-
${colors.bright}Dossier Verification CLI${colors.reset}
|
|
92
|
-
|
|
93
|
-
${colors.bright}Usage:${colors.reset}
|
|
94
|
-
dossier-verify <file-or-url> Verify dossier security
|
|
95
|
-
dossier-verify --run <file-or-url> Verify and execute if safe
|
|
96
|
-
dossier-verify --verbose <file-or-url> Show detailed verification
|
|
97
|
-
dossier-verify --output-path <url> Download and output path
|
|
98
|
-
dossier-verify --help Show this help
|
|
99
|
-
|
|
100
|
-
${colors.bright}Exit Codes:${colors.reset}
|
|
101
|
-
0 - Verification passed (safe to execute)
|
|
102
|
-
1 - Verification failed (do not execute)
|
|
103
|
-
2 - Error occurred (cannot verify)
|
|
104
|
-
|
|
105
|
-
${colors.bright}Examples:${colors.reset}
|
|
106
|
-
# Verify local file
|
|
107
|
-
dossier-verify path/to/dossier.ds.md
|
|
108
|
-
|
|
109
|
-
# Verify remote dossier
|
|
110
|
-
dossier-verify https://example.com/dossier.ds.md
|
|
111
|
-
|
|
112
|
-
# Verify and run if safe
|
|
113
|
-
dossier-verify --run https://example.com/dossier.ds.md
|
|
114
|
-
|
|
115
|
-
# Use in shell script
|
|
116
|
-
if dossier-verify "$URL"; then
|
|
117
|
-
claude-code "run $URL"
|
|
118
|
-
else
|
|
119
|
-
echo "Security verification failed"
|
|
120
|
-
fi
|
|
121
|
-
|
|
122
|
-
${colors.bright}Security Checks:${colors.reset}
|
|
123
|
-
✓ SHA256 checksum verification (required)
|
|
124
|
-
✓ Signature verification (if present)
|
|
125
|
-
✓ Trusted keys check
|
|
126
|
-
✓ Risk level assessment
|
|
127
|
-
|
|
128
|
-
${colors.bright}More Information:${colors.reset}
|
|
129
|
-
Documentation: https://github.com/imboard/ai-dossier
|
|
130
|
-
Security: SECURITY_STATUS.md
|
|
131
|
-
Protocol: PROTOCOL.md
|
|
132
|
-
`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Download file from URL
|
|
136
|
-
async function downloadFile(url) {
|
|
137
|
-
// Convert GitHub blob URLs to raw URLs
|
|
138
|
-
const resolvedUrl = convertGitHubBlobToRaw(url);
|
|
139
|
-
|
|
140
|
-
return new Promise((resolve, reject) => {
|
|
141
|
-
const protocol = resolvedUrl.startsWith('https://') ? https : http;
|
|
142
|
-
|
|
143
|
-
protocol.get(resolvedUrl, (res) => {
|
|
144
|
-
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
145
|
-
// Follow redirect
|
|
146
|
-
return downloadFile(res.headers.location).then(resolve).catch(reject);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (res.statusCode !== 200) {
|
|
150
|
-
return reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
let data = '';
|
|
154
|
-
res.on('data', (chunk) => { data += chunk; });
|
|
155
|
-
res.on('end', () => resolve(data));
|
|
156
|
-
}).on('error', reject);
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Parse dossier frontmatter (using @ai-dossier/core)
|
|
161
|
-
function parseDossier(content) {
|
|
162
|
-
const parsed = parseDossierContent(content);
|
|
163
|
-
return {
|
|
164
|
-
frontmatter: parsed.frontmatter,
|
|
165
|
-
body: parsed.body
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Verify checksum (using @ai-dossier/core)
|
|
170
|
-
function verifyChecksum(body, declaredHash) {
|
|
171
|
-
const result = verifyIntegrity(body, declaredHash);
|
|
172
|
-
return {
|
|
173
|
-
passed: result.status === 'valid',
|
|
174
|
-
declared: result.expectedHash,
|
|
175
|
-
actual: result.actualHash,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Verify signature (using @ai-dossier/core for real cryptographic verification)
|
|
180
|
-
async function checkSignature(body, frontmatter) {
|
|
181
|
-
if (!frontmatter.signature) {
|
|
182
|
-
return {
|
|
183
|
-
present: false,
|
|
184
|
-
verified: false,
|
|
185
|
-
trusted: false,
|
|
186
|
-
message: 'No signature present',
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const signature = frontmatter.signature;
|
|
191
|
-
const trustedKeys = loadTrustedKeys();
|
|
192
|
-
// Check both key_id and public_key for trusted status
|
|
193
|
-
// key_id is easier to manage in trusted-keys.txt file
|
|
194
|
-
const isTrusted = trustedKeys.has(signature.key_id) || trustedKeys.has(signature.public_key);
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
const isValid = await verifySignature(body, signature);
|
|
198
|
-
|
|
199
|
-
if (!isValid) {
|
|
200
|
-
return {
|
|
201
|
-
present: true,
|
|
202
|
-
verified: false,
|
|
203
|
-
trusted: isTrusted,
|
|
204
|
-
message: 'Signature verification FAILED',
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
present: true,
|
|
210
|
-
verified: true,
|
|
211
|
-
trusted: isTrusted,
|
|
212
|
-
message: isTrusted
|
|
213
|
-
? `Verified signature from trusted source: ${trustedKeys.get(signature.key_id) || trustedKeys.get(signature.public_key)}`
|
|
214
|
-
: 'Valid signature but key is not in trusted list',
|
|
215
|
-
};
|
|
216
|
-
} catch (err) {
|
|
217
|
-
return {
|
|
218
|
-
present: true,
|
|
219
|
-
verified: false,
|
|
220
|
-
trusted: false,
|
|
221
|
-
message: `Verification error: ${err.message}`,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Assess risk
|
|
227
|
-
function assessRisk(frontmatter, checksumResult, signatureResult) {
|
|
228
|
-
const issues = [];
|
|
229
|
-
let riskLevel = 'low';
|
|
230
|
-
let shouldBlock = false;
|
|
231
|
-
|
|
232
|
-
// Checksum failure is critical
|
|
233
|
-
if (!checksumResult.passed) {
|
|
234
|
-
issues.push('Checksum verification FAILED - content has been tampered with');
|
|
235
|
-
riskLevel = 'critical';
|
|
236
|
-
shouldBlock = true;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Signature issues
|
|
240
|
-
if (signatureResult.present && !signatureResult.verified) {
|
|
241
|
-
issues.push('Signature verification FAILED or could not be verified');
|
|
242
|
-
if (riskLevel !== 'critical') riskLevel = 'high';
|
|
243
|
-
shouldBlock = true;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Valid signature but not trusted - BLOCK execution
|
|
247
|
-
if (signatureResult.present && signatureResult.verified && !signatureResult.trusted) {
|
|
248
|
-
issues.push('Signature is valid but signer is not in your trusted keys list');
|
|
249
|
-
issues.push('Add the public key to ~/.dossier/trusted-keys.txt to trust this signer');
|
|
250
|
-
if (riskLevel === 'low') riskLevel = 'medium';
|
|
251
|
-
shouldBlock = true;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// No signature on high-risk dossier
|
|
255
|
-
if (!signatureResult.present && frontmatter.risk_level === 'high') {
|
|
256
|
-
issues.push('High-risk dossier without signature');
|
|
257
|
-
if (riskLevel === 'low') riskLevel = 'medium';
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (!signatureResult.present && frontmatter.risk_level === 'critical') {
|
|
261
|
-
issues.push('Critical-risk dossier without signature');
|
|
262
|
-
if (riskLevel !== 'critical') riskLevel = 'high';
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
level: riskLevel,
|
|
267
|
-
issues,
|
|
268
|
-
recommendation: shouldBlock ? 'BLOCK' : 'ALLOW',
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Main verification function
|
|
273
|
-
async function verifyDossier(input, options) {
|
|
274
|
-
try {
|
|
275
|
-
log(`\n${colors.bright}🔐 Dossier Verification Tool${colors.reset}\n`);
|
|
276
|
-
|
|
277
|
-
// Determine if input is URL or file
|
|
278
|
-
const isUrl = input.startsWith('http://') || input.startsWith('https://');
|
|
279
|
-
let content;
|
|
280
|
-
let displayPath = input;
|
|
281
|
-
|
|
282
|
-
if (isUrl) {
|
|
283
|
-
info(`Downloading: ${input}`);
|
|
284
|
-
content = await downloadFile(input);
|
|
285
|
-
success('Downloaded successfully');
|
|
286
|
-
} else {
|
|
287
|
-
info(`Reading: ${input}`);
|
|
288
|
-
content = fs.readFileSync(input, 'utf8');
|
|
289
|
-
displayPath = path.resolve(input);
|
|
290
|
-
success('File read successfully');
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Parse dossier
|
|
294
|
-
info('Parsing dossier...');
|
|
295
|
-
const { frontmatter, body } = parseDossier(content);
|
|
296
|
-
success(`Parsed: ${frontmatter.title} v${frontmatter.version}`);
|
|
297
|
-
|
|
298
|
-
if (options.verbose) {
|
|
299
|
-
console.log(`\n${colors.bright}Dossier Metadata:${colors.reset}`);
|
|
300
|
-
console.log(` Title: ${frontmatter.title}`);
|
|
301
|
-
console.log(` Version: ${frontmatter.version}`);
|
|
302
|
-
console.log(` Risk Level: ${frontmatter.risk_level}`);
|
|
303
|
-
console.log(` Protocol: ${frontmatter.protocol_version}`);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Verify checksum
|
|
307
|
-
console.log(`\n${colors.bright}📊 Integrity Check:${colors.reset}`);
|
|
308
|
-
const checksumResult = verifyChecksum(body, frontmatter.checksum?.hash);
|
|
309
|
-
|
|
310
|
-
if (checksumResult.passed) {
|
|
311
|
-
success('Checksum VALID - content has not been tampered with');
|
|
312
|
-
if (options.verbose) {
|
|
313
|
-
console.log(` Hash: ${checksumResult.actual}`);
|
|
314
|
-
}
|
|
315
|
-
} else {
|
|
316
|
-
error('Checksum INVALID - content has been modified!');
|
|
317
|
-
if (options.verbose) {
|
|
318
|
-
console.log(` Declared: ${checksumResult.declared}`);
|
|
319
|
-
console.log(` Actual: ${checksumResult.actual}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Verify signature
|
|
324
|
-
console.log(`\n${colors.bright}🔏 Authenticity Check:${colors.reset}`);
|
|
325
|
-
const signatureResult = await checkSignature(body, frontmatter);
|
|
326
|
-
|
|
327
|
-
if (signatureResult.present) {
|
|
328
|
-
if (signatureResult.verified && signatureResult.trusted) {
|
|
329
|
-
success('Signature VERIFIED - from trusted author');
|
|
330
|
-
} else if (signatureResult.verified && !signatureResult.trusted) {
|
|
331
|
-
warning(signatureResult.message);
|
|
332
|
-
if (frontmatter.signature?.signed_by) {
|
|
333
|
-
console.log(` Signed by: ${frontmatter.signature.signed_by}`);
|
|
334
|
-
}
|
|
335
|
-
console.log(`\n ${colors.cyan}To trust this signer, run:${colors.reset}`);
|
|
336
|
-
const publicKey = frontmatter.signature.public_key || frontmatter.signature.key_id;
|
|
337
|
-
const identifier = frontmatter.signature.signed_by
|
|
338
|
-
? frontmatter.signature.signed_by.split('<')[0].trim().toLowerCase().replace(/\s+/g, '-')
|
|
339
|
-
: 'unknown-signer';
|
|
340
|
-
console.log(` ${colors.bright}dossier keys add "${publicKey}" "${identifier}"${colors.reset}\n`);
|
|
341
|
-
} else {
|
|
342
|
-
warning(signatureResult.message);
|
|
343
|
-
if (frontmatter.signature?.signed_by) {
|
|
344
|
-
console.log(` Signed by: ${frontmatter.signature.signed_by}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
} else {
|
|
348
|
-
warning('No signature present (dossier is unsigned)');
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Assess risk
|
|
352
|
-
console.log(`\n${colors.bright}🔴 Risk Assessment:${colors.reset}`);
|
|
353
|
-
const risk = assessRisk(frontmatter, checksumResult, signatureResult);
|
|
354
|
-
|
|
355
|
-
const riskColors = {
|
|
356
|
-
low: 'green',
|
|
357
|
-
medium: 'yellow',
|
|
358
|
-
high: 'yellow',
|
|
359
|
-
critical: 'red',
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
log(` Risk Level: ${risk.level.toUpperCase()}`, riskColors[risk.level]);
|
|
363
|
-
|
|
364
|
-
if (risk.issues.length > 0) {
|
|
365
|
-
console.log(`\n Issues Found:`);
|
|
366
|
-
risk.issues.forEach(issue => {
|
|
367
|
-
console.log(` - ${issue}`);
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Recommendation
|
|
372
|
-
console.log(`\n${colors.bright}Recommendation:${colors.reset}`, risk.recommendation);
|
|
373
|
-
|
|
374
|
-
if (risk.recommendation === 'BLOCK') {
|
|
375
|
-
error('\nDO NOT EXECUTE this dossier');
|
|
376
|
-
console.log(' Security verification failed.');
|
|
377
|
-
console.log(' This dossier may have been tampered with or is from an untrusted source.\n');
|
|
378
|
-
return false;
|
|
379
|
-
} else if (risk.level === 'medium' || risk.level === 'high') {
|
|
380
|
-
warning('\nProceed with CAUTION');
|
|
381
|
-
console.log(' Review the dossier code before executing.');
|
|
382
|
-
console.log(' Consider the risk level and your trust in the source.\n');
|
|
383
|
-
return true;
|
|
384
|
-
} else {
|
|
385
|
-
success('\nSafe to execute');
|
|
386
|
-
console.log(' Dossier passed security verification.\n');
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
} catch (err) {
|
|
391
|
-
error(`\nVerification failed: ${err.message}`);
|
|
392
|
-
if (options.verbose) {
|
|
393
|
-
console.error(err);
|
|
394
|
-
}
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Main entry point
|
|
400
|
-
async function main() {
|
|
401
|
-
const options = parseArgs();
|
|
402
|
-
|
|
403
|
-
if (options.help || !options.input) {
|
|
404
|
-
showHelp();
|
|
405
|
-
process.exit(options.help ? 0 : 2);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const passed = await verifyDossier(options.input, options);
|
|
409
|
-
|
|
410
|
-
if (!passed) {
|
|
411
|
-
process.exit(1); // Verification failed
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (options.run) {
|
|
415
|
-
warning('\n--run flag not yet implemented');
|
|
416
|
-
warning('For now, manually execute the dossier if verification passed');
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (options.outputPath) {
|
|
420
|
-
// Would output the path to downloaded file
|
|
421
|
-
warning('\n--output-path flag not yet implemented');
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
process.exit(0); // Success
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Run if called directly
|
|
428
|
-
if (require.main === module) {
|
|
429
|
-
main().catch(err => {
|
|
430
|
-
error(`Fatal error: ${err.message}`);
|
|
431
|
-
process.exit(2);
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
module.exports = { verifyDossier, parseDossier, verifyChecksum };
|
|
6
|
+
main().catch((err) => {
|
|
7
|
+
console.error(`Fatal error: ${err.message}`);
|
|
8
|
+
process.exit(2);
|
|
9
|
+
});
|