Skip to content

🛡️ Sentinel: [HIGH] Harden publish flow against data leakage and symlink vulnerabilities#111

Open
calionauta wants to merge 1 commit into
mainfrom
sentinel/harden-publish-flow-5468783693148882880
Open

🛡️ Sentinel: [HIGH] Harden publish flow against data leakage and symlink vulnerabilities#111
calionauta wants to merge 1 commit into
mainfrom
sentinel/harden-publish-flow-5468783693148882880

Conversation

@calionauta

Copy link
Copy Markdown
Owner

🚨 Severity: HIGH
💡 Vulnerability:

  • Broken file exclusion logic in publish flow: A custom _ignore_func failed to correctly filter sensitive files like models.json.
  • Missing sensitive patterns in default ignore list: Patterns like secrets, credentials, etc., were not explicitly excluded.
  • Symlink content leakage: shutil.copytree and shutil.copy2 were following symbolic links, potentially copying sensitive data from outside the intended directory.
  • Regex newline injection: _is_valid_skill_name used $ which matches before a trailing newline.

🎯 Impact:

  • Accidental publication of API keys, credentials, and local sensitive files to public repositories.
  • Potential argument injection or bypass if skill names are used in shell commands or path construction.

🔧 Fix:

  • Replaced custom _ignore_func with shutil.ignore_patterns in src/agent_sync/publish/git_publish.py.
  • Added critical security patterns to DEFAULT_IGNORE_PATTERNS in src/agent_sync/publish/git_publish.py.
  • Updated shutil calls in src/agent_sync/publish/git_publish.py and src/agent_sync/publish/agents_source.py to use symlinks=True and follow_symlinks=False.
  • Updated regex to use \Z instead of $ in src/agent_sync/publish/local_source.py and src/agent_sync/publish/external_source.py.

✅ Verification:

  • Reproduction script confirmed models.json and new secret patterns are now correctly ignored.
  • Regex test confirmed newline injection is blocked.
  • Full test suite passed with export PYTHONPATH=$PYTHONPATH:$(pwd)/src && python3 -m pytest.

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

…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>
@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 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)

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.

security-high high

Using follow_symlinks=False on shutil.copy2 will copy the symbolic link itself rather than the target file's content. This introduces several issues:

  1. 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.
  2. 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.
  3. Platform Compatibility: On Windows, creating symlinks via shutil.copy2 with follow_symlinks=False can fail with OSError if 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.

Suggested change
shutil.copy2(Path(agent.path), dest, follow_symlinks=False)
shutil.copy2(Path(agent.path), dest)

Comment on lines 24 to 29
'models.json', 'models.yaml', 'config_local.json',
# Environment files
'.env', '.env.*', '*.pem', '*.key',
# Security/Secret patterns
'secrets', 'credentials', 'tokens', 'api_keys', 'mcp-secrets',
]

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.

security-high high

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

Comment on lines +77 to +83
shutil.copytree(
src_path,
dest,
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(*DEFAULT_IGNORE_PATTERNS),
symlinks=True,
)

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.

security-high high

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)

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.

security-high high

Omit follow_symlinks=False to ensure that the actual content of the explicitly selected file is copied and published, rather than committing a broken symlink and leaking local paths.

Suggested change
shutil.copy2(src_path, dest, follow_symlinks=False)
shutil.copy2(src_path, dest)

Comment on lines +153 to +159
shutil.copytree(
skill.path,
dest,
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(*DEFAULT_IGNORE_PATTERNS),
symlinks=True,
)

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.

security-high high

Use the new _safe_ignore_patterns helper to safely copy the skill directory, preventing broken symlinks and out-of-bounds data leakage.

                                shutil.copytree(
                                    skill.path,
                                    dest,
                                    dirs_exist_ok=True,
                                    ignore=_safe_ignore_patterns(skill.path, DEFAULT_IGNORE_PATTERNS),
                                )

else:
dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(skill.path, dest)
shutil.copy2(skill.path, dest, follow_symlinks=False)

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.

security-high high

Omit follow_symlinks=False to ensure that the actual content of the explicitly selected skill file is copied and published.

Suggested change
shutil.copy2(skill.path, dest, follow_symlinks=False)
shutil.copy2(skill.path, dest)

if agent:
dest = agents_dir / f"{agent_name}.md"
shutil.copy2(Path(agent.path), dest)
shutil.copy2(Path(agent.path), dest, follow_symlinks=False)

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.

security-high high

Omit follow_symlinks=False to ensure that the actual content of the explicitly selected agent file is copied and published.

Suggested change
shutil.copy2(Path(agent.path), dest, follow_symlinks=False)
shutil.copy2(Path(agent.path), dest)

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