coverme-scanner 1.2.0 → 1.3.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/README.md +44 -4
- package/dist/cli/index.js +110 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +12 -6
- package/dist/cli/init.js.map +1 -1
- package/dist/prompts/orchestration.md +160 -2
- package/dist/prompts/runtime-verify.md +353 -0
- package/package.json +1 -1
- package/src/cli/index.ts +127 -0
- package/src/cli/init.ts +12 -6
- package/src/prompts/orchestration.md +160 -2
- package/src/prompts/runtime-verify.md +353 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
# CoverMe Runtime Verification Agent
|
|
2
|
+
|
|
3
|
+
Compare your **actual runtime environment** against **code configuration** to find dangerous mismatches.
|
|
4
|
+
|
|
5
|
+
## Arguments
|
|
6
|
+
|
|
7
|
+
$ARGUMENTS - Environment name (from `coverme verify list`)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## CRITICAL: This agent requires SSH access
|
|
12
|
+
|
|
13
|
+
Before running, ensure:
|
|
14
|
+
1. SSH environment is configured: `coverme verify setup --host user@server --name production`
|
|
15
|
+
2. You have SSH key or password ready
|
|
16
|
+
3. The environment name is passed as argument
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## PHASE 1: Load Environment Configuration
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
cat .coverme/runtime.json 2>/dev/null || echo "NO_RUNTIME_CONFIG"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If no config found, output:
|
|
27
|
+
```
|
|
28
|
+
ERROR: No runtime environments configured.
|
|
29
|
+
|
|
30
|
+
To set up runtime verification:
|
|
31
|
+
coverme verify setup --host user@server.com --name production
|
|
32
|
+
|
|
33
|
+
Then run:
|
|
34
|
+
/coverme-verify production
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Extract the environment details:
|
|
38
|
+
- `host`: SSH host (user@server)
|
|
39
|
+
- `port`: SSH port
|
|
40
|
+
- `keyPath`: Optional SSH key path
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## PHASE 2: Gather Code Expectations
|
|
45
|
+
|
|
46
|
+
Before SSH, analyze the codebase to understand EXPECTED runtime configuration:
|
|
47
|
+
|
|
48
|
+
### Check Dockerfile/Docker Compose
|
|
49
|
+
```bash
|
|
50
|
+
cat Dockerfile 2>/dev/null
|
|
51
|
+
cat docker-compose*.yml 2>/dev/null
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Look for:
|
|
55
|
+
- `USER` directive (what user should container run as?)
|
|
56
|
+
- `EXPOSE` ports
|
|
57
|
+
- Environment variables
|
|
58
|
+
- Volume mounts
|
|
59
|
+
|
|
60
|
+
### Check Kubernetes/Helm
|
|
61
|
+
```bash
|
|
62
|
+
cat k8s/*.yaml helm/**/values.yaml 2>/dev/null
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Look for:
|
|
66
|
+
- `securityContext.runAsUser`
|
|
67
|
+
- `securityContext.runAsNonRoot`
|
|
68
|
+
- `securityContext.readOnlyRootFilesystem`
|
|
69
|
+
- Resource limits
|
|
70
|
+
- Service accounts
|
|
71
|
+
|
|
72
|
+
### Check PM2/Process Manager
|
|
73
|
+
```bash
|
|
74
|
+
cat ecosystem.config.js pm2.config.js 2>/dev/null
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Look for:
|
|
78
|
+
- `user` field
|
|
79
|
+
- `cwd` working directory
|
|
80
|
+
- Environment variables
|
|
81
|
+
|
|
82
|
+
### Check Systemd
|
|
83
|
+
```bash
|
|
84
|
+
cat *.service 2>/dev/null
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Look for:
|
|
88
|
+
- `User=` directive
|
|
89
|
+
- `Group=` directive
|
|
90
|
+
- `WorkingDirectory=`
|
|
91
|
+
|
|
92
|
+
### Build Expected State Object
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"expected": {
|
|
96
|
+
"user": "appuser",
|
|
97
|
+
"uid": 1000,
|
|
98
|
+
"workDir": "/app",
|
|
99
|
+
"ports": [3000, 8080],
|
|
100
|
+
"env": ["NODE_ENV=production"],
|
|
101
|
+
"readOnlyFs": true,
|
|
102
|
+
"source": "Dockerfile line 15: USER appuser"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## PHASE 3: SSH and Gather Runtime State
|
|
110
|
+
|
|
111
|
+
**IMPORTANT**: Use the Bash tool with SSH commands. The user has pre-configured SSH access.
|
|
112
|
+
|
|
113
|
+
Build SSH command prefix:
|
|
114
|
+
```
|
|
115
|
+
SSH_CMD="ssh -o StrictHostKeyChecking=no -p {port} {keyPath ? '-i ' + keyPath : ''} {host}"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Check Running Processes
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Find Node.js/Python/Java processes
|
|
122
|
+
$SSH_CMD "ps aux | grep -E 'node|python|java|pm2' | grep -v grep"
|
|
123
|
+
|
|
124
|
+
# Get current user
|
|
125
|
+
$SSH_CMD "whoami"
|
|
126
|
+
|
|
127
|
+
# Get user running the app
|
|
128
|
+
$SSH_CMD "ps -eo user,pid,cmd | grep -E 'node|python|java' | head -5"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Check Docker Containers (if Docker)
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# List running containers
|
|
135
|
+
$SSH_CMD "docker ps --format '{{.Names}}\t{{.Image}}\t{{.Status}}'"
|
|
136
|
+
|
|
137
|
+
# Get user inside container
|
|
138
|
+
$SSH_CMD "docker exec {container_name} whoami"
|
|
139
|
+
|
|
140
|
+
# Get container user ID
|
|
141
|
+
$SSH_CMD "docker exec {container_name} id"
|
|
142
|
+
|
|
143
|
+
# Check container security
|
|
144
|
+
$SSH_CMD "docker inspect {container_name} --format '{{.Config.User}} {{.HostConfig.ReadonlyRootfs}} {{.HostConfig.Privileged}}'"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Check Kubernetes Pods (if K8s)
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Get pods
|
|
151
|
+
$SSH_CMD "kubectl get pods -o wide"
|
|
152
|
+
|
|
153
|
+
# Get security context
|
|
154
|
+
$SSH_CMD "kubectl get pod {pod_name} -o jsonpath='{.spec.securityContext}'"
|
|
155
|
+
|
|
156
|
+
# Get container user
|
|
157
|
+
$SSH_CMD "kubectl exec {pod_name} -- id"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Check PM2 Processes
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# PM2 status
|
|
164
|
+
$SSH_CMD "pm2 list"
|
|
165
|
+
|
|
166
|
+
# PM2 process details
|
|
167
|
+
$SSH_CMD "pm2 show 0 | grep -E 'user|uid|gid|cwd'"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Check System Permissions
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
# Check file permissions on app directory
|
|
174
|
+
$SSH_CMD "ls -la /app/ 2>/dev/null || ls -la /home/*/app/ 2>/dev/null"
|
|
175
|
+
|
|
176
|
+
# Check who owns the app files
|
|
177
|
+
$SSH_CMD "stat -c '%U:%G %a %n' /app/* 2>/dev/null | head -10"
|
|
178
|
+
|
|
179
|
+
# Check sensitive files
|
|
180
|
+
$SSH_CMD "ls -la /app/.env* /app/config/*.json 2>/dev/null"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Check Network
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Check listening ports
|
|
187
|
+
$SSH_CMD "netstat -tlnp 2>/dev/null || ss -tlnp"
|
|
188
|
+
|
|
189
|
+
# Check firewall rules
|
|
190
|
+
$SSH_CMD "iptables -L -n 2>/dev/null || ufw status 2>/dev/null"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Build Actual State Object
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"actual": {
|
|
197
|
+
"user": "root",
|
|
198
|
+
"uid": 0,
|
|
199
|
+
"workDir": "/app",
|
|
200
|
+
"ports": [3000, 8080, 6379],
|
|
201
|
+
"env": ["NODE_ENV=production"],
|
|
202
|
+
"readOnlyFs": false,
|
|
203
|
+
"privileged": false,
|
|
204
|
+
"containerUser": "root",
|
|
205
|
+
"processUser": "root"
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## PHASE 4: Compare and Generate Findings
|
|
213
|
+
|
|
214
|
+
Compare expected vs actual for EACH field:
|
|
215
|
+
|
|
216
|
+
### Critical Mismatches (RUNTIME prefix)
|
|
217
|
+
|
|
218
|
+
| Finding | Severity | When to Report |
|
|
219
|
+
|---------|----------|----------------|
|
|
220
|
+
| RUNTIME-001 | CRITICAL | Code says `USER appuser`, runtime runs as `root` |
|
|
221
|
+
| RUNTIME-002 | CRITICAL | Dockerfile has `USER`, but container runs as root |
|
|
222
|
+
| RUNTIME-003 | HIGH | `runAsNonRoot: true` in K8s, but pod runs as root |
|
|
223
|
+
| RUNTIME-004 | HIGH | ReadOnlyRootFilesystem expected, but writable |
|
|
224
|
+
| RUNTIME-005 | HIGH | Privileged container not expected |
|
|
225
|
+
| RUNTIME-006 | MEDIUM | Port exposed that shouldn't be (e.g., Redis 6379) |
|
|
226
|
+
| RUNTIME-007 | MEDIUM | Environment variables missing or different |
|
|
227
|
+
| RUNTIME-008 | MEDIUM | File permissions too open (777, world-writable) |
|
|
228
|
+
| RUNTIME-009 | LOW | Working directory mismatch |
|
|
229
|
+
| RUNTIME-010 | INFO | Extra processes running not in config |
|
|
230
|
+
|
|
231
|
+
### Output Format
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"id": "RUNTIME-001",
|
|
236
|
+
"title": "Container running as root despite USER directive",
|
|
237
|
+
"severity": "critical",
|
|
238
|
+
"category": "runtime-mismatch",
|
|
239
|
+
"fixOwner": "devops",
|
|
240
|
+
"fixType": "infrastructure",
|
|
241
|
+
|
|
242
|
+
"expected": {
|
|
243
|
+
"value": "appuser (uid 1000)",
|
|
244
|
+
"source": "Dockerfile:15 - USER appuser",
|
|
245
|
+
"code": "USER appuser"
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
"actual": {
|
|
249
|
+
"value": "root (uid 0)",
|
|
250
|
+
"source": "docker exec container_name id",
|
|
251
|
+
"evidence": "uid=0(root) gid=0(root) groups=0(root)"
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
"description": "The Dockerfile specifies USER appuser, but the container is actually running as root. This happened because the docker run command included --user root or the orchestration layer overrode the USER directive.",
|
|
255
|
+
|
|
256
|
+
"impact": "Running as root inside the container means any code execution vulnerability (like the DuckDB file read issue) can access ALL files on the system, not just application files. Attackers can read /etc/shadow, modify system configs, and potentially escape the container.",
|
|
257
|
+
|
|
258
|
+
"recommendation": "1. Check docker-compose.yml for user: root override\n2. Check K8s deployment for securityContext.runAsUser: 0\n3. Ensure no --user root in docker run commands\n4. Add securityContext.runAsNonRoot: true to K8s",
|
|
259
|
+
|
|
260
|
+
"commands": {
|
|
261
|
+
"verify": "docker exec container_name id",
|
|
262
|
+
"fix_docker": "docker run --user 1000:1000 ...",
|
|
263
|
+
"fix_k8s": "kubectl patch deployment app -p '{\"spec\":{\"template\":{\"spec\":{\"securityContext\":{\"runAsNonRoot\":true}}}}}'"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## PHASE 5: Generate Verification Report
|
|
271
|
+
|
|
272
|
+
Save to `.coverme/runtime-verify.json`:
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"environment": "production",
|
|
277
|
+
"host": "user@server.com",
|
|
278
|
+
"verifiedAt": "2024-01-15T10:30:00Z",
|
|
279
|
+
|
|
280
|
+
"summary": {
|
|
281
|
+
"total": 5,
|
|
282
|
+
"critical": 1,
|
|
283
|
+
"high": 2,
|
|
284
|
+
"medium": 1,
|
|
285
|
+
"low": 1,
|
|
286
|
+
"matches": 8
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
"mismatches": [
|
|
290
|
+
{ "...finding objects..." }
|
|
291
|
+
],
|
|
292
|
+
|
|
293
|
+
"matches": [
|
|
294
|
+
{
|
|
295
|
+
"check": "NODE_ENV",
|
|
296
|
+
"expected": "production",
|
|
297
|
+
"actual": "production",
|
|
298
|
+
"status": "match"
|
|
299
|
+
}
|
|
300
|
+
],
|
|
301
|
+
|
|
302
|
+
"rawData": {
|
|
303
|
+
"expected": { "...from code analysis..." },
|
|
304
|
+
"actual": { "...from SSH commands..." }
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## PHASE 6: Display Results
|
|
312
|
+
|
|
313
|
+
Print summary:
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
Runtime Verification: production (user@server.com)
|
|
317
|
+
=================================================
|
|
318
|
+
|
|
319
|
+
CRITICAL MISMATCH: Container running as root!
|
|
320
|
+
|
|
321
|
+
Expected: USER appuser (from Dockerfile:15)
|
|
322
|
+
Actual: root (uid=0)
|
|
323
|
+
|
|
324
|
+
This is why your DuckDB file read vulnerability was exploitable!
|
|
325
|
+
The code assumed it would run as appuser with limited permissions,
|
|
326
|
+
but in production it runs as root with access to everything.
|
|
327
|
+
|
|
328
|
+
Fix: Add to your deployment:
|
|
329
|
+
securityContext:
|
|
330
|
+
runAsNonRoot: true
|
|
331
|
+
runAsUser: 1000
|
|
332
|
+
|
|
333
|
+
--------------------------------------------------
|
|
334
|
+
|
|
335
|
+
Summary:
|
|
336
|
+
Matches: 8 configurations match
|
|
337
|
+
Mismatches: 5 found
|
|
338
|
+
- 1 CRITICAL (user mismatch)
|
|
339
|
+
- 2 HIGH (permissions)
|
|
340
|
+
- 1 MEDIUM (ports)
|
|
341
|
+
- 1 LOW (working directory)
|
|
342
|
+
|
|
343
|
+
Full report: .coverme/runtime-verify.json
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
## Security Notes
|
|
349
|
+
|
|
350
|
+
- SSH credentials are NOT stored by CoverMe
|
|
351
|
+
- Commands are read-only (no modifications to remote system)
|
|
352
|
+
- All SSH output is included in the report for audit
|
|
353
|
+
- Consider using a dedicated read-only SSH user for verification
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -121,4 +121,131 @@ agentCmd
|
|
|
121
121
|
console.log(`Removed agent "${removed.name}"`);
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
// Runtime verification commands
|
|
125
|
+
const verifyCmd = program
|
|
126
|
+
.command('verify')
|
|
127
|
+
.description('Verify runtime environment matches code expectations');
|
|
128
|
+
|
|
129
|
+
verifyCmd
|
|
130
|
+
.command('setup')
|
|
131
|
+
.description('Configure SSH access for runtime verification')
|
|
132
|
+
.option('-h, --host <host>', 'SSH host (e.g., user@server.com)')
|
|
133
|
+
.option('-p, --port <port>', 'SSH port', '22')
|
|
134
|
+
.option('-k, --key <path>', 'Path to SSH private key')
|
|
135
|
+
.option('-n, --name <name>', 'Environment name (e.g., production, staging)')
|
|
136
|
+
.action((options: { host?: string; port?: string; key?: string; name?: string }) => {
|
|
137
|
+
const covermeDir = join(process.cwd(), '.coverme');
|
|
138
|
+
const configPath = join(covermeDir, 'runtime.json');
|
|
139
|
+
|
|
140
|
+
if (!existsSync(covermeDir)) {
|
|
141
|
+
mkdirSync(covermeDir, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let config: any = { environments: [] };
|
|
145
|
+
if (existsSync(configPath)) {
|
|
146
|
+
config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
147
|
+
if (!config.environments) config.environments = [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!options.host) {
|
|
151
|
+
console.log('\nRuntime Verification Setup');
|
|
152
|
+
console.log('==========================\n');
|
|
153
|
+
console.log('This feature allows CoverMe to SSH into your servers and compare');
|
|
154
|
+
console.log('the actual runtime environment against your code configuration.\n');
|
|
155
|
+
console.log('Usage:');
|
|
156
|
+
console.log(' coverme verify setup --host user@server.com --name production');
|
|
157
|
+
console.log(' coverme verify setup --host deploy@staging.example.com --key ~/.ssh/id_rsa --name staging\n');
|
|
158
|
+
console.log('Options:');
|
|
159
|
+
console.log(' -h, --host <host> SSH host (required)');
|
|
160
|
+
console.log(' -n, --name <name> Environment name (default: from host)');
|
|
161
|
+
console.log(' -p, --port <port> SSH port (default: 22)');
|
|
162
|
+
console.log(' -k, --key <path> Path to SSH private key\n');
|
|
163
|
+
|
|
164
|
+
if (config.environments.length > 0) {
|
|
165
|
+
console.log('Configured environments:');
|
|
166
|
+
config.environments.forEach((env: any, i: number) => {
|
|
167
|
+
console.log(` ${i + 1}. ${env.name}: ${env.host}:${env.port}`);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const envName = options.name || options.host.split('@')[1]?.split('.')[0] || 'default';
|
|
174
|
+
|
|
175
|
+
// Remove existing with same name
|
|
176
|
+
config.environments = config.environments.filter((e: any) => e.name !== envName);
|
|
177
|
+
|
|
178
|
+
config.environments.push({
|
|
179
|
+
name: envName,
|
|
180
|
+
host: options.host,
|
|
181
|
+
port: parseInt(options.port || '22'),
|
|
182
|
+
keyPath: options.key || null,
|
|
183
|
+
addedAt: new Date().toISOString()
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
187
|
+
console.log(`\nAdded environment "${envName}"`);
|
|
188
|
+
console.log(` Host: ${options.host}`);
|
|
189
|
+
console.log(` Port: ${options.port || '22'}`);
|
|
190
|
+
if (options.key) console.log(` Key: ${options.key}`);
|
|
191
|
+
console.log('\nRun verification with:');
|
|
192
|
+
console.log(` /coverme-verify ${envName}`);
|
|
193
|
+
console.log('\nOr in Claude Code:');
|
|
194
|
+
console.log(` /coverme --verify ${envName}`);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
verifyCmd
|
|
198
|
+
.command('list')
|
|
199
|
+
.description('List configured environments')
|
|
200
|
+
.action(() => {
|
|
201
|
+
const configPath = join(process.cwd(), '.coverme', 'runtime.json');
|
|
202
|
+
|
|
203
|
+
if (!existsSync(configPath)) {
|
|
204
|
+
console.log('No environments configured.');
|
|
205
|
+
console.log('Run: coverme verify setup --host user@server.com --name production');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
210
|
+
|
|
211
|
+
if (!config.environments || config.environments.length === 0) {
|
|
212
|
+
console.log('No environments configured.');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.log('\nConfigured Environments:\n');
|
|
217
|
+
config.environments.forEach((env: any, i: number) => {
|
|
218
|
+
console.log(` ${i + 1}. ${env.name}`);
|
|
219
|
+
console.log(` Host: ${env.host}:${env.port}`);
|
|
220
|
+
if (env.keyPath) console.log(` Key: ${env.keyPath}`);
|
|
221
|
+
console.log(` Added: ${new Date(env.addedAt).toLocaleDateString()}`);
|
|
222
|
+
console.log('');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
verifyCmd
|
|
227
|
+
.command('remove')
|
|
228
|
+
.description('Remove an environment')
|
|
229
|
+
.argument('<name>', 'Environment name')
|
|
230
|
+
.action((name: string) => {
|
|
231
|
+
const configPath = join(process.cwd(), '.coverme', 'runtime.json');
|
|
232
|
+
|
|
233
|
+
if (!existsSync(configPath)) {
|
|
234
|
+
console.error('No environments configured.');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
239
|
+
const idx = config.environments.findIndex((e: any) => e.name.toLowerCase() === name.toLowerCase());
|
|
240
|
+
|
|
241
|
+
if (idx === -1) {
|
|
242
|
+
console.error(`Environment "${name}" not found`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const removed = config.environments.splice(idx, 1)[0];
|
|
247
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
248
|
+
console.log(`Removed environment "${removed.name}"`);
|
|
249
|
+
});
|
|
250
|
+
|
|
124
251
|
program.parse();
|
package/src/cli/init.ts
CHANGED
|
@@ -609,6 +609,7 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
609
609
|
"Bash(git ls-files:*)",
|
|
610
610
|
"Bash(git log:*)",
|
|
611
611
|
"Bash(grep:*)",
|
|
612
|
+
"Bash(ssh:*)",
|
|
612
613
|
"Read(.coverme/*)",
|
|
613
614
|
"Write(.coverme/*)",
|
|
614
615
|
"Edit(.coverme/*)"
|
|
@@ -649,7 +650,7 @@ export async function init(options: InitOptions): Promise<void> {
|
|
|
649
650
|
Usage:
|
|
650
651
|
1. Open Claude Code in your project
|
|
651
652
|
2. Type /coverme and press Enter
|
|
652
|
-
3. Wait for the scan to complete
|
|
653
|
+
3. Wait for the scan to complete (22 AI agents!)
|
|
653
654
|
4. Report opens automatically in your browser
|
|
654
655
|
|
|
655
656
|
Reports saved to: .coverme/
|
|
@@ -657,13 +658,18 @@ Reports saved to: .coverme/
|
|
|
657
658
|
- scan_YYYY-MM-DD_HH-MM-SS.json
|
|
658
659
|
|
|
659
660
|
Custom Agents:
|
|
660
|
-
Add your own experts to the scan:
|
|
661
|
-
|
|
662
661
|
coverme agent add "John" "Check all .env files for exposed secrets"
|
|
663
|
-
coverme agent
|
|
662
|
+
coverme agent list
|
|
663
|
+
coverme agent remove "John"
|
|
664
|
+
|
|
665
|
+
Runtime Verification (Optional):
|
|
666
|
+
Compare your actual runtime environment against code configuration.
|
|
667
|
+
Catches issues like "Dockerfile says USER appuser but container runs as root"
|
|
668
|
+
|
|
669
|
+
coverme verify setup --host user@server.com --name production
|
|
670
|
+
coverme verify list
|
|
664
671
|
|
|
665
|
-
|
|
666
|
-
Remove agent: coverme agent remove "John"
|
|
672
|
+
Once configured, /coverme will automatically SSH and verify runtime.
|
|
667
673
|
|
|
668
674
|
The .coverme/ folder is automatically added to .gitignore
|
|
669
675
|
|