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.
Files changed (101) hide show
  1. {drydock_cli-3.0.78/drydock_cli.egg-info → drydock_cli-3.0.79}/PKG-INFO +6 -2
  2. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/README.md +5 -1
  3. drydock_cli-3.0.79/drydock/poam.py +73 -0
  4. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/app.py +30 -0
  5. {drydock_cli-3.0.78 → drydock_cli-3.0.79/drydock_cli.egg-info}/PKG-INFO +6 -2
  6. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/SOURCES.txt +2 -0
  7. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/pyproject.toml +1 -1
  8. drydock_cli-3.0.79/tests/test_poam.py +50 -0
  9. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/LICENSE +0 -0
  10. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/NOTICE +0 -0
  11. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/__init__.py +0 -0
  12. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/__main__.py +0 -0
  13. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/agent.py +0 -0
  14. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/bash_safety.py +0 -0
  15. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/__init__.py +0 -0
  16. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/rmf-categorize.md +0 -0
  17. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/rmf-control.md +0 -0
  18. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/rmf-poam.md +0 -0
  19. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/rmf-review.md +0 -0
  20. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/stig-assess.md +0 -0
  21. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/builtin_skills/stig-remediate.md +0 -0
  22. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/cci.py +0 -0
  23. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/cli.py +0 -0
  24. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/compaction.py +0 -0
  25. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/config.py +0 -0
  26. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/detect.py +0 -0
  27. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/extract.py +0 -0
  28. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/gittools.py +0 -0
  29. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/graphrag.py +0 -0
  30. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/guards.py +0 -0
  31. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/loop_detect.py +0 -0
  32. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/mcp.py +0 -0
  33. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/providers.py +0 -0
  34. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/rmf.py +0 -0
  35. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/rmf_graph.py +0 -0
  36. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/skills.py +0 -0
  37. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/stig.py +0 -0
  38. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tool_registry.py +0 -0
  39. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tools/__init__.py +0 -0
  40. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/__init__.py +0 -0
  41. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/approval.py +0 -0
  42. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/messages.py +0 -0
  43. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tui/widgets.py +0 -0
  44. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/tuning.py +0 -0
  45. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock/web.py +0 -0
  46. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/dependency_links.txt +0 -0
  47. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/entry_points.txt +0 -0
  48. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/requires.txt +0 -0
  49. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/drydock_cli.egg-info/top_level.txt +0 -0
  50. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/setup.cfg +0 -0
  51. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_approval.py +0 -0
  52. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_back_command.py +0 -0
  53. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_bash_output_bounding.py +0 -0
  54. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_bash_process_group.py +0 -0
  55. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_bash_safety.py +0 -0
  56. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_bash_timeout_network.py +0 -0
  57. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_cci.py +0 -0
  58. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_cli_agents.py +0 -0
  59. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_compact_command.py +0 -0
  60. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_compaction.py +0 -0
  61. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_config.py +0 -0
  62. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_config_migration.py +0 -0
  63. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_context_limit_config.py +0 -0
  64. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_detect.py +0 -0
  65. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_dispatch.py +0 -0
  66. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_e2e_connected.py +0 -0
  67. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_empty_response.py +0 -0
  68. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_extract.py +0 -0
  69. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_failure_loop.py +0 -0
  70. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_first_run_setup.py +0 -0
  71. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_gittools.py +0 -0
  72. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_graphrag.py +0 -0
  73. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_guards_and_tools.py +0 -0
  74. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_hallucinated_tools.py +0 -0
  75. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_leaked_tool_call.py +0 -0
  76. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_loop_detect.py +0 -0
  77. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_mcp.py +0 -0
  78. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_oneshot_unreachable.py +0 -0
  79. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_plan_autocontinue.py +0 -0
  80. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_providers_unreachable.py +0 -0
  81. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_read_index.py +0 -0
  82. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_rmf.py +0 -0
  83. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_rmf_graph.py +0 -0
  84. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_rmf_stig_graph.py +0 -0
  85. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_runaway_repetition.py +0 -0
  86. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_server_probe.py +0 -0
  87. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_skills.py +0 -0
  88. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_stig.py +0 -0
  89. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_stop.py +0 -0
  90. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_streaming_newlines.py +0 -0
  91. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_subagent.py +0 -0
  92. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_system_prompt_help.py +0 -0
  93. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_todo.py +0 -0
  94. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_tool_arg_parsing.py +0 -0
  95. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_tools_undo.py +0 -0
  96. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_tui.py +0 -0
  97. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_tuning.py +0 -0
  98. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_viewimage.py +0 -0
  99. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_vision_input.py +0 -0
  100. {drydock_cli-3.0.78 → drydock_cli-3.0.79}/tests/test_web_tools.py +0 -0
  101. {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.78
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.78
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.78"
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