Skip to content

🛡️ Sentinel: [Security Enhancement] Harden input validation for IDs and Folder Names#151

Closed
abhimehro wants to merge 1 commit intomainfrom
sentinel-input-validation-6082117876812247905
Closed

🛡️ Sentinel: [Security Enhancement] Harden input validation for IDs and Folder Names#151
abhimehro wants to merge 1 commit intomainfrom
sentinel-input-validation-6082117876812247905

Conversation

@abhimehro
Copy link
Owner

🛡️ Sentinel: [Security Enhancement] Harden input validation for IDs and Folder Names

Enhancements:

  1. Strict ID Validation: Introduced validate_resource_id to enforce a strict whitelist (^[a-zA-Z0-9_-]+$) and length limit (64 chars) for Profile IDs and Folder IDs (PKs).
  2. API Response Verification: Integrated ID validation into list_existing_folders and create_folder to ensure the application does not process or trust malformed IDs returned by the API (Defense in Depth).
  3. Harden Folder Name Validation: Updated is_valid_folder_name from a blacklist (blocking <>) to a strict whitelist (^[a-zA-Z0-9 \-_.()\[\]{}]+$) and added a length limit. This prevents XSS and other injection attacks.
  4. Delete Operation Safety: Added pre-validation of folder_id in delete_folder before using it in API calls.

Verification:

  • Added tests/test_id_validation.py covering new logic.
  • Ran full test suite (uv run python -m pytest), all tests passed.

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

- Add validate_resource_id for strict ID checking
- Switch is_valid_folder_name to whitelist
- Integrate validation in list, create, delete workflows
- Add tests/test_id_validation.py

Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 1, 2026 10:57
@google-labs-jules
Copy link

👋 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 Feb 1, 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.

@github-actions github-actions bot added the python label Feb 1, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @abhimehro, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security posture of the Sentinel application by implementing robust input validation across various critical identifiers and user-defined names. It introduces a unified approach for validating resource IDs, strengthens folder name validation with a strict whitelist, and incorporates defense-in-depth mechanisms to verify IDs received from API responses. These changes collectively mitigate risks associated with injection attacks and ensure data integrity throughout the system.

Highlights

  • Unified ID Validation: Introduced a new validate_resource_id function to centralize and enforce strict whitelist (^[a-zA-Z0-9_-]+$) and length (64 characters) validation for all resource IDs, including Profile IDs and Folder IDs.
  • Enhanced Folder Name Validation: The is_valid_folder_name function was refactored from a blacklist approach to a strict whitelist (^[a-zA-Z0-9 \-_.()\[\]{}]+$) and now includes a 64-character length limit, significantly improving protection against XSS and injection attacks.
  • Defense-in-Depth for API Responses: Integrated ID validation into list_existing_folders and create_folder to ensure that the application only processes valid IDs returned by external APIs, preventing the use of potentially malformed or malicious data.
  • Pre-validation for Delete Operations: Added a pre-validation step for folder_id in the delete_folder function, ensuring that only valid IDs are used in API calls, thereby enhancing the safety of deletion operations.
  • Comprehensive Testing: A new test file, tests/test_id_validation.py, was added to thoroughly cover the new ID and folder name validation logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

Bandit found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

Bandit (reported by Codacy) found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

Pylint (reported by Codacy) found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

Pylintpython3 (reported by Codacy) found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.


if len(resource_id) > 64:
if log_errors:
log.error(f"Invalid {type_name} length (max 64 chars)")

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)
log.error("Invalid profile ID format (contains unsafe characters)")
elif len(profile_id) > 64:
log.error("Invalid profile ID length (max 64 chars)")
log.error(f"Invalid {type_name} format (contains unsafe characters)")

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 str(pk)
if validate_resource_id(str(pk), "New Folder PK"):
log.info(
"Created folder %s (ID %s) [Direct]", sanitize_for_log(name), pk

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)
@@ -819,12 +854,19 @@ def create_folder(

for grp in groups:

Check warning

Code scanning / Prospector (reported by Codacy)

Unnecessary "else" after "return" (no-else-return) Warning

Unnecessary "else" after "return" (no-else-return)
)
return None
except Exception as e:
log.warning(f"Error fetching groups on attempt {attempt}: {e}")

