Skip to content

🛡️ Sentinel: [HIGH] Fix Stored XSS risk in folder names#131

Merged
abhimehro merged 1 commit intomainfrom
sentinel-folder-validation-10716619568147792189
Jan 27, 2026
Merged

🛡️ Sentinel: [HIGH] Fix Stored XSS risk in folder names#131
abhimehro merged 1 commit intomainfrom
sentinel-folder-validation-10716619568147792189

Conversation

@google-labs-jules
Copy link

Implemented strict input validation for folder names in external JSON files. This prevents potential Stored XSS attacks where a malicious folder name could be injected into the Control D dashboard.

Validation rules:

  • Must be a non-empty string.
  • Must be printable.
  • Must NOT contain < > " ' `.

Added comprehensive unit tests covering valid names, XSS payloads, non-string types, and empty strings.


PR created automatically by Jules for task 10716619568147792189 started by @abhimehro

- Added `is_valid_folder_name` to validate folder names against dangerous characters (<, >, ", ', `) and non-printable characters.
- Updated `validate_folder_data` to enforce string type and folder name validity.
- Added `tests/test_folder_validation.py` to verify security constraints.

Risk: Low (Validation only)
Impact: High (Prevents Stored XSS)
@google-labs-jules
Copy link
Author

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@trunk-io
Copy link

trunk-io bot commented Jan 25, 2026

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

1. Implement strict validation on all data items from external lists (`is_valid_rule`).
2. Filter out items containing dangerous characters (`<`, `>`, `"` etc.) or control codes.

## 2026-05-15 - [Stored XSS in Folder Names]

Check notice

Code scanning / Remark-lint (reported by Codacy)

Warn when references to undefined definitions are found. Note

[no-undefined-references] Found reference to undefined definition
1. Implement strict validation on all data items from external lists (`is_valid_rule`).
2. Filter out items containing dangerous characters (`<`, `>`, `"` etc.) or control codes.

## 2026-05-15 - [Stored XSS in Folder Names]

Check notice

Code scanning / Remark-lint (reported by Codacy)

Warn when shortcut reference links are used. Note

[no-shortcut-reference-link] Use the trailing [] on reference links

folder_name = data["group"]["group"]
if not isinstance(folder_name, str):
log.error(f"Invalid data from {sanitize_for_log(url)}: Folder name must be a string.")

Check warning

Code scanning / Prospector (reported by Codacy)

Use lazy % formatting in logging functions (logging-fstring-interpolation) Warning

Use lazy % formatting in logging functions (logging-fstring-interpolation)
return False

if not is_valid_folder_name(folder_name):
log.error(f"Invalid data from {sanitize_for_log(url)}: Invalid folder name (empty, unsafe characters, or non-printable).")

Check warning

Code scanning / Prospector (reported by Codacy)

Use lazy % formatting in logging functions (logging-fstring-interpolation) Warning

Use lazy % formatting in logging functions (logging-fstring-interpolation)

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import main

Check warning

Code scanning / Prospector (reported by Codacy)

Cannot import 'main' due to syntax error 'invalid syntax (, line 1286)' (syntax-error) Warning test

Cannot import 'main' due to syntax error 'invalid syntax (, line 1286)' (syntax-error)
return False

if not is_valid_folder_name(folder_name):
log.error(f"Invalid data from {sanitize_for_log(url)}: Invalid folder name (empty, unsafe characters, or non-printable).")

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (130/100) Warning

Line too long (130/100)
@@ -0,0 +1,44 @@
import unittest

Check warning

Code scanning / Pylint (reported by Codacy)

Missing module docstring Warning test

Missing module docstring

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import main

Check warning

Code scanning / Pylint (reported by Codacy)

Import "import main" should be placed at the top of the module Warning test

Import "import main" should be placed at the top of the module

import main

class TestFolderValidation(unittest.TestCase):

Check warning

Code scanning / Pylint (reported by Codacy)

Missing class docstring Warning test

Missing class docstring
def tearDown(self):
main.log = self.original_log

def test_valid_folder_name(self):

Check warning

Code scanning / Pylint (reported by Codacy)

Missing method docstring Warning test

Missing method docstring
data = {"group": {"group": "Safe Folder"}}
self.assertTrue(main.validate_folder_data(data, "test_url"))

def test_xss_folder_name(self):

Check warning

Code scanning / Pylint (reported by Codacy)

Missing method docstring Warning test

Missing method docstring
# Assuming we will log "Invalid folder name" or similar
self.assertTrue("Invalid folder name" in args or "Unsafe characters" in args)

def test_non_string_folder_name(self):

Check warning

Code scanning / Pylint (reported by Codacy)

Missing method docstring Warning test

Missing method docstring
args = str(self.mock_log.error.call_args_list)
self.assertTrue("must be a string" in args)

def test_empty_folder_name(self):

Check warning

Code scanning / Pylint (reported by Codacy)

Missing method docstring Warning test

Missing method docstring
return False

if not is_valid_folder_name(folder_name):
log.error(f"Invalid data from {sanitize_for_log(url)}: Invalid folder name (empty, unsafe characters, or non-printable).")

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (130/100) Warning

Line too long (130/100)
@@ -0,0 +1,44 @@
import unittest

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing module docstring Warning test

Missing module docstring

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

import main

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Import "import main" should be placed at the top of the module Warning test

Import "import main" should be placed at the top of the module

import main

class TestFolderValidation(unittest.TestCase):

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing class docstring Warning test

Missing class docstring
def tearDown(self):
main.log = self.original_log

def test_valid_folder_name(self):

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing function or method docstring Warning test

Missing function or method docstring
data = {"group": {"group": "Safe Folder"}}
self.assertTrue(main.validate_folder_data(data, "test_url"))

def test_xss_folder_name(self):

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing function or method docstring Warning test

Missing function or method docstring
# Assuming we will log "Invalid folder name" or similar
self.assertTrue("Invalid folder name" in args or "Unsafe characters" in args)

def test_non_string_folder_name(self):

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing function or method docstring Warning test

Missing function or method docstring
args = str(self.mock_log.error.call_args_list)
self.assertTrue("must be a string" in args)

def test_empty_folder_name(self):

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Missing function or method docstring Warning test

Missing function or method docstring

folder_name = data["group"]["group"]
if not isinstance(folder_name, str):
log.error(f"Invalid data from {sanitize_for_log(url)}: Folder name must be a string.")

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Use lazy % formatting in logging functions Note

Use lazy % formatting in logging functions
return False

if not is_valid_folder_name(folder_name):
log.error(f"Invalid data from {sanitize_for_log(url)}: Invalid folder name (empty, unsafe characters, or non-printable).")

Check notice

Code scanning / Pylintpython3 (reported by Codacy)

Use lazy % formatting in logging functions Note

Use lazy % formatting in logging functions
@abhimehro abhimehro marked this pull request as ready for review January 27, 2026 07:28
Copilot AI review requested due to automatic review settings January 27, 2026 07:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a Stored XSS security vulnerability by implementing input validation for folder names loaded from external JSON files. Previously, while rule content was validated using is_valid_rule(), folder names were not validated, allowing malicious JSON files to inject XSS payloads that could be stored via the Control D API.

Changes:

  • Added is_valid_folder_name() validation function that rejects XSS-dangerous characters (<>"'`) while allowing printable characters and brackets/parentheses
  • Integrated validation into validate_folder_data() to check folder names before use
  • Added comprehensive unit tests covering valid names, XSS payloads, non-string types, and empty strings

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
main.py Implements is_valid_folder_name() validation function and integrates it into validate_folder_data() to prevent XSS in folder names
tests/test_folder_validation.py Adds unit tests for folder name validation covering XSS payloads, type validation, and empty strings
.jules/sentinel.md Documents the vulnerability, learning, and prevention measures for this security fix

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +10 to +44
class TestFolderValidation(unittest.TestCase):
def setUp(self):
self.mock_log = MagicMock()
self.original_log = main.log
main.log = self.mock_log

def tearDown(self):
main.log = self.original_log

def test_valid_folder_name(self):
data = {"group": {"group": "Safe Folder"}}
self.assertTrue(main.validate_folder_data(data, "test_url"))

def test_xss_folder_name(self):
# Should be rejected
data = {"group": {"group": "<script>alert(1)</script>"}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
# Check that error was logged
self.assertTrue(self.mock_log.error.called)
# We expect a specific error message about unsafe characters or invalid name
args = str(self.mock_log.error.call_args_list)
# Assuming we will log "Invalid folder name" or similar
self.assertTrue("Invalid folder name" in args or "Unsafe characters" in args)

def test_non_string_folder_name(self):
data = {"group": {"group": 123}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
args = str(self.mock_log.error.call_args_list)
self.assertTrue("must be a string" in args)

def test_empty_folder_name(self):
data = {"group": {"group": " "}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file uses unittest.TestCase while the existing test files in the codebase use pytest (see tests/test_security.py, tests/test_ssrf.py, etc.). For consistency with the established codebase convention, this test should be refactored to use pytest style with plain functions and assert statements instead of unittest.TestCase. Examples: tests/test_security.py:5-88, tests/test_ssrf.py

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +44
def test_valid_folder_name(self):
data = {"group": {"group": "Safe Folder"}}
self.assertTrue(main.validate_folder_data(data, "test_url"))

def test_xss_folder_name(self):
# Should be rejected
data = {"group": {"group": "<script>alert(1)</script>"}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
# Check that error was logged
self.assertTrue(self.mock_log.error.called)
# We expect a specific error message about unsafe characters or invalid name
args = str(self.mock_log.error.call_args_list)
# Assuming we will log "Invalid folder name" or similar
self.assertTrue("Invalid folder name" in args or "Unsafe characters" in args)

def test_non_string_folder_name(self):
data = {"group": {"group": 123}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
args = str(self.mock_log.error.call_args_list)
self.assertTrue("must be a string" in args)

def test_empty_folder_name(self):
data = {"group": {"group": " "}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test coverage is incomplete. While validate_folder_data is tested, the is_valid_folder_name function should also have direct unit tests to cover edge cases like folder names with allowed special characters (parentheses, brackets, braces) and various combinations of dangerous characters. This would provide better isolation and more comprehensive coverage of the validation logic. Similar functions like is_valid_rule (main.py:259) are tested directly in the codebase.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +39
args = str(self.mock_log.error.call_args_list)
# Assuming we will log "Invalid folder name" or similar
self.assertTrue("Invalid folder name" in args or "Unsafe characters" in args)

def test_non_string_folder_name(self):
data = {"group": {"group": 123}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
args = str(self.mock_log.error.call_args_list)
self.assertTrue("must be a string" in args)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test assertion is brittle because it checks for error messages by converting call_args_list to a string and using substring matching. This approach is fragile and could break if the error message format changes. Consider checking the actual error messages directly using mock_log.error.call_args or mock_log.error.call_args_list[0][0][0], similar to how the existing security tests verify specific logged content (see tests/test_security.py:76-83 for an example pattern).

Suggested change
args = str(self.mock_log.error.call_args_list)
# Assuming we will log "Invalid folder name" or similar
self.assertTrue("Invalid folder name" in args or "Unsafe characters" in args)
def test_non_string_folder_name(self):
data = {"group": {"group": 123}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
args = str(self.mock_log.error.call_args_list)
self.assertTrue("must be a string" in args)
error_calls = self.mock_log.error.call_args_list
self.assertGreaterEqual(len(error_calls), 1)
logged_message = error_calls[0][0][0]
self.assertIsInstance(logged_message, str)
# Assuming we will log "Invalid folder name" or similar
self.assertTrue(
"Invalid folder name" in logged_message
or "Unsafe characters" in logged_message
)
def test_non_string_folder_name(self):
data = {"group": {"group": 123}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
error_calls = self.mock_log.error.call_args_list
self.assertGreaterEqual(len(error_calls), 1)
logged_message = error_calls[0][0][0]
self.assertIsInstance(logged_message, str)
self.assertIn("must be a string", logged_message)

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +44
def test_valid_folder_name(self):
data = {"group": {"group": "Safe Folder"}}
self.assertTrue(main.validate_folder_data(data, "test_url"))

def test_xss_folder_name(self):
# Should be rejected
data = {"group": {"group": "<script>alert(1)</script>"}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
# Check that error was logged
self.assertTrue(self.mock_log.error.called)
# We expect a specific error message about unsafe characters or invalid name
args = str(self.mock_log.error.call_args_list)
# Assuming we will log "Invalid folder name" or similar
self.assertTrue("Invalid folder name" in args or "Unsafe characters" in args)

def test_non_string_folder_name(self):
data = {"group": {"group": 123}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
args = str(self.mock_log.error.call_args_list)
self.assertTrue("must be a string" in args)

def test_empty_folder_name(self):
data = {"group": {"group": " "}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test coverage should include test cases for folder names with allowed special characters that are mentioned in the validation comments. For example, "Work (Private)" with parentheses, "Tasks [2024]" with brackets, or "Config {prod}" with braces. This would verify that the intentionally allowed characters actually work as expected and document the validation behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +32
def test_xss_folder_name(self):
# Should be rejected
data = {"group": {"group": "<script>alert(1)</script>"}}
self.assertFalse(main.validate_folder_data(data, "test_url"))
# Check that error was logged
self.assertTrue(self.mock_log.error.called)
# We expect a specific error message about unsafe characters or invalid name
args = str(self.mock_log.error.call_args_list)
# Assuming we will log "Invalid folder name" or similar
self.assertTrue("Invalid folder name" in args or "Unsafe characters" in args)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XSS test coverage should be expanded to test all the dangerous characters individually. Currently only one XSS payload with script tags is tested. Add test cases for each dangerous character mentioned in the validation: angle brackets (<, >), double quotes ("), single quotes ('), and backticks (). Examples: "test&lt;div&gt;", "test\"quote", "test'quote", "testbacktick". This ensures comprehensive coverage of all blocked XSS-related characters.

Copilot uses AI. Check for mistakes.
self.assertFalse(main.validate_folder_data(data, "test_url"))
self.assertTrue(self.mock_log.error.called)
args = str(self.mock_log.error.call_args_list)
self.assertTrue("must be a string" in args)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertTrue(a in b) cannot provide an informative message. Using assertIn(a, b) instead will give more informative messages.

Suggested change
self.assertTrue("must be a string" in args)
self.assertIn("must be a string", args)

Copilot uses AI. Check for mistakes.
@abhimehro abhimehro merged commit d6923da into main Jan 27, 2026
24 checks passed
@abhimehro abhimehro deleted the sentinel-folder-validation-10716619568147792189 branch January 27, 2026 13:27
abhimehro added a commit that referenced this pull request Jan 27, 2026
Reverting all changes as this work has been superseded by PR #131.

Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants