Skip to content

Commit 3809fbd

Browse files
Copilothotlong
andcommitted
feat(system): add ISO 27001:2022 compliance schemas — incident response, supplier security, training, audit scheduling, security impact assessment
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 82c7b6f commit 3809fbd

12 files changed

Lines changed: 2289 additions & 1 deletion

ROADMAP.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,31 @@ business/custom objects, aligning with industry best practices (e.g., ServiceNow
543543
- [ ] GDPR/CCPA compliance utilities (right to erasure, data export)
544544
- [ ] Change management and approval workflows for schema changes
545545

546+
### 6.5 ISO 27001:2022 Compliance
547+
548+
> **Goal:** Full schema coverage for ISO 27001:2022 Annex A controls to support certification readiness.
549+
550+
#### 6.5.1 High Priority (Certification Blockers) — ✅ Schema Complete
551+
552+
- [x] **Incident Response Protocol** (A.5.24–A.5.28) — `system/incident-response.zod.ts`: Incident classification, severity grading, response phases, notification matrix, escalation policies
553+
- [x] **Audit Scheduling & Finding Tracking** (A.5.35) — `system/compliance.zod.ts`: AuditScheduleSchema, AuditFindingSchema for independent review and remediation tracking
554+
- [x] **Change Management Security Approval** (A.8.32) — `system/change-management.zod.ts`: SecurityImpactAssessment with risk level, data classification, security reviewer workflow
555+
556+
#### 6.5.2 Medium Priority (Compliance Completeness) — ✅ Schema Complete
557+
558+
- [x] **Supplier Security Assessment** (A.5.19–A.5.22) — `system/supplier-security.zod.ts`: Supplier risk levels, security requirements, assessment lifecycle, remediation tracking
559+
- [x] **Information Security Training** (A.6.3) — `system/training.zod.ts`: Training courses, completion records, organizational training plans with recertification
560+
561+
#### 6.5.3 Medium Priority (Pending)
562+
563+
- [ ] **OAuth Scope Binding** (A.8.1) — API endpoint schema with required OAuth scopes
564+
- [ ] **Permission Registry** (A.8.2) — Transform `manifest.permissions` from `string[]` to structured registry enum
565+
566+
#### 6.5.4 Low Priority (Enhancements)
567+
568+
- [ ] Permission delegation and temporary privilege elevation protocol (AWS STS-style)
569+
- [ ] Device trust policy extensions
570+
546571
---
547572

548573
## Phase 7: AI & Intelligence (🔴 Planned)

packages/spec/src/system/change-management.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,4 +653,120 @@ describe('ChangeRequestSchema', () => {
653653

654654
expect(() => ChangeRequestSchema.parse(rolledBackChange)).not.toThrow();
655655
});
656+
657+
it('should accept change with security impact assessment (A.8.32)', () => {
658+
const changeWithSecurityImpact = {
659+
id: 'CHG-2024-SEC-001',
660+
title: 'API Gateway Configuration Change',
661+
description: 'Update API gateway security headers',
662+
type: 'normal',
663+
priority: 'high',
664+
status: 'approved',
665+
requestedBy: 'security_team',
666+
requestedAt: Date.now(),
667+
impact: {
668+
level: 'high',
669+
affectedSystems: ['api-gateway'],
670+
},
671+
implementation: {
672+
description: 'Update security headers',
673+
steps: [
674+
{
675+
order: 1,
676+
description: 'Deploy configuration',
677+
estimatedMinutes: 10,
678+
},
679+
],
680+
},
681+
rollbackPlan: {
682+
description: 'Revert configuration',
683+
steps: [
684+
{
685+
order: 1,
686+
description: 'Restore previous config',
687+
estimatedMinutes: 5,
688+
},
689+
],
690+
},
691+
securityImpact: {
692+
assessed: true,
693+
riskLevel: 'high',
694+
affectedDataClassifications: ['pii', 'confidential'],
695+
requiresSecurityApproval: true,
696+
reviewedBy: 'ciso',
697+
reviewedAt: Date.now(),
698+
reviewNotes: 'Approved with monitoring requirement',
699+
},
700+
};
701+
702+
const parsed = ChangeRequestSchema.parse(changeWithSecurityImpact);
703+
expect(parsed.securityImpact?.assessed).toBe(true);
704+
expect(parsed.securityImpact?.riskLevel).toBe('high');
705+
expect(parsed.securityImpact?.requiresSecurityApproval).toBe(true);
706+
});
707+
708+
it('should accept change with minimal security impact', () => {
709+
const change = {
710+
id: 'CHG-2024-SEC-002',
711+
title: 'Minor UI Change',
712+
description: 'Update button color',
713+
type: 'standard',
714+
priority: 'low',
715+
status: 'draft',
716+
requestedBy: 'user_123',
717+
requestedAt: Date.now(),
718+
impact: {
719+
level: 'low',
720+
affectedSystems: ['ui'],
721+
},
722+
implementation: {
723+
description: 'Update CSS',
724+
steps: [{ order: 1, description: 'Deploy', estimatedMinutes: 5 }],
725+
},
726+
rollbackPlan: {
727+
description: 'Revert CSS',
728+
steps: [{ order: 1, description: 'Revert', estimatedMinutes: 5 }],
729+
},
730+
securityImpact: {
731+
assessed: true,
732+
riskLevel: 'none',
733+
},
734+
};
735+
736+
const parsed = ChangeRequestSchema.parse(change);
737+
expect(parsed.securityImpact?.riskLevel).toBe('none');
738+
expect(parsed.securityImpact?.requiresSecurityApproval).toBe(false);
739+
});
740+
741+
it('should accept all security risk levels', () => {
742+
const levels = ['none', 'low', 'medium', 'high', 'critical'] as const;
743+
744+
levels.forEach((riskLevel) => {
745+
const change = {
746+
id: `CHG-${riskLevel}`,
747+
title: 'Test',
748+
description: 'Test',
749+
type: 'standard',
750+
priority: 'low',
751+
status: 'draft',
752+
requestedBy: 'user',
753+
requestedAt: Date.now(),
754+
impact: { level: 'low', affectedSystems: ['test'] },
755+
implementation: {
756+
description: 'Test',
757+
steps: [{ order: 1, description: 'Test', estimatedMinutes: 5 }],
758+
},
759+
rollbackPlan: {
760+
description: 'Test',
761+
steps: [{ order: 1, description: 'Test', estimatedMinutes: 5 }],
762+
},
763+
securityImpact: {
764+
assessed: true,
765+
riskLevel,
766+
},
767+
};
768+
769+
expect(() => ChangeRequestSchema.parse(change)).not.toThrow();
770+
});
771+
});
656772
});