Check warning

Code scanning / Prospector (reported by Codacy)

Unnecessary "else" after "return" (no-else-return) Warning

Unnecessary "else" after "return" (no-else-return)
main.py Outdated

Check warning

Code scanning / Prospector (reported by Codacy)

Unnecessary "else" after "return" (no-else-return) Warning

Unnecessary "else" after "return" (no-else-return)
@@ -0,0 +1,44 @@
from unittest.mock import MagicMock

Check warning

Code scanning / Prospector (reported by Codacy)

Unused MagicMock imported from unittest.mock (unused-import) Warning test

Unused MagicMock imported from unittest.mock (unused-import)
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request significantly improves the security of the application by hardening input validation for resource IDs and folder names, adopting a whitelist approach, and adding defense-in-depth checks on API responses. While these are excellent practices, a subtle bypass exists in the resource ID validation logic due to the behavior of the $ anchor in Python's regex engine, which allows trailing newlines. This vulnerability requires remediation to ensure the defense-in-depth measures are robust. Additionally, I've provided a few suggestions for further code improvement, including a type hint correction, reducing code duplication, and cleaning up an unused import.


def validate_profile_id(profile_id: str, log_errors: bool = True) -> bool:
if not is_valid_profile_id_format(profile_id):
if not re.match(r"^[a-zA-Z0-9_-]+$", resource_id):

Choose a reason for hiding this comment

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

security-medium medium

The regular expression used for validation uses the $ anchor, which in Python's re module matches the end of the string or the position just before a newline at the end of the string. This allows an ID with a trailing newline (e.g., "abc\n") to pass validation, even though the newline character is not in the allowed character set [a-zA-Z0-9_-]. Since these IDs are used to construct API URLs (e.g., in delete_folder and list_existing_folders), an ID with a newline could lead to CRLF injection or malformed HTTP requests. It is recommended to use re.fullmatch() to ensure the entire string matches the allowed character set.

Suggested change
if not re.match(r"^[a-zA-Z0-9_-]+$", resource_id):
if not re.fullmatch(r"[a-zA-Z0-9_-]+", resource_id):

Comment on lines +400 to +402
def validate_resource_id(
resource_id: str, type_name: str = "ID", log_errors: bool = True
) -> bool:

Choose a reason for hiding this comment

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

medium

The type hint for resource_id is str, but the function's logic correctly handles None and the new tests pass None as a valid input. To align the function signature with its implementation and tests, and to satisfy static type checkers, the type hint should be changed to Optional[str]. The Optional type is already imported in this file.

Suggested change
def validate_resource_id(
resource_id: str, type_name: str = "ID", log_errors: bool = True
) -> bool:
def validate_resource_id(
resource_id: Optional[str], type_name: str = "ID", log_errors: bool = True
) -> bool:

Comment on lines 816 to +827
if isinstance(body, dict) and "group" in body and "PK" in body["group"]:
pk = body["group"]["PK"]
log.info(
"Created folder %s (ID %s) [Direct]", sanitize_for_log(name), pk
)
return str(pk)
if validate_resource_id(str(pk), "New Folder PK"):
log.info(
"Created folder %s (ID %s) [Direct]", sanitize_for_log(name), pk
)
return str(pk)
else:
log.error(
f"API returned unsafe PK for folder {sanitize_for_log(name)}: {sanitize_for_log(pk)}"
)
return None

Choose a reason for hiding this comment

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

medium

This block of code for validating the returned PK is substantially repeated twice more within this function (for the list-based response and the polling fallback). This duplication harms maintainability. Consider extracting this logic into a nested helper function to reduce repetition.

For example:

