Skip to content

Commit 1501d0d

Browse files
committed
readme updated, excel checklist added
1 parent 463092e commit 1501d0d

File tree

6 files changed

+257
-1
lines changed

6 files changed

+257
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ The first public version that was suitable for use was released in September 202
2424

2525
As we mature, we will be looking to create a more structured approach to the roadmap. As with most activities we will allow ourselves to be steered by the work completed by the [ASVS project](https://github.com/OWASP/ASVS/wiki/Roadmap-to-version-5.0) to find that strucutre.
2626

27-
27+
In the `utils\Convert-TASVS-Excel` directory, there is a script that can be used to populate an Excel template with the TASVS checklist. This is a useful tool for applying the standard in a practical way. It is not fully release ready yet, but can be used in a pinch. I will endevour to update it over time, for now grab the Excel file that will be named something like `TASVS_v1.6.xlsx`.
2828

2929
## Contributing
3030

Binary file not shown.
119 KB
Binary file not shown.
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import os
2+
import shutil
3+
import requests
4+
import openpyxl
5+
from openpyxl.worksheet.datavalidation import DataValidation
6+
from openpyxl.styles import Alignment, Font
7+
8+
9+
class ChecklistProcessor:
10+
"""Class to process and download checklist data from GitHub markdown files."""
11+
12+
def __init__(self, repo_document_root_url, tasvs_files):
13+
self.repo_document_root_url = repo_document_root_url
14+
self.tasvs_files = tasvs_files
15+
16+
def get_testing_checklist(self, content):
17+
"""Extract the 'Testing Checklist' section and its table from markdown content."""
18+
lines = content.split("\n")
19+
start_idx, end_idx = -1, -1
20+
checklist_lines = []
21+
22+
# Locate the "Testing Checklist" section
23+
for i, line in enumerate(lines):
24+
if "## Testing Checklist" in line:
25+
start_idx = i
26+
elif start_idx != -1 and line.startswith("#"):
27+
end_idx = i
28+
break
29+
30+
# Extract the content if we found a section
31+
if start_idx != -1:
32+
checklist_lines = (
33+
lines[start_idx + 1 : end_idx]
34+
if end_idx != -1
35+
else lines[start_idx + 1 :]
36+
)
37+
38+
# Clean up the lines (remove empty lines or excess spaces)
39+
checklist_lines = [line.strip() for line in checklist_lines if line.strip()]
40+
41+
# Parse the Markdown table from the checklist
42+
table_data = []
43+
in_table = False
44+
for line in checklist_lines:
45+
if "----" not in line and "TASVS-ID" not in line: # Table row line
46+
row = [
47+
cell.strip() for cell in line.split("|")[1:-1]
48+
] # Split and remove leading/trailing pipes
49+
table_data.append(row)
50+
in_table = True
51+
elif in_table and not line.startswith("|"):
52+
break # Stop if we have exited the table section
53+
54+
return table_data
55+
56+
def process_files(self):
57+
"""Download and process all markdown files, extracting checklist data."""
58+
checklist_data = []
59+
60+
for file_name, sheet_name in self.tasvs_files:
61+
url = self.repo_document_root_url + file_name
62+
response = requests.get(url)
63+
64+
if response.status_code == 200:
65+
content = response.text
66+
table_data = self.get_testing_checklist(content)
67+
if table_data:
68+
checklist_data.append((table_data, sheet_name))
69+
print(f"Checklist table extracted from {file_name}")
70+
else:
71+
print(
72+
f"Failed to download {file_name} with status code {response.status_code}"
73+
)
74+
75+
return checklist_data
76+
77+
78+
class ExcelPopulator:
79+
"""Class to handle the population of checklist data into an Excel template."""
80+
81+
def __init__(self, template_path, output_file_path, testing_notes_map):
82+
self.template_path = template_path
83+
self.output_file_path = output_file_path
84+
self.testing_notes_map = testing_notes_map
85+
self._prepare_workbook()
86+
87+
def _prepare_workbook(self):
88+
"""Create a copy of the original Excel file to work on."""
89+
shutil.copy(self.template_path, self.output_file_path)
90+
91+
def populate_spreadsheet(self, checklist_data, sheet_name):
92+
"""Populate the extracted content into the corresponding Excel sheet."""
93+
wb = openpyxl.load_workbook(self.output_file_path)
94+
95+
if sheet_name not in wb.sheetnames:
96+
print(f"Sheet '{sheet_name}' not found in the workbook.")
97+
return
98+
99+
sheet = wb[sheet_name]
100+
dropdown_options = ["Failed", "N/A", "Pending", "Reviewed"]
101+
dropdown = DataValidation(
102+
type="list", formula1=f'"{",".join(dropdown_options)}"', allow_blank=True
103+
)
104+
dropdown.error = "Invalid entry, please select from the dropdown options."
105+
dropdown.prompt = "Please select an option from the list."
106+
sheet.add_data_validation(dropdown)
107+
108+
notes_dict = {item[0]: item[1] for item in self.testing_notes_map}
109+
start_row = 12
110+
111+
for row_data in checklist_data:
112+
sheet[f"B{start_row}"] = row_data[0] if row_data[0] else ""
113+
sheet[f"C{start_row}"] = row_data[1] if row_data[1] else ""
114+
sheet[f"D{start_row}"] = row_data[2] if row_data[2] else ""
115+
sheet[f"E{start_row}"] = row_data[3] if row_data[3] else ""
116+
sheet[f"F{start_row}"] = row_data[4] if row_data[4] else ""
117+
118+
tasvs_id = row_data[0]
119+
if tasvs_id in notes_dict:
120+
sheet[f"K{start_row}"] = notes_dict[tasvs_id]
121+
122+
non_empty_cells = sum(1 for item in row_data if item)
123+
124+
if non_empty_cells == 2:
125+
for col in range(2, 3): # Columns B (2) to C (3)
126+
sheet.cell(row=start_row, column=col).font = Font(bold=True)
127+
sheet.row_dimensions[start_row].height = 26
128+
else:
129+
dropdown.add(sheet[f"G{start_row}"])
130+
sheet.row_dimensions[start_row].height = 90
131+
132+
start_row += 1
133+
134+
self._apply_global_formatting(sheet, start_row)
135+
wb.save(self.output_file_path)
136+
print(
137+
f"Data populated successfully into sheet '{sheet_name}' in {self.output_file_path}"
138+
)
139+
140+
def _apply_global_formatting(self, sheet, end_row):
141+
"""Apply global formatting across the sheet."""
142+
for row in range(12, end_row + 1):
143+
for col in range(2, 12):
144+
cell = sheet.cell(row=row, column=col)
145+
cell.alignment = Alignment(
146+
horizontal="left", vertical="center", wrap_text=True
147+
)
148+
149+
150+
class TASVSConversion:
151+
"""Main class to manage the TASVS conversion process."""
152+
153+
def __init__(
154+
self,
155+
repo_document_root_url,
156+
tasvs_files,
157+
template_path,
158+
output_file_path,
159+
testing_notes_map,
160+
):
161+
self.checklist_processor = ChecklistProcessor(
162+
repo_document_root_url, tasvs_files
163+
)
164+
self.excel_populator = ExcelPopulator(
165+
template_path, output_file_path, testing_notes_map
166+
)
167+
168+
def run(self):
169+
"""Run the complete TASVS conversion process."""
170+
checklist_data = self.checklist_processor.process_files()
171+
172+
for table_data, sheet_name in checklist_data:
173+
self.excel_populator.populate_spreadsheet(table_data, sheet_name)
174+
175+
176+
if __name__ == "__main__":
177+
repo_document_root_url = "https://raw.githubusercontent.com/OWASP/www-project-thick-client-application-security-verification-standard/main/document/1.0/"
178+
tasvs_files = [
179+
("04-TASVS-ARCH.md", "TASVS-ARCH"),
180+
("05-TASVS-CODE.md", "TASVS-CODE"),
181+
("06-TASVS-CONF.md", "TASVS-CONF"),
182+
("07-TASVS-CRYPTO.md", "TASVS-CRYPTO"),
183+
("08-TASVS-NETWORK.md", "TASVS-NETWORK"),
184+
("09-TASVS-STORAGE.md", "TASVS-STORAGE"),
185+
]
186+
template_path = os.path.join(
187+
os.path.dirname(os.path.realpath(__file__)), "TASVS_V0.99999999_orig.xlsx"
188+
)
189+
190+
response = requests.get(
191+
"https://api.github.com/repos/OWASP/www-project-thick-client-application-security-verification-standard/releases/latest"
192+
)
193+
194+
if response.status_code == 200:
195+
# Parse the JSON response
196+
latest_release = response.json()
197+
latest_tag = latest_release["tag_name"]
198+
else:
199+
print(f"Failed to retrieve latest release. Status code: {response.status_code}")
200+
# default the tag to something so it looks good in the filename
201+
latest_tag = "v1.0"
202+
203+
output_file_path = os.path.join(
204+
os.path.dirname(os.path.realpath(__file__)), f"TASVS_{latest_tag}.xlsx"
205+
)
206+
207+
# map for data to be inserted into col K "Testing notes". Format:
208+
# (TASVS-ID, Note, [hyperlink])
209+
#
210+
# fmt is to tell black to ignore the formatting
211+
# fmt: off
212+
testing_notes_map = [
213+
("TASVS-ARCH-1.1", "TASVS-ARCH-1.1 satisfied by TASVS-ARCH-1.3", ""),
214+
("TASVS-ARCH-1.2", "Fidelity score >= 80%", ""),
215+
("TASVS-ARCH-1.3", "External dependencies can be defined as out-of-scope where appropriate.", ""),
216+
("TASVS-ARCH-1.6", "Within prior 90 days", ""),
217+
("TASVS-CODE-1.1", "Recommended: OWASP ASVS", "https://owasp.org/www-project-application-security-verification-standard/"),
218+
("TASVS-CODE-2.5", "Example: C Hardening Cheat Sheet", "https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html"),
219+
("TASVS-CODE-3.1", "Recommended: OWASP Dependency-Check", "https://github.com/jeremylong/DependencyCheck"),
220+
("TASVS-CODE-3.3", "Recommended: BinSkim", "https://github.com/microsoft/binskim"),
221+
("TASVS-CODE-3.4", "E.g: Python=Bandit, C#=Security Code Scan. Fallback to OWASP cheatsheets and/or use SemGrep.", ""),
222+
("TASVS-CODE-3.6", "Case Study", "https://www.henricodolfing.com/2019/06/project-failure-case-study-knight-capital.html"),
223+
("TASVS-CODE-4.10", "Example: batbadbut research", "https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/"),
224+
("TASVS-CODE-6.2", "Also satisfies TASVS-CODE-6.1", ""),
225+
]
226+
# fmt: on
227+
228+
conversion = TASVSConversion(
229+
repo_document_root_url,
230+
tasvs_files,
231+
template_path,
232+
output_file_path,
233+
testing_notes_map,
234+
)
235+
conversion.run()

utils/Convert-TASVS-Excel/readme.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Populates Excel template used as a checklist during AppSec Audits
2+
3+
- Author: Dave Hanson
4+
- Version: 0.1
5+
- Usage: ./Convert-TASVS-Excel/convert-tasvs-excel.py
6+
7+
8+
9+
## Background
10+
11+
A standard is useless unless it can easily be applied practically. This script is designed to make it easier to do that. It will pull the md files straight from Github, extract the checklist section and then insert it into the correct tab in an excel template. Run it in the same directory as the template.
12+
13+
The template is an altered version of the MASVS one, I really liked the style and so i tried to reuse it. One day I would like to align our process with the ASVS and MASVS projects so we have a consistent approach.
14+
15+
I'd also like to integrate this script with the release process so that it gets automatically produced.
16+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
requests==2.32.3
2+
openpyxl==3.1.5
3+
pandas==2.2.2
4+
markdown==3.7
5+
Pillow

0 commit comments

Comments
 (0)