drydock-cli 3.0.65__tar.gz → 3.0.67__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.65/drydock_cli.egg-info → drydock_cli-3.0.67}/PKG-INFO +1 -1
- drydock_cli-3.0.67/drydock/builtin_skills/stig-assess.md +24 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/rmf.py +11 -4
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/tui/app.py +38 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/tui/widgets.py +2 -1
- {drydock_cli-3.0.65 → drydock_cli-3.0.67/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock_cli.egg-info/SOURCES.txt +2 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/pyproject.toml +1 -1
- drydock_cli-3.0.67/tests/test_e2e_connected.py +70 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_skills.py +1 -1
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/LICENSE +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/NOTICE +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/README.md +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/agent.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/cli.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/config.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/detect.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/extract.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/guards.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/providers.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/skills.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/stig.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock/web.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/setup.cfg +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_config.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.65 → drydock_cli-3.0.67}/tests/test_web_tools.py +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stig-assess
|
|
3
|
+
description: Assess the next un-reviewed STIG rule against system evidence
|
|
4
|
+
---
|
|
5
|
+
Assess STIG rules in the checklist at: $ARGS
|
|
6
|
+
|
|
7
|
+
Do exactly ONE rule per run (loop it — /loop N /stig-assess <path> — for the
|
|
8
|
+
whole checklist):
|
|
9
|
+
|
|
10
|
+
1. Call StigRules with that path and status=not_reviewed. If none remain, say the
|
|
11
|
+
checklist is fully assessed and STOP without calling other tools.
|
|
12
|
+
2. Take the FIRST not_reviewed rule. Call StigRule (path + rule_id) to read its
|
|
13
|
+
Check Content and Fix Text.
|
|
14
|
+
3. You MUST gather fresh evidence before deciding — do NOT guess. Read the exact
|
|
15
|
+
configuration file the Check Content names (Read tool) or run its check command
|
|
16
|
+
(Bash). Quote the specific line(s) you found. Reason step by step about whether
|
|
17
|
+
that evidence satisfies the Check Content.
|
|
18
|
+
4. Call StigSet (path + rule_id) with:
|
|
19
|
+
- status = not_a_finding ONLY if the evidence clearly shows compliance;
|
|
20
|
+
open if the evidence shows non-compliance;
|
|
21
|
+
not_applicable if the rule does not apply to this system;
|
|
22
|
+
- finding_details = the exact evidence you observed and why it does/doesn't
|
|
23
|
+
comply (quote the config line).
|
|
24
|
+
Give a one-line summary: rule id, the evidence line, and the status you set.
|
|
@@ -108,16 +108,23 @@ def rmf_dir(cwd: str) -> Path:
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def bootstrap(cwd: str, *, families: list[str] | None = None,
|
|
111
|
-
source: str | Path | None = None) -> dict:
|
|
111
|
+
source: str | Path | None = None, refresh: bool = False) -> dict:
|
|
112
112
|
"""Fetch (if needed) + flatten the 800-53 catalog into <cwd>/.drydock/rmf and
|
|
113
113
|
ingest it into the GraphRAG KB. Returns {families, controls_docs, **kb_stats}.
|
|
114
|
-
`source` reuses an already-downloaded catalog JSON (offline).
|
|
114
|
+
`source` reuses an already-downloaded catalog JSON (offline). `refresh` pulls
|
|
115
|
+
the latest from upstream, but falls back to the cached catalog if upstream is
|
|
116
|
+
unreachable — so a network blip never stalls an assessment (PRD §4)."""
|
|
115
117
|
from drydock import graphrag
|
|
116
118
|
|
|
117
119
|
base = rmf_dir(cwd)
|
|
118
120
|
cat_path = Path(source) if source else base / "catalog.json"
|
|
119
|
-
if not cat_path.exists():
|
|
120
|
-
|
|
121
|
+
if source is None and (refresh or not cat_path.exists()):
|
|
122
|
+
try:
|
|
123
|
+
fetch_catalog(cat_path)
|
|
124
|
+
except Exception: # noqa: BLE001 — any network/HTTP failure
|
|
125
|
+
if not cat_path.exists():
|
|
126
|
+
raise # nothing cached to fall back to
|
|
127
|
+
# else: keep using the cached catalog
|
|
121
128
|
catalog = json.loads(Path(cat_path).read_text("utf-8"))
|
|
122
129
|
docs = catalog_to_family_docs(catalog, base / "800-53", families)
|
|
123
130
|
store = graphrag.default_store_path(cwd)
|
|
@@ -496,6 +496,7 @@ class DrydockApp(App):
|
|
|
496
496
|
" /loop /loop <count> <prompt> — repeat a prompt (Esc stops)\n"
|
|
497
497
|
" /mcp list connected MCP servers and their tools\n"
|
|
498
498
|
" /rmf RMF automation — /rmf bootstrap, then /rmf-control etc.\n"
|
|
499
|
+
" /stig summarize a .ckl/.cklb; assess via /stig-assess\n"
|
|
499
500
|
" /clear reset the conversation\n"
|
|
500
501
|
" /quit exit\n"
|
|
501
502
|
"Type a task and press Enter. ↑/↓ recall history · Esc stops · Ctrl+O expands tools."
|
|
@@ -504,6 +505,8 @@ class DrydockApp(App):
|
|
|
504
505
|
self._cmd_mcp()
|
|
505
506
|
elif cmd == "/rmf":
|
|
506
507
|
self._cmd_rmf(arg)
|
|
508
|
+
elif cmd == "/stig":
|
|
509
|
+
self._cmd_stig(arg)
|
|
507
510
|
elif cmd == "/loop":
|
|
508
511
|
self._cmd_loop(arg)
|
|
509
512
|
elif cmd == "/skills":
|
|
@@ -513,6 +516,41 @@ class DrydockApp(App):
|
|
|
513
516
|
else:
|
|
514
517
|
self._mount(ErrorMessage(f"unknown command: {cmd} (try /help)"))
|
|
515
518
|
|
|
519
|
+
def _cmd_stig(self, arg: str) -> None:
|
|
520
|
+
"""Summarize a STIG checklist (.ckl/.cklb): asset + status counts, or list
|
|
521
|
+
rules of a given status. Assess it with /stig-assess (loop for the whole
|
|
522
|
+
checklist)."""
|
|
523
|
+
from drydock import stig
|
|
524
|
+
|
|
525
|
+
parts = arg.split()
|
|
526
|
+
if not parts:
|
|
527
|
+
self._info("usage: /stig <path-to.ckl|.cklb> [status] "
|
|
528
|
+
"(status: open|not_a_finding|not_applicable|not_reviewed)\n"
|
|
529
|
+
"Assess it: /loop <n> /stig-assess <path>")
|
|
530
|
+
return
|
|
531
|
+
import os as _os
|
|
532
|
+
path = parts[0]
|
|
533
|
+
if not _os.path.isabs(path):
|
|
534
|
+
path = _os.path.join(self.config.get("cwd") or ".", path)
|
|
535
|
+
try:
|
|
536
|
+
cl = stig.load(path)
|
|
537
|
+
except Exception as e: # noqa: BLE001
|
|
538
|
+
self._mount(ErrorMessage(f"could not read checklist: {e}"))
|
|
539
|
+
return
|
|
540
|
+
host = cl.asset.get("HOST_NAME") or cl.asset.get("host_name") or "?"
|
|
541
|
+
c = cl.counts()
|
|
542
|
+
lines = [f"STIG checklist {path} (host: {host}, format: {cl.fmt})",
|
|
543
|
+
f" {len(cl.rules)} rules — " + " · ".join(f"{k}={v}" for k, v in c.items())]
|
|
544
|
+
if len(parts) > 1:
|
|
545
|
+
sf = stig.canonical_status(parts[1])
|
|
546
|
+
hits = [r for r in cl.rules if r.status == sf]
|
|
547
|
+
lines.append(f"\n{sf} ({len(hits)}):")
|
|
548
|
+
lines += [f" {r.group_id} ({r.severity}) — {r.title}" for r in hits[:50]]
|
|
549
|
+
else:
|
|
550
|
+
lines.append("Assess un-reviewed rules: /loop "
|
|
551
|
+
f"{c.get('not_reviewed', 0) or 1} /stig-assess {parts[0]}")
|
|
552
|
+
self._info("\n".join(lines))
|
|
553
|
+
|
|
516
554
|
def _cmd_rmf(self, arg: str) -> None:
|
|
517
555
|
"""RMF automation: bootstrap the NIST 800-53 catalog into the knowledge
|
|
518
556
|
base and surface the bundled RMF skills."""
|
|
@@ -153,7 +153,8 @@ class PromptHistory:
|
|
|
153
153
|
# completes the prefix.
|
|
154
154
|
SLASH_COMMANDS = [
|
|
155
155
|
"/help", "/model", "/cwd", "/undo", "/back", "/stop", "/status",
|
|
156
|
-
"/compact", "/graphrag", "/skills", "/loop", "/mcp", "/rmf", "/
|
|
156
|
+
"/compact", "/graphrag", "/skills", "/loop", "/mcp", "/rmf", "/stig",
|
|
157
|
+
"/clear", "/quit",
|
|
157
158
|
]
|
|
158
159
|
|
|
159
160
|
|
|
@@ -29,6 +29,7 @@ drydock/builtin_skills/rmf-categorize.md
|
|
|
29
29
|
drydock/builtin_skills/rmf-control.md
|
|
30
30
|
drydock/builtin_skills/rmf-poam.md
|
|
31
31
|
drydock/builtin_skills/rmf-review.md
|
|
32
|
+
drydock/builtin_skills/stig-assess.md
|
|
32
33
|
drydock/tools/__init__.py
|
|
33
34
|
drydock/tui/__init__.py
|
|
34
35
|
drydock/tui/app.py
|
|
@@ -55,6 +56,7 @@ tests/test_config_migration.py
|
|
|
55
56
|
tests/test_context_limit_config.py
|
|
56
57
|
tests/test_detect.py
|
|
57
58
|
tests/test_dispatch.py
|
|
59
|
+
tests/test_e2e_connected.py
|
|
58
60
|
tests/test_empty_response.py
|
|
59
61
|
tests/test_extract.py
|
|
60
62
|
tests/test_failure_loop.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.67"
|
|
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,70 @@
|
|
|
1
|
+
"""Connected E2E tests (RMF E2E PRD). The DETERMINISTIC + integration parts:
|
|
2
|
+
schema fidelity, and network-resilience fallback. The LIVE upstream fetch is
|
|
3
|
+
opt-in (set DRYDOCK_E2E_NETWORK=1) so the normal suite stays fast + offline-safe.
|
|
4
|
+
Model-accuracy suites (assessment/remediation correctness) are verified hands-on
|
|
5
|
+
in the TUI, not here (see the operator's TUI-only rule)."""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import xml.etree.ElementTree as ET
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from drydock import rmf, rmf_graph, stig
|
|
15
|
+
|
|
16
|
+
_CAT = {"catalog": {"groups": [{"id": "ac", "title": "Access Control", "controls": [
|
|
17
|
+
{"id": "ac-2", "title": "Account Management",
|
|
18
|
+
"parts": [{"name": "statement", "prose": "Manage accounts."}]}]}]}}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_network_resilience_falls_back_to_cache(tmp_path, monkeypatch):
|
|
22
|
+
# Seed a cached catalog, then make upstream fetch fail on refresh.
|
|
23
|
+
base = rmf.rmf_dir(str(tmp_path)); base.mkdir(parents=True)
|
|
24
|
+
(base / "catalog.json").write_text(json.dumps(_CAT))
|
|
25
|
+
|
|
26
|
+
def boom(*a, **k):
|
|
27
|
+
raise OSError("upstream unreachable")
|
|
28
|
+
monkeypatch.setattr(rmf, "fetch_catalog", boom)
|
|
29
|
+
# refresh=True tries upstream, fails, but falls back to the cache → no crash
|
|
30
|
+
stats = rmf.bootstrap(str(tmp_path), refresh=True)
|
|
31
|
+
assert stats["family_docs"] == 1 and stats["chunks"] >= 1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_first_bootstrap_offline_with_no_cache_raises(tmp_path, monkeypatch):
|
|
35
|
+
monkeypatch.setattr(rmf, "fetch_catalog", lambda *a, **k: (_ for _ in ()).throw(OSError("down")))
|
|
36
|
+
with pytest.raises(OSError):
|
|
37
|
+
rmf.bootstrap(str(tmp_path)) # nothing cached to fall back to
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_ckl_regenerates_wellformed_and_status_enum(tmp_path):
|
|
41
|
+
p = tmp_path / "r.ckl"
|
|
42
|
+
p.write_text('<?xml version="1.0"?><CHECKLIST><ASSET><HOST_NAME>h</HOST_NAME></ASSET>'
|
|
43
|
+
'<STIGS><iSTIG><STIG_INFO></STIG_INFO><VULN>'
|
|
44
|
+
'<STIG_DATA><VULN_ATTRIBUTE>Vuln_Num</VULN_ATTRIBUTE><ATTRIBUTE_DATA>V-1</ATTRIBUTE_DATA></STIG_DATA>'
|
|
45
|
+
'<STIG_DATA><VULN_ATTRIBUTE>Rule_ID</VULN_ATTRIBUTE><ATTRIBUTE_DATA>SV-1</ATTRIBUTE_DATA></STIG_DATA>'
|
|
46
|
+
'<STATUS>Not_Reviewed</STATUS><FINDING_DETAILS></FINDING_DETAILS><COMMENTS></COMMENTS>'
|
|
47
|
+
'</VULN></iSTIG></STIGS></CHECKLIST>')
|
|
48
|
+
cl = stig.load(p); cl.update("V-1", status="open", finding_details="x"); cl.save(p)
|
|
49
|
+
root = ET.parse(p).getroot() # re-parses = well-formed
|
|
50
|
+
statuses = [v.findtext("STATUS") for v in root.iter("VULN")]
|
|
51
|
+
valid = {"Open", "NotAFinding", "Not_Applicable", "Not_Reviewed"}
|
|
52
|
+
assert all(s in valid for s in statuses) # DISA status enum preserved
|
|
53
|
+
assert root.find(".//VULN/STIG_DATA/ATTRIBUTE_DATA").text == "V-1" # data fidelity
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_graph_relationship_semantics(tmp_path):
|
|
57
|
+
# Suite A semantics: COMPONENT -IMPLEMENTS-> CONTROL resolves.
|
|
58
|
+
g = rmf_graph.build_from_catalog(_CAT)
|
|
59
|
+
g.add_edge(rmf_graph.component_id("srv"), "IMPLEMENTS", rmf_graph.control_id("ac-2"))
|
|
60
|
+
assert rmf_graph.control_id("ac-2") in g.neighbors(rmf_graph.component_id("srv"), "IMPLEMENTS")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.mark.skipif(os.environ.get("DRYDOCK_E2E_NETWORK") != "1",
|
|
64
|
+
reason="live network test; set DRYDOCK_E2E_NETWORK=1 to run")
|
|
65
|
+
def test_live_nist_catalog_fetch_and_parse(tmp_path):
|
|
66
|
+
# Suite A: pull the REAL NIST 800-53 catalog from upstream and parse it.
|
|
67
|
+
cat = rmf.fetch_catalog(tmp_path / "cat.json")
|
|
68
|
+
catalog = json.loads(cat.read_text())
|
|
69
|
+
docs = rmf.catalog_to_family_docs(catalog, tmp_path / "out", families=["ac"])
|
|
70
|
+
assert docs and "AC-2" in docs[0].read_text()
|
|
@@ -55,7 +55,7 @@ def test_empty_body_skipped(tmp_path):
|
|
|
55
55
|
def test_no_user_or_project_skills_yields_only_builtins(tmp_path):
|
|
56
56
|
# With no user/project skills, only the bundled built-ins (RMF) are present.
|
|
57
57
|
loaded = skills.load_skills(str(tmp_path))
|
|
58
|
-
assert set(loaded) == {"rmf-control", "rmf-categorize", "rmf-review", "rmf-poam"}
|
|
58
|
+
assert set(loaded) == {"rmf-control", "rmf-categorize", "rmf-review", "rmf-poam", "stig-assess"}
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def test_create_skill_user_scope(tmp_path, monkeypatch):
|
|
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
|