def _validate_and_log_pk(pk: Any, source: str) -> Optional[str]:
    pk_str = str(pk)
    if validate_resource_id(pk_str, f"{source} Folder PK"):
        log.info(
            "Created folder %s (ID %s) [%s]", sanitize_for_log(name), pk, source
        )
        return pk_str
    else:
        log.error(
            f"API returned unsafe PK for folder {sanitize_for_log(name)}: {sanitize_for_log(pk)}"
        )
        return None

This helper could then be called from all three locations, simplifying the main function body.

@@ -0,0 +1,44 @@
from unittest.mock import MagicMock

Choose a reason for hiding this comment

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

medium

The MagicMock import is not used in this file and can be removed to keep the imports clean.

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 hardens input validation around profile and folder identifiers and folder names, and wires those checks into folder-related API operations for better security and robustness.

Changes:

  • Introduces a shared validate_resource_id helper used by validate_profile_id and folder operations to enforce a strict character whitelist and max length on IDs/PKs.
  • Tightens is_valid_folder_name from a blacklist to a whitelist with a 64-character limit to reduce XSS/injection risk.
  • Integrates ID validation into list_existing_folders, create_folder, and delete_folder so that unsafe folder IDs from inputs or API responses are rejected and not acted upon, and adds tests validating the new ID and folder-name rules.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
main.py Adds validate_resource_id, updates validate_profile_id, strengthens is_valid_folder_name, and validates folder PKs/IDs in list_existing_folders, create_folder, and delete_folder before using them.
tests/test_id_validation.py Adds unit tests for validate_resource_id (valid/invalid formats, length, empty/None) and is_valid_folder_name (whitelist behavior and length limit).

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

@@ -0,0 +1,44 @@
from unittest.mock import MagicMock
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

MagicMock is imported but never used in this test module, which adds noise and may confuse readers about intended mocking. Consider removing the unused import to keep the test file minimal and focused.

Suggested change
from unittest.mock import MagicMock

Copilot uses AI. Check for mistakes.
Comment on lines +411 to +416
if len(resource_id) > 64:
if log_errors:
log.error(f"Invalid {type_name} length (max 64 chars)")
return False

def validate_profile_id(profile_id: str, log_errors: bool = True) -> bool:
if not is_valid_profile_id_format(profile_id):
if not re.match(r"^[a-zA-Z0-9_-]+$", resource_id):
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

validate_resource_id assumes resource_id is a string, but it is called with raw PK values from JSON in list_existing_folders, so if the API ever returns a non-string (e.g., numeric PK), this len()/re.match() path will raise a TypeError instead of cleanly rejecting the ID. To make this defensive validator robust against unexpected types, consider coercing resource_id to str (or explicitly checking isinstance(resource_id, str) and returning False for non-strings) before the length and regex checks.

Copilot uses AI. Check for mistakes.
Comment on lines +641 to +651
result = {}
for f in folders:
name = f.get("group")
pk = f.get("PK")
if name and pk:
if validate_resource_id(pk, "Folder PK", log_errors=False):
result[name.strip()] = pk
else:
log.warning(
f"Skipping folder '{sanitize_for_log(name)}' with unsafe PK: {sanitize_for_log(pk)}"
)
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

The new defense-in-depth logic in list_existing_folders that filters out folders with unsafe PKs and logs a warning is not covered by tests; current tests only exercise validate_resource_id in isolation. Given the security focus of this change and existing coverage for other main.py behaviors, consider adding tests that simulate folder lists containing valid and invalid PKs to verify that valid entries are kept, invalid ones are skipped, and the appropriate log warnings are emitted.

Copilot uses AI. Check for mistakes.
Comment on lines +780 to +782
if not validate_resource_id(folder_id, "Folder ID to delete"):
log.error(f"Aborting deletion of {sanitize_for_log(name)}: Unsafe Folder ID")
return False
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

