Skip to content

Conversation

@dogancanbakir
Copy link
Member

@dogancanbakir dogancanbakir commented Jan 6, 2026

Summary

  • Implements recursive scanning of template directories to support nested organizational structures
  • Enables templates to be organized in subdirectories (e.g., by difficulty level, type, or subcategory)
  • Adds security protections: symlink skipping, max depth limit (10), and duplicate ID detection

This change supports the template organization needed for vt-templates PR #6 which adds 104 xbow security benchmarks.

Changes

  • scanDirectoryForTemplates() - Recursive directory traversal that detects templates by presence of index.yaml
  • isTemplateDirectory() - Helper function to check if a directory is a template
  • Security: Skip symlinks, enforce max depth, detect duplicate template IDs
  • Comprehensive tests for all new functionality

Test plan

  • All existing tests pass
  • New tests for recursive scanning
  • New tests for hidden directory skipping
  • New tests for max depth enforcement
  • New tests for duplicate ID detection
  • Linter passes

Closes #102

Summary by CodeRabbit

  • New Features

    • Recursive template discovery with configurable maximum scan depth and skipping of hidden entries/symlinks
  • Improvements

    • Validation that template IDs match directory names and detection of duplicate template IDs
    • More specific, contextual error messages for scanning and loading failures
  • Tests

    • Expanded test suite covering nested templates, hidden entries, max-depth limits, duplicates, and directory scanning scenarios

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 6, 2026

Walkthrough

Replaces per-directory template loading with a recursive scanner using filepath.WalkDir bounded by maxScanDepth. Skips hidden entries and symlinks, detects template directories via index.yaml, validates template IDs against directory names, detects duplicate IDs, and wraps errors with category context.

Changes

Cohort / File(s) Summary
Template scanning & loader
pkg/template/template.go
Replaces direct per-directory loading with a recursive scanDirectoryForTemplates using filepath.WalkDir. Adds maxScanDepth, isTemplateDirectory detection (presence of index.yaml), relative-path/depth checks, symlink/hidden-entry skipping, template ID vs. directory-name validation, duplicate ID detection, and richer error wrapping with category context.
Tests & test helpers
pkg/template/template_test.go
Adds createTestTemplate test helper and a comprehensive test suite covering template-directory detection, nested loading, handling of hidden entries and symlinks, max-depth enforcement, duplicate-ID detection, and loading from category/directory hierarchies.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hop through folders, index in paw,
Seeking index.yaml in every straw,
I skip sly symlinks and shadows that hide,
Match names to IDs with tidy pride.
Tests stash carrots — now templates abide.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing recursive subdirectory scanning for templates to support nested organizational structures.
Linked Issues check ✅ Passed The pull request implements recursive scanning of template subdirectories as required by #102, including nested directory support, max depth enforcement, symlink protection, and duplicate ID detection.
Out of Scope Changes check ✅ Passed All changes are within scope: template loading logic enhancements, helper functions, and comprehensive tests directly support the objective to scan subdirectories for templates.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/recursive-template-scanning

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@recepgunes1 recepgunes1 self-requested a review January 6, 2026 11:05
Copy link
Contributor

@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

🧹 Nitpick comments (2)
pkg/template/template_test.go (1)

121-140: Consider adding a test for symlink handling.

The implementation skips symlinks for security, but there's no explicit test for this behavior. While creating symlinks in tests can be platform-dependent, it would strengthen the test coverage for this security feature.

pkg/template/template.go (1)

139-142: Symlink check is unreachable dead code.

The check at line 135 (!entry.IsDir()) filters out all symlinks before reaching the symlink check on line 139. Since os.ReadDir uses Lstat internally, DirEntry.IsDir() returns false for symlinks (including symlinks to directories), so the debug log message never executes. While symlinks are still correctly skipped, this check is redundant. Remove lines 139-142 or move the symlink check before the IsDir() filter if the debug log is important for observability.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between db6ccbd and 0b024f6.

📒 Files selected for processing (2)
  • pkg/template/template.go
  • pkg/template/template_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/template/template_test.go (1)
