A browser-based Plan of Action and Milestones (POA&M) builder for CMMC Level 2 / NIST SP 800-171 Rev 2. Build, validate, and export compliant POA&Ms — no account, no server, no data leaves your browser.
Live app: https://poam-builder.aptsecuritymanagement.com/
- Build structured POA&M entries tied to specific NIST SP 800-171 Rev 2 practice IDs
- Validate entries against 32 CFR 170.21(a)(2) conditional certification rules — excluded practices, high-point restrictions, 180-day closeout window
- Import gaps from the APT SPRS Calculator — all
not_metcontrols auto-populate as draft POA&M entries - Export to XLSX (two-sheet workbook), Word (DOCX), PDF (landscape dashboard), or JSON (state backup/transfer)
- Score your conditional certification eligibility: SPRS ≥ 88, no excluded practices, no high-point open items
- Persist state in
localStorage— work survives browser refreshes, nothing sent to a server
| Rule | Source | Implementation |
|---|---|---|
| Min SPRS score 88/110 (≥ 80%) for conditional cert | 32 CFR 170.21(a)(2)(i) | CONDITIONAL_CERT_MIN_SCORE = 88 |
| No 3-pt or 5-pt practices on conditional POA&M | 32 CFR 170.21(a)(2)(ii) | HIGH_POINT_PRACTICE warning |
| 6 practices absolutely excluded from POA&M | 32 CFR 170.21(a)(2)(iii) | EXCLUDED_PRACTICE error |
| All POA&M items closed within 180 days of Conditional Status | 32 CFR 170.21(b) | EXCEEDS_180_DAY_WINDOW warning |
| CA.L2-3.12.4 (SSP) is assessment prerequisite | 32 CFR 170.24(c)(2)(i)(B)(5) | Special error message |
Excluded from conditional cert POA&M (32 CFR 170.21(a)(2)(iii)):
| Practice | Title |
|---|---|
| AC.L2-3.1.20 | External Connections (CUI Data) |
| AC.L2-3.1.22 | Control Public Information (CUI Data) |
| CA.L2-3.12.4 | System Security Plan (also assessment prerequisite) |
| PE.L2-3.10.3 | Escort Visitors |
| PE.L2-3.10.4 | Physical Access Logs |
| PE.L2-3.10.5 | Manage Physical Access |
SC.L2-3.13.11 exception: May appear on a conditional cert POA&M only if encryption is employed but not yet FIPS-validated (3-point partial credit per 32 CFR 170.21(a)(2)(ii)). If no encryption is in use (5-point deduction), it cannot appear.
| Layer | Technology |
|---|---|
| Framework | React 19 + TypeScript 6 |
| Build | Vite 8 + Rolldown |
| Styles | Tailwind CSS 4 (@tailwindcss/vite) |
| State | Zustand 5 (persisted to localStorage) |
| XLSX export | ExcelJS 4 |
| DOCX export | docx 9 |
| PDF export | jsPDF 4 + jspdf-autotable 5 |
| Deploy | Cloudflare Pages (@cloudflare/vite-plugin) |
| Tests | Vitest 3 (unit) + Playwright (E2E) |
| License | FSL-1.1-Apache-2.0 |
- Node.js ≥ 22.13.0
- npm ≥ 10
npm install
npm run devApp runs at http://localhost:5173.
npm run build # TypeScript check + Vite production build → dist/
npm run preview # Build + wrangler dev (Cloudflare local preview)npm test # Vitest unit tests (src/lib/)
npm run test:watch # Watch mode
npm run test:e2e # Playwright E2E (requires npm run dev running)
npm run test:e2e:ui # Playwright with interactive UInpm run typecheck # tsc --noEmit across all tsconfig references
npm run lint # ESLintnpm run deploy # Build + wrangler deploy → Cloudflare Pagesapt-poam-builder/
├── src/
│ ├── App.tsx # Root component, drag-and-drop JSON restore
│ ├── main.tsx # React 19 StrictMode entry
│ ├── index.css # APT CSS variables + Tailwind base
│ ├── types.ts # All domain types (PoamEntry, PoamMeta, etc.)
│ ├── store.ts # Zustand store with localStorage persistence
│ ├── data/
│ │ └── nist-800-171-rev2.json # All 110 NIST SP 800-171 Rev 2 controls
│ ├── lib/
│ │ ├── validation.ts # 32 CFR 170.21 regulatory rules + helpers
│ │ ├── utils.ts # Date, slug, ID generation, class names
│ │ ├── lead-capture.ts # Zoho CRM embed helper
│ │ └── export/
│ │ ├── exportXlsx.ts # ExcelJS two-sheet workbook
│ │ ├── exportDocx.ts # Word document (cover + POA&M table)
│ │ ├── exportPdf.ts # jsPDF landscape dashboard
│ │ └── exportJson.ts # JSON state backup/restore
│ └── components/
│ ├── apt/ # APT brand components (Header, Footer, LeadCapture)
│ └── app/ # POA&M app components
│ ├── MetaForm.tsx # Organization metadata fields
│ ├── StatsPanel.tsx # Status/severity cards + eligibility badge
│ ├── Toolbar.tsx # Add entry, import SPRS, export buttons
│ ├── EntryTable.tsx # Sortable/filterable entry table
│ ├── EntryModal.tsx # Add/edit modal with practice multi-select
│ └── ImportSprsModal.tsx # SPRS Calculator JSON import
├── tests/
│ ├── unit/
│ │ ├── validation.test.ts # 32 CFR 170.21 rule tests (46 tests)
│ │ └── utils.test.ts # Utility function tests (26 tests)
│ └── e2e/ # Playwright E2E tests
├── public/
│ └── favicon.png
├── vite.config.ts
├── vitest.config.ts
├── playwright.config.ts
├── wrangler.jsonc
└── .env.example
This app consumes JSON exports from the APT SPRS Calculator.
Expected export shape:
{
"statuses": { "AC.L2-3.1.1": "not_met", "AC.L2-3.1.2": "met", ... },
"notes": { "AC.L2-3.1.1": "Optional note", ... },
"meta": {
"companyName": "Acme Corp",
"assessorName": "Jane Doe",
"assessmentDate": "2026-05-27"
},
"exportedAt": "2026-05-27T12:00:00.000Z"
}The importer creates one POA&M entry per not_met control, deduplicates against existing entries by practice ID, and auto-fills severity from point weight (5pt → Critical, 3pt → High, 1pt → Moderate).
Drag-and-drop a .json state file onto the page to restore a previous session.
{
"schemaVersion": "1.0",
"exportedAt": "2026-05-27T12:00:00.000Z",
"meta": { "companyName": "...", "version": "1.0", ... },
"entries": [ { "id": "POAM-001", "practiceIds": ["AC.L2-3.1.1"], ... } ]
}This POA&M builder is a planning aid, not a finished compliance document. Validation warnings are based on 32 CFR 170.21 and reflect publicly available regulatory guidance as of May 2026. Conditional CMMC Level 2 certification requirements are determined by your C3PAO or self-assessment results — this tool is not a substitute for formal assessment. Consult your contracting officer or CMMC consultant before submitting any POA&M to the DoD.
Functional Source License 1.1 with Apache 2.0 Future License