The new pre-validation in delete_folder that aborts when folder_id fails validate_resource_id is not covered by tests, so regressions here could go unnoticed (e.g., if validation is accidentally relaxed or removed). It would be helpful to add tests that call delete_folder with both valid and invalid folder IDs, asserting that invalid IDs trigger the early False return and log the expected error without invoking _api_delete.

Copilot uses AI. Check for mistakes.
Comment on lines +818 to +822
if validate_resource_id(str(pk), "New Folder PK"):
log.info(
"Created folder %s (ID %s) [Direct]", sanitize_for_log(name), pk
)
return str(pk)
Copy link

Copilot AI Feb 1, 2026

Choose a reason for hiding this comment

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

The new checks in create_folder that validate the returned PK and either return it or log an "unsafe PK" error (here and in the similar branches below for list-based and polled responses) are not covered by tests. To guard this critical security behavior, consider adding tests that exercise responses with valid PKs, responses with malformed PKs, and the polling fallback, asserting both the returned ID/None and the corresponding log messages.

Copilot uses AI. Check for mistakes.
Copilot AI added a commit that referenced this pull request Feb 8, 2026
…itization, add dry-run plan details

Incorporates the best changes from 36 Jules PRs, addressing review feedback:

Bolt (Performance) - from PR #173:
- Pre-compile PROFILE_ID_PATTERN and RULE_PATTERN at module level
- Use compiled patterns in is_valid_profile_id_format, validate_profile_id, and is_valid_rule
- Supersedes PRs: #140, #143, #152, #155, #158, #161, #167, #170, #173

Sentinel (Security) - from PR #172 with review feedback:
- Enhance sanitize_for_log to redact Basic Auth credentials in URLs
- Redact sensitive query parameters (token, key, secret, password, etc.)
- Handle fragment separators (#) per Gemini Code Assist review
- Use [^&#\s]* pattern per Copilot reviewer suggestion
- Update docstring per reviewer suggestion
- Supersedes PRs: #142, #145, #148, #151, #154, #157, #160, #169, #172

Palette (UX) - from PR #174 with lint fixes:
- Add print_plan_details function for dry-run visibility
- Fix duplicate render_progress_bar definition bug
- Supersedes PRs: #139, #141, #144, #147, #150, #153, #156, #159, #162, #165, #168, #171, #174

Also: #146, #149, #164 (parallel folder deletion) and #166 (auto-fix .env perms) are independent features not consolidated here.

Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com>
@abhimehro abhimehro closed this Feb 9, 2026
@abhimehro abhimehro deleted the sentinel-input-validation-6082117876812247905 branch February 9, 2026 00:19
abhimehro added a commit that referenced this pull request Feb 9, 2026
…itization, add dry-run plan details

Incorporates the best changes from 36 Jules PRs, addressing review feedback:

Bolt (Performance) - from PR #173:
- Pre-compile PROFILE_ID_PATTERN and RULE_PATTERN at module level
- Use compiled patterns in is_valid_profile_id_format, validate_profile_id, and is_valid_rule
- Supersedes PRs: #140, #143, #152, #155, #158, #161, #167, #170, #173

Sentinel (Security) - from PR #172 with review feedback:
- Enhance sanitize_for_log to redact Basic Auth credentials in URLs
- Redact sensitive query parameters (token, key, secret, password, etc.)
- Handle fragment separators (#) per Gemini Code Assist review
- Use [^&#\s]* pattern per Copilot reviewer suggestion
- Update docstring per reviewer suggestion
- Supersedes PRs: #142, #145, #148, #151, #154, #157, #160, #169, #172

Palette (UX) - from PR #174 with lint fixes:
- Add print_plan_details function for dry-run visibility
- Fix duplicate render_progress_bar definition bug
- Supersedes PRs: #139, #141, #144, #147, #150, #153, #156, #159, #162, #165, #168, #171, #174

Also: #146, #149, #164 (parallel folder deletion) and #166 (auto-fix .env perms) are independent features not consolidated here.

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.

2 participants