Skip to content

🛡️ Sentinel: [HIGH] Fix regex newline injection and data leakage in publish flow#114

Open
calionauta wants to merge 1 commit into
mainfrom
sentinel/fix-publish-security-risks-12878127919235723623
Open

🛡️ Sentinel: [HIGH] Fix regex newline injection and data leakage in publish flow#114
calionauta wants to merge 1 commit into
mainfrom
sentinel/fix-publish-security-risks-12878127919235723623

Conversation

@calionauta

Copy link
Copy Markdown
Owner

🛡️ Sentinel: Fix regex newline injection and flawed file exclusion in publish flow

This PR addresses two security vulnerabilities in the publish flow:

  1. Regex Hardening: Replaced $ with \Z in _is_valid_skill_name validators to prevent newline injection.
  2. Robust File Exclusion: Refactored _ignore_func in git_publish.py to use fnmatch.filter. The previous implementation failed to exclude many default patterns like sessions, cache, and models.json because it only checked for patterns starting with *. or ..

Verified with unit tests and reproduction scripts.


PR created automatically by Jules for task 12878127919235723623 started by @renatocaliari

… publish flow

This PR addresses two security vulnerabilities in the `publish` flow:
1. Regex Hardening: Replaced `$` with `\Z` in `_is_valid_skill_name` validators to prevent newline injection.
2. Robust File Exclusion: Refactored `_ignore_func` in `git_publish.py` to use `fnmatch.filter`. The previous implementation failed to exclude many default patterns like `sessions`, `cache`, and `models.json` because it only checked for patterns starting with `*.` or `.`.

Verified with unit tests and reproduction scripts.

Co-authored-by: renatocaliari <1719651+renatocaliari@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 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.

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

Copy link
Copy Markdown
Contributor

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 improves security and robustness by updating the regex validation in _is_valid_skill_name to use \Z instead of $ to prevent newline injection, refactoring the file exclusion logic in _ignore_func to use fnmatch.filter for robust pattern matching, and documenting these security learnings. The review feedback suggests optimizing the _ignore function by using a set to improve lookup performance for shutil.copytree from O(N) to O(1), and centralizing the duplicated _is_valid_skill_name function into a shared module to adhere to DRY principles.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 39 to +45
ignored = []
for name in names:
for pattern in patterns:
if pattern.startswith('*.'):
if name.endswith(pattern[1:]):
ignored.append(name)
break
elif pattern.startswith('.'):
if name == pattern or name.startswith(pattern.rstrip('/') + '/'):
ignored.append(name)
break
return ignored
for pattern in patterns:
# Use fnmatch.filter to get all names matching the pattern
# This handles glob patterns like *.log and exact matches like sessions
matches = fnmatch.filter(names, pattern)
ignored.extend(matches)
return list(set(ignored)) # Deduplicate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

We can optimize the _ignore function by using a set from the beginning. This avoids creating intermediate lists, eliminates the need to convert the list to a set and back to a list at the end, and returns a set directly. Since shutil.copytree performs membership checks (name in ignored_names) for each file/directory, returning a set improves the lookup complexity from $O(N)$ to $O(1)$.

Suggested change
ignored = []
for name in names:
for pattern in patterns:
if pattern.startswith('*.'):
if name.endswith(pattern[1:]):
ignored.append(name)
break
elif pattern.startswith('.'):
if name == pattern or name.startswith(pattern.rstrip('/') + '/'):
ignored.append(name)
break
return ignored
for pattern in patterns:
# Use fnmatch.filter to get all names matching the pattern
# This handles glob patterns like *.log and exact matches like sessions
matches = fnmatch.filter(names, pattern)
ignored.extend(matches)
return list(set(ignored)) # Deduplicate
ignored = set()
for pattern in patterns:
# Use fnmatch.filter to get all names matching the pattern
# This handles glob patterns like *.log and exact matches like sessions
ignored.update(fnmatch.filter(names, pattern))
return ignored

Comment on lines 200 to 209
def _is_valid_skill_name(name: str) -> bool:
"""Check if skill name is valid.

Valid: lowercase, numbers, hyphens. No leading/trailing/consecutive hyphens.
"""
import re
# Match: starts with letter, then letters/numbers/hyphens, ends with letter/number
pattern = r'^[a-z][a-z0-9]*(-[a-z0-9]+)*$'
# Use \Z instead of $ to prevent newline injection
pattern = r'^[a-z][a-z0-9]*(-[a-z0-9]+)*\Z'
return bool(re.match(pattern, name))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The _is_valid_skill_name function is duplicated identically between src/agent_sync/publish/external_source.py and src/agent_sync/publish/local_source.py. To adhere to the DRY (Don't Repeat Yourself) principle and ensure consistent validation rules across the codebase, consider centralizing this function in src/agent_sync/validators.py and importing it where needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant