|
| 1 | +# RFC Workstream B — Structured Extraction |
| 2 | + |
| 3 | +This document explains the implementation and behavior of RFC Workstream B |
| 4 | +(Structured Extraction) from Cheatsheet to CRE Mapping RFC. |
| 5 | + |
| 6 | +The goal of this module is to convert OWASP Cheat Sheet markdown into a |
| 7 | +deterministic structured object that downstream RFC workstreams can consume |
| 8 | +for categorization, retrieval, reranking, and mapping generation. |
| 9 | + |
| 10 | +The implementation is primarily located in: |
| 11 | + |
| 12 | +* `cheatsheet_defs.py` |
| 13 | +* `cheatsheet_extractor.py` |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## Sources for more context |
| 18 | + |
| 19 | +* RFC: |
| 20 | + `docs/rfc/cheatsheets-llm-autonomous-mapping-rfc.md` |
| 21 | + |
| 22 | +* Checkpoints B1 & B2 implementation PR: |
| 23 | + `https://github.com/OWASP/OpenCRE/pull/912` |
| 24 | + |
| 25 | +* Checkpoints B3 & B4 implementation PR: |
| 26 | + `https://github.com/OWASP/OpenCRE/pull/921` |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## What Workstream B implements |
| 31 | + |
| 32 | +**The implementation strictly follows the RFC extraction contract and prioritizes deterministic extraction behavior.** |
| 33 | + |
| 34 | +It defines a typed dataclass named `CheatsheetRecord`. |
| 35 | + |
| 36 | +This object represents the structured extraction result returned from: |
| 37 | + |
| 38 | +```python |
| 39 | +extract_cheatsheet_record(markdown, source_path) |
| 40 | +``` |
| 41 | + |
| 42 | +The extractor parses OWASP Cheat Sheet markdown and returns normalized |
| 43 | +structured information about a cheatsheet. |
| 44 | + |
| 45 | +`CheatsheetRecord` contains: |
| 46 | + |
| 47 | +* `source` |
| 48 | +* `source_id` |
| 49 | +* `title` |
| 50 | +* `hyperlink` |
| 51 | +* `summary` |
| 52 | +* `headings` |
| 53 | +* `raw_markdown_path` |
| 54 | +* `category_hints` |
| 55 | +* `metadata` |
| 56 | + |
| 57 | +--- |
| 58 | + |
| 59 | +## Fallback behavior |
| 60 | + |
| 61 | +The extractor contains fallback functions capable of handling incomplete or |
| 62 | +malformed markdown containing: |
| 63 | + |
| 64 | +* missing titles, |
| 65 | +* missing summary sources, |
| 66 | +* malformed headings. |
| 67 | + |
| 68 | +These fallback paths ensure that extraction still returns a valid |
| 69 | +`CheatsheetRecord` object instead of failing entirely. |
| 70 | + |
| 71 | +Fallback behavior is explicitly surfaced through: |
| 72 | + |
| 73 | +```json |
| 74 | +"metadata": { |
| 75 | + "fallback_used": "true" |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +This allows downstream workstreams to identify records that required fallback |
| 80 | +logic during extraction and downstream normalization. |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +## Fallback decision tree |
| 85 | + |
| 86 | +```text |
| 87 | +extract_cheatsheet_record(markdown, source_path) |
| 88 | +
|
| 89 | +│ |
| 90 | +├── _extract_title(markdown) |
| 91 | +│ ├── H1 title exists |
| 92 | +│ │ → extract and normalize title |
| 93 | +│ │ |
| 94 | +│ └── H1 title missing |
| 95 | +│ → _fallback_title() |
| 96 | +│ → "No title found." |
| 97 | +│ → metadata["fallback_used"] = "true" |
| 98 | +│ |
| 99 | +└── _extract_summary(markdown) |
| 100 | + ├── "Introduction" heading exists with body content |
| 101 | + │ → body beneath "Introduction" extracted as summary |
| 102 | + │ → summary normalized and truncated upto specifc length. |
| 103 | + │ |
| 104 | + └── Introduction section missing or invalid |
| 105 | + → _fallback_summary(markdown) |
| 106 | + │ |
| 107 | + ├── first heading with body content exists |
| 108 | + │ → its body returned as summary |
| 109 | + │ |
| 110 | + └── no usable heading/body content exists |
| 111 | + → "No summary found." |
| 112 | + → metadata["fallback_used"] = "true" |
| 113 | +``` |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +## Extraction examples |
| 118 | + |
| 119 | +The following examples demonstrate deterministic extractor behavior across |
| 120 | +different markdown shapes. |
| 121 | + |
| 122 | +Notes: |
| 123 | + |
| 124 | +* Currently, `category_hints` is intentionally returned as an initial empty |
| 125 | + list during v1. |
| 126 | + |
| 127 | +* `raw_markdown_path`, `hyperlink`, and `source_id` are derived from |
| 128 | + `source_path` (Module A) and are independent of markdown content. |
| 129 | + |
| 130 | +--- |
| 131 | + |
| 132 | +## 1. Normal cheat sheet |
| 133 | + |
| 134 | +### Example Input |
| 135 | + |
| 136 | +```markdown |
| 137 | +# Secrets Management Cheat Sheet |
| 138 | + |
| 139 | +## Introduction |
| 140 | +Storage guidance. |
| 141 | + |
| 142 | +## Architectural Patterns |
| 143 | +Use vaults and environment isolation. |
| 144 | +``` |
| 145 | + |
| 146 | +### Output |
| 147 | + |
| 148 | +```json |
| 149 | +{ |
| 150 | + "source": "owasp_cheatsheets", |
| 151 | + "source_id": "Secrets_Management_Cheat_Sheet", |
| 152 | + "title": "Secrets Management Cheat Sheet", |
| 153 | + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html", |
| 154 | + "summary": "Storage guidance.", |
| 155 | + "headings": ["Introduction", "Architectural Patterns"], |
| 156 | + "raw_markdown_path": "cheatsheets/Secrets_Management_Cheat_Sheet.md", |
| 157 | + "category_hints": [], |
| 158 | + "metadata": { |
| 159 | + "parser_version": "v1", |
| 160 | + "fallback_used": "false" |
| 161 | + } |
| 162 | +} |
| 163 | +``` |
| 164 | + |
| 165 | +### Notes |
| 166 | + |
| 167 | +* No fallback logic was required. |
| 168 | + |
| 169 | +--- |
| 170 | + |
| 171 | +## 2. Missing H1 (fallback title) |
| 172 | + |
| 173 | +### Input |
| 174 | + |
| 175 | +```markdown |
| 176 | +## Introduction |
| 177 | +No H1 present. |
| 178 | + |
| 179 | +## Details |
| 180 | +More content. |
| 181 | +``` |
| 182 | + |
| 183 | +### Output |
| 184 | + |
| 185 | +```json |
| 186 | +{ |
| 187 | + "source": "owasp_cheatsheets", |
| 188 | + "source_id": "Example_Cheat_Sheet", |
| 189 | + "title": "No title found.", |
| 190 | + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Example_Cheat_Sheet.html", |
| 191 | + "summary": "No H1 present.", |
| 192 | + "headings": ["Introduction", "Details"], |
| 193 | + "raw_markdown_path": "cheatsheets/Example_Cheat_Sheet.md", |
| 194 | + "category_hints": [], |
| 195 | + "metadata": { |
| 196 | + "parser_version": "v1", |
| 197 | + "fallback_used": "true" |
| 198 | + } |
| 199 | +} |
| 200 | +``` |
| 201 | + |
| 202 | +### Notes |
| 203 | + |
| 204 | +* No H1 title exists, so the title defaults to `"No title found."` |
| 205 | + |
| 206 | +--- |
| 207 | + |
| 208 | +## 3. Missing Introduction section (summary fallback) |
| 209 | + |
| 210 | +### Input |
| 211 | + |
| 212 | +```markdown |
| 213 | +# Single Heading Cheat Sheet |
| 214 | + |
| 215 | +## Authentication |
| 216 | + |
| 217 | +### Storage |
| 218 | +Secrets should be encrypted. |
| 219 | +``` |
| 220 | + |
| 221 | +### Output |
| 222 | + |
| 223 | +```json |
| 224 | +{ |
| 225 | + "source": "owasp_cheatsheets", |
| 226 | + "source_id": "Single_Heading_Cheat_Sheet", |
| 227 | + "title": "Single Heading Cheat Sheet", |
| 228 | + "hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Single_Heading_Cheat_Sheet.html", |
| 229 | + "summary": "Secrets should be encrypted.", |
| 230 | + "headings": ["Authentication"], |
| 231 | + "raw_markdown_path": "cheatsheets/Single_Heading_Cheat_Sheet.md", |
| 232 | + "category_hints": [], |
| 233 | + "metadata": { |
| 234 | + "parser_version": "v1", |
| 235 | + "fallback_used": "true" |
| 236 | + } |
| 237 | +} |
| 238 | +``` |
| 239 | + |
| 240 | +### Notes |
| 241 | + |
| 242 | +* No `Introduction` heading exists, so summary fallback logic is used. |
| 243 | +* The fallback scans all headings and returns the first non-empty body it |
| 244 | + finds — in this case the content beneath `### Storage`. |
| 245 | +* Only `##`-level headings appear in `headings` — `### Storage` is excluded. |
| 246 | + |
| 247 | +--- |
| 248 | + |
| 249 | +## Additional behavior notes |
| 250 | + |
| 251 | +It is ensured that markdown files with malformed title/headings such as: |
| 252 | + |
| 253 | +* With leading whitespace (e.g. ` # My Title`, ` ## My Heading`) |
| 254 | +* No space after the marker (e.g. `##Authentication`) |
| 255 | + |
| 256 | +are extracted correctly. |
| 257 | + |
| 258 | +--- |
0 commit comments