Skip to content

Commit c3337fc

Browse files
Merge branch 'master' into master
2 parents 8803ef9 + 9caeb3d commit c3337fc

File tree

8 files changed

+67
-59
lines changed

8 files changed

+67
-59
lines changed

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,11 @@ Here’s an outline of our upcoming features and enhancements for **PILLAR**:
8383
If you use **PILLAR** in your research or project, please cite our [paper](https://arxiv.org/abs/2410.08755) as follows:
8484

8585
```bash
86-
@article{pillar,
87-
title={PILLAR: an AI-Powered Privacy Threat Modeling Tool},
88-
author={Mollaeefar, Majid, Bissoli, Andrea and Ranise, Silvio},
89-
journal={arXiv preprint arXiv:2410.08755},
90-
url={https://arxiv.org/abs/2410.08755},
91-
year={2024}
86+
@inproceedings{pillar,
87+
title={PILLAR: LINDDUN Privacy Threat Modeling using LLMs},
88+
author={Mollaeefar, Majid, Bissoli, Andrea, Van Landuyt, Dimitri and Ranise, Silvio},
89+
journal={International Workshop on Privacy Engineering (IWPE25)},
90+
year={2025}
9291
}
9392
```
9493

llms/linddun_pro.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def linddun_pro_gen_markdown(threats):
4545
for threat in threats:
4646
color = match_number_color(match_category_number(threat["category"]))
4747
color_html = f"<p style='background-color:{color};color:#ffffff;'>"
48-
markdown_output += f"| {color_html}{threat['category']}</p> | {threat["source_id"].strip()} <br> {threat['source']} | {threat["data_flow_id"].strip()} <br> {threat['data_flow']} | {threat["destination_id"].strip()} <br> {threat['destination']} |\n"
48+
markdown_output += f"| {color_html}{threat['category']}</p> | {threat['source_id'].strip()} <br> {threat['source']} | {threat['data_flow_id'].strip()} <br> {threat['data_flow']} | {threat['destination_id'].strip()} <br> {threat['destination']} |\n"
4949
return markdown_output
5050

5151
def mapping_table(edge, category):

llms/prompts.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -85,32 +85,36 @@
8585
""",
8686
]
8787
def LINDDUN_GO_PREVIOUS_ANALYSIS_PROMPT(previous_analysis):
88-
return f"""
88+
return f"""
8989
I will provide you the detailed opinions and reasoning steps from your team,
9090
which has already analyzed the threat based on the questions. Use these
9191
reasonings as additional advice critically, note that they may be wrong. Do not
92-
copy others entire answer, modify the part you believe is wrong if you think
92+
copy other's entire answer, modify the part you believe is wrong if you think
9393
it is necessary, otherwise elaborate on it and why you think it is correct.
9494
This is the previous analysis from your team:
95-
'''
96-
{f"The Domain Expert thinks the threat is {"" if previous_analysis[0]["reply"] else "not "} present because {previous_analysis[0]["reason"]}." if previous_analysis[0] else ""}
97-
{f"The System Architect thinks the threat is {"" if previous_analysis[1]["reply"] else "not "} present because {previous_analysis[1]["reason"]}." if previous_analysis[1] else ""}
98-
{f"The Software Developer thinks the threat is {"" if previous_analysis[2]["reply"] else "not "} present because {previous_analysis[2]["reason"]}." if previous_analysis[2] else ""}
99-
{f"The Data Protection Officer thinks the threat is {"" if previous_analysis[3]["reply"] else "not "} present because {previous_analysis[3]["reason"]}." if previous_analysis[3] else ""}
100-
{f"The Legal Expert thinks the threat is {"" if previous_analysis[4]["reply"] else "not "} present because {previous_analysis[4]["reason"]}." if previous_analysis[4] else ""}
101-
{f"The Chief Information Security Officer thinks the threat is {"" if previous_analysis[5]["reply"] else "not "} present because {previous_analysis[5]["reason"]}." if previous_analysis[5] else ""}
102-
'''
103-
"""
95+
96+
{f"The Domain Expert thinks the threat is {'present' if previous_analysis[0]['reply'] else 'not present'} because {previous_analysis[0]['reason']}." if previous_analysis[0] else ""}
97+
98+
{f"The System Architect thinks the threat is {'present' if previous_analysis[1]['reply'] else 'not present'} because {previous_analysis[1]['reason']}." if previous_analysis[1] else ""}
99+
100+
{f"The Software Developer thinks the threat is {'present' if previous_analysis[2]['reply'] else 'not present'} because {previous_analysis[2]['reason']}." if previous_analysis[2] else ""}
101+
102+
{f"The Data Protection Officer thinks the threat is {'present' if previous_analysis[3]['reply'] else 'not present'} because {previous_analysis[3]['reason']}." if previous_analysis[3] else ""}
103+
104+
{f"The Legal Expert thinks the threat is {'present' if previous_analysis[4]['reply'] else 'not present'} because {previous_analysis[4]['reason']}." if previous_analysis[4] else ""}
105+
106+
{f"The Chief Information Security Officer thinks the threat is {'present' if previous_analysis[5]['reply'] else 'not present'} because {previous_analysis[5]['reason']}." if previous_analysis[5] else ""}
107+
"""
104108

105109
def LINDDUN_GO_USER_PROMPT(inputs, question, title, description):
106110
if not inputs["dfd_only"]:
107111

108112
prompt = f"""
109-
'''
113+
110114
APPLICATION TYPE: {inputs["app_type"]}
111115
TYPES OF DATA: {inputs["types_of_data"]}
112116
APPLICATION DESCRIPTION: {inputs["app_description"]}
113-
{f"""
117+
{f'''
114118
The user has also provided a Data Flow Diagram to describe the application.
115119
The DFD is described as a list of edges, connecting the "from" node to the
116120
"to" node. "typefrom" and "typeto" describe the type of the node, which can be
@@ -123,7 +127,8 @@ def LINDDUN_GO_USER_PROMPT(inputs, question, title, description):
123127
And this is the dictionary containing the trust boundaries:
124128
each boundary has "id", "name", "description", and "color" keys.
125129
{inputs["boundaries"]}
126-
""" if inputs["use_dfd"] else ""}
130+
''' if inputs["use_dfd"] else ""}
131+
127132
DATABASE_SCHEMA: {inputs["database"]}
128133
DATA POLICY: {inputs["data_policy"]}
129134
USER DATA CONTROL: {inputs["user_data_control"]}
@@ -366,7 +371,7 @@ def THREAT_MODEL_USER_PROMPT(
366371
APPLICATION TYPE: {inputs["app_type"]}
367372
TYPES OF DATA: {inputs["types_of_data"]}
368373
APPLICATION DESCRIPTION: {inputs["app_description"]}
369-
{f"""
374+
{f'''
370375
The user has also provided a Data Flow Diagram to describe the application.
371376
The DFD is described as a list of edges, connecting the "from" node to the
372377
"to" node. "typefrom" and "typeto" describe the type of the node, which can be
@@ -379,7 +384,9 @@ def THREAT_MODEL_USER_PROMPT(
379384
And this is the dictionary containing the trust boundaries:
380385
each boundary has "id", "name", "description", and "color" keys.
381386
{inputs["boundaries"]}
382-
""" if inputs["use_dfd"] else ""}
387+
388+
''' if inputs["use_dfd"] else ""}
389+
383390
DATABASE SCHEMA: {inputs["database"]}
384391
DATA POLICY: {inputs["data_policy"]}
385392
USER DATA CONTROL: {inputs["user_data_control"]}

llms/risk_assessment.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def linddun_pro_gen_individual_markdown(threat):
5252

5353
color = match_number_color(match_category_number(threat["category"]))
5454
color_html = f"<p style='background-color:{color};color:#ffffff;'>"
55-
markdown_output += f"| {color_html}{threat['category']}</p> | {threat["description"]} |\n"
55+
markdown_output += f"| {color_html}{threat['category']}</p> | {threat['description']} |\n"
5656
return markdown_output
5757

5858
def measures_gen_markdown(measures):
@@ -73,7 +73,7 @@ def measures_gen_markdown(measures):
7373
markdown_output += "|--------|-------|-----|\n"
7474

7575
for measure in measures:
76-
markdown_output += f"| [{measure['title']}](https://privacypatterns.org/patterns/{measure["filename"]}) | {measure['explanation']} | {measure['implementation']} |\n"
76+
markdown_output += f"| [{measure['title']}](https://privacypatterns.org/patterns/{measure['filename']}) | {measure['explanation']} | {measure['implementation']} |\n"
7777
return markdown_output
7878

7979

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def init_session_state():
111111
]
112112
st.session_state["input"]["graph"] = graphviz.Digraph()
113113
st.session_state["input"]["graph"].attr(
114-
bgcolor=f"{st.get_option("theme.backgroundColor")}",
114+
bgcolor=f"{st.get_option('theme.backgroundColor')}",
115115
)
116116
if "backup_database" not in st.session_state:
117117
# "backup_database" is a list of dictionaries that stores the backup of the database information, to be able to restore it if needed