packages/spec/src/system/change-management.zod.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,53 @@ export const ChangeRequestSchema = z.object({
319319
actualEnd: z.number().optional().describe('Actual end time'),
320320
}).optional().describe('Schedule'),
321321

322+
/**
323+
* Security impact assessment for the change (A.8.32)
324+
*/
325+
securityImpact: z.object({
326+
/**
327+
* Whether a security impact assessment has been performed
328+
*/
329+
assessed: z.boolean().describe('Whether security impact has been assessed'),
330+
331+
/**
332+
* Security risk level of this change
333+
*/
334+
riskLevel: z.enum(['none', 'low', 'medium', 'high', 'critical']).optional()
335+
.describe('Security risk level'),
336+
337+
/**
338+
* Data classifications affected by this change
339+
*/
340+
affectedDataClassifications: z.array(z.enum([
341+
'pii', 'phi', 'pci', 'financial', 'confidential', 'internal', 'public',
342+
])).optional().describe('Affected data classifications'),
343+
344+
/**
345+
* Whether the change requires security team approval
346+
*/
347+
requiresSecurityApproval: z.boolean().default(false)
348+
.describe('Whether security team approval is required'),
349+
350+
/**
351+
* Security reviewer user ID
352+
*/
353+
reviewedBy: z.string().optional()
354+
.describe('Security reviewer user ID'),
355+
356+
/**
357+
* Security review completion timestamp (Unix milliseconds)
358+
*/
359+
reviewedAt: z.number().optional()
360+
.describe('Security review timestamp'),
361+
362+
/**
363+
* Security review notes or conditions
364+
*/
365+
reviewNotes: z.string().optional()
366+
.describe('Security review notes or conditions'),
367+
}).optional().describe('Security impact assessment per ISO 27001:2022 A.8.32'),
368+
322369
/**
323370
* Approval workflow configuration
324371
*/

