Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion triplets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,16 @@
try:
import duckdb as _duckdb
import logging as _logging
import re as _re # for table name validation (SQL injection prevention)

_duckdb_logger = _logging.getLogger(__name__)

def _duckdb_read_rdf(self, paths, table_name="triplets", **kwargs):
"""Parse RDF/XML files and load into DuckDB table via Arrow (zero-copy)."""
# SECURITY: validate table_name to prevent SQL injection
# allow only safe identifier characters: alphanumeric, underscore, starting with letter/underscore
if not _re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', table_name):
raise ValueError(f"Invalid table name: {table_name}")
arrow_table = parse(paths, return_type="arrow", **kwargs)
self.register("_arrow_import", arrow_table)
self.execute(f"CREATE OR REPLACE TABLE {table_name} AS SELECT * FROM _arrow_import")
Expand All @@ -65,4 +70,3 @@ def _duckdb_read_rdf(self, paths, table_name="triplets", **kwargs):
except ImportError:
logging.getLogger(__name__).debug("duckdb not installed, skipping read_rdf registration")
pass

5 changes: 5 additions & 0 deletions triplets/_accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"""

import logging
import re # security: used to validate table_name in DuckDB export methods
import pandas

from . import tools, export
Expand Down Expand Up @@ -149,7 +150,11 @@ def _duckdb_export(name):
"""Make a connection method: fetch the triplets table, run the pandas export."""
function = getattr(export, name)

# Security fix: validate table_name to prevent SQL injection
# Only allow alphanumeric characters and underscores, must start with letter or underscore
def method(connection, *args, table_name="triplets", **kwargs):
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', table_name):
raise ValueError(f"Invalid table name: {table_name}")
df = connection.execute(f"SELECT * FROM {table_name}").df()
return function(df, *args, **kwargs)

Expand Down
5 changes: 4 additions & 1 deletion triplets/cgmes_tools/static/relations_graph.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
<meta charset="utf-8">
<title>$title</title>
<script>$vis_js</script>
<!-- Include DOMPurify for XSS prevention -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.6/purify.min.js"></script>
<style>
html, body { margin: 0; height: 100%; font-family: sans-serif; }
#graph { width: 100%; height: 100%; }
Expand Down Expand Up @@ -50,7 +52,8 @@
network.on("selectNode", function (params) {
const node = nodes.get(params.nodes[0]);
panelTitle.textContent = node.label;
panelContent.innerHTML = node.objectTable || "";
// Sanitize HTML to prevent XSS
panelContent.innerHTML = DOMPurify.sanitize(node.objectTable || "");
panel.style.display = "flex";
});
network.on("deselectNode", function () { panel.style.display = "none"; });
Expand Down
7 changes: 6 additions & 1 deletion triplets/rdfs_tools/cim_rdfs_to_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,12 @@ def html_datatable(table, path):
#print(filename)

if "entsoeUML" in metadata.keys():
folder = os.path.join(metadata["entsoeUML"], metadata["shortName"], "_".join(entsoeURI_list))
# Sanitize metadata values to prevent path traversal
safe_entsoeUML = os.path.basename(metadata["entsoeUML"])
safe_shortName = os.path.basename(metadata["shortName"])
safe_entsoeURI_list = [os.path.basename(uri) for uri in entsoeURI_list]

folder = os.path.join(safe_entsoeUML, safe_shortName, "_".join(safe_entsoeURI_list))
if not os.path.exists(folder):
os.makedirs(folder)

Expand Down