iso27001-mcp 0.8.4 → 0.8.5
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 +1 -1
- package/dist/index.js +302 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Turn Claude into an ISO 27001 compliance assistant** — controls, risk register, policies, evidence tracking, SoA generation, and full audit workflows in one local encrypted MCP server.
|
|
4
4
|
|
|
5
|
-
[](https://socket.dev/npm/package/iso27001-mcp/overview/0.8.5)
|
|
6
6
|
[](https://npmjs.com/package/iso27001-mcp)
|
|
7
7
|
[](https://npmjs.com/package/iso27001-mcp)
|
|
8
8
|
[](https://github.com/Sushegaad/MCP-Server-for-ISO27001/actions/workflows/ci.yml)
|
package/dist/index.js
CHANGED
|
@@ -24797,7 +24797,7 @@ var require_package = __commonJS({
|
|
|
24797
24797
|
"package.json"(exports2, module2) {
|
|
24798
24798
|
module2.exports = {
|
|
24799
24799
|
name: "iso27001-mcp",
|
|
24800
|
-
version: "0.8.
|
|
24800
|
+
version: "0.8.5",
|
|
24801
24801
|
mcpName: "io.github.Sushegaad/iso27001-mcp",
|
|
24802
24802
|
description: "ISO 27001 compliance workspace for Claude \u2014 controls, risks, policies, evidence, audits, and SoA in one local encrypted MCP server",
|
|
24803
24803
|
license: "MIT",
|
|
@@ -26260,13 +26260,24 @@ var MIGRATION_0006 = `-- =======================================================
|
|
|
26260
26260
|
-- without a default; prev_hash is nullable (NULL = first in chain).
|
|
26261
26261
|
ALTER TABLE audit_log ADD COLUMN prev_hash TEXT;
|
|
26262
26262
|
`;
|
|
26263
|
+
var MIGRATION_0007 = `-- ============================================================
|
|
26264
|
+
-- iso27001-mcp Migration 0007 \u2014 Organisation Profile Branding
|
|
26265
|
+
-- Adds optional branding and document personalisation fields.
|
|
26266
|
+
-- !! Never edit this file after it has been applied !!
|
|
26267
|
+
-- ============================================================
|
|
26268
|
+
ALTER TABLE organization_profile ADD COLUMN logo_url TEXT;
|
|
26269
|
+
ALTER TABLE organization_profile ADD COLUMN primary_color TEXT;
|
|
26270
|
+
ALTER TABLE organization_profile ADD COLUMN document_footer TEXT;
|
|
26271
|
+
ALTER TABLE organization_profile ADD COLUMN certification_body TEXT;
|
|
26272
|
+
`;
|
|
26263
26273
|
var MIGRATIONS = [
|
|
26264
26274
|
{ filename: "0001_initial.sql", sql: MIGRATION_0001 },
|
|
26265
26275
|
{ filename: "0002_fts_index.sql", sql: MIGRATION_0002 },
|
|
26266
26276
|
{ filename: "0003_org_profile_procedures.sql", sql: MIGRATION_0003 },
|
|
26267
26277
|
{ filename: "0004_management_review_improvement.sql", sql: MIGRATION_0004 },
|
|
26268
26278
|
{ filename: "0005_evidence_documents.sql", sql: MIGRATION_0005 },
|
|
26269
|
-
{ filename: "0006_audit_log_hmac.sql", sql: MIGRATION_0006 }
|
|
26279
|
+
{ filename: "0006_audit_log_hmac.sql", sql: MIGRATION_0006 },
|
|
26280
|
+
{ filename: "0007_org_profile_branding.sql", sql: MIGRATION_0007 }
|
|
26270
26281
|
];
|
|
26271
26282
|
|
|
26272
26283
|
// src/security/secrets.ts
|
|
@@ -33675,8 +33686,6 @@ var paginationLimit = import_zod.z.number().int().min(1).max(100).optional().def
|
|
|
33675
33686
|
var paginationOffset = import_zod.z.number().int().min(0).optional().default(0);
|
|
33676
33687
|
var versionEnum = import_zod.z.enum(["2022", "2013"]);
|
|
33677
33688
|
var formatMarkdownCsvJson = import_zod.z.enum(["markdown", "csv", "json"]);
|
|
33678
|
-
var formatMarkdownCsv = import_zod.z.enum(["markdown", "csv"]);
|
|
33679
|
-
var formatMarkdownJson = import_zod.z.enum(["markdown", "json"]);
|
|
33680
33689
|
var riskLevelEnum = import_zod.z.enum(["Low", "Medium", "High", "Critical"]);
|
|
33681
33690
|
var likelihood1to5 = import_zod.z.number().int().min(1).max(5);
|
|
33682
33691
|
var roleEnum = import_zod.z.enum(["viewer", "analyst", "admin"]);
|
|
@@ -33935,7 +33944,7 @@ var UpdateSoaEntrySchema = import_zod.z.object({
|
|
|
33935
33944
|
});
|
|
33936
33945
|
var ExportSoaSchema = import_zod.z.object({
|
|
33937
33946
|
soa_id: uuid,
|
|
33938
|
-
format:
|
|
33947
|
+
format: import_zod.z.enum(["markdown", "csv", "html"])
|
|
33939
33948
|
});
|
|
33940
33949
|
var CreateAuditSchema = import_zod.z.object({
|
|
33941
33950
|
name: shortText(200),
|
|
@@ -33972,7 +33981,7 @@ var UpdateCorrectiveActionSchema = import_zod.z.object({
|
|
|
33972
33981
|
});
|
|
33973
33982
|
var GenerateAuditReportSchema = import_zod.z.object({
|
|
33974
33983
|
audit_id: uuid,
|
|
33975
|
-
format:
|
|
33984
|
+
format: import_zod.z.enum(["markdown", "json", "html"])
|
|
33976
33985
|
});
|
|
33977
33986
|
var RegisterEvidenceSchema = import_zod.z.object({
|
|
33978
33987
|
control_id: import_zod.z.string().min(1).max(20),
|
|
@@ -34037,7 +34046,11 @@ var SetOrganizationProfileSchema = import_zod.z.object({
|
|
|
34037
34046
|
isms_manager: shortText(200).optional(),
|
|
34038
34047
|
internal_auditor: shortText(200).optional()
|
|
34039
34048
|
}).optional(),
|
|
34040
|
-
review_cadence_months: import_zod.z.number().int().min(1).max(36).optional().default(12)
|
|
34049
|
+
review_cadence_months: import_zod.z.number().int().min(1).max(36).optional().default(12),
|
|
34050
|
+
logo_url: import_zod.z.string().url().max(2e3).optional(),
|
|
34051
|
+
primary_color: import_zod.z.string().regex(/^#[0-9a-fA-F]{6}$/, "must be 6-digit hex e.g. #1e3a5f").optional(),
|
|
34052
|
+
document_footer: import_zod.z.string().max(500).optional(),
|
|
34053
|
+
certification_body: import_zod.z.string().max(200).optional()
|
|
34041
34054
|
});
|
|
34042
34055
|
var GetOrganizationProfileSchema = import_zod.z.object({});
|
|
34043
34056
|
var procedureTypeEnum = import_zod.z.enum([
|
|
@@ -34091,7 +34104,7 @@ var ListProceduresSchema = import_zod.z.object({
|
|
|
34091
34104
|
});
|
|
34092
34105
|
var ExportProcedureSchema = import_zod.z.object({
|
|
34093
34106
|
procedure_id: uuid,
|
|
34094
|
-
format:
|
|
34107
|
+
format: import_zod.z.enum(["markdown", "json", "html"])
|
|
34095
34108
|
});
|
|
34096
34109
|
var reviewStatusEnum = import_zod.z.enum(["planned", "in_progress", "completed"]);
|
|
34097
34110
|
var reviewInputCategoryEnum = import_zod.z.enum([
|
|
@@ -35416,6 +35429,171 @@ function stripFrontmatter(raw) {
|
|
|
35416
35429
|
}
|
|
35417
35430
|
return { template, clauseMappings, controlMappings };
|
|
35418
35431
|
}
|
|
35432
|
+
function markdownToHtml(md) {
|
|
35433
|
+
const lines = md.split("\n");
|
|
35434
|
+
const out = [];
|
|
35435
|
+
let inTable = false;
|
|
35436
|
+
let inList = false;
|
|
35437
|
+
let tableHeaderDone = false;
|
|
35438
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
35439
|
+
const inline = (s) => s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/\*([^*]+)\*/g, "<em>$1</em>").replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
35440
|
+
for (const line of lines) {
|
|
35441
|
+
const h1 = line.match(/^# (.+)/);
|
|
35442
|
+
const h2 = line.match(/^## (.+)/);
|
|
35443
|
+
const h3 = line.match(/^### (.+)/);
|
|
35444
|
+
if (h1 || h2 || h3) {
|
|
35445
|
+
if (inList) {
|
|
35446
|
+
out.push("</ul>");
|
|
35447
|
+
inList = false;
|
|
35448
|
+
}
|
|
35449
|
+
if (inTable) {
|
|
35450
|
+
out.push("</tbody></table>");
|
|
35451
|
+
inTable = false;
|
|
35452
|
+
tableHeaderDone = false;
|
|
35453
|
+
}
|
|
35454
|
+
if (h1) {
|
|
35455
|
+
out.push(`<h1>${inline(esc(h1[1]))}</h1>`);
|
|
35456
|
+
continue;
|
|
35457
|
+
}
|
|
35458
|
+
if (h2) {
|
|
35459
|
+
out.push(`<h2>${inline(esc(h2[1]))}</h2>`);
|
|
35460
|
+
continue;
|
|
35461
|
+
}
|
|
35462
|
+
if (h3) {
|
|
35463
|
+
out.push(`<h3>${inline(esc(h3[1]))}</h3>`);
|
|
35464
|
+
continue;
|
|
35465
|
+
}
|
|
35466
|
+
}
|
|
35467
|
+
if (/^---+$/.test(line.trim())) {
|
|
35468
|
+
if (inList) {
|
|
35469
|
+
out.push("</ul>");
|
|
35470
|
+
inList = false;
|
|
35471
|
+
}
|
|
35472
|
+
if (inTable) {
|
|
35473
|
+
out.push("</tbody></table>");
|
|
35474
|
+
inTable = false;
|
|
35475
|
+
tableHeaderDone = false;
|
|
35476
|
+
}
|
|
35477
|
+
out.push("<hr>");
|
|
35478
|
+
continue;
|
|
35479
|
+
}
|
|
35480
|
+
if (line.trim().startsWith("|")) {
|
|
35481
|
+
if (inList) {
|
|
35482
|
+
out.push("</ul>");
|
|
35483
|
+
inList = false;
|
|
35484
|
+
}
|
|
35485
|
+
if (/^\|[-| :]+\|$/.test(line.trim())) {
|
|
35486
|
+
if (!tableHeaderDone) {
|
|
35487
|
+
out.push("<tbody>");
|
|
35488
|
+
tableHeaderDone = true;
|
|
35489
|
+
}
|
|
35490
|
+
continue;
|
|
35491
|
+
}
|
|
35492
|
+
const cells = line.split("|").slice(1, -1).map((c) => c.trim());
|
|
35493
|
+
if (!inTable) {
|
|
35494
|
+
out.push("<table><thead><tr>");
|
|
35495
|
+
cells.forEach((c) => out.push(`<th>${inline(esc(c))}</th>`));
|
|
35496
|
+
out.push("</tr></thead>");
|
|
35497
|
+
inTable = true;
|
|
35498
|
+
tableHeaderDone = false;
|
|
35499
|
+
continue;
|
|
35500
|
+
}
|
|
35501
|
+
out.push("<tr>");
|
|
35502
|
+
cells.forEach((c) => out.push(`<td>${inline(esc(c))}</td>`));
|
|
35503
|
+
out.push("</tr>");
|
|
35504
|
+
continue;
|
|
35505
|
+
}
|
|
35506
|
+
if (inTable) {
|
|
35507
|
+
out.push("</tbody></table>");
|
|
35508
|
+
inTable = false;
|
|
35509
|
+
tableHeaderDone = false;
|
|
35510
|
+
}
|
|
35511
|
+
if (/^[-*] /.test(line)) {
|
|
35512
|
+
if (!inList) {
|
|
35513
|
+
out.push("<ul>");
|
|
35514
|
+
inList = true;
|
|
35515
|
+
}
|
|
35516
|
+
out.push(`<li>${inline(esc(line.replace(/^[-*] /, "")))}</li>`);
|
|
35517
|
+
continue;
|
|
35518
|
+
}
|
|
35519
|
+
if (inList && line.trim() === "") {
|
|
35520
|
+
out.push("</ul>");
|
|
35521
|
+
inList = false;
|
|
35522
|
+
}
|
|
35523
|
+
if (line.trim() === "") {
|
|
35524
|
+
out.push("");
|
|
35525
|
+
continue;
|
|
35526
|
+
}
|
|
35527
|
+
if (!inList) out.push(`<p>${inline(esc(line))}</p>`);
|
|
35528
|
+
}
|
|
35529
|
+
if (inList) out.push("</ul>");
|
|
35530
|
+
if (inTable) out.push("</tbody></table>");
|
|
35531
|
+
return out.join("\n");
|
|
35532
|
+
}
|
|
35533
|
+
function renderHtmlDocument(bodyHtml, meta) {
|
|
35534
|
+
const color = meta.primary_color ?? "#1e3a5f";
|
|
35535
|
+
const footer = meta.document_footer ?? meta.organisation_name;
|
|
35536
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
35537
|
+
return `<!DOCTYPE html>
|
|
35538
|
+
<html lang="en">
|
|
35539
|
+
<head>
|
|
35540
|
+
<meta charset="UTF-8">
|
|
35541
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
35542
|
+
<title>${meta.title} \u2014 ${meta.organisation_name}</title>
|
|
35543
|
+
<style>
|
|
35544
|
+
*,*::before,*::after{box-sizing:border-box}
|
|
35545
|
+
body{font-family:'Segoe UI',Arial,sans-serif;font-size:11pt;line-height:1.6;color:#1a1a1a;margin:0;padding:0;background:#fff}
|
|
35546
|
+
.doc-header{background:${color};color:#fff;padding:24px 40px 18px;display:flex;align-items:center;gap:20px}
|
|
35547
|
+
.doc-header-logo{max-height:48px;max-width:160px;object-fit:contain}
|
|
35548
|
+
.doc-header-text{flex:1}
|
|
35549
|
+
.doc-header h1{margin:0 0 4px;font-size:18pt;font-weight:700;color:#fff}
|
|
35550
|
+
.doc-header-sub{font-size:10pt;opacity:0.85}
|
|
35551
|
+
.doc-meta-bar{background:#f5f7fa;border-bottom:2px solid ${color};padding:10px 40px;display:flex;gap:32px;font-size:9pt;color:#444}
|
|
35552
|
+
.doc-meta-item strong{color:#111}
|
|
35553
|
+
.doc-body{padding:28px 40px;max-width:900px}
|
|
35554
|
+
h1{font-size:16pt;color:${color};margin-top:24px;border-bottom:2px solid ${color};padding-bottom:4px}
|
|
35555
|
+
h2{font-size:13pt;color:${color};margin-top:20px}
|
|
35556
|
+
h3{font-size:11pt;color:#333;margin-top:16px}
|
|
35557
|
+
table{width:100%;border-collapse:collapse;margin:14px 0;font-size:10pt}
|
|
35558
|
+
th{background:${color};color:#fff;padding:7px 10px;text-align:left;font-weight:600}
|
|
35559
|
+
td{padding:6px 10px;border-bottom:1px solid #e0e4ea;vertical-align:top}
|
|
35560
|
+
tr:nth-child(even) td{background:#f8f9fb}
|
|
35561
|
+
ul{padding-left:20px;margin:8px 0}
|
|
35562
|
+
li{margin:3px 0}
|
|
35563
|
+
hr{border:none;border-top:1px solid #dce1ea;margin:20px 0}
|
|
35564
|
+
p{margin:8px 0}
|
|
35565
|
+
code{background:#f0f2f5;padding:1px 5px;border-radius:3px;font-family:monospace;font-size:10pt}
|
|
35566
|
+
.doc-footer{margin-top:40px;padding:14px 40px;background:#f5f7fa;border-top:2px solid ${color};font-size:9pt;color:#666;display:flex;justify-content:space-between}
|
|
35567
|
+
@media print{
|
|
35568
|
+
body{font-size:10pt}
|
|
35569
|
+
.doc-header,.doc-meta-bar,th,.doc-footer{-webkit-print-color-adjust:exact;print-color-adjust:exact}
|
|
35570
|
+
h1,h2{page-break-after:avoid}
|
|
35571
|
+
table{page-break-inside:avoid}
|
|
35572
|
+
}
|
|
35573
|
+
</style>
|
|
35574
|
+
</head>
|
|
35575
|
+
<body>
|
|
35576
|
+
<div class="doc-header">
|
|
35577
|
+
${meta.logo_url ? `<img class="doc-header-logo" src="${meta.logo_url}" alt="${meta.organisation_name} logo">` : ""}
|
|
35578
|
+
<div class="doc-header-text">
|
|
35579
|
+
<h1>${meta.title}</h1>
|
|
35580
|
+
<div class="doc-header-sub">${meta.organisation_name}${meta.doc_type ? ` \xB7 ${meta.doc_type}` : ""}</div>
|
|
35581
|
+
</div>
|
|
35582
|
+
</div>
|
|
35583
|
+
<div class="doc-meta-bar">
|
|
35584
|
+
${meta.version ? `<span><strong>Version:</strong> ${meta.version}</span>` : ""}
|
|
35585
|
+
${meta.effective_date ? `<span><strong>Effective:</strong> ${meta.effective_date}</span>` : ""}
|
|
35586
|
+
${meta.owner ? `<span><strong>Owner:</strong> ${meta.owner}</span>` : ""}
|
|
35587
|
+
<span><strong>Generated:</strong> ${today}</span>
|
|
35588
|
+
</div>
|
|
35589
|
+
<div class="doc-body">${bodyHtml}</div>
|
|
35590
|
+
<div class="doc-footer">
|
|
35591
|
+
<span>${footer}</span>
|
|
35592
|
+
<span>INTERNAL \u2014 ISMS Controlled Document \xB7 Generated ${today}</span>
|
|
35593
|
+
</div>
|
|
35594
|
+
</body>
|
|
35595
|
+
</html>`;
|
|
35596
|
+
}
|
|
35419
35597
|
|
|
35420
35598
|
// src/tools/org-profile.ts
|
|
35421
35599
|
var ORG_PROFILE_ID = "00000000-0000-4000-8000-000000000001";
|
|
@@ -35423,11 +35601,15 @@ function ok4(data) {
|
|
|
35423
35601
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], isError: false };
|
|
35424
35602
|
}
|
|
35425
35603
|
function loadOrgProfileDefaults(db) {
|
|
35426
|
-
const row = db.prepare("SELECT legal_entity_name, isms_scope_statement FROM organization_profile WHERE id = ?").get(ORG_PROFILE_ID);
|
|
35604
|
+
const row = db.prepare("SELECT legal_entity_name, isms_scope_statement, logo_url, primary_color, document_footer, certification_body FROM organization_profile WHERE id = ?").get(ORG_PROFILE_ID);
|
|
35427
35605
|
if (!row) return null;
|
|
35428
35606
|
return {
|
|
35429
35607
|
organisation_name: row.legal_entity_name,
|
|
35430
|
-
scope: row.isms_scope_statement
|
|
35608
|
+
scope: row.isms_scope_statement,
|
|
35609
|
+
logo_url: row.logo_url,
|
|
35610
|
+
primary_color: row.primary_color,
|
|
35611
|
+
document_footer: row.document_footer,
|
|
35612
|
+
certification_body: row.certification_body
|
|
35431
35613
|
};
|
|
35432
35614
|
}
|
|
35433
35615
|
function handleSetOrganizationProfile(args2) {
|
|
@@ -35439,17 +35621,22 @@ function handleSetOrganizationProfile(args2) {
|
|
|
35439
35621
|
isms_scope_statement,
|
|
35440
35622
|
declared_exclusions,
|
|
35441
35623
|
raci_roles,
|
|
35442
|
-
review_cadence_months = 12
|
|
35624
|
+
review_cadence_months = 12,
|
|
35625
|
+
logo_url,
|
|
35626
|
+
primary_color,
|
|
35627
|
+
document_footer,
|
|
35628
|
+
certification_body
|
|
35443
35629
|
} = args2;
|
|
35444
35630
|
const ts = now();
|
|
35445
35631
|
getDb().prepare(`
|
|
35446
35632
|
INSERT OR REPLACE INTO organization_profile
|
|
35447
35633
|
(id, legal_entity_name, registered_jurisdiction, regulatory_licences,
|
|
35448
35634
|
in_scope_activities, isms_scope_statement, declared_exclusions,
|
|
35449
|
-
raci_roles, review_cadence_months, created_at, updated_at
|
|
35635
|
+
raci_roles, review_cadence_months, created_at, updated_at,
|
|
35636
|
+
logo_url, primary_color, document_footer, certification_body)
|
|
35450
35637
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, COALESCE(
|
|
35451
35638
|
(SELECT created_at FROM organization_profile WHERE id = ?), ?
|
|
35452
|
-
), ?)
|
|
35639
|
+
), ?, ?, ?, ?, ?)
|
|
35453
35640
|
`).run(
|
|
35454
35641
|
ORG_PROFILE_ID,
|
|
35455
35642
|
legal_entity_name,
|
|
@@ -35462,7 +35649,11 @@ function handleSetOrganizationProfile(args2) {
|
|
|
35462
35649
|
review_cadence_months,
|
|
35463
35650
|
ORG_PROFILE_ID,
|
|
35464
35651
|
ts,
|
|
35465
|
-
ts
|
|
35652
|
+
ts,
|
|
35653
|
+
logo_url ?? null,
|
|
35654
|
+
primary_color ?? null,
|
|
35655
|
+
document_footer ?? null,
|
|
35656
|
+
certification_body ?? null
|
|
35466
35657
|
);
|
|
35467
35658
|
return ok4({
|
|
35468
35659
|
id: ORG_PROFILE_ID,
|
|
@@ -35830,6 +36021,44 @@ function handleExportSoa(args2) {
|
|
|
35830
36021
|
`).all(soa.isms_version, soa_id);
|
|
35831
36022
|
const includedCount = entries.filter((e) => e.included === 1).length;
|
|
35832
36023
|
const excludedCount = entries.filter((e) => e.included === 0).length;
|
|
36024
|
+
const ORG_PROFILE_ID_SOA = "00000000-0000-4000-8000-000000000001";
|
|
36025
|
+
if (format === "html") {
|
|
36026
|
+
const profileRow = db.prepare(
|
|
36027
|
+
"SELECT legal_entity_name, logo_url, primary_color, document_footer FROM organization_profile WHERE id = ?"
|
|
36028
|
+
).get(ORG_PROFILE_ID_SOA);
|
|
36029
|
+
const tableRows = entries.map((e) => {
|
|
36030
|
+
const inc = e.included === 1;
|
|
36031
|
+
return `<tr>
|
|
36032
|
+
<td><strong>${e.control_id}</strong></td>
|
|
36033
|
+
<td>${e.control_name ?? "\u2014"}</td>
|
|
36034
|
+
<td>${e.theme ?? "\u2014"}</td>
|
|
36035
|
+
<td style="color:${inc ? "#065f46" : "#991b1b"};font-weight:600">${inc ? "\u2713 Yes" : "\u2717 No"}</td>
|
|
36036
|
+
<td>${e.justification}</td>
|
|
36037
|
+
<td>${e.status ?? "\u2014"}</td>
|
|
36038
|
+
<td>${e.responsible_party ?? "\u2014"}</td>
|
|
36039
|
+
</tr>`;
|
|
36040
|
+
}).join("\n");
|
|
36041
|
+
const bodyHtml = `
|
|
36042
|
+
<p><strong>ISMS Version:</strong> ISO 27001:${soa.isms_version} \xB7
|
|
36043
|
+
<strong>Total:</strong> ${entries.length} \xB7
|
|
36044
|
+
<strong>Included:</strong> ${includedCount} \xB7
|
|
36045
|
+
<strong>Excluded:</strong> ${excludedCount}</p>
|
|
36046
|
+
<table>
|
|
36047
|
+
<thead>
|
|
36048
|
+
<tr><th>Control ID</th><th>Name</th><th>Theme</th><th>Included</th><th>Justification</th><th>Status</th><th>Responsible Party</th></tr>
|
|
36049
|
+
</thead>
|
|
36050
|
+
<tbody>${tableRows}</tbody>
|
|
36051
|
+
</table>`;
|
|
36052
|
+
const html = renderHtmlDocument(bodyHtml, {
|
|
36053
|
+
title: "Statement of Applicability",
|
|
36054
|
+
organisation_name: profileRow?.legal_entity_name ?? "Organisation",
|
|
36055
|
+
logo_url: profileRow?.logo_url,
|
|
36056
|
+
primary_color: profileRow?.primary_color,
|
|
36057
|
+
document_footer: profileRow?.document_footer,
|
|
36058
|
+
doc_type: `ISO 27001:${soa.isms_version}`
|
|
36059
|
+
});
|
|
36060
|
+
return ok6({ format: "html", content: html });
|
|
36061
|
+
}
|
|
35833
36062
|
if (format === "csv") {
|
|
35834
36063
|
const header = "control_id,name,theme,included,justification,status,responsible_party";
|
|
35835
36064
|
const rows = entries.map(
|
|
@@ -36063,6 +36292,46 @@ function handleGenerateAuditReport(args2) {
|
|
|
36063
36292
|
carsByFinding.set(car.finding_id, existing);
|
|
36064
36293
|
}
|
|
36065
36294
|
const countByType = (type) => findings.filter((f) => f.type === type).length;
|
|
36295
|
+
if (args2.format === "html") {
|
|
36296
|
+
const mdLines = [
|
|
36297
|
+
`# ${audit.name}`,
|
|
36298
|
+
``,
|
|
36299
|
+
`**Auditor:** ${audit.auditor} `,
|
|
36300
|
+
`**Planned Date:** ${audit.planned_date} `,
|
|
36301
|
+
`**Status:** ${audit.status} `,
|
|
36302
|
+
`**Scope:** ${audit.scope}`,
|
|
36303
|
+
``,
|
|
36304
|
+
`## Findings (${findings.length})`,
|
|
36305
|
+
``,
|
|
36306
|
+
`| ID | Type | Severity | Clause/Control | Description |`,
|
|
36307
|
+
`|---|---|---|---|---|`,
|
|
36308
|
+
...findings.map(
|
|
36309
|
+
(f) => `| ${f.id.slice(-8)} | ${f.type.toUpperCase()} | ${f.severity ?? "\u2014"} | ${f.clause_or_control} | ${f.description.slice(0, 80)} |`
|
|
36310
|
+
),
|
|
36311
|
+
``,
|
|
36312
|
+
`## Corrective Actions (${cars.length})`,
|
|
36313
|
+
``,
|
|
36314
|
+
`| CAR ID | Finding | Owner | Due Date | Status | Verified |`,
|
|
36315
|
+
`|---|---|---|---|---|---|`,
|
|
36316
|
+
...cars.map(
|
|
36317
|
+
(c) => `| ${c.id.slice(-8)} | ${c.finding_id.slice(-8)} | ${c.owner} | ${c.due_date} | ${c.status} | ${c.effectiveness_verified ? "\u2713 Yes" : "No"} |`
|
|
36318
|
+
)
|
|
36319
|
+
];
|
|
36320
|
+
const db2 = getDb();
|
|
36321
|
+
const ORG_PROFILE_ID_AUDIT = "00000000-0000-4000-8000-000000000001";
|
|
36322
|
+
const profileRow = db2.prepare(
|
|
36323
|
+
"SELECT legal_entity_name, logo_url, primary_color, document_footer FROM organization_profile WHERE id = ?"
|
|
36324
|
+
).get(ORG_PROFILE_ID_AUDIT);
|
|
36325
|
+
const html = renderHtmlDocument(markdownToHtml(mdLines.join("\n")), {
|
|
36326
|
+
title: audit.name,
|
|
36327
|
+
organisation_name: profileRow?.legal_entity_name ?? "Organisation",
|
|
36328
|
+
logo_url: profileRow?.logo_url,
|
|
36329
|
+
primary_color: profileRow?.primary_color,
|
|
36330
|
+
document_footer: profileRow?.document_footer,
|
|
36331
|
+
doc_type: "Internal Audit Report"
|
|
36332
|
+
});
|
|
36333
|
+
return ok7({ format: "html", content: html });
|
|
36334
|
+
}
|
|
36066
36335
|
if (format === "json") {
|
|
36067
36336
|
return ok7({
|
|
36068
36337
|
audit: shapeAudit(audit),
|
|
@@ -36658,6 +36927,23 @@ function handleExportProcedure(args2) {
|
|
|
36658
36927
|
const db = getDb();
|
|
36659
36928
|
const row = db.prepare("SELECT * FROM procedures WHERE id = ?").get(procedure_id);
|
|
36660
36929
|
if (!row) throw notFound("procedure", procedure_id);
|
|
36930
|
+
if (format === "html") {
|
|
36931
|
+
const db2 = getDb();
|
|
36932
|
+
const defaults = loadOrgProfileDefaults(db2);
|
|
36933
|
+
const bodyHtml = markdownToHtml(row.content);
|
|
36934
|
+
const html = renderHtmlDocument(bodyHtml, {
|
|
36935
|
+
title: row.procedure_type.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()) + " Procedure",
|
|
36936
|
+
organisation_name: row.organisation_name,
|
|
36937
|
+
logo_url: defaults?.logo_url,
|
|
36938
|
+
primary_color: defaults?.primary_color,
|
|
36939
|
+
document_footer: defaults?.document_footer,
|
|
36940
|
+
version: String(row.version),
|
|
36941
|
+
effective_date: row.effective_date,
|
|
36942
|
+
owner: row.owner,
|
|
36943
|
+
doc_type: "Procedure"
|
|
36944
|
+
});
|
|
36945
|
+
return ok9({ format: "html", content: html });
|
|
36946
|
+
}
|
|
36661
36947
|
if (format === "markdown") {
|
|
36662
36948
|
const relatedControls = fromJsonArray(row.related_controls);
|
|
36663
36949
|
const controlsSection = relatedControls.length > 0 ? `
|
|
@@ -38627,12 +38913,12 @@ ${divider}
|
|
|
38627
38913
|
} else {
|
|
38628
38914
|
try {
|
|
38629
38915
|
const rows = openDb(dbPath2).prepare("SELECT filename FROM _migrations ORDER BY id").all();
|
|
38630
|
-
const passed2 = rows.length >=
|
|
38916
|
+
const passed2 = rows.length >= 7;
|
|
38631
38917
|
check(
|
|
38632
38918
|
"Migrations",
|
|
38633
38919
|
passed2,
|
|
38634
38920
|
false,
|
|
38635
|
-
`${rows.length}/
|
|
38921
|
+
`${rows.length}/7 applied${passed2 ? "" : " \u2014 DB may need re-initialisation"}`
|
|
38636
38922
|
);
|
|
38637
38923
|
record(passed2);
|
|
38638
38924
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iso27001-mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"mcpName": "io.github.Sushegaad/iso27001-mcp",
|
|
5
5
|
"description": "ISO 27001 compliance workspace for Claude — controls, risks, policies, evidence, audits, and SoA in one local encrypted MCP server",
|
|
6
6
|
"license": "MIT",
|