drydock-cli 3.0.78__tar.gz → 3.0.79__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {drydock_cli-3.0.78/drydock_cli.egg-info → drydock_cli-3.0.79}/PKG-INFO +6 -2
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/README.md +5 -1
- drydock_cli-3.0.79/drydock/poam.py +73 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/app.py +30 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79/drydock_cli.egg-info}/PKG-INFO +6 -2
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/SOURCES.txt +2 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/pyproject.toml +1 -1
- drydock_cli-3.0.79/tests/test_poam.py +50 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/LICENSE +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/NOTICE +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/agent.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/cci.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/cli.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/config.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/detect.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/extract.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/guards.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/providers.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/rmf.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/skills.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/stig.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/web.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/setup.cfg +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_cci.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_config.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_xccdf.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drydock-cli
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.79
|
|
4
4
|
Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
|
|
5
5
|
Author: Frank Bobe III
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -167,13 +167,17 @@ compatible checklist — entirely local (hostnames, IPs, and findings are CUI):
|
|
|
167
167
|
/stig app.ckl open # list the open findings
|
|
168
168
|
/stig-remediate app.ckl SV-900010r1_rule # write an idempotent fix script
|
|
169
169
|
/stig graph app.ckl # ingest + auto-link rules → NIST controls
|
|
170
|
+
/stig poam app.ckl # eMASS POA&M CSV of the open findings
|
|
170
171
|
```
|
|
171
172
|
|
|
172
173
|
`/stig new` parses the XCCDF benchmark (validated against the full 286-rule
|
|
173
174
|
Application STIG); `/stig-assess` reads your evidence and writes status +
|
|
174
175
|
finding-details back in place; `/stig graph` builds `STIG`/`STIG-Rule` nodes and
|
|
175
176
|
**auto-links each rule to its NIST 800-53 control** through DISA's CCI map
|
|
176
|
-
(`Control —SATISFIED_BY→ rule`), fetched once and cached offline.
|
|
177
|
+
(`Control —SATISFIED_BY→ rule`), fetched once and cached offline. `/stig poam`
|
|
178
|
+
exports the open findings to a deterministic **eMASS POA&M CSV** — Control (from
|
|
179
|
+
the CCI map), Vulnerability Description, `POA&M Status=Ongoing`, Milestone (the
|
|
180
|
+
Fix Text), and Severity (CAT I/II/III → High/Moderate/Low) — no LLM, stdlib only.
|
|
177
181
|
|
|
178
182
|
## Install
|
|
179
183
|
|
|
@@ -143,13 +143,17 @@ compatible checklist — entirely local (hostnames, IPs, and findings are CUI):
|
|
|
143
143
|
/stig app.ckl open # list the open findings
|
|
144
144
|
/stig-remediate app.ckl SV-900010r1_rule # write an idempotent fix script
|
|
145
145
|
/stig graph app.ckl # ingest + auto-link rules → NIST controls
|
|
146
|
+
/stig poam app.ckl # eMASS POA&M CSV of the open findings
|
|
146
147
|
```
|
|
147
148
|
|
|
148
149
|
`/stig new` parses the XCCDF benchmark (validated against the full 286-rule
|
|
149
150
|
Application STIG); `/stig-assess` reads your evidence and writes status +
|
|
150
151
|
finding-details back in place; `/stig graph` builds `STIG`/`STIG-Rule` nodes and
|
|
151
152
|
**auto-links each rule to its NIST 800-53 control** through DISA's CCI map
|
|
152
|
-
(`Control —SATISFIED_BY→ rule`), fetched once and cached offline.
|
|
153
|
+
(`Control —SATISFIED_BY→ rule`), fetched once and cached offline. `/stig poam`
|
|
154
|
+
exports the open findings to a deterministic **eMASS POA&M CSV** — Control (from
|
|
155
|
+
the CCI map), Vulnerability Description, `POA&M Status=Ongoing`, Milestone (the
|
|
156
|
+
Fix Text), and Severity (CAT I/II/III → High/Moderate/Low) — no LLM, stdlib only.
|
|
153
157
|
|
|
154
158
|
## Install
|
|
155
159
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""eMASS-compatible POA&M (Plan of Action & Milestones) export from STIG findings.
|
|
2
|
+
|
|
3
|
+
Deterministic and stdlib-only: takes the OPEN findings in a parsed checklist,
|
|
4
|
+
maps each to its NIST SP 800-53 control (via the DISA CCI map in cci.py) and its
|
|
5
|
+
eMASS severity (STIG CAT I/II/III), and writes the standard eMASS POA&M CSV. No
|
|
6
|
+
LLM in this path — it's a faithful transform of facts already in the checklist +
|
|
7
|
+
the CCI map, so it's reproducible and ungameable. Complements the `/rmf-poam`
|
|
8
|
+
skill (which writes a narrative entry); this is the bulk machine-importable CSV.
|
|
9
|
+
|
|
10
|
+
All logic original to Drydock.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import csv
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# STIG severity (CAT I/II/III) → eMASS Severity + the raw CAT label.
|
|
18
|
+
_EMASS_SEVERITY = {"high": "High", "medium": "Moderate", "low": "Low"}
|
|
19
|
+
_CAT = {"high": "CAT I", "medium": "CAT II", "low": "CAT III"}
|
|
20
|
+
|
|
21
|
+
# eMASS POA&M columns (the prompt's required fields + the standard companions
|
|
22
|
+
# eMASS expects so the CSV imports cleanly).
|
|
23
|
+
EMASS_HEADERS = [
|
|
24
|
+
"Control",
|
|
25
|
+
"Vulnerability Description",
|
|
26
|
+
"Source Identifying Vulnerability",
|
|
27
|
+
"POA&M Status",
|
|
28
|
+
"Milestone Description",
|
|
29
|
+
"Severity",
|
|
30
|
+
"Raw Severity",
|
|
31
|
+
"Comments",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def poam_rows(checklist, cci_map: dict | None = None) -> list[dict]:
|
|
36
|
+
"""One eMASS POA&M row per OPEN finding. `cci_map` (CCI→control) populates the
|
|
37
|
+
Control column; missing mappings are marked '(unmapped CCI)' rather than
|
|
38
|
+
dropped, so nothing is silently lost."""
|
|
39
|
+
cci_map = cci_map or {}
|
|
40
|
+
rows: list[dict] = []
|
|
41
|
+
for r in checklist.rules:
|
|
42
|
+
if r.status != "open":
|
|
43
|
+
continue
|
|
44
|
+
ctl = cci_map.get(r.cci, "")
|
|
45
|
+
control = ctl.upper() if ctl else (f"(unmapped {r.cci})" if r.cci else "(no CCI)")
|
|
46
|
+
rows.append({
|
|
47
|
+
"Control": control,
|
|
48
|
+
"Vulnerability Description": f"{r.group_id} ({r.rule_id}): {r.title}".strip(),
|
|
49
|
+
"Source Identifying Vulnerability": r.rule_id or r.group_id,
|
|
50
|
+
"POA&M Status": "Ongoing",
|
|
51
|
+
"Milestone Description": (r.fix_text or "").strip() or "See STIG Fix Text.",
|
|
52
|
+
"Severity": _EMASS_SEVERITY.get(r.severity, "Moderate"),
|
|
53
|
+
"Raw Severity": _CAT.get(r.severity, "CAT II"),
|
|
54
|
+
"Comments": (r.finding_details or "").strip(),
|
|
55
|
+
})
|
|
56
|
+
return rows
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def write_csv(rows: list[dict], path: str | Path) -> int:
|
|
60
|
+
"""Write POA&M rows to an eMASS-headered CSV. Returns the row count."""
|
|
61
|
+
with open(path, "w", newline="", encoding="utf-8") as f:
|
|
62
|
+
w = csv.DictWriter(f, fieldnames=EMASS_HEADERS)
|
|
63
|
+
w.writeheader()
|
|
64
|
+
for row in rows:
|
|
65
|
+
w.writerow({h: row.get(h, "") for h in EMASS_HEADERS})
|
|
66
|
+
return len(rows)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def export(checklist, cci_map: dict | None, out_path: str | Path) -> dict:
|
|
70
|
+
"""Build + write the POA&M CSV for a checklist's open findings."""
|
|
71
|
+
rows = poam_rows(checklist, cci_map)
|
|
72
|
+
write_csv(rows, out_path)
|
|
73
|
+
return {"rows": len(rows), "path": str(out_path)}
|
|
@@ -547,6 +547,7 @@ class DrydockApp(App):
|
|
|
547
547
|
" /stig new <benchmark-xccdf.xml> [out.ckl] — blank .ckl from a STIG\n"
|
|
548
548
|
" /stig <path.ckl|.cklb> [status] — summary / list by status\n"
|
|
549
549
|
" /stig graph <path.ckl> — ingest into the RMF graph\n"
|
|
550
|
+
" /stig poam <path.ckl> [out.csv] — eMASS POA&M CSV (open findings)\n"
|
|
550
551
|
"Assess: /loop <n> /stig-assess <path>")
|
|
551
552
|
return
|
|
552
553
|
import os as _os
|
|
@@ -569,6 +570,15 @@ class DrydockApp(App):
|
|
|
569
570
|
f" /graphrag build <app-docs> · /loop {len(cl.rules)} /stig-assess {out}"
|
|
570
571
|
)
|
|
571
572
|
return
|
|
573
|
+
# /stig poam <path> [out.csv] — export open findings to an eMASS POA&M CSV
|
|
574
|
+
if parts[0].lower() == "poam" and len(parts) > 1:
|
|
575
|
+
cp = _abs(parts[1])
|
|
576
|
+
out = _abs(parts[2]) if len(parts) > 2 else \
|
|
577
|
+
_os.path.splitext(cp)[0] + "_poam.csv"
|
|
578
|
+
self._info("Building the eMASS POA&M CSV (mapping open findings to NIST "
|
|
579
|
+
"controls via the CCI map)…")
|
|
580
|
+
self.run_worker(lambda: self._stig_poam(cwd, cp, out), thread=True)
|
|
581
|
+
return
|
|
572
582
|
# /stig graph <path> — ingest the checklist into the RMF typed graph
|
|
573
583
|
if parts[0].lower() == "graph" and len(parts) > 1:
|
|
574
584
|
gp = parts[1] if _os.path.isabs(parts[1]) else _os.path.join(cwd, parts[1])
|
|
@@ -640,6 +650,26 @@ class DrydockApp(App):
|
|
|
640
650
|
"catalog download; after that it works offline.)")
|
|
641
651
|
self.call_from_thread(self._info, msg)
|
|
642
652
|
|
|
653
|
+
def _stig_poam(self, cwd: str, path: str, out: str) -> None:
|
|
654
|
+
"""Worker: export a checklist's open findings to an eMASS POA&M CSV,
|
|
655
|
+
pulling each finding's NIST control from the CCI map. Deterministic."""
|
|
656
|
+
from drydock import cci, poam, stig
|
|
657
|
+
|
|
658
|
+
try:
|
|
659
|
+
cl = stig.load(path)
|
|
660
|
+
cci_map = cci.load_map(cwd) # offline-safe ({} → Controls show '(unmapped)')
|
|
661
|
+
r = poam.export(cl, cci_map, out)
|
|
662
|
+
except Exception as e: # noqa: BLE001
|
|
663
|
+
self.call_from_thread(self._mount, ErrorMessage(f"could not build POA&M: {e}"))
|
|
664
|
+
return
|
|
665
|
+
note = "" if cci_map else " (CCI map unavailable offline — Control column shows '(unmapped)'; re-run online to populate it)"
|
|
666
|
+
self.call_from_thread(
|
|
667
|
+
self._info,
|
|
668
|
+
f"✓ Wrote {r['rows']} POA&M row(s) for the open findings to {r['path']} "
|
|
669
|
+
f"(eMASS headers: Control · Vulnerability Description · POA&M Status=Ongoing · "
|
|
670
|
+
f"Milestone · Severity).{note}"
|
|
671
|
+
)
|
|
672
|
+
|
|
643
673
|
def _stig_graph(self, cwd: str, path: str) -> None:
|
|
644
674
|
"""Worker-thread body: ingest a checklist into the RMF graph, auto-linking
|
|
645
675
|
rules to NIST controls via the DISA CCI map. Reports back on the UI."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drydock-cli
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.79
|
|
4
4
|
Summary: Drydock — a local, provider-agnostic terminal coding agent for local LLMs
|
|
5
5
|
Author: Frank Bobe III
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -167,13 +167,17 @@ compatible checklist — entirely local (hostnames, IPs, and findings are CUI):
|
|
|
167
167
|
/stig app.ckl open # list the open findings
|
|
168
168
|
/stig-remediate app.ckl SV-900010r1_rule # write an idempotent fix script
|
|
169
169
|
/stig graph app.ckl # ingest + auto-link rules → NIST controls
|
|
170
|
+
/stig poam app.ckl # eMASS POA&M CSV of the open findings
|
|
170
171
|
```
|
|
171
172
|
|
|
172
173
|
`/stig new` parses the XCCDF benchmark (validated against the full 286-rule
|
|
173
174
|
Application STIG); `/stig-assess` reads your evidence and writes status +
|
|
174
175
|
finding-details back in place; `/stig graph` builds `STIG`/`STIG-Rule` nodes and
|
|
175
176
|
**auto-links each rule to its NIST 800-53 control** through DISA's CCI map
|
|
176
|
-
(`Control —SATISFIED_BY→ rule`), fetched once and cached offline.
|
|
177
|
+
(`Control —SATISFIED_BY→ rule`), fetched once and cached offline. `/stig poam`
|
|
178
|
+
exports the open findings to a deterministic **eMASS POA&M CSV** — Control (from
|
|
179
|
+
the CCI map), Vulnerability Description, `POA&M Status=Ongoing`, Milestone (the
|
|
180
|
+
Fix Text), and Severity (CAT I/II/III → High/Moderate/Low) — no LLM, stdlib only.
|
|
177
181
|
|
|
178
182
|
## Install
|
|
179
183
|
|
|
@@ -17,6 +17,7 @@ drydock/graphrag.py
|
|
|
17
17
|
drydock/guards.py
|
|
18
18
|
drydock/loop_detect.py
|
|
19
19
|
drydock/mcp.py
|
|
20
|
+
drydock/poam.py
|
|
20
21
|
drydock/providers.py
|
|
21
22
|
drydock/rmf.py
|
|
22
23
|
drydock/rmf_graph.py
|
|
@@ -73,6 +74,7 @@ tests/test_loop_detect.py
|
|
|
73
74
|
tests/test_mcp.py
|
|
74
75
|
tests/test_oneshot_unreachable.py
|
|
75
76
|
tests/test_plan_autocontinue.py
|
|
77
|
+
tests/test_poam.py
|
|
76
78
|
tests/test_providers_unreachable.py
|
|
77
79
|
tests/test_read_index.py
|
|
78
80
|
tests/test_rmf.py
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
# PyPI distribution name is drydock-cli (the established install name, continued
|
|
8
8
|
# from the retired v2 fork); the import package + CLI command stay `drydock`.
|
|
9
9
|
name = "drydock-cli"
|
|
10
|
-
version = "3.0.
|
|
10
|
+
version = "3.0.79"
|
|
11
11
|
description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""eMASS POA&M CSV export from open STIG findings (deterministic)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import csv
|
|
5
|
+
|
|
6
|
+
from drydock import poam, stig
|
|
7
|
+
|
|
8
|
+
_CKL = ('<?xml version="1.0"?><CHECKLIST><ASSET><HOST_NAME>h1</HOST_NAME></ASSET><STIGS><iSTIG><STIG_INFO></STIG_INFO>'
|
|
9
|
+
'<VULN><STIG_DATA><VULN_ATTRIBUTE>Vuln_Num</VULN_ATTRIBUTE><ATTRIBUTE_DATA>V-1</ATTRIBUTE_DATA></STIG_DATA>'
|
|
10
|
+
'<STIG_DATA><VULN_ATTRIBUTE>Rule_ID</VULN_ATTRIBUTE><ATTRIBUTE_DATA>SV-1</ATTRIBUTE_DATA></STIG_DATA>'
|
|
11
|
+
'<STIG_DATA><VULN_ATTRIBUTE>Rule_Title</VULN_ATTRIBUTE><ATTRIBUTE_DATA>Disable debug</ATTRIBUTE_DATA></STIG_DATA>'
|
|
12
|
+
'<STIG_DATA><VULN_ATTRIBUTE>Severity</VULN_ATTRIBUTE><ATTRIBUTE_DATA>high</ATTRIBUTE_DATA></STIG_DATA>'
|
|
13
|
+
'<STIG_DATA><VULN_ATTRIBUTE>Fix_Text</VULN_ATTRIBUTE><ATTRIBUTE_DATA>Set debug=false.</ATTRIBUTE_DATA></STIG_DATA>'
|
|
14
|
+
'<STIG_DATA><VULN_ATTRIBUTE>CCI_REF</VULN_ATTRIBUTE><ATTRIBUTE_DATA>CCI-000366</ATTRIBUTE_DATA></STIG_DATA>'
|
|
15
|
+
'<STATUS>Open</STATUS></VULN>'
|
|
16
|
+
'<VULN><STIG_DATA><VULN_ATTRIBUTE>Vuln_Num</VULN_ATTRIBUTE><ATTRIBUTE_DATA>V-2</ATTRIBUTE_DATA></STIG_DATA>'
|
|
17
|
+
'<STATUS>NotAFinding</STATUS></VULN></iSTIG></STIGS></CHECKLIST>')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_poam_rows_only_open_with_control_and_severity(tmp_path):
|
|
21
|
+
p = tmp_path / "s.ckl"; p.write_text(_CKL)
|
|
22
|
+
cl = stig.load(p)
|
|
23
|
+
rows = poam.poam_rows(cl, {"CCI-000366": "cm-6"})
|
|
24
|
+
assert len(rows) == 1 # only the Open finding
|
|
25
|
+
row = rows[0]
|
|
26
|
+
assert row["Control"] == "CM-6" # from the CCI map
|
|
27
|
+
assert row["Severity"] == "High" and row["Raw Severity"] == "CAT I" # high → CAT I/High
|
|
28
|
+
assert row["POA&M Status"] == "Ongoing"
|
|
29
|
+
assert "Set debug=false." in row["Milestone Description"]
|
|
30
|
+
assert "V-1" in row["Vulnerability Description"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_poam_unmapped_cci_is_marked_not_dropped(tmp_path):
|
|
34
|
+
p = tmp_path / "s.ckl"; p.write_text(_CKL)
|
|
35
|
+
cl = stig.load(p)
|
|
36
|
+
rows = poam.poam_rows(cl, {}) # no map
|
|
37
|
+
assert rows[0]["Control"] == "(unmapped CCI-000366)" # marked, not silently lost
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_write_csv_has_emass_headers(tmp_path):
|
|
41
|
+
p = tmp_path / "s.ckl"; p.write_text(_CKL)
|
|
42
|
+
cl = stig.load(p)
|
|
43
|
+
out = tmp_path / "poam.csv"
|
|
44
|
+
n = poam.export(cl, {"CCI-000366": "cm-6"}, out)["rows"]
|
|
45
|
+
assert n == 1
|
|
46
|
+
with open(out, newline="") as f:
|
|
47
|
+
reader = csv.DictReader(f)
|
|
48
|
+
assert reader.fieldnames == poam.EMASS_HEADERS
|
|
49
|
+
row = next(reader)
|
|
50
|
+
assert row["Control"] == "CM-6" and row["Severity"] == "High"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|