Skip to content

Conversation

@ishaankalra
Copy link
Contributor

@ishaankalra ishaankalra commented Aug 22, 2025

Summary

  • Added followlinks=True parameter to os.walk() in the create_module_zip function
  • This allows symbolic links to be followed when creating module zip files
  • Existing exception handling already covers potential errors from following symlinks

Test plan

  • Test zip creation with directories containing symbolic links
  • Verify that symbolic links are properly included in the generated zip files
  • Ensure existing error handling works correctly with symlink scenarios

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Module packaging now follows symbolic links when creating archives, ensuring files and directories referenced by symlinks are included.
    • This provides broader coverage of project assets during packaging, which may result in larger archives.
    • Users should review included paths to avoid unintentionally packaging files outside the expected project scope, especially when symlinks point to external locations.

@coderabbitai
Copy link

coderabbitai bot commented Aug 22, 2025

Walkthrough

Updated create_module_zip to call os.walk with followlinks=True, causing the zip creation process to traverse and include files/directories reachable through symbolic links. No other logic, signatures, or error handling were changed.

Changes

Cohort / File(s) Summary of Changes
Archive traversal behavior
ftf_cli/operations.py
Modified os.walk(...) to use followlinks=True inside create_module_zip, enabling traversal of symlinked paths during archive creation; other logic unchanged.

Sequence Diagram(s)

sequenceDiagram
    participant Dev as Developer
    participant CLI as FTF CLI
    participant Op as create_module_zip
    participant FS as FileSystem (os.walk: followlinks=true)
    participant Zip as ZipFile

    Dev->>CLI: Run zip command
    CLI->>Op: create_module_zip(path)
    rect rgba(230,240,255,0.6)
    note right of Op: Traverse directory tree
    Op->>FS: os.walk(path, followlinks=true)
    FS-->>Op: Yield dirs/files (incl. symlink targets)
    end
    alt Regular file/dir
        Op->>Zip: add file to archive
    else Symlink encountered
        note over Op,FS: Follows link and traverses target
        Op->>Zip: add resolved files
    end
    Op-->>CLI: Return created archive
    CLI-->>Dev: Success / path to zip
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I hop through links, a nimble knight,
Following paths of silvery light—
Zipping burrows, left and right,
Symlink trails now in my sight.
Thump-thump! The archive’s tight. 🐇📦

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch zip-followlinks

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ftf_cli/operations.py (2)

46-51: Guard against symlink cycles and out-of-root traversal when enabling followlinks=True

Following directory symlinks can:

  • create infinite/very long traversals via cycles
  • accidentally include content outside the module root (secrets, huge dirs)
    Harden traversal by pruning dirs that (a) resolve outside the root or (b) have already been visited. Also defensively skip Terraform artifacts even if reached via symlinks.

Apply this minimal, local diff:

-            for root, dirs, files in os.walk(path, followlinks=True):
-                for file in files:
-                    file_path = os.path.join(root, file)
-                    arcname = os.path.relpath(file_path, path)
-                    zipf.write(file_path, arcname)
+            base_real = os.path.realpath(path)
+            visited_dirs = set()
+            for root, dirs, files in os.walk(path, topdown=True, followlinks=True):
+                # Prune directories that would cause cycles or leave the module root
+                pruned = []
+                for d in dirs:
+                    if d == ".terraform":
+                        continue  # defend against symlinked terraform caches
+                    dpath = os.path.join(root, d)
+                    try:
+                        real_d = os.path.realpath(dpath)
+                        st = os.stat(real_d)
+                    except OSError:
+                        continue  # skip dirs we can't stat
+                    if not (real_d == base_real or real_d.startswith(base_real + os.sep)):
+                        continue  # don't traverse outside module root
+                    key = (st.st_dev, st.st_ino)
+                    if key in visited_dirs:
+                        continue  # avoid cycles via symlinks/junctions
+                    visited_dirs.add(key)
+                    pruned.append(d)
+                dirs[:] = pruned
+
+                for fname in files:
+                    if fname == ".terraform.lock.hcl":
+                        continue
+                    file_path = os.path.join(root, fname)
+                    arcname = os.path.relpath(file_path, path)
+                    zipf.write(file_path, arcname)

46-51: Ensure create_module_zip safely handles symlinks and add a regression test

