@ai-dossier/cli 0.4.1 → 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 +30 -25
- package/bin/dossier-verify +6 -440
- package/dist/cli.js +80 -21
- package/dist/cli.js.map +1 -1
- package/dist/commands/cache.d.ts.map +1 -1
- package/dist/commands/cache.js.map +1 -1
- package/dist/commands/checksum.js.map +1 -1
- package/dist/commands/commands.d.ts.map +1 -1
- package/dist/commands/commands.js +4 -2
- package/dist/commands/commands.js.map +1 -1
- 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 +19 -8
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +15 -5
- 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/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 +20 -6
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/install-skill.d.ts.map +1 -1
- package/dist/commands/install-skill.js +16 -9
- package/dist/commands/install-skill.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 +54 -41
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +25 -5
- 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 +35 -22
- 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 +25 -12
- 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 +75 -48
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +29 -27
- package/dist/commands/search.js.map +1 -1
- 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.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +1 -8
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +108 -25
- 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 -9
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +119 -49
- 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 +2 -10
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +10 -28
- 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/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 +1 -1
package/README.md
CHANGED
|
@@ -36,8 +36,8 @@ 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
|
---
|
|
@@ -74,10 +74,10 @@ Commands that require confirmation (`publish`, `remove`, `cache clean`) will fai
|
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
# Verify local file
|
|
77
|
-
dossier
|
|
77
|
+
ai-dossier verify path/to/dossier.ds.md
|
|
78
78
|
|
|
79
79
|
# Verify remote dossier
|
|
80
|
-
dossier
|
|
80
|
+
ai-dossier verify https://example.com/dossier.ds.md
|
|
81
81
|
```
|
|
82
82
|
|
|
83
83
|
**Exit codes**:
|
|
@@ -88,7 +88,7 @@ dossier-verify https://example.com/dossier.ds.md
|
|
|
88
88
|
### Verbose Mode
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
-
dossier
|
|
91
|
+
ai-dossier verify --verbose path/to/dossier.ds.md
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
Shows:
|
|
@@ -103,7 +103,7 @@ Shows:
|
|
|
103
103
|
```bash
|
|
104
104
|
# Shell function wrapper
|
|
105
105
|
claude-run-dossier() {
|
|
106
|
-
if dossier
|
|
106
|
+
if ai-dossier verify "$1"; then
|
|
107
107
|
claude-code "The dossier at $1 has been verified. Please execute it."
|
|
108
108
|
else
|
|
109
109
|
echo "❌ Security verification failed. Not executing."
|
|
@@ -117,7 +117,7 @@ claude-run-dossier https://example.com/dossier.ds.md
|
|
|
117
117
|
**Cursor**:
|
|
118
118
|
```bash
|
|
119
119
|
cursor-run-dossier() {
|
|
120
|
-
if dossier
|
|
120
|
+
if ai-dossier verify "$1"; then
|
|
121
121
|
cursor "Execute the verified dossier at $1"
|
|
122
122
|
else
|
|
123
123
|
echo "❌ Verification failed"
|
|
@@ -132,7 +132,7 @@ safe-run-dossier() {
|
|
|
132
132
|
local url="$1"
|
|
133
133
|
local tool="${2:-claude-code}"
|
|
134
134
|
|
|
135
|
-
if dossier
|
|
135
|
+
if ai-dossier verify "$url"; then
|
|
136
136
|
echo "✅ Dossier verified. Passing to $tool..."
|
|
137
137
|
"$tool" "run $url"
|
|
138
138
|
else
|
|
@@ -199,7 +199,7 @@ safe-run-dossier https://example.com/dossier.ds.md cursor
|
|
|
199
199
|
### Example 1: Legitimate Dossier (Passes)
|
|
200
200
|
|
|
201
201
|
```bash
|
|
202
|
-
$ dossier
|
|
202
|
+
$ ai-dossier verify examples/data-science/train-ml-model.ds.md
|
|
203
203
|
|
|
204
204
|
🔐 Dossier Verification Tool
|
|
205
205
|
|
|
@@ -228,7 +228,7 @@ $ echo $?
|
|
|
228
228
|
### Example 2: Malicious Dossier (Blocked)
|
|
229
229
|
|
|
230
230
|
```bash
|
|
231
|
-
$ dossier
|
|
231
|
+
$ ai-dossier verify https://raw.githubusercontent.com/imboard-ai/ai-dossier/main/examples/security/validate-project-config.ds.md
|
|
232
232
|
|
|
233
233
|
🔐 Dossier Verification Tool
|
|
234
234
|
|
|
@@ -268,7 +268,7 @@ $ echo $?
|
|
|
268
268
|
# Wrapper function for Claude Code
|
|
269
269
|
claude-run-dossier() {
|
|
270
270
|
echo "Verifying dossier security..."
|
|
271
|
-
if
|
|
271
|
+
if ai-dossier verify "$1"; then
|
|
272
272
|
echo ""
|
|
273
273
|
echo "✅ Verification passed. Executing with Claude Code..."
|
|
274
274
|
claude-code "Execute the verified dossier at $1"
|
|
@@ -292,7 +292,7 @@ claude-run-dossier https://example.com/dossier.ds.md
|
|
|
292
292
|
|
|
293
293
|
```
|
|
294
294
|
User Command:
|
|
295
|
-
dossier
|
|
295
|
+
ai-dossier verify https://example.com/dossier.ds.md
|
|
296
296
|
↓
|
|
297
297
|
Download/Read File
|
|
298
298
|
↓
|
|
@@ -357,23 +357,28 @@ Exit 0 (safe) or 1 (unsafe)
|
|
|
357
357
|
|
|
358
358
|
## Roadmap
|
|
359
359
|
|
|
360
|
-
### v0.1.0
|
|
360
|
+
### v0.1.0
|
|
361
361
|
- ✅ Basic checksum verification
|
|
362
362
|
- ✅ Signature presence detection
|
|
363
363
|
- ✅ Exit code support
|
|
364
364
|
- ✅ URL download support
|
|
365
365
|
|
|
366
|
-
### v0.2.0
|
|
367
|
-
-
|
|
368
|
-
-
|
|
369
|
-
-
|
|
370
|
-
- ⏳ Better error messages
|
|
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
|
|
371
370
|
|
|
372
|
-
### v0.3.0
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
376
|
-
-
|
|
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
|
|
376
|
+
|
|
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
|
|
377
382
|
|
|
378
383
|
### v1.0.0 (Stable)
|
|
379
384
|
- ⏳ Complete signature verification
|
|
@@ -392,10 +397,10 @@ cd cli
|
|
|
392
397
|
npm link # For local testing
|
|
393
398
|
|
|
394
399
|
# Test
|
|
395
|
-
dossier
|
|
400
|
+
ai-dossier verify ../examples/devops/deploy-to-aws.ds.md
|
|
396
401
|
|
|
397
402
|
# Test with malicious example
|
|
398
|
-
dossier
|
|
403
|
+
ai-dossier verify ../examples/security/validate-project-config.ds.md
|
|
399
404
|
```
|
|
400
405
|
|
|
401
406
|
### Adding Features
|
package/bin/dossier-verify
CHANGED
|
@@ -1,443 +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(`\u274C ${message}`, 'red');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function success(message) {
|
|
49
|
-
log(`\u2705 ${message}`, 'green');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function warning(message) {
|
|
53
|
-
log(`\u26A0\uFE0F ${message}`, 'yellow');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function info(message) {
|
|
57
|
-
log(`\u2139\uFE0F ${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
|
-
\u2713 SHA256 checksum verification (required)
|
|
124
|
-
\u2713 Signature verification (if present)
|
|
125
|
-
\u2713 Trusted keys check
|
|
126
|
-
\u2713 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
|
-
const result = await verifySignature(body, signature);
|
|
197
|
-
|
|
198
|
-
if (result.valid) {
|
|
199
|
-
return {
|
|
200
|
-
present: true,
|
|
201
|
-
verified: true,
|
|
202
|
-
trusted: isTrusted,
|
|
203
|
-
message: isTrusted
|
|
204
|
-
? `Verified signature from trusted source: ${trustedKeys.get(signature.key_id) || trustedKeys.get(signature.public_key)}`
|
|
205
|
-
: 'Valid signature but key is not in trusted list',
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (result.error) {
|
|
210
|
-
return {
|
|
211
|
-
present: true,
|
|
212
|
-
verified: false,
|
|
213
|
-
trusted: false,
|
|
214
|
-
message: `Verification error: ${result.error}`,
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return {
|
|
219
|
-
present: true,
|
|
220
|
-
verified: false,
|
|
221
|
-
trusted: isTrusted,
|
|
222
|
-
message: 'Signature verification FAILED',
|
|
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}\uD83D\uDD10 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}\uD83D\uDCCA 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}\uD83D\uDD0F 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}\uD83D\uDD34 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 = {
|
|
436
|
-
verifyDossier,
|
|
437
|
-
parseDossier,
|
|
438
|
-
verifyChecksum,
|
|
439
|
-
assessRisk,
|
|
440
|
-
checkSignature,
|
|
441
|
-
parseArgs,
|
|
442
|
-
downloadFile,
|
|
443
|
-
};
|
|
6
|
+
main().catch((err) => {
|
|
7
|
+
console.error(`Fatal error: ${err.message}`);
|
|
8
|
+
process.exit(2);
|
|
9
|
+
});
|