# SPDX-FileCopyrightText: 2025 German Aerospace Center (DLR)
#
# SPDX-License-Identifier: BSD-3-Clause
# import re
from flowstrider import settings
from flowstrider.models import dataflowdiagram
from flowstrider.models.common_models import Edge, Node
from flowstrider.rules import attributes_dict
from flowstrider.rules.common_rules import (
DataflowDiagramRule,
DataflowDiagramRuleCollection,
EdgeRule,
NodeRule,
meet_any_requirement,
)
tag_interactor = "STRIDE:Interactor"
tag_process = "STRIDE:Process"
tag_datastore = "STRIDE:DataStore"
# Rules derived from LINDDUN https://linddun.org/
# ...especially the LINDDUN GO cards https://linddun.org/go/
# ===== LINKING: ==========================================
class L1_LinkedUserRequests(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "L1"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Linked User Requests")
cls.short_description = _(
"User requests can be linked because they contain a unique identifier"
)
cls.long_description = (
_(
"A unique identifier means different requests/data can be linked to a"
+ " singular user profile or a specific group. Unique identifiers can"
+ " exist globally or locally, within the system or across the context"
+ " boundary. Examples: IP address or email address. Even if the"
+ " identifier does not reveal one's identity directly, accumulated"
+ " amounts of personal data can lead to 'identifying' threats."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["transmits_unique_user_id"]
cls.mitigation_options = [_("Remove unique identifiers")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
potentially_transmits_uuid = not meet_any_requirement(
edge.attributes.get(cls.attribute_names[0], True), [False]
)
return potentially_transmits_uuid
class L2_LinkableUserRequests(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "L2"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Linkable User Requests Through Combination")
cls.short_description = _(
"User requests can be linked because they contain attributes that can be"
+ " combined into quasi-identifiers"
)
cls.long_description = (
_(
"Many requests contain a lot of different properties that, when"
+ " combined, pose quasi-identifiers enabling the linking to unique"
+ " individuals or groups. Examples for these properties: OS, browser,"
+ " display size, language. Even if the properties do not reveal one's"
+ " identity directly, accumulated amounts of personal data can lead to"
+ " 'identifying' threats."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["transmits_user_properties"]
cls.mitigation_options = [_("Minimize user properties being transmitted")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
potentially_transmits_quasi_id = not meet_any_requirement(
edge.attributes.get(cls.attribute_names[0], True), [False]
)
return potentially_transmits_quasi_id
class L3_LinkableUserPatterns(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "L3"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Linkable User Requests Through Patterns")
cls.short_description = _(
"Patterns in the (meta)data contained in user requests can be used to link"
+ " them to each other"
)
cls.long_description = (
_(
"Profiles can be constructed to distinguish users from one another and"
+ " accumulate associated data. Dinstinguishing can happen based on"
+ " things like the timing of messages, the writing style or other"
+ " message patterns. Even if this data does not reveal one's identity"
+ " directly, accumulated amounts of personal data can lead to"
+ " 'identifying' threats."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["transmits_user_data"]
cls.mitigation_options = [
_("Transmit decoy requests"),
_("Minimize user properties being transmitted"),
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
potentially_transmits_metadata = not meet_any_requirement(
edge.attributes.get(cls.attribute_names[0], True), [False]
)
return potentially_transmits_metadata
class L4_LinkableDataset(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "L4"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Linkable Dataset")
cls.short_description = _("Stored personal data can be linked to individuals")
cls.long_description = (
_(
"Data can contain a lot of different properties that, when combined,"
+ " pose quasi-identifiers enabling the linking of data to unique"
+ " individuals or groups. An example would be querying average salary"
+ " with a strict set of criteria to reveal the salary of an individual"
+ " employee. Even if the properties do not reveal one's identity"
+ " directly, accumulated amounts of personal data can lead to"
+ " 'identifying' threats."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["handles_user_data"]
cls.mitigation_options = [_("Minimize collection and storage of user data")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
could_handle_user_data = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return is_data_store and could_handle_user_data
class L5_ProfilingUsers(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "L5"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Profiling Users")
cls.short_description = _(
"Users can be profiled by analyzing their data for patterns"
)
cls.long_description = (
_(
"It may be possible to derive data about individuals by analyzing their"
+ " data. Adversaries could try to collect as much detailed data as"
+ " possible to link data that wasn't intended to be linked. Example:"
+ " Infering a persons medical condition by the frequency of data"
+ " exchanges with a health monitoring machine."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["handles_user_data"]
cls.mitigation_options = [
_("Minimize collection and storage of user data"),
_("Leave out unnecessarily detailed data"),
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_process = tag_process in node.tags
could_handle_user_data = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return is_process and could_handle_user_data
# ===== Identifying: ======================================
class I1_IdentifiedUserRequests(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "I1"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Identified User Requests")
cls.short_description = _(
"The incoming user requests contain data that directly reveal the user"
+ " identity"
)
cls.long_description = (
_(
"Individuals may be identified directly through data sent to the system"
+ " such as their full name. Identified data can severely amplify the"
+ " impact of future data breaches and needs stronger security"
+ " measures."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["transmits_user_identity"]
cls.mitigation_options = [_("Minimize transmission of user identities")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
could_transmit_ui = not meet_any_requirement(
edge.attributes.get(cls.attribute_names[0], True), [False]
)
return could_transmit_ui
class I2_IdentifiableUserRequests(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "I2"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Identifiable User Requests")
cls.short_description = _(
"The user can be identified because the data in their requests can be used"
+ " to infer who they are"
)
cls.long_description = (
_(
"Individuals may be identified through data sent to the system. This"
+ " data does not have to be identity information but can still be"
+ " unintentionally specific to a user. Examples: looking up nearby"
+ " businesses, info about a rare illness or specific timing."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["transmits_user_data"]
cls.mitigation_options = [_("Minimize transmission of user data")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
could_transmit_user_data = not meet_any_requirement(
edge.attributes.get(cls.attribute_names[0], True), [False]
)
return could_transmit_user_data
class I3_IdentifiableDataFlows(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "I3"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Identifiable Data Flows")
cls.short_description = _(
"Data sent to the system is sufficiently revealing to identify the user"
)
cls.long_description = (
_(
"Individuals may be identified through identifiable attributes in"
+ " user-submitted data. For example in a feedback form."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["transmits_user_data"]
cls.mitigation_options = [_("Minimize transmission of user data")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
could_transmit_user_data = not meet_any_requirement(
edge.attributes.get(cls.attribute_names[0], True), [False]
)
return could_transmit_user_data
class I4_IdentifiableDataRequests(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "I4"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Identifiable Data Requests")
cls.short_description = _("Communication contains (quasi-)identifiers")
cls.long_description = (
_(
"Individuals may be identified through quasi-identifiers such as"
+ " IP-address or email-address. The use of pseudonyms to refer to"
+ " individuals may also lead to the identification of the person"
+ " behind it. The possibility of identification rises with the amount"
+ " of data connected with the quasi-identifier and the number of"
+ " services using it."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["transmits_unique_user_id"]
cls.mitigation_options = [
_("Remove unique identifiers"),
_("Don't reuse identifiers in another service"),
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
could_transmit_uui = not meet_any_requirement(
edge.attributes.get(cls.attribute_names[0], True), [False]
)
return could_transmit_uui
class I5_IdentifiableDataset(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "I5"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Identifiable Dataset")
cls.short_description = _("Stored data can be used to identify individuals")
cls.long_description = (
_(
"Individuals may be identified through connection of unique references"
+ " to an individual or their data. The use of pseudonyms to refer to"
+ " individuals may lead to the identification of the person behind it."
+ " The possibility of identification rises with the amount of data"
+ " connected with the quasi-identifier and the number of services"
+ " using it."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["handles_user_data"]
cls.mitigation_options = [
_("Minimize collection and storage of user data"),
_("Don't reuse identifiers in another service"),
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
could_handle_user_data = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return is_data_store and could_handle_user_data
# ===== Non-Repudiation: ==================================
class Nr1_NonRepudiationOfServiceUsage(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nr1"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Non-Repudiation of Service Usage")
cls.short_description = _(
"Users cannot deny having used a service because of authentication or"
+ " logged access"
)
cls.long_description = (
_(
"If a service stores credentials with identity information, the"
+ " individuals deniability gets affected. For example: log files"
+ " linking an entry in an internal complaint system to an individual"
+ " employee."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["logs_access"]
cls.mitigation_options = [
_(
"If deniability is required, do not store the data at all or remove any"
+ " attributable data."
),
_("Avoid credentials with identity information."),
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_process = tag_process in node.tags
could_log_access = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return is_process and could_log_access
class Nr2_NonRepudiationOfSending(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nr2"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Non-Repudiation of Sending")
cls.short_description = _("Users cannot deny having sent a message")
cls.long_description = (
_(
"Sent or uploaded data that is digitally signed affects the individuals"
+ " deniability. For example: signed emails but also documents,"
+ " requests, etc."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["transmits_signed_data"]
cls.mitigation_options = [_("Don't require data to be signed")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
could_sign_data = not meet_any_requirement(
edge.attributes.get(cls.attribute_names[0], True), [False]
)
return could_sign_data
class Nr3_NonRepudiationOfReceipt(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nr3"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Non-Repudiation of Receipt")
cls.short_description = _("Users cannot deny having received a message")
cls.long_description = (
_(
"If (passive) interactions with the system, such as receiving a"
+ " message, have side-effects like logging, the deniability of receipt"
+ " gets affected for the recipient."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["logs_receipt"]
cls.mitigation_options = [
_("Don't require read receipts from the recipient"),
_("Don't log user's browser histories"),
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_process = tag_process in node.tags
could_log_receipt = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return is_process and could_log_receipt
class Nr4_NonRepudiationOfStorage(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nr4"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Non-Repudiation of Storage")
cls.short_description = _(
"Users cannot deny claims about data stored in non-repudiable storage"
)
cls.long_description = (
_(
"If data stored in a database is digitally signed, the repudiation of"
+ " users gets affected. An example would be append-only storage"
+ " systems like blockchains where it is impossible for the data"
+ " subject to later remove their personal data."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["stores_signed_data"]
cls.mitigation_options = [_("Don't require data to be signed")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
could_store_signed_data = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return is_data_store and could_store_signed_data
class Nr5_NonRepudiationOfMetadata(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nr5"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Non-Repudiation of Hidden Data or Metadata")
cls.short_description = _(
"Hidden or metadata in a document prevent users from denying claims"
+ " associated with it"
)
cls.long_description = (
_(
"Metadata, hidden data or specific patterns in stored or transmitted"
+ " data may lead to undesirable deniability issues. For example,"
+ " author or revision metadata in documents or data watermarked with"
+ " hidden artifacts prevents users from denying claims about the data."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["stores_user_associated_metadata"]
cls.mitigation_options = [_("Minimize included metadata")]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
could_store_metadata = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return is_data_store and could_store_metadata
# ===== Detecting: ========================================
class D1_DetectableUsers(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "D1"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Detectable Users")
cls.short_description = _(
"Inferring the existence of a user from the system's response"
)
cls.long_description = (
_(
"Systems may unintentionally reveal the existence of a user by the way"
+ " status messages respond to queries. This is especially relevant"
+ " with informational messages, warnings or errors which respond"
+ " differently when a user does not exist compared to not having"
+ " access rights. An example would be a 'wrong password' error"
+ " message revealing the existence of the account. Even though no"
+ " contents have been leaked, the existence of certain items alone can"
+ " be a stepping stone to security threats."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["discloses_responses", "handles_user_data"]
cls.mitigation_options = [
_(
"Prevent information leakage by not revealing the existence of items in"
+ " system responses"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
is_process = tag_process in node.tags
could_handle_user_data = not meet_any_requirement(
node.attributes.get(cls.attribute_names[1], True), [False]
)
could_disclose_responses = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return (
(is_process or is_data_store)
and could_handle_user_data
and could_disclose_responses
)
class D2_DetectableServiceUsage(EdgeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "D2"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Detectable Service Usage")
cls.short_description = _(
"Detecting communication between a service and its users"
)
cls.long_description = (
_(
"If the communication from a user to a service can be observed,"
+ " information may be inferred from that. For example, communication"
+ " with the Tor network can be detected even though the destination is"
+ " concealed. Especially in sensitive contexts (medical,"
+ " whistleblower) the detection of service usage alone may have a"
+ " severe impact."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names1 = ["is_private_network"]
cls.mitigation_options = (
_("Minimize communications outside of private networks"),
_("Transmit decoy data"),
)
cls.requirement = _(
"Every communication outside of trust boundaries with property: "
+ "'{private_network} = True' will trigger this rule"
).format(
private_network=attributes_dict.attributes[
cls.attribute_names1[0]
].display_name
)
@classmethod
def _test(cls, edge: Edge, dfd: dataflowdiagram.DataflowDiagram) -> bool:
source_clusters = dfd.get_clusters_for_node_id(edge.source_id)
sink_clusters = dfd.get_clusters_for_node_id(edge.sink_id)
source_sink_in_same_private_net = False
# If sink and source aren't in the same private network cluster, trigger
# ...(even if both are in a private network (but separate ones), the edge could
# ...traverse a public network)
for cluster in source_clusters:
if meet_any_requirement(
cluster.attributes.get(cls.attribute_names1[0], False), [True]
):
for cluster2 in sink_clusters:
if cluster.id == cluster2.id:
source_sink_in_same_private_net = True
if not source_sink_in_same_private_net:
return True
class D3_DetectableEvents(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "D3"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Detectable Events")
cls.short_description = _(
"Detecting side effects or communications triggered by application events"
)
cls.long_description = (
_(
"Various (unknown) side effects may lead to the detection of used"
+ " applications or user actions. This can include log files on a"
+ " shared system, traces of temporary files by deleted applications"
+ " or the size of data. Sensitive information may be deduced from the"
+ " observation of these side effects."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["leaves_usage_traces"]
cls.mitigation_options = [
_(
"Ensure that all log files get deleted and that deleted data doesn't "
+ "leave traces"
),
_("Dummy traffic may be able to conceal the actual traffic"),
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
is_process = tag_process in node.tags
could_leave_traces = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return (is_process or is_data_store) and could_leave_traces
class D4_DetectableRecords(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "D4"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Detectable Records")
cls.short_description = _("Detecting the existence of records in a system")
cls.long_description = (
_(
"Systems may unintentionally reveal the existence of data by the way"
+ " status messages respond to queries. This is especially relevant"
+ " with informational messages, warnings or errors which respond"
+ " differently when an item does not exist compared to not having"
+ " access rights. An example would be an 'insufficient access rights'"
+ " error message revealing the existence of a specific record. Even"
+ " though no contents have been leaked, the existence of certain items"
+ " alone can be a stepping stone to security threats."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["discloses_responses"]
cls.mitigation_options = [
_(
"Prevent information leakage by not revealing the existence of items in"
+ " system responses"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = False"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
is_process = tag_process in node.tags
could_disclose_responses = not meet_any_requirement(
node.attributes.get(cls.attribute_names[0], True), [False]
)
return (is_process or is_data_store) and could_disclose_responses
# ===== Data Disclosure: ==================================
class DD1_ExcessivelySensitiveData(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "DD1"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Excessively Sensitive Data Collected")
cls.short_description = _(
"The system acquires more sensitive or finegrained data than strictly"
+ " necessary for its functionality"
)
cls.long_description = (
_(
"It should be considered if certain data should really be collected in"
+ " terms of the data being too sensitive, more fine-grained than"
+ " strictly necessary or it being unnecessary metadata. For example, a"
+ " camera application does not necessarily need to record the pictures"
+ " location. Processing excessively sensitive data poses a bigger risk"
+ " in case of potential data breaches."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = [
"only_necessary_data_collected",
"handles_confidential_data",
"handles_personal_data",
]
cls.mitigation_options = [
_(
"Assess whether all the data is genuinely necessary for providing the"
+ " system's functionality"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
is_process = tag_process in node.tags
collects_only_nec_data = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
could_handle_confidential = not meet_any_requirement(
node.attributes.get(cls.attribute_names[1], True), [False]
)
could_handle_personal = not meet_any_requirement(
node.attributes.get(cls.attribute_names[2], True), [False]
)
return (
(is_process or is_data_store)
and (could_handle_confidential or could_handle_personal)
and not collects_only_nec_data
)
class DD2_ExcessiveDataAmount(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "DD2"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Excessive Amount of Data Collected")
cls.short_description = _(
"The system acquires more data than strictly needed for its functionality"
)
cls.long_description = (
_(
"It should be considered if the amount of data collected is too large,"
+ " the processing happens too frequently or if there are more data"
+ " subjects involved than necessary. For example, posts on social"
+ " networks may include personal data about other individuals."
+ " Processing excessive amounts of data poses a bigger risk as it may"
+ " give way to privacy threats such as pattern analysis."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["only_necessary_data_collected"]
cls.mitigation_options = [
_(
"Evaluate whether regular data collection is necessary for the system's"
+ " functionality."
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
is_process = tag_process in node.tags
collects_only_nec_data = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
return (is_process or is_data_store) and not collects_only_nec_data
class DD3_UnnecessaryDataAnalysis(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "DD3"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Unnecessary Data Analysis")
cls.short_description = _(
"Data is further processed, analyzed, or enriched in a way that is not"
+ " strictly necessary for the functionality"
)
cls.long_description = (
_(
"A system should not enrich/analyze the data more than it is necessary"
+ " for the system's functionality. For example, a camera application"
+ " does not need to perform face-based recognition. Processing of data"
+ " can be used to learn additional sensitive information."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["only_necessary_data_analyzed"]
cls.mitigation_options = [
_(
"Evaluate which types of personal data processing are necessary for"
+ " providing the system's functionality."
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
is_process = tag_process in node.tags
analyzes_only_nec_data = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
return (is_process or is_data_store) and not analyzes_only_nec_data
class DD4_UnnecessaryDataRetention(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "DD4"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Unnecessary Data Retention")
cls.short_description = _("Data is stored for longer than needed")
cls.long_description = (
_(
"If data is stored for a longer time than necessary it poses a privacy"
+ " risk in case of a data breach. For example, storing email addresses"
+ " of newsletter subscribers long after they have unsubscribed."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["data_retention_minimized"]
cls.mitigation_options = [
_(
"Evaluate your storage policies. Consider how long you store personal"
+ " data and whether you have a process to remove data you no longer"
+ " need."
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
retention_minimized = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
return is_data_store and not retention_minimized
class DD5_OverexposurePersonalData(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "DD5"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Overexposure of Personal Data")
cls.short_description = _(
"Personal data is shared with more services or external parties than"
+ " necessary"
)
cls.long_description = (
_(
"For personal data it should be considered to only send it to"
+ " recipients who need it, to not involve unnecessary parties and keep"
+ " the accessibility as low as possible. For example, medical data"
+ " should not be made publicly available and location data from a"
+ " navigation application should not be propagated to the calender or"
+ " mail. Overexposure of personal data may lead to unintended"
+ " consequences, as others could reuse the data for unforeseen"
+ " purposes."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["data_sharing_minimized", "handles_personal_data"]
cls.mitigation_options = [
_(
"Carefully assess the necessity of sharing personal data and ensure"
+ " that the involved parties genuinely require access to that data."
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_data_store = tag_datastore in node.tags
is_process = tag_process in node.tags
sharing_minimized = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
could_handle_personal = not meet_any_requirement(
node.attributes.get(cls.attribute_names[1], True), [False]
)
return (
(is_data_store or is_process)
and could_handle_personal
and not sharing_minimized
)
# ===== Unawareness and Unintervenability: ================
class U1_InsufficientTransparency(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "U1"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Insufficient Transparency")
cls.short_description = _(
"Data subjects are insufficiently informed about the collection and"
+ " processing of their personal data"
)
cls.long_description = (
_(
"Data subjects should be sufficiently informed about what kind of"
+ " personal"
+ " data is collected, the purposes and methods of involved processing"
+ " and with whom the data is being shared in a clear and"
+ " understandable way. For example, awareness about traffic cameras"
+ " collecting facial images next to number plates. Insufficient"
+ " transparency may lead to data subjects being unaware about the"
+ " utilization of their personal data, influencing their right to"
+ " privacy."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["data_collection_informed", "is_user"]
cls.mitigation_options = [
_(
"Data subjects must also be informed on any indirect data collection,"
+ " i.e. from third parties."
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_interactor = tag_interactor in node.tags
data_coll_informed = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
could_be_user = not meet_any_requirement(
node.attributes.get(cls.attribute_names[1], True), [False]
)
return is_interactor and could_be_user and not data_coll_informed
class U2_InsufficientTransparencyOthers(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "U2"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Insufficient Information when Sharing Data of Others")
cls.short_description = _(
"When sharing personal data of others, users are insufficiently informed"
+ " about the further data processing"
)
cls.long_description = (
_(
"Data subjects should be sufficiently informed about what kind of data"
+ " of others is collected, the purposes and methods of involved"
+ " processing and with whom the data is being shared in a clear and"
+ " understandable way. For example, a user posting a picture on social"
+ " media may not be aware that others in the picture are automatically"
+ " tagged with a facial recognition system. Insufficient transparency"
+ " means, users may not realize they are unintentionally sharing"
+ " personal data of other individuals."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["data_collection_informed", "is_user"]
cls.mitigation_options = [
_(
"Data subjects must also be informed on any processing of personal data"
+ " from others"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_interactor = tag_interactor in node.tags
data_coll_informed = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
could_be_user = not meet_any_requirement(
node.attributes.get(cls.attribute_names[1], True), [False]
)
return is_interactor and could_be_user and not data_coll_informed
class U3_InsufficientPrivacyControls(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "U3"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Insufficient Privacy Controls")
cls.short_description = _(
"Data subjects have insufficient controls to manage their preferences"
)
cls.long_description = (
_(
"Users should be given the option to configure what personal data is"
+ " processed and for what purposes. They should also be able alter"
+ " their preferences afterwards. Appropriate control mechanisms are"
+ " required to record data subject preferences and keep track of how"
+ " the data may be further processed."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["personal_data_preferences", "is_user"]
cls.mitigation_options = [
_("Privacy-frendly settings should be the default."),
_(
"Nudging can raise awareness and induce more privacy-preserving"
+ " behaviour."
),
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_interactor = tag_interactor in node.tags
data_pref = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
could_be_user = not meet_any_requirement(
node.attributes.get(cls.attribute_names[1], True), [False]
)
return is_interactor and could_be_user and not data_pref
class U4_InsufficientAccess(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "U4"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Insufficient Access")
cls.short_description = _(
"Data subjects do not have access to their personal data"
)
cls.long_description = (
_(
"Users should be given the option to access their collected personal"
+ " information through the system or a helpdesk. Lack of access may"
+ " violate the legal rights of data subjects."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["own_data_access", "is_user"]
cls.mitigation_options = [
_(
"Enable the user to access their personal data. (The right to access "
"is not always absolute. Limitations may exist depending on applicable "
+ "laws (e.g., trade secrets, rights of other data subjects))"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_interactor = tag_interactor in node.tags
data_access = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
could_be_user = not meet_any_requirement(
node.attributes.get(cls.attribute_names[1], True), [False]
)
return is_interactor and could_be_user and not data_access
class U5_InsufficientErasure(NodeRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "U5"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Insufficient Rectification or Erasure")
cls.short_description = _(
"Data subjects cannot rectify or erase their personal data"
)
cls.long_description = (
_(
"Users should be given the option to correct or delete their collected"
+ " personal data. For example, when a user deletes his social media"
+ " account, the data should be deleted and not just the account"
+ " disabled."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["own_data_modification", "is_user"]
cls.mitigation_options = [
_(
"Enable the user to correct or delete their personal data. "
+ "(Rectification or erasure can also be performed indirectly (e.g., "
+ "through a customer service ticket))"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, node: Node, dfd: dataflowdiagram.DataflowDiagram) -> bool:
is_interactor = tag_interactor in node.tags
data_modification = meet_any_requirement(
node.attributes.get(cls.attribute_names[0], False), [True]
)
could_be_user = not meet_any_requirement(
node.attributes.get(cls.attribute_names[1], True), [False]
)
return is_interactor and could_be_user and not data_modification
# ===== Non-Compliance: ===================================
class Nc1_NonCompliantProcessing(DataflowDiagramRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nc1"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Non-Compliance of Processing with Applicable Regulations")
cls.short_description = _(
"The processing of personal data by the system is not compliant with"
+ " applicable privacy regulations"
)
cls.long_description = (
_(
"Processing and sharing of personal information must adhere to"
+ " jurisdictions in regions it is used in. For example, the system"
+ " must not process information of EU citizens without a valid legal"
+ " ground under GDPR."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["privacy_regulation_compliance"]
cls.mitigation_options = [
_(
"Before processing any personal data, perform an assessment on the"
+ " applicable regulations for your processing activities and system"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, dfd: dataflowdiagram.DataflowDiagram) -> bool:
privacy_regulations_comply = meet_any_requirement(
dfd.attributes.get(cls.attribute_names[0], False), [True]
)
return not privacy_regulations_comply
class Nc2_NonAdherencePrivacyStandards(DataflowDiagramRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nc2"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Non-Adherence to Privacy Standards")
cls.short_description = _(
"The system is not compliant with privacy standards and best practices"
)
cls.long_description = (
_(
"The system should adhere to (industry) specific privacy standards and"
+ " implement them adequately. Non-adherence to industry standards and"
+ " best practices makes it more difficult to demonstrate compliance"
+ " with applicable laws."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["privacy_standards_compliance"]
cls.mitigation_options = [
_(
"Check whether there is industry-specific guidance on data processing"
+ " for your sector (e.g., healthcare, manufacturing)"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, dfd: dataflowdiagram.DataflowDiagram) -> bool:
privacy_standards_comply = meet_any_requirement(
dfd.attributes.get(cls.attribute_names[0], False), [True]
)
return not privacy_standards_comply
class Nc3_ImproperDataLifecycle(DataflowDiagramRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nc3"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Improper Data Lifecycle Management")
cls.short_description = _(
"Data is not properly managed throughout its entire lifecycle within the"
+ " system"
)
cls.long_description = (
_(
"There should be a data lifecycle management policy defined for the"
+ " data processed within the system. The policy should outline clear"
+ " principles for each phase of the lifecycle (creation, storage,"
+ " sharing, usage, archival, destruction). Improper data lifecycle"
+ " management can result in a loss of overview of the data within the"
+ " system and its maintanance, posing concerns not only for privacy"
+ " and data protection but also security and availability."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["data_lifecycle_policy_exists"]
cls.mitigation_options = [
_(
"Data lifecycle management is a continuous process that must be"
+ " consistently carried out as long as the system is designed,"
+ " developed, and used"
)
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, dfd: dataflowdiagram.DataflowDiagram) -> bool:
lifecycle_policy = meet_any_requirement(
dfd.attributes.get(cls.attribute_names[0], False), [True]
)
return not lifecycle_policy
class Nc4_InsufficientProcessingSecurity(DataflowDiagramRule):
BASE_SEVERITY = 1.0
severity = BASE_SEVERITY
linddun_id = "Nc4"
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.display_name = _("Insufficient Security of Processing")
cls.short_description = _(
"Data security measures and processes do not adhere to risk and security"
+ " management best practices and standards"
)
cls.long_description = (
_(
"There should be a process established to manage security risks and"
+ " identify required countermeasures. The system should then"
+ " incorporate the required countermeasures while regarding industry"
+ " standards and best practices."
)
+ "\n"
+ _("LINDDUN card ID: ")
+ cls.linddun_id
)
cls.attribute_names = ["security_standards_compliance"]
cls.mitigation_options = [
_("Consider complementary methods like security threat modeling")
]
cls.requirement = (
attributes_dict.attributes[cls.attribute_names[0]].display_name + " = True"
)
@classmethod
def _test(cls, dfd: dataflowdiagram.DataflowDiagram) -> bool:
security_standards_comply = meet_any_requirement(
dfd.attributes.get(cls.attribute_names[0], False), [True]
)
return not security_standards_comply
[docs]
class LINDDUNRuleCollection(DataflowDiagramRuleCollection):
tags = {"linddun_rules"}
[docs]
@classmethod
def init_texts(cls):
_ = settings.lang_out_linddun.gettext
cls.name = _("LINDDUN rule collection")
cls.references = [
("https://linddun.org/"),
("https://downloads.linddun.org/linddun-go/default/v241203/go.pdf"),
]
node_rules = [
L4_LinkableDataset,
L5_ProfilingUsers,
#
I5_IdentifiableDataset,
#
Nr1_NonRepudiationOfServiceUsage,
Nr3_NonRepudiationOfReceipt,
Nr4_NonRepudiationOfStorage,
Nr5_NonRepudiationOfMetadata,
#
D1_DetectableUsers,
D3_DetectableEvents,
D4_DetectableRecords,
#
DD1_ExcessivelySensitiveData,
DD2_ExcessiveDataAmount,
DD3_UnnecessaryDataAnalysis,
DD4_UnnecessaryDataRetention,
DD5_OverexposurePersonalData,
#
U1_InsufficientTransparency,
U2_InsufficientTransparencyOthers,
U3_InsufficientPrivacyControls,
U4_InsufficientAccess,
U5_InsufficientErasure,
]
edge_rules = [
L1_LinkedUserRequests,
L2_LinkableUserRequests,
L3_LinkableUserPatterns,
#
I1_IdentifiedUserRequests,
I2_IdentifiableUserRequests,
I3_IdentifiableDataFlows,
I4_IdentifiableDataRequests,
#
Nr2_NonRepudiationOfSending,
#
D2_DetectableServiceUsage,
]
dfd_rules = [
Nc1_NonCompliantProcessing,
Nc2_NonAdherencePrivacyStandards,
Nc3_ImproperDataLifecycle,
Nc4_InsufficientProcessingSecurity,
]
__all__ = ["LINDDUNRuleCollection"]