The manual test reveals that when create_module_zip encounters a symlink cycle, it follows links indefinitely until the OS errors out with “Too many levels of symbolic links,” and it doesn’t explicitly guard against including content outside the specified root. We need to:

  • Update the implementation in ftf_cli/operations.py to
    • Track visited paths or inodes and skip revisiting them (avoiding infinite loops).
    • Refuse to follow symlinks that escape the root directory.
  • Add a regression test (e.g. in tests/test_operations.py) that
    • Creates a symlink cycle under a temporary module root.
    • Creates a symlink pointing outside the module root.
    • Asserts that create_module_zip(root) completes normally (no error or hang).
    • Verifies the zip archive includes expected files (e.g. A/B/file.txt) and excludes external files (secret.txt).

ftf_cli/operations.py:

--- a/ftf_cli/operations.py
+++ b/ftf_cli/operations.py
@@ -40,7 +40,25 @@ def create_module_zip(path: str) -> str:
         with zipfile.ZipFile(tmp_zip, "w", zipfile.ZIP_DEFLATED) as zipf:
-            for root, dirs, files in os.walk(path, followlinks=True):
+            seen = set()
+            for curr_root, dirs, files in os.walk(path, followlinks=True):
+                real_root = os.path.realpath(curr_root)
+                if real_root in seen:
+                    # Skip cycles
+                    continue
+                seen.add(real_root)
+
+                # Filter out dirs that escape the module root
+                dirs[:] = [
+                    d for d in dirs
+                    if os.path.realpath(os.path.join(curr_root, d)).startswith(os.path.realpath(path))
+                ]
+
+                for file in files:
+                    file_path = os.path.join(curr_root, file)
+                    real_file = os.path.realpath(file_path)
+                    # Skip files outside the module root
+                    if not real_file.startswith(os.path.realpath(path)):
+                        continue
+                    arcname = os.path.relpath(file_path, path)
+                    zipf.write(file_path, arcname)

tests/test_operations.py (new):

import os
import tempfile
import zipfile
import pytest
from ftf_cli.operations import create_module_zip

def test_symlink_cycle_and_escape(tmp_path, monkeypatch):
    # Setup module root
    mod = tmp_path / "mod"
    (mod / "A" / "B").mkdir(parents=True)
    (mod / "A" / "B" / "file.txt").write_text("ok")

    # External directory and escape symlink
    outside = tmp_path / "outside"
    outside.mkdir()
    (outside / "secret.txt").write_text("secret")
    (mod / "A" / "link_out").symlink_to(outside)

    # Symlink cycle
    (mod / "A" / "B" / "loop").symlink_to(mod / "A")

    # Point create_module_zip at our temp repo root
    monkeypatch.setenv("PYTHONPATH", os.getcwd())

    zip_path = create_module_zip(str(mod))
    with zipfile.ZipFile(zip_path) as z:
        names = sorted(z.namelist())
    assert "A/B/file.txt" in names
    assert not any("secret.txt" in n for n in names)

This change ensures non-hanging behavior on cycles, enforces root boundaries, and adds a reproducible regression test.

🧹 Nitpick comments (2)
ftf_cli/operations.py (2)

46-46: Address Ruff B007: currently dirs is unused in the loop

If you decide to keep the simple loop without pruning, rename dirs to _dirs to satisfy Ruff. If you adopt the pruning fix above, this becomes moot because dirs is used.

-            for root, dirs, files in os.walk(path, followlinks=True):
+            for root, _dirs, files in os.walk(path, followlinks=True):

46-51: Make followlinks behavior configurable to reduce blast radius

Consider making symlink traversal opt-in via a CLI flag (e.g., --follow-links/--no-follow-links, default off) or environment variable, then thread that boolean into create_module_zip(path, followlinks=False). This avoids surprising users with larger zips or leaking external content.

Happy to draft the CLI/plumbing changes if you want this made configurable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 57607c2 and d2e01aa.

📒 Files selected for processing (1)
  • ftf_cli/operations.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.12.2)
ftf_cli/operations.py

46-46: Loop control variable dirs not used within loop body

Rename unused dirs to _dirs

(B007)

@anujhydrabadi anujhydrabadi merged commit 49e36fe into main Aug 22, 2025
6 checks passed
@anujhydrabadi anujhydrabadi deleted the zip-followlinks branch August 22, 2025 08:38
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.

3 participants