packages/spec/src/system/compliance.test.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import {
55
PCIDSSConfigSchema,
66
AuditLogConfigSchema,
77
ComplianceConfigSchema,
8+
AuditFindingSeveritySchema,
9+
AuditFindingStatusSchema,
10+
AuditFindingSchema,
11+
AuditScheduleSchema,
812
} from './compliance.zod';
913

1014
describe('GDPRConfigSchema', () => {
@@ -226,4 +230,178 @@ describe('ComplianceConfigSchema', () => {
226230
it('should reject missing auditLog', () => {
227231
expect(() => ComplianceConfigSchema.parse({})).toThrow();
228232
});
233+
234+
it('should accept configuration with audit schedules', () => {
235+
const config = ComplianceConfigSchema.parse({
236+
auditLog: {
237+
events: ['create', 'update'],
238+
},
239+
auditSchedules: [
240+
{
241+
id: 'AUDIT-2024-Q1',
242+
title: 'Q1 ISO 27001 Internal Audit',
243+
scope: ['access_control', 'encryption'],
244+
framework: 'iso27001',
245+
scheduledAt: 1711929600000,
246+
assessor: 'internal_audit_team',
247+
},
248+
],
249+
});
250+
251+
expect(config.auditSchedules).toHaveLength(1);
252+
expect(config.auditSchedules![0].framework).toBe('iso27001');
253+
});
254+
});
255+
256+
describe('AuditFindingSeveritySchema', () => {
257+
it('should accept all valid severities', () => {
258+
const severities = ['critical', 'major', 'minor', 'observation'];
259+
260+
severities.forEach((severity) => {
261+
expect(() => AuditFindingSeveritySchema.parse(severity)).not.toThrow();
262+
});
263+
});
264+
265+
it('should reject invalid severity', () => {
266+
expect(() => AuditFindingSeveritySchema.parse('warning')).toThrow();
267+
});
268+
});
269+
270+
describe('AuditFindingStatusSchema', () => {
271+
it('should accept all valid statuses', () => {
272+
const statuses = ['open', 'in_remediation', 'remediated', 'verified', 'accepted_risk', 'closed'];
273+
274+
statuses.forEach((status) => {
275+
expect(() => AuditFindingStatusSchema.parse(status)).not.toThrow();
276+
});
277+
});
278+
279+
it('should reject invalid status', () => {
280+
expect(() => AuditFindingStatusSchema.parse('pending')).toThrow();
281+
});
282+
});
283+
284+
describe('AuditFindingSchema', () => {
285+
it('should accept valid finding', () => {
286+
const finding = AuditFindingSchema.parse({
287+
id: 'FIND-2024-001',
288+
title: 'Insufficient access logging',
289+
description: 'PHI access events are not being logged for HIPAA compliance',
290+
severity: 'major',
291+
status: 'in_remediation',
292+
controlReference: 'A.8.15',
293+
framework: 'iso27001',
294+
identifiedAt: 1704067200000,
295+
identifiedBy: 'external_auditor',
296+
remediationPlan: 'Implement audit logging for all PHI access events',
297+
remediationDeadline: 1706745600000,
298+
});
299+
300+
expect(finding.severity).toBe('major');
301+
expect(finding.framework).toBe('iso27001');
302+
});
303+
304+
it('should accept minimal finding', () => {
305+
const finding = AuditFindingSchema.parse({
306+
id: 'FIND-2024-002',
307+
title: 'Missing encryption',
308+
description: 'Field-level encryption not enabled',
309+
severity: 'minor',
310+
status: 'open',
311+
identifiedAt: Date.now(),
312+
identifiedBy: 'internal_audit',
313+
});
314+
315+
expect(finding.controlReference).toBeUndefined();
316+
expect(finding.remediationPlan).toBeUndefined();
317+
});
318+
319+
it('should accept verified finding', () => {
320+
const finding = AuditFindingSchema.parse({
321+
id: 'FIND-2024-003',
322+
title: 'Weak password policy',
323+
description: 'Password minimum length below 12 characters',
324+
severity: 'observation',
325+
status: 'verified',
326+
identifiedAt: 1704067200000,
327+
identifiedBy: 'auditor',
328+
verifiedAt: 1706745600000,
329+
verifiedBy: 'senior_auditor',
330+
notes: 'Password policy updated and verified',
331+
});
332+
333+
expect(finding.verifiedAt).toBe(1706745600000);
334+
expect(finding.verifiedBy).toBe('senior_auditor');
335+
});
336+
337+
it('should reject missing required fields', () => {
338+
expect(() => AuditFindingSchema.parse({})).toThrow();
339+
});
340+
});
341+
342+
describe('AuditScheduleSchema', () => {
343+
it('should accept valid schedule with defaults', () => {
344+
const schedule = AuditScheduleSchema.parse({
345+
id: 'AUDIT-2024-Q1',
346+
title: 'Q1 ISO 27001 Internal Audit',
347+
scope: ['access_control', 'encryption', 'incident_response'],
348+
framework: 'iso27001',
349+
scheduledAt: 1711929600000,
350+
assessor: 'internal_audit_team',
351+
});
352+
353+
expect(schedule.isExternal).toBe(false);
354+
expect(schedule.recurrenceMonths).toBe(0);
355+
expect(schedule.findings).toBeUndefined();
356+
});
357+
358+
it('should accept full schedule with findings', () => {
359+
const schedule = AuditScheduleSchema.parse({
360+
id: 'AUDIT-2024-EXT',
361+
title: 'Annual External ISO 27001 Audit',
362+
scope: ['all_controls'],
363+
framework: 'iso27001',
364+
scheduledAt: 1711929600000,
365+
completedAt: 1712534400000,
366+
assessor: 'External Audit Firm LLC',
367+
isExternal: true,
368+
recurrenceMonths: 12,
369+
findings: [
370+
{
371+
id: 'FIND-001',
372+
title: 'Missing incident response plan',
373+
description: 'No documented incident response procedure',
374+
severity: 'major',
375+
status: 'open',
376+
controlReference: 'A.5.24',
377+
framework: 'iso27001',
378+
identifiedAt: 1712534400000,
379+
identifiedBy: 'External Audit Firm LLC',
380+
},
381+
],
382+
});
383+
384+
expect(schedule.isExternal).toBe(true);
385+
expect(schedule.recurrenceMonths).toBe(12);
386+
expect(schedule.findings).toHaveLength(1);
387+
});
388+
389+
it('should accept all framework values', () => {
390+
const frameworks = ['gdpr', 'hipaa', 'sox', 'pci_dss', 'ccpa', 'iso27001'];
391+
392+
frameworks.forEach((framework) => {
393+
expect(() => AuditScheduleSchema.parse({
394+
id: `AUDIT-${framework}`,
395+
title: `${framework} audit`,
396+
scope: ['general'],
397+
framework,
398+
scheduledAt: Date.now(),
399+
assessor: 'auditor',
400+
})).not.toThrow();
401+
});
402+
});
403+
404+
it('should reject missing required fields', () => {
405+
expect(() => AuditScheduleSchema.parse({})).toThrow();
406+
});
229407
});

0 commit comments

Comments
 (0)