Skip to content

Commit 784fca0

Browse files
zkoppertCopilot
andcommitted
fix: validate OUTPUT_FILENAME to prevent path traversal and unsafe characters
Reject filenames containing path separators, special characters, or absolute paths. Only alphanumeric characters, hyphens, underscores, and dots are allowed. Adds four security tests covering path traversal, absolute paths, directory separators, and shell metacharacters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e60a905 commit 784fca0

2 files changed

Lines changed: 66 additions & 0 deletions

File tree

env.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import datetime
77
import os
8+
import re
89
from os.path import dirname, join
910

1011
from dotenv import load_dotenv
@@ -179,6 +180,15 @@ def get_env_vars(
179180
sponsor_info = get_bool_env_var("SPONSOR_INFO", False)
180181
link_to_profile = get_bool_env_var("LINK_TO_PROFILE", False)
181182
output_filename = os.getenv("OUTPUT_FILENAME", "").strip() or "contributors.md"
183+
if not re.match(r"^[a-zA-Z0-9_\-\.]+$", output_filename):
184+
raise ValueError(
185+
"OUTPUT_FILENAME must contain only alphanumeric characters, "
186+
"hyphens, underscores, and dots"
187+
)
188+
if output_filename != os.path.basename(output_filename):
189+
raise ValueError(
190+
"OUTPUT_FILENAME must be a simple filename without path separators"
191+
)
182192

183193
# Separate repositories_str into a list based on the comma separator
184194
repositories_list = []

test_env.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,62 @@ def test_get_env_vars_custom_output_filename(self):
233233

234234
self.assertEqual(output_filename, "custom-report.md")
235235

236+
@patch.dict(
237+
os.environ,
238+
{
239+
"ORGANIZATION": "org",
240+
"GH_TOKEN": "token",
241+
"OUTPUT_FILENAME": "../../../etc/passwd",
242+
},
243+
clear=True,
244+
)
245+
def test_get_env_vars_output_filename_path_traversal_rejected(self):
246+
"""Test that OUTPUT_FILENAME rejects path traversal attempts."""
247+
with self.assertRaises(ValueError):
248+
env.get_env_vars()
249+
250+
@patch.dict(
251+
os.environ,
252+
{
253+
"ORGANIZATION": "org",
254+
"GH_TOKEN": "token",
255+
"OUTPUT_FILENAME": "/tmp/output.md",
256+
},
257+
clear=True,
258+
)
259+
def test_get_env_vars_output_filename_absolute_path_rejected(self):
260+
"""Test that OUTPUT_FILENAME rejects absolute paths."""
261+
with self.assertRaises(ValueError):
262+
env.get_env_vars()
263+
264+
@patch.dict(
265+
os.environ,
266+
{
267+
"ORGANIZATION": "org",
268+
"GH_TOKEN": "token",
269+
"OUTPUT_FILENAME": "reports/output.md",
270+
},
271+
clear=True,
272+
)
273+
def test_get_env_vars_output_filename_directory_separator_rejected(self):
274+
"""Test that OUTPUT_FILENAME rejects filenames with directory separators."""
275+
with self.assertRaises(ValueError):
276+
env.get_env_vars()
277+
278+
@patch.dict(
279+
os.environ,
280+
{
281+
"ORGANIZATION": "org",
282+
"GH_TOKEN": "token",
283+
"OUTPUT_FILENAME": "file;rm -rf /.md",
284+
},
285+
clear=True,
286+
)
287+
def test_get_env_vars_output_filename_special_chars_rejected(self):
288+
"""Test that OUTPUT_FILENAME rejects filenames with special characters."""
289+
with self.assertRaises(ValueError):
290+
env.get_env_vars()
291+
236292
@patch.dict(os.environ, {}, clear=True)
237293
def test_get_env_vars_missing_org_or_repo(self):
238294
"""Test that an error is raised if required environment variables are not set"""

0 commit comments

Comments
 (0)