AI-driven pipeline: upload SSP content → AI generates a 20-tab SCTM, an eMASS-ready export, POA&M items, and a 20-family migration tracker. Progress accumulates incrementally and persists across sessions.
This package is provider-agnostic by design. The frontend never
talks to an LLM directly — it calls your own local backend, which
talks to whichever model API you configure. Swapping providers (e.g.
to an Anthropic-compatible endpoint or AWS Bedrock) is a .env
change, not a code change.
┌──────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ Browser UI │─────▶│ Local backend │─────▶│ Your LLM provider │
│ (React) │ │ (Node/Express) │ │ (configured in .env)│
│ │◀─────│ + storage.json │◀─────│ │
└──────────────┘ └──────────────────┘ └─────────────────────┘
Requirements: Node.js 18+ (or Docker)
cd nist-pipeline-app
cp server/.env.example server/.env
# edit server/.env -> set LLM_API_KEY, LLM_BASE_URL, LLM_MODEL for your provider
docker compose up --buildcd nist-pipeline-app
# 1. Backend
cd server
cp .env.example .env
# edit .env -> set LLM_API_KEY, LLM_BASE_URL, LLM_MODEL for your provider
npm install
npm start
# Backend now running on http://localhost:3001
# 2. Frontend (separate terminal)
cd ../client
npm install
npm run dev
# Open http://localhost:5173 (Vite dev server, proxies /api to :3001)For a single deployable build (no separate dev server):
cd client && npm install && npm run build
cd ../server && npm install && npm start
# Open http://localhost:3001 -- backend now serves the built frontend tooEverything is controlled by server/.env. No code changes needed.
LLM_PROVIDER=anthropic
LLM_BASE_URL=https://api.anthropic.com
LLM_MODEL=claude-sonnet-4-20250514
LLM_API_KEY=sk-ant-your-keyLLM_PROVIDER=openai
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=gpt-4o
LLM_API_KEY=sk-your-keyBedrock's native API shape differs slightly from both adapters above. Two ways to use it:
- Recommended: put a small Bedrock-compatible proxy in front
(many teams already run one) that exposes either an OpenAI-style
/chat/completionsor Anthropic-style/v1/messagesendpoint — then pointLLM_BASE_URLat that proxy and pick the matchingLLM_PROVIDER. - Direct integration: add a
callBedrock()function toserver/llm.jsusing@aws-sdk/client-bedrock-runtimewith theInvokeModelAPI for an Anthropic Claude model on Bedrock. AWS credentials would come from the instance role / IAM, notLLM_API_KEY. This is a small, contained change — onlyserver/llm.jsneeds to be touched.
If your endpoint uses a different request/response shape entirely,
add a new case in server/llm.js following the pattern of the
existing two adapters — each is ~20 lines. This is the only file
that ever needs to change to support a new provider.
To keep this simple across teams, output formats are standardized rather than user-configurable:
- SCTM — always a 20-tab workbook (one tab per NIST control
family), fixed 10-column header row including a Guidance column
populated with the official NIST SP 800-53 Rev 5 Discussion text
for each control (sourced from
server/data/nist-catalog.json, not AI-generated). - eMASS export — one row per base control (e.g.
AC-2), with any documented enhancements (AC-2(1),AC-2(2), ...) rolled up into theImplementation_Narrative, hard-capped at 2,000 characters per the eMASS field limit. Includes aCCIcolumn populated fromserver/data/cci-mapping.json.
server/data/cci-mapping.json ships with a small starter mapping
(placeholder-format CCI numbers for the demo control families) so the
CCI column has something to show out of the box. For real use:
- Get
U_CCI_List.xmlfrompublic.cyber.mil-> STIGs -> CCI (CAC login required) - Run:
python3 server/scripts/parse_cci_xml.py /path/to/U_CCI_List.xml > server/data/cci-mapping.json - Restart the server — no code changes needed.
Every per-control AI call includes the official NIST control text and
discussion from nist-catalog.json as context, so the AI evaluates
your SSP statement against the actual control requirements (e.g.,
all sub-parts a through l of AC-2) rather than a paraphrase from
training data. Expect gap analysis to be stricter and more
accurate than a model working from memory alone.
server/data/storage.json — one entry per documented control
(ctrl:AC-2, ctrl:AC-3(1), etc.), holding the AI-generated SCTM
status, implementation narrative, gap analysis, NIST guidance, CCIs,
and eMASS fields.
- Survives restarts (mounted as a Docker volume in
docker-compose.yml) - Back it up like any file — copy it, version it in git (it contains no credentials, only control data), or move it between environments
- The dashboard's "Reset all progress" button clears this file via the API — useful for starting fresh on a new system
- For team/shared deployments, replace
server/storage.jswith a real database (Postgres, DynamoDB, etc.) — it's a 4-function module (get,set,del,listKeys) so the swap is contained
The Docker image is environment-agnostic. Typical paths:
- ECS Fargate / EC2: push the image to ECR, run as a task/service,
mount an EFS volume at
/app/server/datafor persistence, setLLM_*env vars via task definition or Secrets Manager - App Runner: point at the ECR image, set env vars in the console
- EC2 + Docker Compose: simplest —
docker compose up -don an instance, EBS volume for./server/data
None of this requires internet egress beyond whatever URL you put in
LLM_BASE_URL — if that's an internal VPC endpoint, the container
never needs to leave your network boundary.
nist-pipeline-app/
├── server/
│ ├── index.js # Express API: /api/chat, /api/storage/*, /api/catalog/:id
│ ├── llm.js # Provider adapters (THE file to extend for new providers)
│ ├── storage.js # File-based KV store (swap for a DB if needed)
│ ├── .env.example # Copy to .env and configure (see Quick Start)
│ ├── data/
│ │ ├── storage.json # Documented controls — gitignored, persists progress
│ │ ├── nist-catalog.json # Official SP 800-53 Rev 5 control catalog (1,189 entries)
│ │ └── cci-mapping.json # Control -> CCI mapping (starter/demo data)
│ └── scripts/
│ └── parse_cci_xml.py # Converts official DISA U_CCI_List.xml -> cci-mapping.json
├── client/
│ └── src/App.jsx # The full pipeline UI
├── Dockerfile
├── docker-compose.yml
└── README.md
This package is just code — it makes no assumptions about classification level. What determines whether it's appropriate for a given network is:
- Whatever LLM endpoint you point
LLM_BASE_URLat, and whether that endpoint is approved for that environment's data - Standard ATO/accreditation review for any new tool running on that network, regardless of what it does
Nothing in this codebase calls out to the public internet except the single configurable LLM endpoint — there's no telemetry, analytics, or external dependencies fetched at runtime once built.