misc/utils.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Copyright 2024 Fondazione Bruno Kessler
2-
#
2+
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
55
# You may obtain a copy of the License at
6-
#
6+
#
77
# https://www.apache.org/licenses/LICENSE-2.0
8-
#
8+
#
99
# Unless required by applicable law or agreed to in writing, software
1010
# distributed under the License is distributed on an "AS IS" BASIS,
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,10 +14,10 @@
1414
def match_color(threat_type):
1515
"""
1616
This function matches the letter of a LINDDUN category to a hex color value, based on the LINDDUN color scheme.
17-
17+
1818
Args:
1919
threat_type (str): The LINDDUN category, in the form of the first letter: "L", "I", "Nr", "D ", "Dd", "U" or "Nc".
20-
20+
2121
Returns:
2222
str: The color associated with the category, as a hex value.
2323
"""
@@ -45,7 +45,7 @@ def match_letter(threat_type_number):
4545
4646
Args:
4747
threat_type_number (int): The LINDDUN category, in the form of a number from 1 to 7.
48-
48+
4949
Returns:
5050
str: The letter associated with the category, in the form of "L", "I", "Nr", "D ", "Dd", "U" or "Nc".
5151
"""
@@ -64,26 +64,28 @@ def match_letter(threat_type_number):
6464
elif threat_type_number == 7:
6565
return "Nc"
6666

67+
6768
def match_number_color(threat_type_number):
6869
"""
6970
This function matches the number of a LINDDUN category to the specific color, based on the LINDDUN color scheme.
7071
7172
Args:
7273
threat_type_number (int): The LINDDUN category, in the form of a number from 1 to 7.
73-
74+
7475
Returns:
7576
str: The color associated with the category, as a hex value.
7677
"""
7778
letter = match_letter(threat_type_number)
7879
return match_color(letter)
7980

81+
8082
def match_category_number(category):
8183
"""
8284
This function matches the LINDDUN category to the specific number.
8385
8486
Args:
8587
category (str): The LINDDUN category, in the form of "Linking", "Identifying", etc.
86-
88+
8789
Returns:
8890
int: The number associated with the category, in the form of a number from 1 to 7.
8991
"""
@@ -102,13 +104,14 @@ def match_category_number(category):
102104
elif category == "Non-compliance":
103105
return 7
104106

107+
105108
def match_number_category(threat_type_number):
106109
"""
107110
This function matches the number of a LINDDUN category to the specific category.
108111
109112
Args:
110113
threat_type_number (int): The LINDDUN category, in the form of a number from 1 to 7.
111-
114+
112115
Returns:
113116
str: The category associated with the number, in the form of "Linking", "Identifying", etc.
114117
"""
@@ -128,7 +131,6 @@ def match_number_category(threat_type_number):
128131
return "Non-compliance"
129132

130133

131-
132134
def format_correct(state):
133135
"""
134136
This function formats the schema in the correct format for the data
@@ -137,7 +139,7 @@ def format_correct(state):
137139
columns of the data editor. Thus, it transforms the DFD from a list of
138140
dictionaries to a dictionary of lists, or essentially from a row-based
139141
to a column-based format.
140-
142+
141143
Args:
142144
state (list): The DFD in the format of a list of dictionaries, with
143145
each dictionary representing a row of the data editor. Each dictionary
@@ -147,9 +149,9 @@ def format_correct(state):
147149
keys as the elements in the list of dictionaries, where each list
148150
represents a column of the data editor.
149151
"""
150-
152+
151153
# Extract keys directly from the first dictionary in the state list
152154
keys = state[0].keys()
153-
155+
154156
# Use dictionary comprehension and zip to transpose the list of dictionaries
155-
return {key: [d[key] for d in state] for key in keys}
157+
return {key: [d[key] for d in state] for key in keys}

tabs/linddun_pro.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def linddun_pro():
8686

8787
# Display the threats for the selected edge
8888
if st.session_state["linddun_pro_threats"][st.session_state["edge_num"]]:
89-
st.markdown(f"### Threats found for DF{st.session_state["edge_num"]}")
89+
st.markdown(f"### Threats found for DF{st.session_state['edge_num']}")
9090
markdown = linddun_pro_gen_markdown(st.session_state["linddun_pro_threats"][st.session_state["edge_num"]])
9191
st.markdown(markdown, unsafe_allow_html=True)
9292
st.session_state["linddun_pro_output"] = markdown

tabs/report.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ def generate_report():
154154
if object["trusted"]:
155155
c.node(object["to"])
156156
for (i, object) in enumerate(st.session_state["input"]["dfd"]):
157-
graph.node(object["from"], shape=f"{"box" if object["typefrom"] == "Entity" else "ellipse" if object["typefrom"] == "Process" else "cylinder"}")
158-
graph.node(object["to"], shape=f"{"box" if object["typeto"] == "Entity" else "ellipse" if object["typeto"] == "Process" else "cylinder"}")
157+
graph.node(object["from"], shape=f"{'box' if object['typefrom'] == 'Entity' else 'ellipse' if object['typefrom'] == 'Process' else 'cylinder'}")
158+
graph.node(object["to"], shape=f"{{'box' if object['typeto'] == 'Entity' else 'ellipse' if object['typeto'] == 'Process' else 'cylinder'}}")
159159
graph.edge(object["from"], object["to"], taillabel=f"DF{i}", constraint="false")
160160

161161
# Add the graph to the report as an SVG image
162-
text += f"![Data Flow Diagram](data:image/svg+xml,{urllib.parse.quote(graph.pipe(encoding="utf-8"))})\n"
162+
text += f"![Data Flow Diagram](data:image/svg+xml,{urllib.parse.quote(graph.pipe(encoding='utf-8'))})\n"
163163

164164
# Add the threats found with the selected methodology to the report
165165
if st.session_state["threat_source"] == "threat_model":
@@ -180,7 +180,7 @@ def generate_report():
180180
colgroup_html = "<colgroup>" + "".join([f"<col style='width: {width}%;'>" for width in column_widths]) + "</colgroup>"
181181
html = html.replace("<table>", f"<table table-layout='fixed'>{colgroup_html}", 1)
182182
html = html.replace(f"<td><strong>{description_message}</strong></td>\n<td>{st.session_state['high_level_description']}</td>\n<td></td>\n<td></td>",
183-
f"<td><strong>{description_message}</strong></td>\n<td colspan='3'>{st.session_state["high_level_description"]}</td>\n", 1)
183+
f"<td><strong>{description_message}</strong></td>\n<td colspan='3'>{st.session_state['high_level_description']}</td>\n", 1)
184184

185185

186186
# Add the CSS styles to the HTML
@@ -236,16 +236,16 @@ def from_threat_model(text):
236236
text += "## Threats found with the simple threat model\n"
237237
for (i, threat) in enumerate(st.session_state["to_assess"]):
238238
if st.session_state["to_report"][i]:
239-
text += f"## Threat {i+1}: {threat["title"]}\n\n"
239+
text += f"## Threat {i+1}: {threat['title']}\n\n"
240240
color = match_color(threat["threat_type"])
241241
color_html = f"<span style='background-color:{color};color:#ffffff;'>"
242242
text += f"**Category**: {color_html}{threat['threat_type']}</span>\n\n"
243243
text += f"**Reason for detection**: {threat['Reason']}\n\n"
244244
text += f"**Scenario**: {threat['Scenario']}\n\n"
245245
if st.session_state["assessments"][i]["impact"]:
246-
text += f"**Impact assessment**: {st.session_state["assessments"][i]["impact"]}\n\n"
246+
text += f"**Impact assessment**: {st.session_state['assessments'][i]['impact']}\n\n"
247247
if st.session_state["control_measures"][i]:
248-
text += f"**Suggested control measures**: \n\n{measures_gen_markdown(st.session_state["control_measures"][i])}\n\n"
248+
text += f"**Suggested control measures**: \n\n{measures_gen_markdown(st.session_state['control_measures'][i])}\n\n"
249249

250250
return text
251251

@@ -256,16 +256,16 @@ def from_linddun_go(text):
256256
text += "## Threats found with the LINDDUN Go methodology\n"
257257
for (i, threat) in enumerate(st.session_state["to_assess"]):
258258
if st.session_state["to_report"][i]:
259-
text += f"## Threat {i+1}: {threat["threat_title"]}\n\n"
259+
text += f"## Threat {i+1}: {threat['threat_title']}\n\n"
260260
color = match_number_color(threat["threat_type"])
261261
color_html = f"<span style='background-color:{color};color:#ffffff;'>"
262-
text += f"**Category**: {color_html}{match_letter(threat["threat_type"])} - {match_number_category(threat["threat_type"])}</span>\n\n"
262+
text += f"**Category**: {color_html}{match_letter(threat['threat_type'])} - {match_number_category(threat['threat_type'])}</span>\n\n"
263263
text += f"**Threat description**: {threat['threat_description']}\n\n"
264264
text += f"**Reason for detection**: {threat['reason']}\n\n"
265265
if st.session_state["assessments"][i]["impact"]:
266-
text += f"**Impact assessment**: {st.session_state["assessments"][i]["impact"]}\n\n"
266+
text += f"**Impact assessment**: {st.session_state['assessments'][i]['impact']}\n\n"
267267
if st.session_state["control_measures"][i]:
268-
text += f"**Suggested control measures**: \n\n{measures_gen_markdown(st.session_state["control_measures"][i])}\n\n"
268+
text += f"**Suggested control measures**: \n\n{measures_gen_markdown(st.session_state['control_measures'][i])}\n\n"
269269

270270
return text
271271

@@ -276,23 +276,23 @@ def from_linddun_pro(text):
276276
text += "## Threats found with the LINDDUN Pro methodology\n"
277277
for (i, threat) in enumerate(st.session_state["to_assess"]):
278278
if st.session_state["to_report"][i]:
279-
text += f"## Threat {i+1}: {threat["threat_title"]}\n\n"
279+
text += f"## Threat {i+1}: {threat['threat_title']}\n\n"
280280
color = match_number_color(match_category_number(threat["category"]))
281281
color_html = f"<span style='background-color:{color};color:#ffffff;'>"
282-
text += f"**Category**: {color_html}{match_letter(match_category_number(threat["category"]))} - {threat["category"]}</span>\n\n"
282+
text += f"**Category**: {color_html}{match_letter(match_category_number(threat['category']))} - {threat['category']}</span>\n\n"
283283
text += f"**DFD edge**: "
284284
# Underline the source, data flow, or destination node in the edge, depending on the threat location
285285
if threat["threat_location"] == "source":
286-
text += f"<u>{threat['edge']['from']}</u>, DF{threat["data_flow_number"]}, {threat['edge']['to']}\n\n"
286+
text += f"<u>{threat['edge']['from']}</u>, DF{threat['data_flow_number']}, {threat['edge']['to']}\n\n"
287287
elif threat["threat_location"] == "data_flow":
288-
text += f"{threat['edge']['from']}, <u>DF{threat["data_flow_number"]}</u>, {threat['edge']['to']}\n\n"
288+
text += f"{threat['edge']['from']}, <u>DF{threat['data_flow_number']}</u>, {threat['edge']['to']}\n\n"
289289
elif threat["threat_location"] == "destination":
290-
text += f"{threat['edge']['from']}, DF{threat["data_flow_number"]}, <u>{threat['edge']['to']}</u>\n\n"
290+
text += f"{threat['edge']['from']}, DF{threat['data_flow_number']}, <u>{threat['edge']['to']}</u>\n\n"
291291
text += f"**Threat tree involved nodes**: {threat['threat_tree_node']}\n\n"
292292
text += f"**Threat description**: {threat['description']}\n\n"
293293
if st.session_state["assessments"][i]["impact"]:
294-
text += f"**Impact assessment**: {st.session_state["assessments"][i]["impact"]}\n\n"
294+
text += f"**Impact assessment**: {st.session_state['assessments'][i]['impact']}\n\n"
295295
if st.session_state["control_measures"][i]:
296-
text += f"**Suggested control measures**: \n\n{measures_gen_markdown(st.session_state["control_measures"][i])}\n\n"
296+
text += f"**Suggested control measures**: \n\n{measures_gen_markdown(st.session_state['control_measures'][i])}\n\n"
297297

298298
return text

0 commit comments

Comments
 (0)