First public release

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-28 14:56:25 -04:00
co-authored by Claude Opus 5.5
commit d376475a91
16 changed files with 2360 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+9
View File
@@ -0,0 +1,9 @@
node_modules/
dist/
*.log
.DS_Store
.env*
!.env.example
.npmrc
dist/
dist-cjs/
+6
View File
@@ -0,0 +1,6 @@
stages: [test]
test:
stage: test
image: node:22-alpine
script: [npm ci, npm run build, npm test]
+14
View File
@@ -0,0 +1,14 @@
# Changelog
## 0.2.0 - 2026-09-28
First public release under MIT.
- `renderControlDoc()` builds a seven-section NIST 800-53 control document
in HTML and markdown from verification checks, with an Evidence section.
- Checks without evidence are shown as NOT ATTACHED and counted.
Unrecognised statuses are counted, never dropped.
- Output is reproducible: the date is a parameter.
- All interpolated values are HTML-escaped.
- `toReport()` and `formatControl()` produce an `@extant2000/evidence-record`
report of the same assessment.
+22
View File
@@ -0,0 +1,22 @@
# Contributing
Issues and merge requests are welcome at
https://gitlab.com/extant2000/cert-pack.
## Ground rules
- **A test that cannot fail proves nothing.** If you fix a bug, add a test
that fails without your fix, and check that it does fail before you submit.
- Measure, do not assume. Two modules with the same line count can be
different programs.
- Keep dependencies minimal. Every new dependency needs a reason.
- Explain why in comments, not what.
## Before you open a merge request
Run both of these and make sure they pass:
```
npm run build
npm test
```
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Extant 2000 LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+102
View File
@@ -0,0 +1,102 @@
# CertPack
Generates a NIST 800-53 control document (a System Security Plan section with
a remediation plan) from automated verification checks, with the evidence for
each check attached. It is used in Hitt Hosting products.
## What it does and why
An accreditation package that lists results without the scan output behind
them is a set of claims. **CertPack attaches the evidence to each check and
labels any check without evidence as a claim.** It does not drop the check or
leave the cell blank, because a blank cell reads as "nothing to report" and a
missing check is worse than a weak one.
You pass a control (document number, title, control family, the gap, the fix)
and a list of checks, each with a status and optional evidence in the
`@extant2000/evidence-record` format. `renderControlDoc()` returns HTML and
markdown built from one list of seven sections, so the two never drift apart:
1. Document information
2. Purpose and scope, including the gap and the deployments in scope
3. Current implementation status: a table of checks with their evidence, a
summary line, and a count of checks with no attached evidence
4. Remediation plan with a target completion time (30 days by default)
5. Evidence: the full citation for every check, or a statement that the
result is a claim and not a verified finding
6. References (NIST SP 800-53 Rev. 5, SP 800-53A and the FedRAMP SSP template
by default)
7. Approval and sign-off table
The rules that keep the document from claiming more than it knows:
- A status other than `pass`, `fail` or `warn` is counted as unrecognised and
shown in the summary, never folded into another status or dropped.
- A control with no checks says that an unassessed control is not an
implemented control, instead of printing "0 failing".
- Missing fields print "Not specified", never `undefined` or `null`.
- The date is a parameter, not read from the clock, so the same input always
produces byte-identical output. A document that changes when regenerated is
not reproducible evidence.
Every value interpolated into the HTML goes through `escapeHtml()`, including
titles, table cells and evidence, with no "trusted field" exception. The
markdown output decodes entities because markdown is not an HTML context; if
you inject that markdown into a page as HTML, escape it there.
`toReport()` and `formatControl()` give the same assessment as an
evidence-record report, so a control's status can be rolled up without
parsing HTML. `pass` passes, `fail` and `warn` fail, and an unrecognised
status is not assessed.
## Usage
```ts
import { renderControlDoc, formatControl, type ControlDocInput } from '@extant2000/cert-pack'
import { evidence } from '@extant2000/evidence-record'
const input: ControlDocInput = {
docNumber: 'SSP-AC-01',
title: 'Access Control',
family: 'AC',
familyName: 'Access Control',
gap: 'Tenant isolation is not documented',
fix: 'Document the row-level security policy',
date: '2026-09-28',
systemName: 'Example App',
checks: [
{ check: 'Row-level security on every tenant table', status: 'pass',
evidence: [evidence.command('psql -c "select relname, relrowsecurity from pg_class ..."', 0, '42 of 42 tables')] },
{ check: 'Least privilege enforced', status: 'pass' },
{ check: 'MFA for admins', status: 'skipped' },
],
}
const { html, markdown, sections } = renderControlDoc(input)
console.log(formatControl(input))
```
In the generated document, section 3 ends with "2 passing, 0 failing, 0
warnings, 1 unrecognised out of 3 checks" and "2 of 3 check(s) carry no
attached evidence". The two checks without evidence show NOT ATTACHED.
`formatControl()` prints:
```
✗ HIGH MFA for admins [NOT ASSESSED]
Status "skipped" is not recognised. An unrecognised status is not a passing one.
◦ info Row-level security on every tenant table [MET]
◦ info Least privilege enforced [MET]
```
`controlDocSections()`, `renderDocument()`, `table()`, `stripTags()`,
`escapeHtml()` and `tally()` are exported for building other documents.
## Install
```
npm install @extant2000/cert-pack
```
## License
MIT. See [LICENSE](LICENSE).
+6
View File
@@ -0,0 +1,6 @@
# Security
Please report vulnerabilities privately by email to security@extant2000.com.
Do not open a public issue for a security problem.
We aim to acknowledge every report within 5 business days.
+1504
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
{
"name": "@extant2000/cert-pack",
"version": "0.2.0",
"private": false,
"description": "Builds certification evidence packages from scan results.",
"license": "MIT",
"type": "module",
"main": "dist/index.js",
"files": [
"dist",
"src",
"README.md",
"LICENSE"
],
"repository": {
"type": "git",
"url": "https://gitlab.com/extant2000/cert-pack.git"
},
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsc",
"test": "vitest run",
"prepublishOnly": "npm run build"
},
"devDependencies": {
"typescript": "^5.6.0",
"vitest": "^2.1.0"
},
"dependencies": {
"@extant2000/evidence-record": "^0.1.2"
},
"author": "Extant 2000 LLC",
"homepage": "https://gitlab.com/extant2000/cert-pack",
"bugs": {
"url": "https://gitlab.com/extant2000/cert-pack/-/issues"
}
}
+229
View File
@@ -0,0 +1,229 @@
import { type Evidence, cite, citeVerbose } from '@extant2000/evidence-record'
import {
escapeHtml,
renderDocument,
table,
type DocSection,
type RenderedDocument,
} from './html.js'
/** Outcome of a single automated verification check against a control. */
export interface VerificationCheck {
check: string
/** 'pass' | 'fail' | 'warn' — anything else counts as unknown. */
status: string
/**
* What produced this result: the scan output, the file, the row, the
* command that was run.
*
* For an accreditation package this is the difference between a claim and
* evidence, and it is exactly what SustainScore's rubric penalises under
* "Unverified = absent". A check with no evidence still renders — it is
* labelled as unevidenced rather than dropped, because a missing check is
* worse than a weak one.
*/
evidence?: Evidence[]
}
export interface VerificationTally {
pass: number
fail: number
warn: number
/** Checks whose status is none of the above. Counted, never ignored. */
unknown: number
total: number
}
/**
* Tally verification outcomes.
*
* `unknown` is tracked explicitly rather than folded into `warn` or dropped: a
* status the tool does not recognise is not a passing status, and an
* accreditation package that quietly loses checks is worse than one that says
* it does not know.
*/
export function tally(checks: VerificationCheck[]): VerificationTally {
const t: VerificationTally = { pass: 0, fail: 0, warn: 0, unknown: 0, total: checks.length }
for (const c of checks) {
switch (c.status) {
case 'pass': t.pass++; break
case 'fail': t.fail++; break
case 'warn': t.warn++; break
default: t.unknown++
}
}
return t
}
export interface ControlDocInput {
/** Document identifier, e.g. "SSP-AC-01". */
docNumber: string
title: string
/** Control family short code, e.g. "AC". */
family?: string | null
/** Control family long name, e.g. "Access Control". */
familyName?: string | null
/** The gap this document exists to address. */
gap?: string | null
/** The remediation. */
fix?: string | null
checks?: VerificationCheck[]
/** ISO date. Injected rather than read from the clock, so output is testable. */
date: string
classification?: string
status?: string
version?: string
/** Deployment configurations in scope. */
deployments?: string[]
/** Reference list. Sensible NIST/FedRAMP defaults if omitted. */
references?: string[]
/** Sign-off roles. */
approvers?: string[]
/** Product/system name used in prose. */
systemName?: string
/** Days allowed for remediation, used in the timeline sentence. */
remediationDays?: number
}
const DEFAULT_REFERENCES = [
'NIST Special Publication 800-53 Rev. 5 — Security and Privacy Controls for Information Systems',
'NIST SP 800-53A — Assessing Security and Privacy Controls',
'FedRAMP System Security Plan (SSP) Template',
]
const DEFAULT_APPROVERS = [
'Document Author',
'Security Reviewer',
'System Owner',
'Authorizing Official',
]
/**
* Build the section list for a NIST 800-53 control document (SSP body with an
* embedded POA&M-style remediation plan).
*
* Pure: `date` is a parameter, not `new Date()`. A document generator that
* reads the clock cannot be tested for exact output, and an accreditation
* artifact that changes when regenerated is not reproducible evidence.
*/
export function controlDocSections(input: ControlDocInput): DocSection[] {
const family = input.family || 'Unknown'
const familyName = input.familyName || 'Unknown'
const gap = input.gap || 'Not specified'
const fix = input.fix || 'Not specified'
const checks = input.checks ?? []
const t = tally(checks)
const system = input.systemName || 'the system'
const days = input.remediationDays ?? 30
const deployments = input.deployments ?? []
const deploymentList = deployments.length
? `<p>This document applies to all ${escapeHtml(system)} deployment configurations including:</p><ul>${
deployments.map(d => `<li>${escapeHtml(d)}</li>`).join('')}</ul>`
: ''
const unevidenced = checks.filter(c => (c.evidence?.length ?? 0) === 0).length
const summary = t.unknown > 0
? `${t.pass} passing, ${t.fail} failing, ${t.warn} warnings, ${t.unknown} unrecognised out of ${t.total} checks.`
: `${t.pass} passing, ${t.fail} failing, ${t.warn} warnings out of ${t.total} checks.`
return [
{
title: '1. Document Information',
content: [
`<p><strong>Document Number:</strong> ${escapeHtml(input.docNumber)}</p>`,
`<p><strong>Title:</strong> ${escapeHtml(input.title)}</p>`,
`<p><strong>NIST 800-53 Control Family:</strong> ${escapeHtml(family)} — ${escapeHtml(familyName)}</p>`,
`<p><strong>Classification:</strong> ${escapeHtml(input.classification || 'Unclassified')}</p>`,
`<p><strong>Status:</strong> ${escapeHtml(input.status || 'Draft')}</p>`,
`<p><strong>Generated:</strong> ${escapeHtml(input.date)}</p>`,
`<p><strong>Version:</strong> ${escapeHtml(input.version || '1.0')}</p>`,
].join(''),
},
{
title: '2. Purpose and Scope',
content: [
`<p>This document addresses a compliance gap identified during the NIST 800-53 control assessment for ${escapeHtml(system)}. `,
`The ${escapeHtml(family)} (${escapeHtml(familyName)}) control family requires specific documentation to demonstrate implementation of security controls.</p>`,
`<h3>Gap Identified</h3><p>${escapeHtml(gap)}</p>`,
deploymentList ? `<h3>Scope</h3>${deploymentList}` : '',
].join(''),
},
{
title: '3. Current Implementation Status',
content: [
`<p>The following verification checks were evaluated for the ${escapeHtml(family)} control family:</p>`,
checks.length
? table(
['Check', 'Status', 'Evidence'],
checks.map(c => [
c.check || 'Unknown check',
(c.status || 'unknown').toUpperCase(),
// Unevidenced is stated, not left blank. A blank cell in an
// accreditation package reads as "nothing to report"; this
// reads as what it is.
(c.evidence?.length ?? 0) === 0
? 'NOT ATTACHED'
: c.evidence!.map(e => cite(e)).join('; '),
]),
)
: '<p><em>No automated verification checks have been run for this control family. '
+ 'An unassessed control is not an implemented control.</em></p>',
`<p><strong>Summary:</strong> ${escapeHtml(summary)}</p>`,
unevidenced > 0
? `<p><strong>${unevidenced} of ${t.total} check(s) carry no attached evidence.</strong> `
+ 'An asserted result is not a verified one; these are recorded as claims pending evidence.</p>'
: '',
].join(''),
},
{
title: '4. Remediation Plan',
content: [
`<h3>Required Action</h3><p>${escapeHtml(fix)}</p>`,
'<h3>Implementation Steps</h3><ol>',
'<li>Review the current implementation status above and identify all failing controls</li>',
'<li>Implement the required action described above</li>',
'<li>Verify the implementation by re-running the verification checks</li>',
'<li>Update this document with the verification results</li>',
'<li>Submit for security review and approval</li>',
'</ol>',
`<h3>Timeline</h3><p>This remediation should be completed prior to authorization submission. `,
`Target completion: within ${escapeHtml(days)} days of document generation.</p>`,
].join(''),
},
{
title: '5. Evidence',
content: checks.length
? checks.map(c => {
const head = `<h3>${escapeHtml(c.check || 'Unknown check')} — ${escapeHtml((c.status || 'unknown').toUpperCase())}</h3>`
if ((c.evidence?.length ?? 0) === 0) {
return head + '<p><em>No evidence attached. This result is a claim, not a verified finding.</em></p>'
}
return head + `<ul>${c.evidence!.map(e =>
`<li>${citeVerbose(e).map(l => escapeHtml(l.trim())).join('<br>')}</li>`).join('')}</ul>`
}).join('')
: '<p><em>No checks were run, so no evidence exists to attach.</em></p>',
},
{
title: '6. References',
content: `<ul>${(input.references ?? DEFAULT_REFERENCES).map(r => `<li>${escapeHtml(r)}</li>`).join('')}</ul>`,
},
{
title: '7. Approval and Sign-Off',
content: [
table(
['Role', 'Name', 'Signature', 'Date'],
(input.approvers ?? DEFAULT_APPROVERS).map(role => [role, '', '', '']),
),
`<p><em>This document was auto-generated on ${escapeHtml(input.date)}. `,
'It should be reviewed, completed, and approved by qualified security personnel before submission.</em></p>',
].join(''),
},
]
}
/** Render a control document to HTML + markdown. */
export function renderControlDoc(input: ControlDocInput): RenderedDocument {
return renderDocument(controlDocSections(input))
}
+88
View File
@@ -0,0 +1,88 @@
/**
* HTML/markdown primitives for compliance document rendering.
*
* These are separated out because escaping is the part that must be right
* everywhere and is easiest to forget in one template out of six. Any renderer
* that interpolates caller-controlled values into markup uses `escapeHtml`;
* there is no "this field is trusted" exception.
*/
/**
* Escape HTML entities in caller-controllable strings interpolated into
* document templates. Prevents stored-XSS when data reaches control metadata,
* finding descriptions, or evidence references.
*/
export function escapeHtml(input: unknown): string {
const str = input == null ? '' : String(input)
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
/**
* Strip tags and collapse whitespace to produce a markdown body.
*
* NOTE the entity decoding at the end: it deliberately UNDOES `escapeHtml`,
* because markdown is not an HTML context and a reader should see `<` rather
* than `&lt;`. That is only safe while the consumer re-escapes when it renders
* markdown to HTML. If you add a consumer that injects this string into the
* DOM directly, escape it there — the safety lives at the render boundary, not
* in this function.
*/
export function stripTags(html: string): string {
return html
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/(p|li|tr|div|h[1-6])>/gi, '\n')
.replace(/<li>/gi, '- ')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/\n{3,}/g, '\n\n')
}
export interface DocSection {
title: string
/** HTML fragment. Interpolated values MUST already be escaped. */
content: string
}
export interface RenderedDocument {
html: string
markdown: string
sections: DocSection[]
}
/**
* Render sections into both an HTML document and a markdown equivalent.
*
* Both are returned so a consumer can pick: a rich renderer takes `html`, a
* plain one takes `markdown`. Producing them from one section list is what
* keeps the two from drifting into different documents.
*/
export function renderDocument(sections: DocSection[]): RenderedDocument {
const html = sections
.map(s => `<section><h2>${escapeHtml(s.title)}</h2>${s.content}</section>`)
.join('\n')
const markdown = sections
.map(s => `## ${s.title}\n\n${stripTags(s.content).trim()}\n`)
.join('\n')
return { html, markdown, sections }
}
/** Build an escaped HTML table. Cells are escaped; headers are escaped. */
export function table(headers: string[], rows: unknown[][]): string {
const head = headers.map(h => `<th>${escapeHtml(h)}</th>`).join('')
const body = rows
.map(r => `<tr>${r.map(c => `<td>${escapeHtml(c)}</td>`).join('')}</tr>`)
.join('')
return `<table><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`
}
+3
View File
@@ -0,0 +1,3 @@
export * from './html.js'
export * from './control-doc.js'
export { toReport, formatControl } from './report.js'
+53
View File
@@ -0,0 +1,53 @@
import {
type CapabilityReport,
type Determination,
type FormatOptions,
formatReport as renderReport,
} from '@extant2000/evidence-record'
import type { ControlDocInput, VerificationCheck } from './control-doc.js'
function determinationFor(status: string): Determination {
switch (status) {
case 'pass': return 'pass'
case 'fail': return 'fail'
case 'warn': return 'fail'
// An unrecognised status is not a passing status. Folding it into `warn`
// or dropping it is how an accreditation package quietly loses a check.
default: return 'not-assessed'
}
}
/**
* Report the control assessment behind the document.
*
* The document is the deliverable; this is the machine-readable view of the
* same facts, so a control's status can be rolled up across a portfolio
* without parsing HTML.
*/
export function toReport(input: ControlDocInput): CapabilityReport {
const checks: VerificationCheck[] = input.checks ?? []
return {
capability: 'CertPack',
scope: `${input.docNumber} — ${input.family ?? 'Unknown'} control family`,
examined: checks.length,
findings: checks.map((c, i) => ({
id: `check-${i}`,
summary: c.check || 'Unknown check',
determination: determinationFor(c.status),
severity: c.status === 'fail' ? 'critical' : c.status === 'warn' ? 'high' : c.status === 'pass' ? 'info' : 'high',
detail: determinationFor(c.status) === 'not-assessed' && c.status !== 'pass' && c.status !== 'fail' && c.status !== 'warn'
? `Status "${c.status}" is not recognised. An unrecognised status is not a passing one.`
: undefined,
evidence: c.evidence ?? [],
})),
notes: checks.length === 0
? ['No automated verification checks have been run for this control family. An unassessed control is not an implemented control.']
: [],
}
}
/** Render the control assessment for a terminal or CI log. */
export function formatControl(input: ControlDocInput, opts: FormatOptions = {}): string {
return renderReport(toReport(input), opts)
}
+240
View File
@@ -0,0 +1,240 @@
import { describe, expect, it } from 'vitest'
import { evidence, overall, validateReport } from '@extant2000/evidence-record'
import {
controlDocSections,
toReport,
formatControl,
escapeHtml,
renderControlDoc,
renderDocument,
stripTags,
table,
tally,
} from '../src/index.js'
const base = { docNumber: 'SSP-AC-01', title: 'Access Control', date: '2026-07-28' }
describe('escapeHtml', () => {
it('escapes every dangerous character', () => {
expect(escapeHtml(`<script>"x"&'y'`))
.toBe('&lt;script&gt;&quot;x&quot;&amp;&#39;y&#39;')
})
it('escapes ampersands first so entities are not double-broken', () => {
expect(escapeHtml('&lt;')).toBe('&amp;lt;')
})
it('renders null and undefined as empty, not "null"', () => {
expect(escapeHtml(null)).toBe('')
expect(escapeHtml(undefined)).toBe('')
})
it('coerces non-strings', () => {
expect(escapeHtml(42)).toBe('42')
})
})
describe('table', () => {
it('escapes both headers and cells', () => {
const html = table(['<h>'], [['<script>alert(1)</script>']])
expect(html).toContain('&lt;h&gt;')
expect(html).toContain('&lt;script&gt;')
expect(html).not.toContain('<script>')
})
})
describe('renderDocument', () => {
it('escapes section titles', () => {
const { html } = renderDocument([{ title: '<img onerror=x>', content: '<p>ok</p>' }])
expect(html).toContain('&lt;img onerror=x&gt;')
expect(html).not.toContain('<img')
})
it('produces html and markdown from one section list', () => {
const r = renderDocument([{ title: 'One', content: '<p>Body text</p>' }])
expect(r.html).toContain('<section><h2>One</h2>')
expect(r.markdown).toContain('## One')
expect(r.markdown).toContain('Body text')
expect(r.sections).toHaveLength(1)
})
})
describe('stripTags', () => {
it('decodes entities — markdown is not an HTML context', () => {
// Documented and deliberate: it undoes escapeHtml so a reader sees `<`.
// Safe only because the consumer re-escapes at the render boundary.
expect(stripTags('<p>&lt;tag&gt;</p>').trim()).toBe('<tag>')
})
it('turns list items into markdown bullets', () => {
expect(stripTags('<ul><li>a</li><li>b</li></ul>')).toContain('- a')
})
it('collapses runs of blank lines', () => {
expect(stripTags('<p>a</p><p></p><p></p><p></p><p>b</p>')).not.toMatch(/\n{3,}/)
})
})
describe('tally', () => {
it('counts each status', () => {
expect(tally([
{ check: 'a', status: 'pass' },
{ check: 'b', status: 'fail' },
{ check: 'c', status: 'warn' },
])).toEqual({ pass: 1, fail: 1, warn: 1, unknown: 0, total: 3 })
})
it('counts an unrecognised status as unknown rather than dropping it', () => {
// A status the tool does not recognise is not a passing status, and an
// accreditation package must not quietly lose checks.
const t = tally([{ check: 'a', status: 'skipped' }, { check: 'b', status: 'pass' }])
expect(t.unknown).toBe(1)
expect(t.total).toBe(2)
expect(t.pass).toBe(1)
})
it('handles an empty set', () => {
expect(tally([])).toEqual({ pass: 0, fail: 0, warn: 0, unknown: 0, total: 0 })
})
})
describe('controlDocSections', () => {
it('emits the seven standard sections in order', () => {
const titles = controlDocSections(base).map(s => s.title)
expect(titles).toEqual([
'1. Document Information',
'2. Purpose and Scope',
'3. Current Implementation Status',
'4. Remediation Plan',
'5. Evidence',
'6. References',
'7. Approval and Sign-Off',
])
})
it('escapes hostile input in every field it interpolates', () => {
const { html } = renderControlDoc({
...base,
title: '<script>alert(1)</script>',
family: '<img src=x onerror=1>',
gap: '"><script>bad()</script>',
fix: `'; DROP TABLE--`,
checks: [{ check: '<b>evil</b>', status: '<i>pass</i>' }],
})
expect(html).not.toContain('<script>')
expect(html).not.toContain('<img src=x')
expect(html).not.toContain('<b>evil</b>')
expect(html).toContain('&lt;script&gt;')
})
it('does NOT report an unassessed control as clean', () => {
// With no checks, "0 failing" would read as a pass. Say so explicitly.
const s = controlDocSections({ ...base, checks: [] })
const status = s.find(x => x.title.startsWith('3.'))!
expect(status.content).toContain('An unassessed control is not an implemented control')
})
it('surfaces unrecognised statuses in the summary line', () => {
const s = controlDocSections({
...base,
checks: [{ check: 'a', status: 'weird' }, { check: 'b', status: 'pass' }],
})
expect(s.find(x => x.title.startsWith('3.'))!.content).toContain('1 unrecognised')
})
it('omits the unrecognised clause when there are none', () => {
const s = controlDocSections({ ...base, checks: [{ check: 'a', status: 'pass' }] })
expect(s.find(x => x.title.startsWith('3.'))!.content).not.toContain('unrecognised')
})
it('is reproducible — same input, byte-identical output', () => {
// The date is a parameter, not new Date(). An accreditation artifact that
// changes when regenerated is not reproducible evidence.
const a = renderControlDoc({ ...base, gap: 'g', fix: 'f' })
const b = renderControlDoc({ ...base, gap: 'g', fix: 'f' })
expect(a.html).toBe(b.html)
expect(a.markdown).toBe(b.markdown)
})
it('falls back to "Not specified" rather than printing undefined', () => {
const { markdown } = renderControlDoc(base)
expect(markdown).toContain('Not specified')
expect(markdown).not.toContain('undefined')
expect(markdown).not.toContain('null')
})
it('accepts custom references, approvers, deployments and timeline', () => {
const { html } = renderControlDoc({
...base,
references: ['ISO 27001'],
approvers: ['CISO'],
deployments: ['Air-gapped'],
remediationDays: 14,
systemName: 'Extant 2000',
})
expect(html).toContain('ISO 27001')
expect(html).toContain('CISO')
expect(html).toContain('Air-gapped')
expect(html).toContain('within 14 days')
expect(html).toContain('Extant 2000')
})
it('omits the scope list entirely when no deployments are given', () => {
expect(renderControlDoc(base).html).not.toContain('<h3>Scope</h3>')
})
})
describe('evidence attached, not asserted', () => {
const base = { docNumber: 'SSP-AC-01', title: 'Access Control', family: 'AC', date: '2026-08-01' }
it('renders the evidence behind each check, not just its status', () => {
// The gap this closes: CertPack rendered a document asserting results
// without attaching the scan output that produced them.
const doc = renderControlDoc({
...base,
checks: [{
check: 'RLS enabled on every tenant table',
status: 'pass',
evidence: [evidence.command('psql -c "select relrowsecurity..."', 0, '42 of 42 tables')],
}],
})
expect(doc.html).toContain('42 of 42 tables')
expect(doc.html).toContain('5. Evidence')
})
it('labels an unevidenced check as a claim rather than leaving it blank', () => {
// A blank cell in an accreditation package reads as "nothing to report".
const doc = renderControlDoc({
...base,
checks: [{ check: 'Least privilege enforced', status: 'pass' }],
})
expect(doc.html).toContain('NOT ATTACHED')
expect(doc.html).toContain('carry no attached evidence')
expect(doc.html).toContain('This result is a claim, not a verified finding')
})
it('says so when there are no checks at all', () => {
const doc = renderControlDoc(base)
expect(doc.html).toContain('An unassessed control is not an implemented control')
expect(doc.html).toContain('no evidence exists to attach')
})
it('an unrecognised status is not-assessed, never a pass', () => {
const r = toReport({ ...base, checks: [{ check: 'x', status: 'skipped' }] })
expect(r.findings[0]!.determination).toBe('not-assessed')
expect(overall(r)).toBe('not-assessed')
})
it('a zero-check control reports NOT ASSESSED', () => {
expect(overall(toReport(base))).toBe('not-assessed')
expect(formatControl(base)).toContain('NOT ASSESSED')
})
it('every conclusion carries a citation', () => {
const r = toReport({
...base,
checks: [{ check: 'RLS enabled', status: 'pass', evidence: [evidence.command('psql', 0)] }],
})
expect(validateReport(r)).toEqual([])
})
})
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "ES2022", "module": "ES2022", "moduleResolution": "bundler",
"declaration": true, "outDir": "dist", "rootDir": "src",
"strict": true, "skipLibCheck": true
},
"include": ["src/**/*.ts"]
}