pkg/template/template.go (1)
  • Template (17-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (12)
pkg/template/template.go (4)

95-97: LGTM!

The depth limit constant is appropriately defined and documented. A value of 10 provides reasonable flexibility for nested directory structures while preventing resource exhaustion from excessively deep hierarchies.


99-110: LGTM!

The refactored function cleanly delegates to the new scanner and appropriately wraps errors with category context for better debugging.


112-118: LGTM!

The helper correctly identifies template directories by checking for index.yaml. Treating any os.Stat error as "not a template directory" is reasonable defensive behavior for this use case.


146-162: LGTM!

Solid implementation with proper validation:

  • Template ID must match directory name (prevents misconfigurations)
  • Duplicate ID detection prevents silent overwrites
  • Informative error messages aid debugging
pkg/template/template_test.go (8)

36-63: LGTM!

Well-designed test helper that creates realistic template structures. The YAML content includes all necessary fields for valid templates, and proper error handling with t.Helper() ensures clean test output.


65-84: LGTM!

Comprehensive test covering all three expected cases: directory without index.yaml, directory with index.yaml, and non-existent directory.


86-119: LGTM!

Well-structured test with clear documentation of the expected directory hierarchy. Validates that recursive scanning correctly discovers templates at multiple nesting levels.


121-140: LGTM!

Effectively tests that hidden directories (prefixed with .) are properly skipped during scanning.


142-158: LGTM!

Correctly tests the max depth enforcement by creating a directory structure that exceeds maxScanDepth and verifying the expected error is returned.


160-176: LGTM!

Properly tests duplicate template ID detection across different subdirectories.


178-202: LGTM!

Good integration test verifying that loadTemplatesFromCategory correctly uses the new scanner to find both direct and nested templates.


204-241: LGTM!

Comprehensive end-to-end test that validates the full template loading pipeline with nested categories and subdirectories, mirroring realistic usage patterns.

@recepgunes1
Copy link
Member

What is the reason using recursion instead of https://pkg.go.dev/path/filepath#WalkDir

@dogancanbakir
Copy link
Member Author

No real advantage, updating.

Implement recursive scanning of template directories using filepath.WalkDir
to support nested organizational structures (e.g., templates organized by
difficulty level or subcategory).

Changes:
- Use filepath.WalkDir for idiomatic directory traversal
- Add isTemplateDirectory() helper to detect template directories
- Skip symlinks to prevent path traversal attacks
- Add max depth limit (10) to prevent excessive resource usage
- Detect and error on duplicate template IDs
- Add comprehensive tests for all new functionality

Closes #102
@dogancanbakir dogancanbakir force-pushed the feat/recursive-template-scanning branch from 0b024f6 to e4f0028 Compare January 6, 2026 11:25
Copy link
Contributor

@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: 1

🤖 Fix all issues with AI Agents
In @pkg/template/template.go:
- Around line 114-125: The symlink branch never runs because d.IsDir() is
checked before symlink detection; move the explicit symlink handling (the
d.Type()&os.ModeSymlink check and the log.Debug().Msgf("skipping symlink: %s",
d.Name()) / return filepath.SkipDir) above the d.IsDir() check so symlinks are
detected, logged and skipped explicitly; ensure you still return
filepath.SkipDir for symlinks and retain the existing hidden-file/directory
check.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b024f6 and e4f0028.

📒 Files selected for processing (2)
  • pkg/template/template.go
  • pkg/template/template_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/template/template_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/template/template.go (1)
pkg/template/parser.go (1)
  • LoadTemplate (12-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
pkg/template/template.go (3)

95-97: LGTM!

A depth limit of 10 is reasonable for nested template structures. This safeguard prevents excessive resource consumption during directory traversal.


127-149: LGTM!

The depth calculation, template ID validation against directory names, and duplicate detection logic are all correctly implemented. Returning filepath.SkipDir after loading a template properly prevents descending into template directories.


161-167: LGTM!

Simple and correct implementation. Using os.Stat appropriately handles the case where index.yaml might be a symlink to the actual file.

Move symlink detection before the d.IsDir() check so symlinks are
properly detected and skipped. Previously, symlinks were filtered out
by the !d.IsDir() check before the symlink handling could run.
Copy link
Contributor

@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: 1

🤖 Fix all issues with AI Agents
In @pkg/template/template.go:
- Around line 121-124: The symlink branch in the WalkDir handler (the
d.Type()&os.ModeSymlink check) currently returns filepath.SkipDir which causes
remaining siblings in the same directory to be skipped; change the handler so
symlinks are logged/skipped but the function returns nil instead of
filepath.SkipDir (i.e., remove the SkipDir return for symlinks) so WalkDir
continues processing sibling entries.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4f0028 and 8600b6e.

📒 Files selected for processing (1)
  • pkg/template/template.go
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/template/template.go (1)
pkg/template/parser.go (1)
  • LoadTemplate (12-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (3)
pkg/template/template.go (3)

95-97: LGTM! Appropriate depth limit.

The depth limit of 10 is reasonable for nested template organization while preventing resource exhaustion and infinite loops from circular symlinks.


130-152: LGTM! Robust depth enforcement and template validation.

The implementation correctly:

  • Calculates directory depth relative to the category root
  • Enforces the maximum depth limit with a clear error message
  • Validates that template IDs match their directory names (preventing mismatched metadata)
  • Detects duplicate template IDs across the hierarchy (preventing ID collisions)
  • Returns SkipDir after loading a template to avoid scanning inside template directories
  • Provides comprehensive error context for all failure modes

157-159: LGTM! Clean helper and error handling.

The isTemplateDirectory helper provides a clear, single-purpose check for template directories by detecting index.yaml. The error wrapping at line 158 adds valuable context about which category failed to scan.

Also applies to: 164-170

Comment on lines +121 to +124
if d.Type()&os.ModeSymlink != 0 {
log.Debug().Msgf("skipping symlink: %s", d.Name())
return filepath.SkipDir
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix symlink handling to avoid skipping sibling entries.

According to filepath.WalkDir documentation, returning filepath.SkipDir for a non-directory entry skips all remaining files in the containing directory. For a symlink to a file (where d.IsDir() returns false), this causes sibling templates to be incorrectly skipped.

For example, if a directory contains:

template1/
symlink-to-file
template2/

When the symlink is encountered, template2/ would never be processed.

🔎 Proposed fix
 if d.Type()&os.ModeSymlink != 0 {
   log.Debug().Msgf("skipping symlink: %s", d.Name())
-  return filepath.SkipDir
+  if d.IsDir() {
+    return filepath.SkipDir
+  }
+  return nil
 }

Alternatively, always return nil for symlinks since WalkDir won't descend into symlinked directories anyway:

 if d.Type()&os.ModeSymlink != 0 {
   log.Debug().Msgf("skipping symlink: %s", d.Name())
-  return filepath.SkipDir
+  return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if d.Type()&os.ModeSymlink != 0 {
log.Debug().Msgf("skipping symlink: %s", d.Name())
return filepath.SkipDir
}
if d.Type()&os.ModeSymlink != 0 {
log.Debug().Msgf("skipping symlink: %s", d.Name())
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
🤖 Prompt for AI Agents
In @pkg/template/template.go around lines 121 - 124, The symlink branch in the
WalkDir handler (the d.Type()&os.ModeSymlink check) currently returns
filepath.SkipDir which causes remaining siblings in the same directory to be
skipped; change the handler so symlinks are logged/skipped but the function
returns nil instead of filepath.SkipDir (i.e., remove the SkipDir return for
symlinks) so WalkDir continues processing sibling entries.

@recepgunes1 recepgunes1 merged commit aa38992 into main Jan 6, 2026
6 checks passed
@recepgunes1 recepgunes1 deleted the feat/recursive-template-scanning branch January 6, 2026 16:10
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.

scan sub folders for the templates when loading

3 participants