🛡️ Sentinel: [HIGH] Harden publish flow against data leakage and symlink vulnerabilities#111
🛡️ Sentinel: [HIGH] Harden publish flow against data leakage and symlink vulnerabilities#111calionauta wants to merge 1 commit into
Conversation
…ink vulnerabilities - Fix broken file exclusion logic by using shutil.ignore_patterns - Add critical secret/sensitive patterns to DEFAULT_IGNORE_PATTERNS - Prevent symlink content leakage by preserving links during copy - Remediate regex newline injection in skill name validation - Improve defense-in-depth for public repository publishing Co-authored-by: renatocaliari <1719651+renatocaliari@users.noreply.github.com>
|
👋 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 New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
There was a problem hiding this comment.
Code Review
This pull request updates regex patterns to prevent newline injection, expands ignore patterns for secrets, and modifies symlink handling during file and directory copying. However, using follow_symlinks=False on shutil.copy2 and symlinks=True on shutil.copytree introduces functional bugs (broken symlinks in the published repository), information leakage of local directory structures, and potential platform compatibility issues on Windows. It is recommended to revert follow_symlinks=False for individual file copies and implement a custom ignore helper to safely copy internal symlinks while filtering out unsafe external symlinks.
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.
| safe_name = agent_name.replace("/", "_").replace(" ", "_") | ||
| dest = agents_dir / f"{safe_name}.md" | ||
| shutil.copy2(Path(agent.path), dest) | ||
| shutil.copy2(Path(agent.path), dest, follow_symlinks=False) |
There was a problem hiding this comment.
Using follow_symlinks=False on shutil.copy2 will copy the symbolic link itself rather than the target file's content. This introduces several issues:
- Functional Bug: The published git repository will contain a broken symlink pointing to a local path on the developer's machine, meaning the actual content of the agent is not published.
- Information Leakage: The symlink's target path (which can contain absolute paths like
/Users/username/projects/...) is committed to the public repository, leaking the developer's local username and directory structure. - Platform Compatibility: On Windows, creating symlinks via
shutil.copy2withfollow_symlinks=Falsecan fail withOSErrorif the user does not have symlink creation privileges.
Since the user explicitly selected this agent to publish, we should follow the symlink (the default behavior of shutil.copy2) to ensure the actual content of the agent is copied and published.
| shutil.copy2(Path(agent.path), dest, follow_symlinks=False) | |
| shutil.copy2(Path(agent.path), dest) |
| 'models.json', 'models.yaml', 'config_local.json', | ||
| # Environment files | ||
| '.env', '.env.*', '*.pem', '*.key', | ||
| # Security/Secret patterns | ||
| 'secrets', 'credentials', 'tokens', 'api_keys', 'mcp-secrets', | ||
| ] |
There was a problem hiding this comment.
To securely handle symlinks during directory copying without breaking functionality or leaking local paths, we can define a custom ignore helper. This helper will use shutil.ignore_patterns for standard patterns and additionally ignore any symlink that points outside the source directory. This prevents directory traversal and data leakage of files outside the source directory, while safely copying the contents of internal symlinks.
'models.json', 'models.yaml', 'config_local.json',
# Environment files
'.env', '.env.*', '*.pem', '*.key',
# Security/Secret patterns
'secrets', 'credentials', 'tokens', 'api_keys', 'mcp-secrets',
]
def _safe_ignore_patterns(src_dir: Path, patterns: list[str]):
standard_ignore = shutil.ignore_patterns(*patterns)
def _ignore(path, names):
ignored = set(standard_ignore(path, names))
for name in names:
full_path = Path(path) / name
if full_path.is_symlink():
try:
resolved = full_path.resolve()
resolved.relative_to(src_dir.resolve())
except ValueError:
ignored.add(name)
return list(ignored)
return _ignore| shutil.copytree( | ||
| src_path, | ||
| dest, | ||
| dirs_exist_ok=True, | ||
| ignore=shutil.ignore_patterns(*DEFAULT_IGNORE_PATTERNS), | ||
| symlinks=True, | ||
| ) |
There was a problem hiding this comment.
Use the new _safe_ignore_patterns helper to safely copy the directory. By omitting symlinks=True (defaulting to False), shutil.copytree will copy the actual content of safe internal symlinks, while the helper will completely ignore any unsafe symlinks pointing outside the source directory. This prevents both broken symlinks in the repository and out-of-bounds data leakage.
shutil.copytree(
src_path,
dest,
dirs_exist_ok=True,
ignore=_safe_ignore_patterns(src_path, DEFAULT_IGNORE_PATTERNS),
)| else: | ||
| dest.parent.mkdir(parents=True, exist_ok=True) | ||
| shutil.copy2(src_path, dest) | ||
| shutil.copy2(src_path, dest, follow_symlinks=False) |
There was a problem hiding this comment.
| shutil.copytree( | ||
| skill.path, | ||
| dest, | ||
| dirs_exist_ok=True, | ||
| ignore=shutil.ignore_patterns(*DEFAULT_IGNORE_PATTERNS), | ||
| symlinks=True, | ||
| ) |
There was a problem hiding this comment.
| else: | ||
| dest.parent.mkdir(parents=True, exist_ok=True) | ||
| shutil.copy2(skill.path, dest) | ||
| shutil.copy2(skill.path, dest, follow_symlinks=False) |
| if agent: | ||
| dest = agents_dir / f"{agent_name}.md" | ||
| shutil.copy2(Path(agent.path), dest) | ||
| shutil.copy2(Path(agent.path), dest, follow_symlinks=False) |
🚨 Severity: HIGH
💡 Vulnerability:
_ignore_funcfailed to correctly filter sensitive files likemodels.json.secrets,credentials, etc., were not explicitly excluded.shutil.copytreeandshutil.copy2were following symbolic links, potentially copying sensitive data from outside the intended directory._is_valid_skill_nameused$which matches before a trailing newline.🎯 Impact:
🔧 Fix:
_ignore_funcwithshutil.ignore_patternsinsrc/agent_sync/publish/git_publish.py.DEFAULT_IGNORE_PATTERNSinsrc/agent_sync/publish/git_publish.py.shutilcalls insrc/agent_sync/publish/git_publish.pyandsrc/agent_sync/publish/agents_source.pyto usesymlinks=Trueandfollow_symlinks=False.\Zinstead of$insrc/agent_sync/publish/local_source.pyandsrc/agent_sync/publish/external_source.py.✅ Verification:
models.jsonand new secret patterns are now correctly ignored.export PYTHONPATH=$PYTHONPATH:$(pwd)/src && python3 -m pytest.PR created automatically by Jules for task 5468783693148882880 started by @renatocaliari