Skip to main content
Glama

@mactech/stig-tools

DISA STIG catalog, STIG Viewer .ckl writer, CLI and MCP server. 2,029 rules across 15 benchmarks, compiled in; no network needed.

npx @mactech/stig-tools ckl --benchmark windows2022 --host srv-01 --severity high > srv-01.ckl

That file opens in STIG Viewer, imports into eMASS, and is what a POA&M gets written from. Every rule you did not assess comes out Not_Reviewed: an unopened checklist asserts nothing, and defaulting to NotAFinding would manufacture compliance nobody verified.

The .ckl format is STIG Viewer's own XML schema, rigid and undocumented outside the tool. STIG Viewer refuses files whose STIG_DATA children are out of order, so the element order in src/ckl.ts is load-bearing and tested.

Install

npm install @mactech/stig-tools     # library + CLI
npx @mactech/stig-tools --help      # CLI without installing

Node 20 or newer. The MCP server additionally needs @modelcontextprotocol/server (optional peer dependency).

Related MCP server: NIST MCP Server

CLI

stig benchmarks                                   shipped benchmarks and counts
stig search <text> [--benchmark rhel9] [--severity high] [--limit 20] [--json]
stig rule <SV-id>                                 full rule: discussion, check, fix
stig ckl --benchmark <id> --host <name> [--ip] [--fqdn] [--severity] [--status open|not_a_finding|not_applicable]

Product ids: rhel8, rhel9, ubuntu2204, windows11, windows2022, cisco_ios_router_ndm, cisco_ios_router_rtr, cisco_ios_switch_l2s, cisco_ios_switch_ndm, cisco_ios_switch_rtr, cisco_ise_nac, cisco_ise_ndm, cisco_nxos_switch_l2s, cisco_nxos_switch_ndm, cisco_nxos_switch_rtr.

Library

import { STIG_CATALOG, PRODUCTS, searchStig, findRule, severityToCat } from '@mactech/stig-tools/catalog'
import { buildCkl, summarizeCkl } from '@mactech/stig-tools/ckl'

const page = searchStig('ssh banner', { product: 'rhel9', severity: 'high', limit: 20 })
// { total_matches, offset, returned, next_offset?, rules: StigRule[] }

const rules = STIG_CATALOG.filter((r) => r.product === 'rhel9')
const ckl = buildCkl({
  rules,
  benchmark: rules[0].benchmark,
  asset: { host_name: 'web-01', host_fqdn: 'web-01.example.mil' },
  findings: [{ sv_id: 'SV-257777r991589_rule', status: 'Open', finding_details: 'Banner absent' }],
  exporter: 'Your Tool Name', // recorded in STIG_INFO; defaults to this package's name
})
summarizeCkl(rules, findings) // { Open, NotAFinding, Not_Applicable, Not_Reviewed }

StigRule fields: sv_id, nist_id (the CCI, e.g. CCI-000366), severity (high/medium/low = CAT I/II/III), title, description, check_text, fix_text, product, benchmark, category, automation_level, automation_source.

MCP server

Mount the tools on any MCP host:

import { McpServer } from '@modelcontextprotocol/server'
import { registerStigServer, STIG_INSTRUCTIONS } from '@mactech/stig-tools/mcp'

const server = new McpServer({ name: 'stig', version: '1.0.0' }, { instructions: STIG_INSTRUCTIONS })
registerStigServer(server, {
  attributionSuffix: 'Served by Example Corp.',   // optional, appended to every text result
  instrument: (tool, handler) => handler,          // optional telemetry / rate-limit wrapper
  exporter: 'Example Corp',                        // optional, recorded in exported .ckl files
})
// connect your transport (stdio, Streamable HTTP, ...)

Tools: search_stig, get_stig_rule, list_stig_benchmarks, export_stig_checklist. Resource: stig://rule/{sv_id} with completion. Prompt: harden_host. The package adds no attribution or telemetry of its own; hosts add theirs through the options.

A hosted instance runs at https://www.mactechsolutionsllc.com/api/mcp/stig.

Data provenance

data/*.json is parsed from DISA's official *_Manual-xccdf.xml benchmarks, which are works of the United States Government and not subject to copyright (17 U.S.C. § 105). The benchmark version is in each filename (rhel9_v2r6_controls.json = RHEL 9 STIG V2R6). The nist_id field is DISA's CCI identifier, selected by value from the rule's <ident> elements rather than by position, because some Cisco benchmarks list a legacy V-key first. category is a coarse grouping inferred from titles; it is remediation metadata, not part of the STIG.

Refreshing a benchmark

  1. Download the STIG ZIP from https://public.cyber.mil/stigs/downloads/ and unzip it locally. Do not commit the ZIP or the XCCDF.

  2. Convert:

    node scripts/ingest-stig-xccdf.mjs path/to/U_RHEL_9_V2R7_Manual-xccdf.xml rhel9 data/rhel9_v2r7_controls.json --preserve-categories
  3. Point the rhel9 row in src/catalog.ts BENCHMARKS at the new file and delete the old one.

  4. npm test (update the rule count assertion in test/catalog.test.ts), note the version bump in CHANGELOG.md.

--preserve-categories carries the previous file's category values across by sv_id; without it categories are re-derived from titles.

What this is not

  • Not a scanner. It does not evaluate a host; it knows what the STIG says and writes the checklist you fill in.

  • Not SCAP or OVAL evaluation. automation_level is a hint from the check text, not a promise that a tool exists.

  • Not the whole STIG library. DISA publishes roughly a thousand benchmarks; this ships fifteen. The ingester makes adding one a download and a command.

  • Not a compliance determination. A .ckl records what an assessor observed; it does not decide anything.

License

MIT for the code. The DISA content in data/ is public domain. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides comprehensive access to NIST cybersecurity frameworks and controls, enabling AI assistants and applications to query, analyze, and manage NIST security controls through a standardized interface.
    10
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    Provides search, detail lookup, and gap listing tools for a security control inventory, enabling natural language queries about control status and gaps.
    -