iso27001-mcp 0.7.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 ADDED
@@ -0,0 +1,1084 @@
1
+ # iso27001-mcp
2
+
3
+ A stateful [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that gives Claude a complete ISO 27001:2022 Information Security Management System (ISMS). Ask Claude to run gap assessments, manage risks, generate policies, track evidence, and run audits — all backed by an encrypted SQLite database on your own machine.
4
+
5
+ ```
6
+ Claude ──MCP──► iso27001-mcp ──► encrypted SQLite (isms.db)
7
+
8
+ ├── 93 ISO 27001:2022 controls (seeded)
9
+ ├── 114 ISO 27001:2013 controls (seeded)
10
+ ├── Gap assessments & remediation roadmaps
11
+ ├── Risk register & treatment plans
12
+ ├── Policy documents (Mustache templates)
13
+ ├── Statement of Applicability
14
+ ├── Audit findings & corrective actions
15
+ └── Evidence tracking (+ Jira / GitHub)
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Table of Contents
21
+
22
+ - [Quick Start](#quick-start)
23
+ - [Use Cases](#use-cases)
24
+ - [Installation](#installation)
25
+ - [Configuration](#configuration)
26
+ - [Connecting to Claude](#connecting-to-claude)
27
+ - [Tools Reference](#tools-reference)
28
+ - [Architecture](#architecture)
29
+ - [Modes](#modes)
30
+ - [Integrations](#integrations)
31
+ - [Development](#development)
32
+ - [Security](#security)
33
+
34
+ ---
35
+
36
+ ## Quick Start
37
+
38
+ Get the server connected to Claude Desktop in five minutes.
39
+
40
+ ### Prerequisites
41
+
42
+ - **Node.js ≥ 20.11.0** — use [nvm](https://github.com/nvm-sh/nvm) or [Volta](https://volta.sh)
43
+
44
+ ```bash
45
+ node --version # should print v20.x or higher (not v22 — use v20 LTS)
46
+ ```
47
+
48
+ - **Build tools** — needed by the encrypted SQLite native module:
49
+ - **macOS:** `xcode-select --install`
50
+ - **Ubuntu/Debian:** `sudo apt-get install build-essential python3`
51
+ - **Windows:** `npm install --global windows-build-tools` (run as Administrator)
52
+
53
+ ### Step 1 — Install from npm
54
+
55
+ ```bash
56
+ npm install -g iso27001-mcp
57
+ ```
58
+
59
+ This installs the `iso27001-mcp` command globally. The native SQLite module downloads a prebuilt binary automatically on macOS and Linux x64; it compiles from source on other platforms.
60
+
61
+ ### Step 2 — Generate secrets
62
+
63
+ Generate two random 32-byte secrets — these encrypt your database and sign your API keys:
64
+
65
+ ```bash
66
+ openssl rand -hex 32 # → copy as HMAC_SECRET
67
+ openssl rand -hex 32 # → copy as DB_ENCRYPTION_KEY
68
+ ```
69
+
70
+ ### Step 3 — Generate an API key
71
+
72
+ ```bash
73
+ iso27001-mcp keygen --label "Me" --role admin \
74
+ HMAC_SECRET=<your_hmac_secret> \
75
+ DB_ENCRYPTION_KEY=<your_db_key> \
76
+ DB_PATH=$HOME/.iso27001/isms.db
77
+ ```
78
+
79
+ Or set the env vars in your shell first:
80
+
81
+ ```bash
82
+ export HMAC_SECRET=your_hmac_secret
83
+ export DB_ENCRYPTION_KEY=your_db_encryption_key
84
+ export DB_PATH=$HOME/.iso27001/isms.db
85
+
86
+ iso27001-mcp keygen --label "Me" --role admin
87
+ ```
88
+
89
+ The raw key (`iso27001_...`) is printed **once** — copy it immediately, it cannot be retrieved again.
90
+
91
+ ### Step 4 — Add to Claude Desktop
92
+
93
+ Open your Claude Desktop config file:
94
+
95
+ - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
96
+ - **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
97
+
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "iso27001": {
102
+ "command": "iso27001-mcp",
103
+ "env": {
104
+ "HMAC_SECRET": "your_hmac_secret",
105
+ "DB_ENCRYPTION_KEY": "your_db_encryption_key",
106
+ "MCP_API_KEY": "iso27001_your_key_here",
107
+ "DB_PATH": "/Users/you/.iso27001/isms.db"
108
+ }
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ### Step 5 — Restart Claude Desktop and verify
115
+
116
+ Fully quit and reopen Claude Desktop. Then ask:
117
+
118
+ > *"Use get_server_info to check the server is running."*
119
+
120
+ You should get back version, uptime, and database stats confirming all 93 + 114 controls are seeded.
121
+
122
+ ### First things to try
123
+
124
+ ```
125
+ "Create a gap assessment for Acme Ltd covering all ISO 27001:2022 controls."
126
+ "Show me the gap summary for that assessment."
127
+ "Generate a remediation roadmap with a 26-week timeline."
128
+ "Create an information security policy for Acme Ltd. Owner: CISO. Effective from today."
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Use Cases
134
+
135
+ ### 1 — Run a Gap Assessment
136
+
137
+ Ask Claude to assess your organisation against ISO 27001:2022, track the status of each control, and generate a prioritised remediation roadmap.
138
+
139
+ > *"Create a gap assessment for Acme Ltd covering all 2022 controls. Our scope is cloud infrastructure and development. Exclude physical security controls."*
140
+
141
+ Claude will create the assessment, pre-populate all 93 controls as `not_started`, and let you work through them one by one or in bulk. When you're done:
142
+
143
+ > *"Generate a remediation roadmap grouped by risk level. Give us 26 weeks to get to certification."*
144
+
145
+ The roadmap groups work by theme (Technological first), links controls to open risks, and assigns recommended due dates.
146
+
147
+ ---
148
+
149
+ ### 2 — Manage the Risk Register
150
+
151
+ Track information security risks end-to-end from identification through treatment.
152
+
153
+ > *"Register a new risk: our customer database is at risk from SQL injection due to unparameterised queries. Likelihood 4, impact 5."*
154
+
155
+ > *"Create a treatment plan to mitigate this risk. Link it to controls 8.26 and 8.28. Owner: head of engineering. Due: end of Q3."*
156
+
157
+ > *"Show me all critical and high risks that still have open treatment plans."*
158
+
159
+ Risk scores are computed automatically (likelihood × impact) and reflected in summaries and heatmaps without any manual input.
160
+
161
+ ---
162
+
163
+ ### 3 — Generate ISMS Policies
164
+
165
+ Generate a full suite of ISO 27001-aligned policy documents in seconds.
166
+
167
+ > *"Generate an information security policy for Acme Ltd. Scope: all cloud-hosted systems and remote employees. Owner: CISO. Effective from 1 June 2026."*
168
+
169
+ Policies are rendered from Mustache templates with automatic ISO clause and control mappings. Twelve policy types are included out of the box:
170
+
171
+ `information_security` · `access_control` · `risk_management` · `asset_management` · `incident_response` · `business_continuity` · `supplier_security` · `cryptography` · `physical_security` · `acceptable_use` · `data_classification` · `secure_development`
172
+
173
+ ---
174
+
175
+ ### 4 — Produce a Statement of Applicability
176
+
177
+ Generate an SoA directly from your gap assessment, pre-populated with inclusion/exclusion decisions and justifications.
178
+
179
+ > *"Generate a Statement of Applicability from assessment A-001. Export it as a CSV for the auditors."*
180
+
181
+ ---
182
+
183
+ ### 5 — Run Internal Audits
184
+
185
+ Plan audits, record findings (NCs, observations, OFIs), raise corrective action requests, and track effectiveness.
186
+
187
+ > *"Create an audit of our access control and cryptography controls. Auditor: Jane Smith. Planned for 15 June 2026."*
188
+
189
+ > *"Record a major non-conformity against clause 9.1: no evidence of ongoing monitoring of security objectives."*
190
+
191
+ > *"Raise a CAR for this finding. Owner: compliance manager. Due in 30 days."*
192
+
193
+ The server enforces ISO 27001:2022 Clause 10.1 — a corrective action cannot be closed unless `effectiveness_verified` is `true`.
194
+
195
+ ---
196
+
197
+ ### 6 — Track Evidence
198
+
199
+ Register evidence artefacts for each control, spot gaps, and link them directly to Jira tickets or GitHub issues.
200
+
201
+ > *"Show me all controls marked as implemented or partial that have no current evidence."*
202
+
203
+ > *"Register a screenshot of our firewall config as evidence for control 8.20. Collector: ops team. Expires in 12 months."*
204
+
205
+ > *"Link this evidence to a new Jira ticket in the SEC project: 'Firewall config screenshot — annual review'."*
206
+
207
+ ---
208
+
209
+ ### 7 — Query the Audit Log
210
+
211
+ Every tool call is logged in a tamper-evident audit trail. Admins can query it at any time.
212
+
213
+ > *"Show me all tool calls made in the last 7 days that resulted in an error."*
214
+
215
+ > *"List all API keys and when they were last used."*
216
+
217
+ ---
218
+
219
+ ## Installation
220
+
221
+ ### Prerequisites
222
+
223
+ - **Node.js ≥ 20.11.0** — use [nvm](https://github.com/nvm-sh/nvm) or [Volta](https://volta.sh)
224
+ - **npm ≥ 10**
225
+ - **Build tools** for the native SQLite module:
226
+ - macOS: `xcode-select --install`
227
+ - Ubuntu/Debian: `sudo apt-get install build-essential python3`
228
+ - Windows: `npm install --global windows-build-tools` (run as Administrator)
229
+
230
+ ### Step 1 — Install
231
+
232
+ ```bash
233
+ npm install -g iso27001-mcp
234
+ ```
235
+
236
+ The `iso27001-mcp` command is now available globally. The encrypted SQLite module (`better-sqlite3-multiple-ciphers`) downloads a prebuilt binary on supported platforms; it compiles from source if none is available.
237
+
238
+ **Run from source** (for development or to get the latest unreleased changes):
239
+
240
+ ```bash
241
+ git clone https://github.com/Sushegaad/MCP-Server-for-ISO27001
242
+ cd iso27001-mcp
243
+ npm install
244
+ npm run build
245
+ # Use `node dist/index.js` instead of `iso27001-mcp` in all commands below
246
+ ```
247
+
248
+ ### Step 2 — Set Up Environment Variables
249
+
250
+ ```bash
251
+ cp .env.example .env
252
+ ```
253
+
254
+ Edit `.env` and fill in the two required secrets:
255
+
256
+ ```bash
257
+ # Generate a 32-byte HMAC signing secret
258
+ openssl rand -hex 32 # → paste into HMAC_SECRET
259
+
260
+ # Generate a 32-byte AES-256 database encryption key
261
+ openssl rand -hex 32 # → paste into DB_ENCRYPTION_KEY
262
+ ```
263
+
264
+ Full variable reference:
265
+
266
+ | Variable | Required | Default | Description |
267
+ |----------|----------|---------|-------------|
268
+ | `HMAC_SECRET` | ✅ | — | 32-byte hex secret for HMAC-signing API keys |
269
+ | `DB_ENCRYPTION_KEY` | ✅ | — | 32-byte hex key for AES-256 SQLite encryption |
270
+ | `DB_PATH` | | `./isms.db` | Path to the encrypted database file |
271
+ | `AUDIT_LOG_PATH` | | `./audit.log` | Path for the append-only JSON-L audit log |
272
+ | `RATE_LIMIT_RPM` | | `500` | Tool calls per minute per API key |
273
+ | `SESSION_TTL_HOURS` | | `4` | SSE session TTL (hosted/team modes) |
274
+ | `SSE_PORT` | | `3000` | Port for the SSE server (hosted/team modes) |
275
+ | `BEHIND_TLS_PROXY` | | `false` | Set `true` when behind nginx/Caddy in production |
276
+ | `JIRA_BASE_URL` | | — | e.g. `https://your-org.atlassian.net` |
277
+ | `JIRA_API_TOKEN` | | — | Jira API token for the integration |
278
+ | `JIRA_PROJECT_KEY` | | — | e.g. `SEC` |
279
+ | `JIRA_USER_EMAIL` | | — | Email address associated with the Jira API token |
280
+ | `GITHUB_TOKEN` | | — | GitHub personal access token (scope: `issues:write`) |
281
+ | `GITHUB_REPO` | | — | e.g. `your-org/your-repo` |
282
+
283
+ ### Step 3 — Generate an API Key
284
+
285
+ The server requires an API key on every tool call. Generate one for yourself:
286
+
287
+ ```bash
288
+ # Viewer — read-only access to 22 tools
289
+ iso27001-mcp keygen --label "Alice" --role viewer
290
+
291
+ # Analyst — read + write for gap/risk/policy/evidence tools (35 tools)
292
+ iso27001-mcp keygen --label "Bob" --role analyst --expires 90d
293
+
294
+ # Admin — all 43 tools including audit log and key management
295
+ iso27001-mcp keygen --label "CISO" --role admin --expires 1y
296
+ ```
297
+
298
+ The raw key is printed **once** and never stored in plaintext. Save it immediately.
299
+
300
+ ```bash
301
+ # List all keys
302
+ iso27001-mcp keys list
303
+
304
+ # Revoke a key immediately
305
+ iso27001-mcp keys revoke --label "Alice"
306
+ ```
307
+
308
+ ---
309
+
310
+ ## Connecting to Claude
311
+
312
+ ### Claude Desktop
313
+
314
+ Add the server to your Claude Desktop MCP configuration file:
315
+
316
+ **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
317
+ **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
318
+
319
+ ```json
320
+ {
321
+ "mcpServers": {
322
+ "iso27001": {
323
+ "command": "iso27001-mcp",
324
+ "env": {
325
+ "HMAC_SECRET": "your_hmac_secret",
326
+ "DB_ENCRYPTION_KEY": "your_db_encryption_key",
327
+ "MCP_API_KEY": "iso27001_your_api_key_here",
328
+ "DB_PATH": "/Users/you/.iso27001/isms.db"
329
+ }
330
+ }
331
+ }
332
+ }
333
+ ```
334
+
335
+ Restart Claude Desktop. The ISO 27001 tools will appear in the tools panel.
336
+
337
+ > **Tip:** Store your `isms.db` in a stable location like `~/.iso27001/isms.db` so it persists across upgrades.
338
+
339
+ ### Claude Code
340
+
341
+ ```bash
342
+ # Add to your project's MCP config
343
+ claude mcp add iso27001 iso27001-mcp
344
+ ```
345
+
346
+ Then set the required env vars in your shell or `.env`:
347
+
348
+ ```bash
349
+ export HMAC_SECRET=your_hmac_secret
350
+ export DB_ENCRYPTION_KEY=your_db_encryption_key
351
+ export MCP_API_KEY=iso27001_your_key_here
352
+ export DB_PATH=$HOME/.iso27001/isms.db
353
+ ```
354
+
355
+ ---
356
+
357
+ ## Tools Reference
358
+
359
+ The server exposes **43 tools** across 9 groups. All tools require a valid API key. The minimum role required is noted per group; `✅` marks required parameters, `—` marks optional ones.
360
+
361
+ ---
362
+
363
+ ### Group 1 — Control Registry *(minimum role: viewer)*
364
+
365
+ #### `get_control`
366
+ Fetch a single control by ID and version.
367
+
368
+ | Parameter | Req | Type | Values / Notes |
369
+ |-----------|-----|------|----------------|
370
+ | `control_id` | ✅ | string | e.g. `5.1`, `A.8.1` |
371
+ | `version` | — | enum | `2022` \| `2013` |
372
+
373
+ #### `list_controls`
374
+ List controls with optional filters.
375
+
376
+ | Parameter | Req | Type | Values / Notes |
377
+ |-----------|-----|------|----------------|
378
+ | `version` | — | enum | `2022` \| `2013` |
379
+ | `theme` | — | string | e.g. `Technological` |
380
+ | `control_type` | — | enum | `Preventive` \| `Detective` \| `Corrective` |
381
+ | `new_in_2022` | — | boolean | Filter to controls added in the 2022 revision |
382
+ | `cybersecurity_concept` | — | enum | `Identify` \| `Protect` \| `Detect` \| `Respond` \| `Recover` |
383
+ | `include_guidance` | — | boolean | Default: `false` |
384
+ | `limit` | — | integer | Default: `50`, max `100` |
385
+ | `offset` | — | integer | Default: `0` |
386
+
387
+ #### `search_controls`
388
+ Full-text search across control names, descriptions, and guidance (FTS5).
389
+
390
+ | Parameter | Req | Type | Values / Notes |
391
+ |-----------|-----|------|----------------|
392
+ | `query` | ✅ | string | Search terms |
393
+ | `version` | — | enum | `2022` \| `2013` |
394
+ | `limit` | — | integer | Default: `10`, max `50` |
395
+ | `offset` | — | integer | Default: `0` |
396
+
397
+ #### `get_control_attributes`
398
+ Retrieve 2022 attribute taxonomy (cybersecurity concepts, operational capabilities) for a control.
399
+
400
+ | Parameter | Req | Type | Values / Notes |
401
+ |-----------|-----|------|----------------|
402
+ | `control_id` | ✅ | string | 2022 control ID |
403
+
404
+ #### `compare_versions`
405
+ Show the mapping between a 2013 control and its 2022 equivalent. Provide at least one ID.
406
+
407
+ | Parameter | Req | Type | Values / Notes |
408
+ |-----------|-----|------|----------------|
409
+ | `v2013_id` | — | string | ISO 27001:2013 control ID |
410
+ | `v2022_id` | — | string | ISO 27001:2022 control ID |
411
+
412
+ #### `get_clause_requirement`
413
+ Fetch a clause requirement (clauses 4–10) with optional sub-clauses.
414
+
415
+ | Parameter | Req | Type | Values / Notes |
416
+ |-----------|-----|------|----------------|
417
+ | `clause_id` | ✅ | string | e.g. `4.1`, `9.2` |
418
+ | `include_sub_clauses` | — | boolean | Default: `false` |
419
+
420
+ #### `list_clause_requirements`
421
+ List all clause requirements, optionally filtered by parent.
422
+
423
+ | Parameter | Req | Type | Values / Notes |
424
+ |-----------|-----|------|----------------|
425
+ | `parent_id` | — | string | e.g. `9` to list all sub-clauses of clause 9 |
426
+
427
+ ---
428
+
429
+ ### Group 2 — Gap Analysis *(reads: viewer+, writes: analyst+)*
430
+
431
+ #### `create_gap_assessment`
432
+ Create a new gap assessment. Pre-populates all in-scope controls as `not_started`.
433
+
434
+ | Parameter | Req | Type | Values / Notes |
435
+ |-----------|-----|------|----------------|
436
+ | `name` | ✅ | string | Assessment name |
437
+ | `scope` | — | string | ISMS scope description |
438
+ | `isms_version` | — | enum | `2022` \| `2013` — default: `2022` |
439
+ | `themes_in_scope` | — | array | e.g. `["Organizational","Technological"]` |
440
+ | `exclude_controls` | — | array | Control IDs to exclude |
441
+ | `exclude_justification` | — | string | Reason for exclusions |
442
+
443
+ #### `update_control_status`
444
+ Set a control's implementation status within an assessment.
445
+
446
+ | Parameter | Req | Type | Values / Notes |
447
+ |-----------|-----|------|----------------|
448
+ | `assessment_id` | ✅ | string (UUID) | |
449
+ | `control_id` | ✅ | string | |
450
+ | `status` | ✅ | enum | `implemented` \| `partial` \| `not_implemented` \| `na` \| `not_started` |
451
+ | `evidence_refs` | — | array | Evidence UUIDs |
452
+ | `notes` | — | string | Implementation notes |
453
+ | `na_justification` | — | string | Required when `status=na` |
454
+ | `assessed_by` | — | string | Assessor name |
455
+
456
+ #### `get_gap_summary`
457
+ Return compliance %, counts by status, and a top-10 remediation priority list.
458
+
459
+ | Parameter | Req | Type | Values / Notes |
460
+ |-----------|-----|------|----------------|
461
+ | `assessment_id` | ✅ | string (UUID) | |
462
+ | `breakdown_by` | — | enum | `theme` \| `control_type` \| `cybersecurity_concept` |
463
+
464
+ #### `list_gap_assessments`
465
+ List assessments with a status filter.
466
+
467
+ | Parameter | Req | Type | Values / Notes |
468
+ |-----------|-----|------|----------------|
469
+ | `filter` | — | enum | `active` \| `archived` \| `all` — default: `active` |
470
+
471
+ #### `export_gap_report`
472
+ Export a full gap report.
473
+
474
+ | Parameter | Req | Type | Values / Notes |
475
+ |-----------|-----|------|----------------|
476
+ | `assessment_id` | ✅ | string (UUID) | |
477
+ | `format` | ✅ | enum | `markdown` \| `csv` \| `json` |
478
+
479
+ #### `generate_remediation_roadmap`
480
+ Generate a prioritised remediation roadmap with phases, risk linkage, and due dates.
481
+
482
+ | Parameter | Req | Type | Values / Notes |
483
+ |-----------|-----|------|----------------|
484
+ | `assessment_id` | ✅ | string (UUID) | |
485
+ | `timeline_weeks` | — | integer | 1–52, default: `12` |
486
+
487
+ #### `archive_gap_assessment`
488
+ Archive a completed assessment.
489
+
490
+ | Parameter | Req | Type | Values / Notes |
491
+ |-----------|-----|------|----------------|
492
+ | `assessment_id` | ✅ | string (UUID) | |
493
+ | `reason` | — | string | Archival reason |
494
+
495
+ ---
496
+
497
+ ### Group 3 — Risk Management *(reads: viewer+, writes: analyst+)*
498
+
499
+ #### `create_risk`
500
+ Register a new risk. `risk_score` is computed automatically as `likelihood × impact`.
501
+
502
+ | Parameter | Req | Type | Values / Notes |
503
+ |-----------|-----|------|----------------|
504
+ | `asset` | ✅ | string | Asset at risk |
505
+ | `threat` | ✅ | string | Threat description |
506
+ | `vulnerability` | ✅ | string | Vulnerability description |
507
+ | `likelihood` | ✅ | integer | 1–5 |
508
+ | `impact` | ✅ | integer | 1–5 |
509
+ | `owner` | — | string | Risk owner |
510
+ | `related_controls` | — | array | Control IDs |
511
+ | `status` | — | enum | `open` \| `accepted` \| `mitigated` \| `transferred` \| `closed` — default: `open` |
512
+
513
+ #### `get_risk`
514
+ Fetch a risk record with optional treatment plans.
515
+
516
+ | Parameter | Req | Type | Values / Notes |
517
+ |-----------|-----|------|----------------|
518
+ | `risk_id` | ✅ | string (UUID) | |
519
+ | `include_treatments` | — | boolean | Default: `false` |
520
+
521
+ #### `update_risk`
522
+ Update any mutable field; `risk_score` recomputes automatically.
523
+
524
+ | Parameter | Req | Type | Values / Notes |
525
+ |-----------|-----|------|----------------|
526
+ | `risk_id` | ✅ | string (UUID) | |
527
+ | `asset` | — | string | |
528
+ | `threat` | — | string | |
529
+ | `vulnerability` | — | string | |
530
+ | `likelihood` | — | integer | 1–5 |
531
+ | `impact` | — | integer | 1–5 |
532
+ | `owner` | — | string | |
533
+ | `status` | — | enum | `open` \| `accepted` \| `mitigated` \| `transferred` \| `closed` |
534
+ | `related_controls` | — | array | Control IDs |
535
+
536
+ #### `list_risks`
537
+ List risks with optional filters.
538
+
539
+ | Parameter | Req | Type | Values / Notes |
540
+ |-----------|-----|------|----------------|
541
+ | `risk_level` | — | enum | `Low` \| `Medium` \| `High` \| `Critical` |
542
+ | `status` | — | enum | `open` \| `accepted` \| `mitigated` \| `transferred` \| `closed` |
543
+ | `owner` | — | string | |
544
+ | `limit` | — | integer | Default: `50`, max `100` |
545
+ | `offset` | — | integer | Default: `0` |
546
+
547
+ #### `get_risk_summary`
548
+ Return aggregated stats: counts by level, 5×5 heatmap matrix, top 10 by score. No parameters.
549
+
550
+ #### `create_treatment_plan`
551
+ Create a risk treatment plan. `mitigate` type requires at least one control reference.
552
+
553
+ | Parameter | Req | Type | Values / Notes |
554
+ |-----------|-----|------|----------------|
555
+ | `risk_id` | ✅ | string (UUID) | |
556
+ | `treatment_type` | ✅ | enum | `mitigate` \| `accept` \| `avoid` \| `transfer` |
557
+ | `description` | ✅ | string | |
558
+ | `owner` | ✅ | string | |
559
+ | `due_date` | ✅ | string | `YYYY-MM-DD` |
560
+ | `controls` | — | array | Required for `mitigate` type |
561
+ | `residual_likelihood` | — | integer | 1–5 |
562
+ | `residual_impact` | — | integer | 1–5 |
563
+ | `evidence_ref` | — | string | |
564
+
565
+ #### `update_treatment_status`
566
+ Update a treatment plan's status and link evidence.
567
+
568
+ | Parameter | Req | Type | Values / Notes |
569
+ |-----------|-----|------|----------------|
570
+ | `treatment_id` | ✅ | string (UUID) | |
571
+ | `status` | ✅ | enum | `planned` \| `in_progress` \| `implemented` \| `verified` \| `cancelled` |
572
+ | `evidence_ref` | — | string | |
573
+ | `residual_likelihood` | — | integer | 1–5 |
574
+ | `residual_impact` | — | integer | 1–5 |
575
+
576
+ #### `generate_risk_register`
577
+ Export the full risk register.
578
+
579
+ | Parameter | Req | Type | Values / Notes |
580
+ |-----------|-----|------|----------------|
581
+ | `format` | ✅ | enum | `markdown` \| `csv` \| `json` |
582
+ | `risk_level_filter` | — | enum | `Low` \| `Medium` \| `High` \| `Critical` |
583
+ | `status_filter` | — | enum | `open` \| `accepted` \| `mitigated` \| `transferred` \| `closed` |
584
+
585
+ ---
586
+
587
+ ### Group 4 — Policy Management *(reads: viewer+, create: analyst+, update: admin)*
588
+
589
+ #### `create_policy`
590
+ Render a policy from a Mustache template with org-specific variables.
591
+
592
+ | Parameter | Req | Type | Values / Notes |
593
+ |-----------|-----|------|----------------|
594
+ | `type` | ✅ | enum | `information_security` \| `access_control` \| `risk_management` \| `asset_management` \| `incident_response` \| `business_continuity` \| `supplier_security` \| `cryptography` \| `physical_security` \| `acceptable_use` \| `data_classification` \| `secure_development` |
595
+ | `organisation_name` | ✅ | string | |
596
+ | `scope` | ✅ | string | |
597
+ | `owner` | ✅ | string | |
598
+ | `approver` | — | string | |
599
+ | `review_cycle_months` | — | integer | 1–36, default: `12` |
600
+ | `effective_date` | ✅ | string | `YYYY-MM-DD` |
601
+
602
+ #### `get_policy`
603
+ Fetch a policy with optional version history.
604
+
605
+ | Parameter | Req | Type | Values / Notes |
606
+ |-----------|-----|------|----------------|
607
+ | `policy_id` | ✅ | string (UUID) | |
608
+ | `include_versions` | — | boolean | Default: `false` |
609
+
610
+ #### `update_policy`
611
+ Archive the current version and create a new one. Admin only.
612
+
613
+ | Parameter | Req | Type | Values / Notes |
614
+ |-----------|-----|------|----------------|
615
+ | `policy_id` | ✅ | string (UUID) | |
616
+ | `scope` | — | string | |
617
+ | `owner` | — | string | |
618
+ | `approver` | — | string | |
619
+ | `reviewed_by` | ✅ | string | |
620
+ | `change_summary` | ✅ | string | |
621
+
622
+ #### `list_policies`
623
+ List policies with optional filters.
624
+
625
+ | Parameter | Req | Type | Values / Notes |
626
+ |-----------|-----|------|----------------|
627
+ | `status` | — | enum | `draft` \| `active` \| `archived` |
628
+ | `type` | — | enum | Any of the 12 policy types above |
629
+ | `owner` | — | string | |
630
+ | `overdue_only` | — | boolean | Filter to policies past their review date — default: `false` |
631
+ | `limit` | — | integer | Default: `50`, max `100` |
632
+ | `offset` | — | integer | Default: `0` |
633
+
634
+ ---
635
+
636
+ ### Group 5 — Statement of Applicability *(minimum role: analyst)*
637
+
638
+ #### `generate_soa`
639
+ Create an SoA from an assessment, pre-populating all 93 (2022) or 114 (2013) entries.
640
+
641
+ | Parameter | Req | Type | Values / Notes |
642
+ |-----------|-----|------|----------------|
643
+ | `assessment_id` | ✅ | string (UUID) | |
644
+ | `isms_version` | — | enum | `2022` \| `2013` — default: `2022` |
645
+
646
+ #### `update_soa_entry`
647
+ Update a single SoA entry's inclusion, justification, status, and responsible party.
648
+
649
+ | Parameter | Req | Type | Values / Notes |
650
+ |-----------|-----|------|----------------|
651
+ | `soa_id` | ✅ | string (UUID) | |
652
+ | `control_id` | ✅ | string | |
653
+ | `included` | ✅ | boolean | |
654
+ | `justification` | ✅ | string | |
655
+ | `status` | — | enum | `implemented` \| `partial` \| `not_implemented` \| `na` \| `not_started` |
656
+ | `responsible_party` | — | string | |
657
+
658
+ #### `export_soa`
659
+ Export the Statement of Applicability.
660
+
661
+ | Parameter | Req | Type | Values / Notes |
662
+ |-----------|-----|------|----------------|
663
+ | `soa_id` | ✅ | string (UUID) | |
664
+ | `format` | ✅ | enum | `markdown` \| `csv` |
665
+
666
+ ---
667
+
668
+ ### Group 6 — Audit Management *(reads: viewer+, writes: admin)*
669
+
670
+ #### `create_audit`
671
+ Create an internal audit with auditor, planned date, and scope.
672
+
673
+ | Parameter | Req | Type | Values / Notes |
674
+ |-----------|-----|------|----------------|
675
+ | `name` | ✅ | string | Audit name |
676
+ | `scope` | ✅ | string | |
677
+ | `auditor` | ✅ | string | |
678
+ | `planned_date` | ✅ | string | `YYYY-MM-DD` |
679
+ | `controls_in_scope` | — | array | Control IDs |
680
+ | `clauses_in_scope` | — | array | Clause IDs |
681
+
682
+ #### `record_finding`
683
+ Record a finding. Non-conformities (`nc`) require a severity.
684
+
685
+ | Parameter | Req | Type | Values / Notes |
686
+ |-----------|-----|------|----------------|
687
+ | `audit_id` | ✅ | string (UUID) | |
688
+ | `type` | ✅ | enum | `nc` \| `obs` \| `ofi` |
689
+ | `clause_or_control` | ✅ | string | |
690
+ | `description` | ✅ | string | |
691
+ | `objective_evidence` | ✅ | string | |
692
+ | `severity` | — | enum | `major` \| `minor` — required for `type=nc` |
693
+
694
+ #### `create_corrective_action`
695
+ Raise a Corrective Action Request (CAR) linked to a finding.
696
+
697
+ | Parameter | Req | Type | Values / Notes |
698
+ |-----------|-----|------|----------------|
699
+ | `finding_id` | ✅ | string (UUID) | |
700
+ | `description` | ✅ | string | |
701
+ | `owner` | ✅ | string | |
702
+ | `due_date` | ✅ | string | `YYYY-MM-DD` |
703
+ | `root_cause` | — | string | |
704
+
705
+ #### `update_corrective_action`
706
+ Update CAR status. Closing (`status=closed`) requires `effectiveness_verified: true` (ISO 27001 Clause 10.1).
707
+
708
+ | Parameter | Req | Type | Values / Notes |
709
+ |-----------|-----|------|----------------|
710
+ | `car_id` | ✅ | string (UUID) | |
711
+ | `description` | — | string | |
712
+ | `owner` | — | string | |
713
+ | `due_date` | — | string | `YYYY-MM-DD` |
714
+ | `status` | — | enum | `open` \| `in_progress` \| `implemented` \| `verified` \| `closed` |
715
+ | `root_cause` | — | string | |
716
+ | `effectiveness_verified` | — | boolean | Must be `true` to close the CAR |
717
+ | `evidence_ref` | — | string | |
718
+
719
+ #### `generate_audit_report`
720
+ Export a full audit report (executive summary, findings, CARs).
721
+
722
+ | Parameter | Req | Type | Values / Notes |
723
+ |-----------|-----|------|----------------|
724
+ | `audit_id` | ✅ | string (UUID) | |
725
+ | `format` | ✅ | enum | `markdown` \| `json` |
726
+
727
+ ---
728
+
729
+ ### Group 7 — Evidence Tracking *(reads: viewer+, writes: analyst+)*
730
+
731
+ #### `register_evidence`
732
+ Register an evidence artefact for a control.
733
+
734
+ | Parameter | Req | Type | Values / Notes |
735
+ |-----------|-----|------|----------------|
736
+ | `control_id` | ✅ | string | |
737
+ | `type` | ✅ | enum | `policy` \| `procedure` \| `log` \| `screenshot` \| `report` \| `certificate` \| `configuration` \| `meeting_minutes` \| `training_record` \| `contract` \| `audit_report` \| `test_result` \| `ticket` \| `other` |
738
+ | `description` | ✅ | string | |
739
+ | `source_url` | — | string | URL to the artefact |
740
+ | `collected_by` | ✅ | string | |
741
+ | `collected_date` | ✅ | string | `YYYY-MM-DD` |
742
+ | `expiry_date` | — | string | `YYYY-MM-DD` |
743
+
744
+ #### `list_evidence`
745
+ List evidence for a control, optionally filtered by currency.
746
+
747
+ | Parameter | Req | Type | Values / Notes |
748
+ |-----------|-----|------|----------------|
749
+ | `control_id` | ✅ | string | |
750
+ | `status` | — | enum | `current` \| `stale` \| `expired` |
751
+
752
+ #### `get_evidence_gaps`
753
+ Find controls marked `implemented` or `partial` that have no current evidence.
754
+
755
+ | Parameter | Req | Type | Values / Notes |
756
+ |-----------|-----|------|----------------|
757
+ | `assessment_id` | ✅ | string (UUID) | |
758
+
759
+ #### `link_jira_ticket`
760
+ Link evidence to an existing Jira issue (`jira_key`) or create a new one (`summary`). Provide at least one.
761
+
762
+ | Parameter | Req | Type | Values / Notes |
763
+ |-----------|-----|------|----------------|
764
+ | `evidence_id` | ✅ | string (UUID) | |
765
+ | `jira_key` | — | string | e.g. `ISMS-42` — links to existing issue |
766
+ | `summary` | — | string | Creates a new Jira task with this title |
767
+ | `description` | — | string | Body for the new issue |
768
+
769
+ #### `link_github_issue`
770
+ Link evidence to an existing GitHub issue (`issue_number`) or create a new one (`title`). Provide at least one.
771
+
772
+ | Parameter | Req | Type | Values / Notes |
773
+ |-----------|-----|------|----------------|
774
+ | `evidence_id` | ✅ | string (UUID) | |
775
+ | `issue_number` | — | integer | Links to an existing issue |
776
+ | `title` | — | string | Creates a new issue with this title |
777
+ | `body` | — | string | Body for the new issue |
778
+
779
+ ---
780
+
781
+ ### Group 8 — Server Info *(minimum role: viewer)*
782
+
783
+ #### `get_server_info`
784
+ Return version, uptime, DB stats, control counts, and rate limit config. No parameters.
785
+
786
+ ---
787
+
788
+ ### Group 9 — Admin & Key Management *(minimum role: admin)*
789
+
790
+ #### `query_audit_log`
791
+ Query the tamper-evident audit log.
792
+
793
+ | Parameter | Req | Type | Values / Notes |
794
+ |-----------|-----|------|----------------|
795
+ | `start_date` | — | string | `YYYY-MM-DD` |
796
+ | `end_date` | — | string | `YYYY-MM-DD` |
797
+ | `tool` | — | string | Filter by tool name |
798
+ | `outcome` | — | enum | `success` \| `denied` \| `error` |
799
+ | `role` | — | enum | `viewer` \| `analyst` \| `admin` |
800
+ | `key_hash` | — | string | Filter by API key hash (max 64 chars) |
801
+ | `limit` | — | integer | Default: `50`, max `100` |
802
+ | `offset` | — | integer | Default: `0` |
803
+
804
+ #### `list_api_keys`
805
+ List all API keys with metadata. Never returns key hashes. No parameters.
806
+
807
+ #### `revoke_api_key`
808
+ Immediately revoke a key by label.
809
+
810
+ | Parameter | Req | Type | Values / Notes |
811
+ |-----------|-----|------|----------------|
812
+ | `label` | ✅ | string | The label assigned at key generation |
813
+
814
+ ---
815
+
816
+ ## Architecture
817
+
818
+ ```
819
+ ┌─────────────────────────────────────────────────────────┐
820
+ │ Claude (LLM) │
821
+ └──────────────────────────┬──────────────────────────────┘
822
+ │ MCP (stdio or SSE)
823
+ ┌──────────────────────────▼──────────────────────────────┐
824
+ │ iso27001-mcp server │
825
+ │ │
826
+ │ ┌─────────────────────────────────────────────────┐ │
827
+ │ │ 9-Step Security Pipeline │ │
828
+ │ │ │ │
829
+ │ │ 1. Extract API key (meta or MCP_API_KEY env) │ │
830
+ │ │ 2. validateKey() HMAC-SHA256, timing-safe │ │
831
+ │ │ 3. checkRateLimit() sliding 60s window (RPM) │ │
832
+ │ │ 4. loadRole() viewer | analyst | admin │ │
833
+ │ │ 5. assertPermission() RBAC check │ │
834
+ │ │ 6. sanitiseParams() strip injection patterns │ │
835
+ │ │ 7. Domain handler business logic │ │
836
+ │ │ 8. writeAuditEvent() tamper-evident row_hash │ │
837
+ │ │ 9. Return result or structured McpError │ │
838
+ │ └─────────────────────────────────────────────────┘ │
839
+ │ │
840
+ │ ┌─────────────┐ ┌──────────┐ ┌────────────────────┐ │
841
+ │ │ Controls │ │ Risks │ │ Policies │ │
842
+ │ │ Gap Assess │ │ Register │ │ (Mustache tmpl) │ │
843
+ │ │ SoA │ │ Treatmts │ │ Version history │ │
844
+ │ └─────────────┘ └──────────┘ └────────────────────┘ │
845
+ │ ┌─────────────┐ ┌──────────┐ ┌────────────────────┐ │
846
+ │ │ Audits │ │ Evidence │ │ Audit Log │ │
847
+ │ │ Findings │ │ Jira/GH │ │ (tamper-evident) │ │
848
+ │ │ CARs │ │ Gaps │ │ │ │
849
+ │ └─────────────┘ └──────────┘ └────────────────────┘ │
850
+ │ │
851
+ │ ┌─────────────────────────────────────────────────┐ │
852
+ │ │ AES-256 encrypted SQLite (isms.db) │ │
853
+ │ │ better-sqlite3-multiple-ciphers │ │
854
+ │ │ WAL mode · foreign keys · FTS5 index │ │
855
+ │ └─────────────────────────────────────────────────┘ │
856
+ └─────────────────────────────────────────────────────────┘
857
+ ```
858
+
859
+ ### Database
860
+
861
+ All data is stored in a single encrypted SQLite file (`isms.db`) using AES-256 via `better-sqlite3-multiple-ciphers`. The schema is managed by two SQL migrations applied automatically on first startup:
862
+
863
+ - `0001_initial.sql` — 17 tables covering every ISMS domain (controls, gap assessments, risks, policies, audits, evidence, API keys, audit log, and more)
864
+ - `0002_fts_index.sql` — FTS5 full-text search index on controls, plus 12 performance indexes
865
+
866
+ ### Seed Data
867
+
868
+ On first startup, `seedAll()` inserts all ISO 27001 reference data and verifies SHA-256 checksums before inserting:
869
+
870
+ - **93** ISO 27001:2022 Annex A controls across 4 themes (Organizational, People, Physical, Technological)
871
+ - **114** ISO 27001:2013 controls across 14 Annex A domains
872
+ - **125** version mappings (2013 ↔ 2022), including direct, merged, split, and new_2022 relationships
873
+ - **41** clause requirements for clauses 4–10 with sub-clauses, requirement text, and implementation notes
874
+
875
+ ### Security Pipeline
876
+
877
+ Every tool call passes through the same 9-step pipeline before any business logic runs. Audit events are always written — including on authentication failure and RBAC denial — so the log is a complete record of all attempts, not just successful ones.
878
+
879
+ ### Business Rules Enforced
880
+
881
+ The server encodes ISO 27001 requirements as hard constraints, not just guidance:
882
+
883
+ | Rule | Tool | Behaviour |
884
+ |------|------|-----------|
885
+ | `implemented` without evidence → silently downgraded | `update_control_status` | Status becomes `partial`; response includes `warning` field |
886
+ | `na` status requires justification | `update_control_status` | `BUSINESS_RULE` error if `na_justification` absent |
887
+ | Cannot update an archived assessment | `update_control_status` | `BUSINESS_RULE` error |
888
+ | `mitigate` treatment requires control references | `create_treatment_plan` | `BUSINESS_RULE` error if `controls[]` is empty |
889
+ | CAR closure requires effectiveness verified | `update_corrective_action` | Enforces Clause 10.1; `BUSINESS_RULE` error otherwise |
890
+ | NC findings require severity | `record_finding` | `BUSINESS_RULE` error if `severity` absent for `type=nc` |
891
+
892
+ ### RBAC
893
+
894
+ Three roles with strict hierarchy. A key can only call tools at or below its assigned role level.
895
+
896
+ | Role | Tools available | Typical user |
897
+ |------|----------------|--------------|
898
+ | `viewer` | 22 (all read-only tools) | Auditor, stakeholder |
899
+ | `analyst` | 35 (reads + gap/risk/policy/evidence writes) | ISMS practitioner, consultant |
900
+ | `admin` | 43 (all tools, including audit log and key management) | CISO, ISMS owner |
901
+
902
+ ---
903
+
904
+ ## Modes
905
+
906
+ The server supports four operating modes selected with the `--mode` flag:
907
+
908
+ | Mode | Transport | Typical use |
909
+ |------|-----------|-------------|
910
+ | `local` *(default)* | stdio | Claude Desktop, single user |
911
+ | `ci` | stdio | Automated pipelines, no TTY |
912
+ | `team` | SSE over HTTP | Shared instance, multiple users |
913
+ | `hosted` | SSE over HTTP | Production, behind a TLS proxy |
914
+
915
+ ### Local / CI (stdio)
916
+
917
+ ```bash
918
+ iso27001-mcp --mode local --db ./isms.db
919
+ ```
920
+
921
+ Claude Desktop manages the process. The server reads from stdin and writes to stdout.
922
+
923
+ ### Team / Hosted (SSE)
924
+
925
+ ```bash
926
+ iso27001-mcp --mode hosted --db /data/isms.db
927
+ ```
928
+
929
+ Starts an Express HTTP server on `SSE_PORT` (default `3000`):
930
+
931
+ | Endpoint | Description |
932
+ |----------|-------------|
933
+ | `GET /health` | Health check — no auth required |
934
+ | `GET /sse` | Open SSE connection; first event contains `{ type: "session", sessionId }` |
935
+ | `POST /messages?sessionId=X` | Send an MCP message on an active session |
936
+
937
+ Sessions expire after `SESSION_TTL_HOURS` hours of inactivity. In `NODE_ENV=production`, CORS is restricted to `https://claude.ai` and `/messages` is rate-limited to 100 requests per minute per IP.
938
+
939
+ ---
940
+
941
+ ## Integrations
942
+
943
+ ### Jira
944
+
945
+ ```bash
946
+ JIRA_BASE_URL=https://your-org.atlassian.net
947
+ JIRA_API_TOKEN=your_api_token
948
+ JIRA_PROJECT_KEY=SEC
949
+ JIRA_USER_EMAIL=you@your-org.com
950
+ ```
951
+
952
+ Use `link_jira_ticket` to create a Task in your Jira project or link an existing one. The Jira key and browse URL are stored on the evidence record. Requests time out after 10 seconds and retry once on 5xx responses. A clear `INTEGRATION_ERROR` is returned — with the exact missing variable names — if credentials are not configured.
953
+
954
+ ### GitHub
955
+
956
+ ```bash
957
+ GITHUB_TOKEN=ghp_your_token # requires issues:write, metadata:read
958
+ GITHUB_REPO=your-org/your-repo
959
+ ```
960
+
961
+ Use `link_github_issue` to create an issue with `compliance` and `iso27001` labels or link an existing one by number. The issue URL and number are stored on the evidence record.
962
+
963
+ ---
964
+
965
+ ## Development
966
+
967
+ ```bash
968
+ # Install dependencies
969
+ npm install
970
+
971
+ # Typecheck (strict — zero errors tolerated)
972
+ npm run typecheck
973
+
974
+ # Build dist/
975
+ npm run build
976
+
977
+ # Run all tests (183 unit + integration tests)
978
+ npm test
979
+
980
+ # Watch mode
981
+ npm run test:watch
982
+
983
+ # Lint
984
+ npm run lint
985
+
986
+ # Verify seed data SHA-256 checksums
987
+ npm run verify-checksums
988
+
989
+ # Run from source without building
990
+ npm run dev
991
+ ```
992
+
993
+ ### Project Structure
994
+
995
+ ```
996
+ src/
997
+ ├── index.ts CLI entry (keygen, keys, server startup)
998
+ ├── server.ts McpServer factory
999
+ ├── auth/
1000
+ │ ├── api-key.ts Key generation, HMAC validation, expiry, revocation
1001
+ │ └── rbac.ts Permission matrix (43 tools × 3 roles)
1002
+ ├── security/
1003
+ │ ├── sanitise.ts Prompt-injection stripping for free-text fields
1004
+ │ ├── rate-limiter.ts Sliding-window RPM counter per key hash
1005
+ │ ├── secrets.ts Env var validation (fail-fast on startup)
1006
+ │ └── validate.ts Zod schemas for all 43 tool inputs
1007
+ ├── audit/
1008
+ │ └── logger.ts Tamper-evident audit event writer
1009
+ ├── db/
1010
+ │ ├── connection.ts Encrypted SQLite open/close/migrate
1011
+ │ ├── dal.ts Shared helpers: newId, now, toJson, computeEvidenceStatus
1012
+ │ └── migrations/ 0001_initial.sql, 0002_fts_index.sql
1013
+ ├── seed/
1014
+ │ ├── seeder.ts Idempotent seed runner with checksum verification
1015
+ │ ├── controls-2022.json 93 ISO 27001:2022 Annex A controls
1016
+ │ ├── controls-2013.json 114 ISO 27001:2013 controls
1017
+ │ ├── version-mapping.json 125 cross-version mappings
1018
+ │ ├── clause-requirements.json 41 clause requirements (clauses 4–10)
1019
+ │ └── policy-templates/ 12 Mustache .md policy templates
1020
+ ├── tools/
1021
+ │ ├── index.ts Tool registry and security pipeline
1022
+ │ ├── controls.ts Group 1: Control Registry (7 tools)
1023
+ │ ├── gap-analysis.ts Group 2: Gap Analysis (7 tools)
1024
+ │ ├── risks.ts Group 3: Risk Management (8 tools)
1025
+ │ ├── policies.ts Group 4: Policy Management (4 tools)
1026
+ │ ├── soa.ts Group 5: Statement of Applicability (3 tools)
1027
+ │ ├── audit-management.ts Group 6: Audit Management (5 tools)
1028
+ │ ├── evidence-tracking.ts Group 7: Evidence Tracking (5 tools)
1029
+ │ └── server-info.ts Group 8: Server Info (1 tool)
1030
+ └── transport/
1031
+ └── sse.ts Express SSE server for team/hosted modes
1032
+
1033
+ tests/
1034
+ ├── fixtures/
1035
+ │ ├── mock-db.ts In-memory mock DB for unit tests
1036
+ │ └── test-db.ts Real encrypted DB fixture (macOS only)
1037
+ ├── unit/
1038
+ │ ├── auth/ api-key, rbac
1039
+ │ ├── security/ sanitise, rate-limiter
1040
+ │ ├── audit/ logger
1041
+ │ └── tools/ One file per handler module
1042
+ └── integration/
1043
+ ├── mcp-protocol.test.ts Schema and registration validation
1044
+ ├── db-operations.test.ts Migrations, seed counts, FTS5 (macOS only)
1045
+ └── business-rules.test.ts All 6 enforced business rules end-to-end
1046
+ ```
1047
+
1048
+ ---
1049
+
1050
+ ## Security
1051
+
1052
+ ### API Key Storage
1053
+
1054
+ API keys are never stored in plaintext. Only an HMAC-SHA256 hash is persisted in the database. The raw `iso27001_...` key is printed once to stdout at generation time — there is no way to retrieve it afterwards.
1055
+
1056
+ ### Database Encryption
1057
+
1058
+ The SQLite database (`isms.db`) is encrypted at rest using AES-256 via `better-sqlite3-multiple-ciphers`. The `DB_ENCRYPTION_KEY` is required at every startup and is never written to disk by the server.
1059
+
1060
+ ### Tamper-Evident Audit Trail
1061
+
1062
+ Every tool call writes a row to `audit_log` with a `row_hash` computed as:
1063
+
1064
+ ```
1065
+ SHA-256(timestamp | tool | key_hash | outcome)
1066
+ ```
1067
+
1068
+ Any modification to an audit row after insertion will cause `verifyRowHash()` to fail. The same events are also appended in JSON-L format to `AUDIT_LOG_PATH` for off-database retention and SIEM ingestion.
1069
+
1070
+ ### Production Checklist
1071
+
1072
+ - [ ] Set `NODE_ENV=production`
1073
+ - [ ] Set `BEHIND_TLS_PROXY=true` (the server logs a loud warning if this is absent in production)
1074
+ - [ ] Terminate TLS upstream (nginx, Caddy, AWS ALB, etc.)
1075
+ - [ ] Use `--expires` on all API keys — especially admin keys
1076
+ - [ ] Set `RATE_LIMIT_RPM` appropriate to your team size
1077
+ - [ ] Store `DB_ENCRYPTION_KEY` and `HMAC_SECRET` in a secrets manager, not in `.env`
1078
+ - [ ] Back up `isms.db` regularly — it is the single source of truth for your entire ISMS
1079
+
1080
+ ---
1081
+
1082
+ ## License
1083
+
1084
+ MIT © 2026