Skip to content

Commit 19a0e06

Browse files
author
Peter Wagstaff
committed
Update content, highight sl5 enhancements
1 parent 50c07a9 commit 19a0e06

7 files changed

Lines changed: 481 additions & 559 deletions

index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,10 @@
455455
align-items: center;
456456
transition: background-color 0.2s;
457457
}
458+
/* SL5 highlight */
459+
.enhancement-header.sl5-style {
460+
background: #fffde7;
461+
}
458462
.enhancement-header:hover {
459463
background: #f1f5f9;
460464
}
@@ -1628,7 +1632,7 @@ <h3 class="overlays-title">Overlays</h3>
16281632

16291633
return `
16301634
<div class="enhancement-card" style="display: ${isExpanded ? 'block' : 'none'}; ${grayedOutStyle}">
1631-
<div class="enhancement-header" onclick="toggleControl('${enhancementId}')">
1635+
<div class="enhancement-header ${enhancement.catalog === 'sl5' ? 'sl5-style' : ''}" onclick="toggleControl('${enhancementId}')">
16321636
<div style="display: flex; align-items: center; flex: 1;">
16331637
<span class="enhancement-id">${enhancement.id}</span>
16341638
${isWithdrawn ? `<span class="withdrawn-badge">Withdrawn</span>` : ''}

sl5_catalog/sl5_control_catalog.json

Lines changed: 240 additions & 368 deletions
Large diffs are not rendered by default.
-35.1 KB
Binary file not shown.

sl5_catalog/sl5_control_catalog_extractor.py

Lines changed: 84 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- Converts to NIST-style JSON format with proper metadata
1111
- Handles control text, discussions, and related controls
1212
- Provides comprehensive summary statistics
13+
- Filters out controls with no or empty text attributes
1314
1415
Usage:
1516
python sl5_control_catalog_extractor.py <pdf_file>
@@ -77,68 +78,56 @@ def extract_controls(self) -> List[Dict]:
7778
self._process_page(page, page_num + 1)
7879

7980
doc.close()
80-
81-
# Sort controls by ID
82-
self.controls.sort(key=lambda x: self._sort_key(x['id']))
83-
81+
print(f"Extracted {len(self.controls)} controls")
8482
return self.controls
8583

8684
except Exception as e:
8785
print(f"Error processing PDF: {e}")
8886
return []
8987

90-
def _sort_key(self, control_id: str) -> tuple:
91-
"""Generate sort key for control ID."""
92-
# Parse control ID like "SC-61" or "SC-61(1)"
93-
match = re.match(r'^([A-Z]{2})-(\d+)(?:\((\d+)\))?', control_id)
94-
if match:
95-
family, base_num, enhancement = match.groups()
96-
return (family, int(base_num), int(enhancement) if enhancement else 0)
97-
return (control_id, 0, 0)
98-
9988
def _process_page(self, page, page_num: int):
100-
"""Process a single page."""
89+
"""Process a single page extracting control information."""
10190
try:
102-
text_dict = page.get_text("dict")
103-
formatted_text = self._extract_formatted_text(text_dict)
104-
self._find_controls_and_attributes(formatted_text, page_num)
91+
# Get formatted text with style information
92+
formatted_text = self._get_formatted_text(page)
93+
self._extract_controls_from_text(formatted_text, page_num)
10594
except Exception as e:
10695
if self.debug_mode:
10796
print(f"Error processing page {page_num}: {e}")
10897

109-
def _extract_formatted_text(self, text_dict: dict) -> List[dict]:
110-
"""Extract text with formatting information."""
111-
formatted_text = []
98+
def _get_formatted_text(self, page) -> List[Dict]:
99+
"""Extract text with formatting information from the page."""
100+
text_dict = page.get_text("dict")
101+
formatted_lines = []
112102

113-
for block in text_dict.get("blocks", []):
114-
if block.get("type") == 0: # Text block
115-
for line in block.get("lines", []):
103+
for block in text_dict["blocks"]:
104+
if "lines" in block:
105+
for line in block["lines"]:
116106
line_text = ""
117-
line_formats = []
107+
formats = []
118108

119-
for span in line.get("spans", []):
120-
text = span.get("text", "")
121-
flags = span.get("flags", 0)
109+
for span in line["spans"]:
110+
span_text = span["text"]
111+
line_text += span_text
122112

123-
line_text += text
124-
line_formats.append({
125-
"text": text,
126-
"bold": bool(flags & 16),
127-
"underline": bool(flags & 2),
128-
"font": span.get("font", ""),
129-
"flags": flags
113+
# Track format information
114+
formats.append({
115+
"text": span_text,
116+
"bold": "Bold" in span["font"] or span["flags"] & 2**4,
117+
"italic": "Italic" in span["font"] or span["flags"] & 2**1,
118+
"size": span["size"]
130119
})
131120

132121
if line_text.strip():
133-
formatted_text.append({
122+
formatted_lines.append({
134123
"text": line_text.strip(),
135-
"formats": line_formats
124+
"formats": formats
136125
})
137126

138-
return formatted_text
127+
return formatted_lines
139128

140-
def _find_controls_and_attributes(self, formatted_text: List[dict], page_num: int):
141-
"""Find controls and attributes in the formatted text."""
129+
def _extract_controls_from_text(self, formatted_text: List[Dict], page_num: int):
130+
"""Extract control information from formatted text."""
142131
processing_enhancements = False
143132

144133
for line_data in formatted_text:
@@ -336,13 +325,48 @@ def _is_header_footer(self, text: str) -> bool:
336325
'security level' in text
337326
)
338327

328+
def _has_meaningful_text_content(self, control: Dict) -> bool:
329+
"""Check if a control has meaningful text content in its text attribute."""
330+
text = control.get("text", "").strip()
331+
332+
# Consider control as having meaningful content if:
333+
# 1. It has non-empty text content
334+
# 2. The text is not just placeholder values like "TODO" or empty strings
335+
if not text:
336+
return False
337+
338+
# Check for common placeholder values that should be considered "empty"
339+
placeholder_values = ["TODO", "TBD", "N/A", "", " "]
340+
if text in placeholder_values:
341+
return False
342+
343+
# Must have at least some meaningful content (more than just whitespace)
344+
if len(text.strip()) < 5: # Arbitrary minimum length for meaningful text
345+
return False
346+
347+
return True
348+
339349
def create_nist_catalog_json(self) -> Dict:
340350
"""Create the full NIST-style catalog JSON structure."""
341-
# Clean up controls - remove temporary attributes
351+
# Clean up controls - remove temporary attributes and filter out controls with no text
342352
cleaned_controls = []
353+
filtered_count = 0
354+
343355
for control in self.controls:
344-
cleaned_control = {k: v for k, v in control.items() if k != "_attributes"}
345-
cleaned_controls.append(cleaned_control)
356+
# Check if control has meaningful text content
357+
if self._has_meaningful_text_content(control):
358+
# Remove temporary attributes and add to final list
359+
cleaned_control = {k: v for k, v in control.items() if k != "_attributes"}
360+
cleaned_controls.append(cleaned_control)
361+
else:
362+
filtered_count += 1
363+
if self.debug_mode:
364+
print(f"Filtered out control {control.get('id', 'unknown')} - no meaningful text content")
365+
366+
# Print filtering summary
367+
if filtered_count > 0:
368+
print(f"Filtered out {filtered_count} controls with no or empty text attributes")
369+
print(f"Remaining controls: {len(cleaned_controls)}")
346370

347371
# Calculate summary statistics
348372
summary = self._calculate_summary(cleaned_controls)
@@ -398,7 +422,7 @@ def save_to_json(self, output_file: str):
398422
with open(output_file, 'w', encoding='utf-8') as f:
399423
json.dump(catalog, f, indent=2, ensure_ascii=False)
400424

401-
print(f"\nSuccessfully saved catalog with {len(self.controls)} controls to {output_file}")
425+
print(f"\nSuccessfully saved catalog with {len(catalog['controls'])} controls to {output_file}")
402426
except Exception as e:
403427
print(f"Error saving to JSON: {e}")
404428

@@ -439,45 +463,28 @@ def debug_page(self, page_num: int):
439463
"""Debug a specific page to understand formatting."""
440464
try:
441465
doc = fitz.open(self.pdf_path)
442-
443-
if page_num > len(doc):
444-
print(f"Page {page_num} does not exist. Document has {len(doc)} pages.")
466+
if page_num < 1 or page_num > len(doc):
467+
print(f"Invalid page number. Document has {len(doc)} pages.")
445468
return
446469

447470
page = doc[page_num - 1] # Convert to 0-based index
448-
text_dict = page.get_text("dict")
449-
450471
print(f"\n=== DEBUG PAGE {page_num} ===")
451472

452-
for block_num, block in enumerate(text_dict.get("blocks", [])):
453-
if block.get("type") == 0: # Text block
454-
print(f"\nBlock {block_num}:")
455-
for line_num, line in enumerate(block.get("lines", [])):
456-
line_text = ""
457-
for span in line.get("spans", []):
458-
line_text += span.get("text", "")
459-
460-
if line_text.strip():
461-
print(f" Line {line_num}: '{line_text.strip()}'")
462-
463-
# Check for control patterns
464-
if re.match(r'^[A-Z]{2}-\d{1,2}[,\s]', line_text.strip()):
465-
print(f" *** POTENTIAL CONTROL ID ***")
466-
467-
# Check if potential enhancement
468-
if (line_text.strip().startswith('●') or line_text.strip().startswith('•')):
469-
enhancement_match = re.search(r'([A-Z]{2}-\d{1,2}\(\d+\))', line_text)
470-
if enhancement_match:
471-
print(f" *** POTENTIAL ENHANCEMENT: {enhancement_match.group(1)} ***")
472-
473-
for span in line.get("spans", []):
474-
text = span.get("text", "")
475-
flags = span.get("flags", 0)
476-
font = span.get("font", "")
477-
if text.strip():
478-
bold = bool(flags & 16)
479-
underline = bool(flags & 2)
480-
print(f" Span: '{text}' | Font: {font} | Bold: {bold} | Underline: {underline}")
473+
formatted_text = self._get_formatted_text(page)
474+
475+
print(f"Found {len(formatted_text)} text lines:")
476+
for i, line_data in enumerate(formatted_text):
477+
line_text = line_data["text"]
478+
formats = line_data["formats"]
479+
is_bold = any(fmt.get("bold", False) for fmt in formats)
480+
481+
print(f"{i+1:3d}: {'[B]' if is_bold else ' '} {line_text[:80]}")
482+
483+
# Show detailed format info for first few lines
484+
if i < 5:
485+
for fmt in formats:
486+
if fmt["text"].strip():
487+
print(f" Format: '{fmt['text']}' - Bold: {fmt['bold']}, Size: {fmt['size']}")
481488

482489
doc.close()
483490

sl5_overlay/extracted_sl5_overlay.json

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
11
{
2-
"SC-03": {
2+
"CP-10(2)": {
3+
"name": "System Recovery and Reconstitution | Offline Storage",
4+
"attributes": {
5+
"Justification to Select": "SL5 requires offline storage of certain data as well as offline data transfer devices. ​ ​",
6+
"Test Method": "Examine, Interview",
7+
"Open Questions": "Do we need supplemental guidance about offline storage facilities, should other controls be involved here?"
8+
},
9+
"selected": true
10+
},
11+
"CP-11": {
12+
"name": "ALTERNATE COMMUNICATIONS PROTOCOLS",
13+
"attributes": {
14+
"Justification to Select": "Already selected in IL6, but adding more content.",
15+
"Parameter Value(s)": "Alternate communications protocols as defined in the contingency plan.",
16+
"Test Method": "Examine, Interview, Test",
17+
"Supplemental Guidance": "An attacker who compromises the system may be able to eavesdrop on communications occurring over the network. If these communications are used to plan or implement a response, the attacker may be able to preempt it. Instead responders should coordinate their efforts over a separate communication system that is not visible to the attacker. This should include even the initial report of the attack."
18+
},
19+
"selected": true
20+
},
21+
"SC-3": {
322
"name": "SECURITY FUNCTION ISOLATION",
423
"attributes": {
524
"Open Questions": "Unsure what enhancements to select"
625
},
726
"selected": false
827
},
9-
"SC-03(1)": {
28+
"SC-3(1)": {
1029
"name": "Security Function Isolation | Hardware Separation",
1130
"attributes": {
1231
"Open Questions": "Unsure of test methods"
1332
},
1433
"selected": false
1534
},
16-
"SC-06": {
35+
"SC-6": {
1736
"name": "RESOURCE AVAILABILITY",
1837
"attributes": {
1938
"Open Questions": "Unsure if should select"
2039
},
2140
"selected": false
2241
},
23-
"SC-07": {
42+
"SC-7": {
2443
"name": "BOUNDARY PROTECTION",
2544
"attributes": {
2645
"Justification to Select": "Already selected by IL6.",
@@ -35,11 +54,6 @@
3554
},
3655
"selected": false
3756
},
38-
"SC-12": {
39-
"name": "CRYPTOGRAPHIC KEY ESTABLISHMENT AND MANAGEMENT",
40-
"attributes": {},
41-
"selected": false
42-
},
4357
"SC-12(1)": {
4458
"name": "Cryptographic Key Establishment and Management | Availability",
4559
"attributes": {
@@ -51,6 +65,16 @@
5165
},
5266
"SC-12(2)": {
5367
"name": "Cryptographic Key Establishment and Management | Symmetric Keys",
68+
"attributes": {
69+
"Justification to Select": "Already selected by IL6.",
70+
"Parameter Value(s)": "NIST FIPS-validated​ ​ ​",
71+
"Test Method": "Examine, Interview",
72+
"Open Questions": "Unsure of test methods"
73+
},
74+
"selected": true
75+
},
76+
"SC-12(3)": {
77+
"name": "Cryptographic Key Establishment and Management | Asymmetric Keys",
5478
"attributes": {
5579
"Justification to Select": "Already selected by IL6.",
5680
"Parameter Value(s)": "Certificates issued in accordance with organization-defined requirements ​ ​",
@@ -84,27 +108,13 @@
84108
},
85109
"selected": true
86110
},
87-
"SC-28": {
88-
"name": "PROTECTION OF INFORMATION AT REST",
89-
"attributes": {},
90-
"selected": false
91-
},
92111
"SC-28(2)": {
93112
"name": "Protection of Information at Rest | Offline Storage",
94-
"attributes": {
95-
"Justification to Select": "SL5 requires offline storage of certain data as well as offline data transfer devices. ​ ​",
96-
"Test Method": "Examine, Interview",
97-
"Open Questions": "Do we need supplemental guidance about offline storage facilities, should other controls be involved here?"
98-
},
99-
"selected": true
100-
},
101-
"SC-28(3)": {
102-
"name": "Protection of Information at Rest | Cryptographic Keys",
103113
"attributes": {
104114
"Justification to Select": "Keys should be stored in TPMs.",
105-
"Parameter Value(s)": "Hardware-protected key store ​ ​",
106115
"Test Method": "Examine, Interview",
107-
"Open Questions": "Should we say more here, should other controls be involved?"
116+
"Open Questions": "Should we say more here, should other controls be involved?",
117+
"Parameter Value(s)": "Hardware-protected key store ​ ​"
108118
},
109119
"selected": true
110120
},
@@ -156,11 +166,24 @@
156166
},
157167
"selected": true
158168
},
159-
"SC-32": {
160-
"name": "SYSTEM PARTITIONING",
169+
"SI-12(3)": {
170+
"name": "Information Management and Retention | Information Disposal",
161171
"attributes": {
162-
"Open Questions": "Unsure if should select, seems related to network separation family"
172+
"Justification to Select": "Not selected in IL6, but cryptographic erasure is described in IL6.",
173+
"Parameter Value(s)": "Cryptographic erasure",
174+
"Supplemental Guidance": "Cryptographic erasure is used to reliably erase data that is stored by a cloud provider without relying on that cloud provider to erase it. It works by having sole custody of the keys used to encrypt the data, and destroying those keys, thereby effectively erasing the encrypted data. ​",
175+
"Test Method": "Examine, Interview"
163176
},
164-
"selected": false
177+
"selected": true
178+
},
179+
"SI-15": {
180+
"name": "INFORMATION OUTPUT FILTERING",
181+
"attributes": {
182+
"Justification to Select": "Not selected in IL6, but output filtering is needed to prevent misuse of APIs.",
183+
"Parameter Value(s)": "CSP-defined software programs and/or applications",
184+
"Supplemental Guidance": "API responses, especially those including the output from models, are checked for content that could reveal sensitive information, especially pertaining to weights. If detected, the response is blocked and flagged. ​",
185+
"Test Method": "Examine, Interview, Test"
186+
},
187+
"selected": true
165188
}
166189
}

sl5_overlay/sl5_overlay.pdf

17.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)