Skip to content

Conversation

osterman
Copy link
Member

@osterman osterman commented Sep 3, 2025

what

  • Implement configurable symlink security policies for vendor operations
  • Add three security policies: allow_safe (default), reject_all, and allow_all
  • Validate symlinks stay within vendor boundaries to prevent directory traversal attacks
  • Create comprehensive PRD documenting the security implementation

why

  • CVE-2025-8959 (CVSS 7.5) is a critical vulnerability in HashiCorp's go-getter library that allows symlink attacks
  • Malicious repositories can use symlinks to gain unauthorized read access to files outside vendor boundaries
  • HashiCorp's fix (v1.7.9) simply disables all symlinks, breaking legitimate use cases
  • This implementation addresses the root cause while maintaining functionality for valid symlinks

references

Summary

This PR implements a security feature that protects against symlink attacks in vendor operations without breaking existing workflows that rely on legitimate symlinks.

Key Changes

  1. Security Module (pkg/security/symlink_validator.go)

    • Three configurable policies: allow_safe, reject_all, allow_all
    • Boundary validation to prevent directory traversal
    • Integration with otiai10/copy library callbacks
  2. Configuration (vendor.policy.symlinks in atmos.yaml)

    vendor:
      policy:
        symlinks: "allow_safe"  # Default - blocks escaping symlinks
  3. Git Operations (pkg/downloader/git_getter.go)

    • Changed from removing all symlinks to validating based on policy
    • Maintains compatibility with go-getter v1.7.9
  4. Comprehensive Testing

    • Unit tests for symlink validation logic
    • Integration tests specifically for CVE-2025-8959 attack scenarios
    • Tests for all three security policies
  5. Documentation

    • Complete PRD following CloudPosse standards
    • Updated vendor-pull.mdx with configuration examples
    • Security best practices and troubleshooting guide

Security Impact

  • Default Protection: The default allow_safe policy immediately protects users from CVE-2025-8959
  • Backward Compatibility: Users can opt into allow_all for legacy behavior if needed
  • Maximum Security: reject_all available for high-security environments

Test Results

All tests pass, including specific tests for:

  • CVE-2025-8959 attack vectors (absolute paths, directory traversal, /etc/passwd)
  • Legitimate internal symlinks continue to work with allow_safe
  • Policy configuration and parsing
  • Cross-platform compatibility (Linux/macOS)

🤖 Generated with Claude Code

Co-Authored-By: Claude [email protected]

Summary by CodeRabbit

  • New Features

    • Configurable symlink security for vendoring with policies: allow_safe (default), reject_all, allow_all; configurable via atmos.yaml and ATMOS_VENDOR_POLICY_SYMLINKS. Vendor pulls and git downloads now honor the selected policy.
  • Documentation

    • Expanded vendor-pull docs with examples, best practices, CVE guidance.
    • Added PRD guidance and a vendor-symlink-security PRD.
  • Tests

    • Comprehensive unit/integration tests covering symlink policies and attack scenarios.

## Summary
- Add configurable symlink security policies to prevent directory traversal attacks
- Implement three policies: allow_safe (default), reject_all, allow_all
- Validate symlinks stay within vendor boundaries with allow_safe policy

## Details
This change addresses CVE-2025-8959, a critical vulnerability in HashiCorp's go-getter
library that allows symlink attacks during subdirectory downloads. Instead of disabling
symlinks entirely (HashiCorp's approach), we implement boundary validation to maintain
functionality while preventing attacks.

The default 'allow_safe' policy blocks symlinks that escape the vendor directory while
allowing legitimate internal symlinks to continue working. Users can configure their
preferred security posture in atmos.yaml.

## Testing
- Added comprehensive unit tests for symlink validation
- Added integration tests for CVE-2025-8959 attack scenarios
- Tested all three security policies

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
@osterman osterman requested review from a team as code owners September 3, 2025 15:53
@github-actions github-actions bot added the size/xl Extra large size PR label Sep 3, 2025
@mergify mergify bot added the triage Needs triage label Sep 3, 2025
Copy link

mergify bot commented Sep 3, 2025

Warning

This PR exceeds the recommended limit of 1,000 lines.

Large PRs are difficult to review and may be rejected due to their size.

Please verify that this PR does not address multiple issues.
Consider refactoring it into smaller, more focused PRs to facilitate a smoother review process.

Copy link
Contributor

coderabbitai bot commented Sep 3, 2025

📝 Walkthrough

Walkthrough

Adds a policy-driven symlink security module, integrates it into vendoring and git download copy flows, updates schema/config and tests, removes a legacy copy helper, and adds PRD/docs and CLI import alias normalizations. No public API signature removals.

Changes

Cohort / File(s) Summary
Documentation & PRDs
CLAUDE.md, prd/README.md, prd/vendor-symlink-security.prd.md, website/docs/cli/commands/vendor/vendor-pull.mdx
Adds PRD guidance and a vendor-symlink-security PRD; expands vendor-pull docs with symlink policy guidance and examples.
Symlink Security Subsystem
pkg/security/symlink_validator.go, pkg/security/symlink_validator_test.go
New SymlinkPolicy type, parsing, IsSymlinkSafe, CreateSymlinkHandler, ValidateSymlinks, policy extraction from config, and comprehensive tests (including CVE scenarios).
Schema / Config
pkg/schema/schema.go, pkg/config/utils.go
Adds VendorPolicy type and Vendor.Policy.Symlinks field; supports ATMOS_VENDOR_POLICY_SYMLINKS env var for overriding.
Vendoring / Exec Integration
internal/exec/vendor_component_utils.go, internal/exec/vendor_model.go, internal/exec/copy_glob.go, internal/exec/copy_glob_test.go
Threads atmosConfig through copy flows, replaces hard-coded cp.Deep with policy-derived OnSymlink handlers via security.CreateSymlinkHandler, updates function signatures and tests (nil at test sites).
Vendoring Utils Cleanup
internal/exec/vendor_utils.go
Removes the cp-based copy helper (github.com/otiai10/copy usage and copyToTarget), eliminating that copy path.
Downloader / Git Getter
pkg/downloader/git_getter.go
Adds Policy field to CustomGitGetter and validates/handles symlinks using security.ValidateSymlinks (defaults to allow_safe), deprecating previous removeSymlinks approach.
Tests — Integration
tests/vendor_symlink_security_test.go
New end-to-end vendor symlink policy tests exercising allow_safe/reject_all/allow_all behaviors and CVE-like attack scenarios.
Import Alias Normalization
cmd/root.go, pkg/hooks/store_cmd.go, tests/cli_test.go
Aliases github.com/charmbracelet/log as log to avoid stdlib conflict; no behavior change.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant CLI as CLI / Vendor Command
  participant VM as VendorModel
  participant Exec as Exec (copy paths)
  participant Sec as Security Module
  participant CP as cp.Copy

  CLI->>VM: vendor pull/install (with atmosConfig)
  VM->>Sec: GetPolicyFromConfig(atmosConfig)
  Sec-->>VM: SymlinkPolicy
  VM->>Exec: downloadAndInstall(..., atmosConfig)
  Exec->>Sec: CreateSymlinkHandler(baseDir, policy)
  Sec-->>Exec: OnSymlink handler
  Exec->>CP: Copy(src, dst, Options{OnSymlink: handler})
  CP-->>Exec: result
  Exec-->>VM: done
  VM-->>CLI: completed
Loading
sequenceDiagram
  autonumber
  participant Caller as VendorSource (git)
  participant GG as CustomGitGetter
  participant Sec as Security Module
  participant FS as Filesystem

  Caller->>GG: Get(repo, dst)
  GG->>Sec: ValidateSymlinks(dst, gg.Policy|default)
  Sec->>FS: Walk & remove/allow symlinks per policy
  FS-->>Sec: done
  Sec-->>GG: ok/error
  GG-->>Caller: result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

major

Suggested reviewers

  • aknysh
  • Benbentwo

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • CVE-2025: Entity not found: Issue - Could not find referenced Issue.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch secure-symlink-handling

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
🪧 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 or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or @auto-summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @auto-title anywhere in the PR title to generate the title automatically.

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.

osterman and others added 2 commits September 3, 2025 11:04
- Fix gofumpt formatting in multiple files
- Add 'log' alias for charmbracelet/log imports
- Refactor nested if statements to reduce complexity
- Remove unused copyTargetParams struct and copyToTarget function
- Extract helper functions to reduce code duplication
- Add constants for repeated string literals
- Reduce function parameters from 6 to 5 by using parameter structs

All 11 linting issues identified by golangci-lint have been resolved.
Copy link
Contributor

coderabbitai bot commented Sep 3, 2025

📝 Walkthrough

Walkthrough

Introduces configurable symlink-handling policies and integrates them across vendoring, copy, and git download paths. Adds a new security module, schema support for policy, updates function signatures to pass config, replaces hardcoded symlink behavior with policy-driven handlers, and adds tests and docs (including PRDs and CLI docs) for the new behavior.

Changes

Cohort / File(s) Change summary
Security module
pkg/security/symlink_validator.go, pkg/security/symlink_validator_test.go
New policy-based symlink validator: policy enum, parsing, safety checks, cp.OnSymlink handler, tree validation, config lookup; comprehensive unit tests including CVE scenarios.
Schema
pkg/schema/schema.go
Adds VendorPolicy and Vendor.Policy.Symlinks to configure symlink handling.
Downloader (git getter)
pkg/downloader/git_getter.go
Adds CustomGitGetter.Policy; switches from removing symlinks to policy-based validation after fetch; updates imports/comments; deprecates old removal helper.
Vendor integration
internal/exec/vendor_component_utils.go, internal/exec/vendor_model.go, internal/exec/vendor_utils.go
Threads atmosConfig; replaces hardcoded cp.Deep with security.CreateSymlinkHandler(...); adds policy retrieval; adds file-skip func in component copy; updates copy targets to pass config.
Copy with globs
internal/exec/copy_glob.go, internal/exec/copy_glob_test.go
Extends copyToTargetWithPatterns signature to accept atmosConfig; in no-pattern path uses policy-driven OnSymlink; tests updated to pass nil.
End-to-end tests
tests/vendor_symlink_security_test.go
Adds integration tests validating policy behavior across copy/vendor flows and CVE-like attack blocking (skips on Windows).
Docs & PRDs
CLAUDE.md, prd/README.md, prd/vendor-symlink-security.prd.md, website/docs/cli/commands/vendor/vendor-pull.mdx
Adds PRD workflow and dedicated vendor symlink security PRD; documents vendor.policy.symlinks options, behavior, examples, troubleshooting; note: doc block appears twice in vendor-pull.mdx.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Developer
  participant CFG as Atmos Config
  participant VND as Vendor Ops
  participant SEC as Security Module
  participant CP as cp.Copy / FS
  participant GIT as Git Getter

  Dev->>VND: vendor pull / copy
  VND->>CFG: Read vendor.policy.symlinks
  CFG-->>VND: symlink policy (string)
  VND->>SEC: Parse/GetPolicyFromConfig
  SEC-->>VND: SymlinkPolicy
  VND->>SEC: CreateSymlinkHandler(baseDir, policy)
  SEC-->>VND: OnSymlink handler
  VND->>CP: Copy(..., OnSymlink=handler, Skip=...)
  CP-->>VND: Copy result

  rect rgb(242,248,255)
    note right of GIT: New/changed
    VND->>GIT: Fetch source (git/https)
    GIT->>SEC: ValidateSymlinks(root, policy)
    SEC-->>GIT: ok/error (removes/keeps per policy)
  end

  VND-->>Dev: Completed vendoring
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Suggested labels

patch

Suggested reviewers

  • aknysh
  • milldr
  • osterman

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • CVE-2025: Entity not found: Issue - Could not find referenced Issue.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch secure-symlink-handling

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
🪧 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 or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary or @auto-summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @auto-title anywhere in the PR title to generate the title automatically.

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
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: 9

Caution

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

⚠️ Outside diff range comments (1)
internal/exec/vendor_component_utils.go (1)

154-156: Wrap copy errors with context.

Return contextual errors to aid triage of copy failures.

-  if err := cp.Copy(tempDir, componentPath2, copyOptions); err != nil {
-    return err
-  }
+  if err := cp.Copy(tempDir, componentPath2, copyOptions); err != nil {
+    return fmt.Errorf("copying component from %q to %q failed: %w", tempDir, componentPath2, err)
+  }
🧹 Nitpick comments (15)
pkg/schema/schema.go (2)

900-904: Add a brief field doc for Vendor.Policy.

Exported field without a comment. Keeps godoc/lint happy and clarifies purpose.

 type Vendor struct {
   // Path to vendor configuration file or directory containing vendor files
   // If a directory is specified, all .yaml files in the directory will be processed in lexicographical order
   BasePath string       `yaml:"base_path" json:"base_path" mapstructure:"base_path"`
   List     ListConfig   `yaml:"list,omitempty" json:"list,omitempty" mapstructure:"list"`
+  // Policy controls security-sensitive behavior during vendoring (e.g., symlink handling).
   Policy   VendorPolicy `yaml:"policy,omitempty" json:"policy,omitempty" mapstructure:"policy"`
 }

905-910: Punctuate the “Options” comment.

Match repo rule: “All comments must end with periods.”

 type VendorPolicy struct {
   // Symlinks defines how symlinks are handled during vendoring.
-  // Options: "allow_safe" (default), "reject_all", "allow_all"
+  // Options: "allow_safe" (default), "reject_all", "allow_all".
   Symlinks string `yaml:"symlinks,omitempty" json:"symlinks,omitempty" mapstructure:"symlinks"`
 }
CLAUDE.md (2)

17-27: End the paragraph with a period.

Minor style nit to satisfy lint and keep consistency.

-When implementing features, consult relevant PRDs first to understand the full context and requirements
+When implementing features, consult relevant PRDs first to understand the full context and requirements.

291-295: Add periods to list items (style consistency).

Keeps markdown uniform with other sections.

-1. **Check for existing PRDs** in `prd/` directory for design decisions and requirements
-2. **Create a PRD** for significant features following the template in `prd/`
-3. Follow the implementation plan outlined in the relevant PRD
+1. **Check for existing PRDs** in `prd/` directory for design decisions and requirements.
+2. **Create a PRD** for significant features following the template in `prd/`.
+3. Follow the implementation plan outlined in the relevant PRD.
website/docs/cli/commands/vendor/vendor-pull.mdx (1)

234-239: Punctuate the inline comment.

Minor polish.

-    symlinks: "allow_safe"  # Default: validates symlink targets
+    symlinks: "allow_safe"  # Default: validates symlink targets.
prd/vendor-symlink-security.prd.md (1)

121-129: Tighten validation algorithm wording.

Edge case: “relative path is absolute” is redundant. Make absolute-target check explicit first, then boundary check.

-1. Read symlink target
-2. Resolve to absolute path
-3. Check if path is within boundary:
-   - Calculate relative path from boundary to target
-   - If relative path starts with `..` → unsafe
-   - If relative path is absolute → unsafe
-   - Otherwise → safe
+1. Read symlink target.
+2. Resolve target to an absolute path.
+3. Reject if the target is absolute outside the boundary (e.g., `/etc/passwd`).
+4. Compute `rel := filepath.Rel(boundary, target)`.
+5. If `rel` starts with `..` or is `.` outside boundary → unsafe; otherwise → safe.
pkg/downloader/git_getter.go (2)

25-29: Punctuation nit in comment.

End the inline comment with a period to match repo’s doc style.

-  // Validate symlinks based on policy (default to allow_safe if not configured)
+  // Validate symlinks based on policy (default to allow_safe if not configured).

11-16: Consider a constructor to set a default policy.

Add NewCustomGitGetter(...) that defaults Policy to PolicyAllowSafe to avoid per-call defaulting and ease testing.

Would you like a small patch introducing a constructor plus interface seam for swapping the validator in tests?

internal/exec/vendor_component_utils.go (2)

224-233: Error string consistency.

Tiny nit: follow static-first wrapping consistently (already used elsewhere). Current string starts with capital in some places and lower in others. Align to one style.


484-493: Optional: log policy choice once per operation.

A debug log with component name, policy, and boundary would help during incident response without leaking secrets.

I can add a single log.Debug with keys: component, policy, boundary.

internal/exec/copy_glob_test.go (2)

611-611: Add tests that exercise non-nil atmosConfig policies.

Current call-sites pass nil, so allow_safe default isn’t explicitly verified, and reject_all/allow_all aren’t covered. Add table-driven cases with an AtmosConfiguration whose Vendor.Policy.Symlinks is set to "reject_all" and "allow_all", including a real symlink fixture, to validate behavior of the no-patterns cp.Copy branch.

Example sketch (new test file or extended suite):

+func TestCopyToTargetWithPatterns_PolicyVariants(t *testing.T) {
+  if runtime.GOOS == "windows" { t.Skip("symlink tests require non-Windows") }
+  src, _ := os.MkdirTemp("", "policy-src")
+  dst, _ := os.MkdirTemp("", "policy-dst")
+  defer os.RemoveAll(src); defer os.RemoveAll(dst)
+  target := filepath.Join(src, "t.txt")
+  _ = os.WriteFile(target, []byte("x"), 0o600)
+  link := filepath.Join(src, "l.txt")
+  _ = os.Symlink("t.txt", link)
+  vendor := &schema.AtmosVendorSource{}
+
+  mkCfg := func(p string) *schema.AtmosConfiguration {
+    return &schema.AtmosConfiguration{Vendor: schema.Vendor{Policy: schema.VendorPolicy{Symlinks: p}}}
+  }
+
+  // reject_all: symlink should not be copied.
+  _ = copyToTargetWithPatterns(src, dst, vendor, false, mkCfg("reject_all"))
+  if _, err := os.Stat(filepath.Join(dst, "l.txt")); err == nil { t.Fatalf("expected symlink skipped") }
+
+  // allow_all: symlink should be copied (followed).
+  dst2, _ := os.MkdirTemp("", "policy-dst2"); defer os.RemoveAll(dst2)
+  _ = copyToTargetWithPatterns(src, dst2, vendor, false, mkCfg("allow_all"))
+  if _, err := os.Stat(filepath.Join(dst2, "l.txt")); os.IsNotExist(err) { t.Fatalf("expected symlink copied") }
+}

Also applies to: 642-642, 674-674, 916-916


883-911: Patch uses cp.Copy monkeypatching; consider asserting options.

If feasible, assert that OnSymlink is present when patterns are empty by wrapping cp.Copy and inspecting behavior with a symlink in src.

pkg/security/symlink_validator_test.go (1)

318-385: Avoid flakiness when HOME is unset; add intermediate-dir link attack.

HOME may be empty in CI. Build the attacks slice programmatically and append the home-directory case conditionally. Also include an attack that uses an internal directory symlink pointing outside.

Apply this diff:

@@
-	// Create attack symlinks.
-	attacks := []struct {
+	// Create attack symlinks.
+	type attackDef struct {
 		name   string
 		target string
 		link   string
-	}{
-		{
-			name:   "directory_traversal_attack",
-			target: "../../../../etc/passwd",
-			link:   filepath.Join(componentsDir, "passwd_link"),
-		},
-		{
-			name:   "absolute_path_attack",
-			target: "/etc/shadow",
-			link:   filepath.Join(componentsDir, "shadow_link"),
-		},
-		{
-			name:   "home_directory_attack",
-			target: filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa"),
-			link:   filepath.Join(componentsDir, "ssh_key_link"),
-		},
-	}
+	}
+	var attacks []attackDef
+	attacks = append(attacks,
+		attackDef{
+			name:   "directory_traversal_attack",
+			target: "../../../../etc/passwd",
+			link:   filepath.Join(componentsDir, "passwd_link"),
+		},
+		attackDef{
+			name:   "absolute_path_attack",
+			target: "/etc/shadow",
+			link:   filepath.Join(componentsDir, "shadow_link"),
+		},
+	)
+	// Create an internal directory symlink inside the repo that points outside the boundary.
+	outdir := filepath.Join(componentsDir, "outdir")
+	require.NoError(t, os.Symlink(sensitiveDir, outdir))
+	attacks = append(attacks,
+		attackDef{
+			name:   "intermediate_dir_symlink_escape",
+			target: filepath.Join("outdir", "sensitive.txt"),
+			link:   filepath.Join(componentsDir, "via_outdir_link"),
+		},
+	)
+	// Add $HOME attack only if HOME is set.
+	if home := os.Getenv("HOME"); home != "" {
+		attacks = append(attacks,
+			attackDef{
+				name:   "home_directory_attack",
+				target: filepath.Join(home, ".ssh", "id_rsa"),
+				link:   filepath.Join(componentsDir, "ssh_key_link"),
+			},
+		)
+	}
tests/vendor_symlink_security_test.go (2)

136-147: Assert content read, don’t ignore read errors.

Strengthen the positive path by requiring read success and validating content for policies that deep-copy.

Apply this diff:

-				if tc.policy != security.PolicyRejectAll {
-					content, err := os.ReadFile(destLegitimate)
-					if err == nil {
-						assert.Equal(t, "LEGITIMATE DATA", string(content),
-							"Content should match for legitimate symlink")
-					}
-				}
+				if tc.policy != security.PolicyRejectAll {
+					content, err := os.ReadFile(destLegitimate)
+					require.NoError(t, err, "Should read content for legitimate symlink.")
+					assert.Equal(t, "LEGITIMATE DATA", string(content))
+				}

25-97: Consider adding an intermediate-dir symlink attack to this test.

Mirror the pkg test by creating components/outdir -> sensitiveDir and a symlink to outdir/sensitive.txt. Expect blocked under allow_safe and reject_all, allowed under allow_all.

I can draft the subtest diff if you want it in this suite as well.

📜 Review details

Configuration used: Path: .coderabbit.yaml

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 4c89983 and e9f90ca.

📒 Files selected for processing (14)
  • CLAUDE.md (2 hunks)
  • internal/exec/copy_glob.go (3 hunks)
  • internal/exec/copy_glob_test.go (4 hunks)
  • internal/exec/vendor_component_utils.go (6 hunks)
  • internal/exec/vendor_model.go (3 hunks)
  • internal/exec/vendor_utils.go (2 hunks)
  • pkg/downloader/git_getter.go (1 hunks)
  • pkg/schema/schema.go (1 hunks)
  • pkg/security/symlink_validator.go (1 hunks)
  • pkg/security/symlink_validator_test.go (1 hunks)
  • prd/README.md (1 hunks)
  • prd/vendor-symlink-security.prd.md (1 hunks)
  • tests/vendor_symlink_security_test.go (1 hunks)
  • website/docs/cli/commands/vendor/vendor-pull.mdx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
website/**

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

website/**: Update website documentation in the website/ directory when adding new features
Follow the website's documentation structure and style
Keep website code in the website/ directory
Follow the existing website architecture and style
Document new features on the website
Include examples and use cases in website documentation

Files:

  • website/docs/cli/commands/vendor/vendor-pull.mdx
website/docs/cli/commands/**/**/*.mdx

📄 CodeRabbit inference engine (CLAUDE.md)

website/docs/cli/commands/**/**/*.mdx: All new commands/flags/parameters must have Docusaurus documentation under website/docs/cli/commands//.mdx.
Use definition lists

instead of tables for arguments and flags; follow existing frontmatter and section ordering (Usage → Examples → Arguments → Flags).

Files:

  • website/docs/cli/commands/vendor/vendor-pull.mdx
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags
Use interfaces for external dependencies to facilitate mocking
All code must pass golangci-lint checks
Follow Go's error handling idioms
Use meaningful error messages
Wrap errors with context using fmt.Errorf("context: %w", err)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All comments must end with periods (complete sentences).
Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.
Target >80% coverage on new/changed lines and include comprehensive unit tests for new features.
Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.
Use logging only for system/debug info; structured logging without string interpolation; follow logging hierarchy per docs/logging.md.
Most text UI must go to stderr; only data/results to stdout for piping compatibility.
Prefer SDKs over shelling out to binaries; use filepath.Join, os.PathSeparator/ListSeparator; gate OS-specific logic with runtime.GOOS when unavoidable.
For non-standard execution paths, capture telemetry via pkg/telemetry (CaptureCmd or CaptureCmdString) without collecting user data.

Files:

  • pkg/schema/schema.go
  • tests/vendor_symlink_security_test.go
  • internal/exec/copy_glob_test.go
  • pkg/security/symlink_validator_test.go
  • internal/exec/vendor_utils.go
  • internal/exec/copy_glob.go
  • internal/exec/vendor_model.go
  • internal/exec/vendor_component_utils.go
  • pkg/security/symlink_validator.go
  • pkg/downloader/git_getter.go
**/*_test.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for testing multiple scenarios
Include integration tests for command flows
Test CLI end-to-end when possible
Use test fixtures for complex inputs
Consider using testify/mock for creating mock implementations

**/*_test.go: Use table-driven unit tests for pure functions.
Co-locate tests alongside implementation files (use *_test.go).

Files:

  • tests/vendor_symlink_security_test.go
  • internal/exec/copy_glob_test.go
  • pkg/security/symlink_validator_test.go
tests/**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Place integration tests under tests with fixtures in tests/test-cases/.

Files:

  • tests/vendor_symlink_security_test.go
tests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Provide integration tests for CLI commands using fixtures under tests/test-cases/.

Files:

  • tests/vendor_symlink_security_test.go
pkg/**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Place unit tests for packages under pkg with *_test.go naming.

Files:

  • pkg/security/symlink_validator_test.go
🧠 Learnings (14)
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Clearly describe the changes in the PR description

Applied to files:

  • prd/README.md
  • CLAUDE.md
📚 Learning: 2025-09-02T19:59:52.081Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.081Z
Learning: Applies to pkg/datafetcher/schema/vendor/package/1.0.json : Update the vendor/package schema when vendor configuration changes.

Applied to files:

  • pkg/schema/schema.go
📚 Learning: 2025-09-02T19:59:52.081Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.081Z
Learning: Applies to cmd/**/*_test.go : Add command tests under cmd with *_test.go naming.

Applied to files:

  • tests/vendor_symlink_security_test.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*_test.go : Every new feature must include comprehensive unit tests

Applied to files:

  • tests/vendor_symlink_security_test.go
  • pkg/security/symlink_validator_test.go
📚 Learning: 2025-03-21T16:14:35.272Z
Learnt from: Listener430
PR: cloudposse/atmos#984
File: internal/exec/vendor_model.go:0-0
Timestamp: 2025-03-21T16:14:35.272Z
Learning: The function `copyToTarget` is being replaced with `copyToTargetWithPatterns` to support enhanced glob pattern matching for vendoring, but is temporarily kept for reference until the new implementation is fully confirmed.

Applied to files:

  • internal/exec/copy_glob_test.go
  • internal/exec/vendor_utils.go
  • internal/exec/copy_glob.go
📚 Learning: 2025-07-05T20:59:02.914Z
Learnt from: aknysh
PR: cloudposse/atmos#1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (`_ "github.com/hairyhenderson/gomplate/v4"`) alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.

Applied to files:

  • internal/exec/vendor_utils.go
  • internal/exec/copy_glob.go
  • internal/exec/vendor_model.go
  • internal/exec/vendor_component_utils.go
📚 Learning: 2025-09-02T19:59:52.081Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.081Z
Learning: Applies to pkg/utils/yaml_utils.go : Reuse YAML processing helpers from pkg/utils/yaml_utils.go.

Applied to files:

  • internal/exec/vendor_utils.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.

Applied to files:

  • internal/exec/vendor_component_utils.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like `checkAtmosConfig` in `cmd/cmd_utils.go`, avoid suggesting refactoring to return errors instead of calling `os.Exit` if such changes would significantly increase the scope due to the need to update multiple call sites.

Applied to files:

  • internal/exec/vendor_component_utils.go
📚 Learning: 2024-12-11T18:40:12.808Z
Learnt from: Listener430
PR: cloudposse/atmos#844
File: cmd/helmfile.go:37-37
Timestamp: 2024-12-11T18:40:12.808Z
Learning: In the atmos project, `cliConfig` is initialized within the `cmd` package in `root.go` and can be used in other command files.

Applied to files:

  • internal/exec/vendor_component_utils.go
📚 Learning: 2025-08-16T23:33:07.477Z
Learnt from: aknysh
PR: cloudposse/atmos#1405
File: internal/exec/describe_dependents_test.go:651-652
Timestamp: 2025-08-16T23:33:07.477Z
Learning: In the cloudposse/atmos Go codebase, ExecuteDescribeDependents expects a pointer to AtmosConfiguration (*schema.AtmosConfiguration), so when calling it with a value returned by cfg.InitCliConfig (which returns schema.AtmosConfiguration), the address-of operator (&) is necessary: ExecuteDescribeDependents(&atmosConfig, ...).

Applied to files:

  • internal/exec/vendor_component_utils.go
📚 Learning: 2025-02-13T07:30:28.946Z
Learnt from: Listener430
PR: cloudposse/atmos#1061
File: internal/exec/go_getter_utils.go:74-75
Timestamp: 2025-02-13T07:30:28.946Z
Learning: In the `CustomGitDetector.Detect` method of `internal/exec/go_getter_utils.go`, verbose debug logging of raw URLs is intentionally kept for debugging purposes, despite potential credential exposure risks.

Applied to files:

  • pkg/downloader/git_getter.go
📚 Learning: 2025-02-05T11:10:51.031Z
Learnt from: mss
PR: cloudposse/atmos#1024
File: internal/exec/go_getter_utils.go:31-33
Timestamp: 2025-02-05T11:10:51.031Z
Learning: The path traversal check in `ValidateURI` function in `internal/exec/go_getter_utils.go` is intentionally kept despite potentially blocking valid Git URLs, as this validation is planned to be addressed in a separate ticket.

Applied to files:

  • pkg/downloader/git_getter.go
📚 Learning: 2025-03-21T17:35:56.827Z
Learnt from: Listener430
PR: cloudposse/atmos#984
File: internal/exec/go_getter_utils.go:234-248
Timestamp: 2025-03-21T17:35:56.827Z
Learning: When removing symlinks in Go, using os.Remove(path) is sufficient as it removes the symlink reference itself without affecting the target. filepath.Walk doesn't follow symlinks by default, so there's no need for special handling of nested symlink structures.

Applied to files:

  • pkg/downloader/git_getter.go
🧬 Code graph analysis (8)
tests/vendor_symlink_security_test.go (2)
pkg/security/symlink_validator.go (7)
  • SymlinkPolicy (15-15)
  • PolicyAllowSafe (19-19)
  • PolicyRejectAll (22-22)
  • PolicyAllowAll (25-25)
  • CreateSymlinkHandler (30-52)
  • GetPolicyFromConfig (165-175)
  • ParsePolicy (148-161)
pkg/schema/schema.go (3)
  • AtmosConfiguration (25-60)
  • Vendor (897-903)
  • VendorPolicy (906-910)
pkg/security/symlink_validator_test.go (1)
pkg/security/symlink_validator.go (8)
  • SymlinkPolicy (15-15)
  • PolicyAllowSafe (19-19)
  • PolicyRejectAll (22-22)
  • PolicyAllowAll (25-25)
  • ParsePolicy (148-161)
  • IsSymlinkSafe (56-97)
  • CreateSymlinkHandler (30-52)
  • ValidateSymlinks (103-144)
internal/exec/vendor_utils.go (2)
pkg/schema/schema.go (2)
  • AtmosVendorSource (860-869)
  • AtmosConfiguration (25-60)
pkg/security/symlink_validator.go (2)
  • GetPolicyFromConfig (165-175)
  • CreateSymlinkHandler (30-52)
internal/exec/copy_glob.go (2)
pkg/schema/schema.go (1)
  • AtmosConfiguration (25-60)
pkg/security/symlink_validator.go (2)
  • GetPolicyFromConfig (165-175)
  • CreateSymlinkHandler (30-52)
internal/exec/vendor_model.go (1)
pkg/security/symlink_validator.go (2)
  • GetPolicyFromConfig (165-175)
  • CreateSymlinkHandler (30-52)
internal/exec/vendor_component_utils.go (2)
pkg/schema/schema.go (2)
  • VendorComponentSpec (622-625)
  • AtmosConfiguration (25-60)
pkg/security/symlink_validator.go (2)
  • GetPolicyFromConfig (165-175)
  • CreateSymlinkHandler (30-52)
pkg/security/symlink_validator.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Vendor (897-903)
pkg/downloader/git_getter.go (1)
pkg/security/symlink_validator.go (4)
  • SymlinkPolicy (15-15)
  • PolicyAllowSafe (19-19)
  • ValidateSymlinks (103-144)
  • PolicyRejectAll (22-22)
🪛 LanguageTool
prd/README.md

[grammar] ~26-~26: There might be a mistake here.
Context: ...hen: - Adding a significant new feature - Making breaking changes - Implementing s...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...nt new feature - Making breaking changes - Implementing security features - Changin...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...changes - Implementing security features - Changing core architecture - Adding new ...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...ty features - Changing core architecture - Adding new configuration options that af...

(QB_NEW_EN)

website/docs/cli/commands/vendor/vendor-pull.mdx

[grammar] ~335-~335: There might be a mistake here.
Context: ...ymlinks using ../ to escape boundaries - Absolute paths: Symlinks pointing to s...

(QB_NEW_EN)


[grammar] ~336-~336: There might be a mistake here.
Context: ... Symlinks pointing to system files like /etc/passwd - Boundary escapes: Any symlink that res...

(QB_NEW_EN)

prd/vendor-symlink-security.prd.md

[grammar] ~26-~26: There might be a mistake here.
Context: ...ry 1: Security-Conscious DevOps Engineer As a DevOps engineer vendoring from pu...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...** protection against malicious symlinks So that I don't accidentally expose sens...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ...s from my system Acceptance Criteria: - Default configuration blocks symlinks th...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...n ### Story 2: Enterprise Security Team As a security team member I want...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ...ty when vendoring from untrusted sources So that we maintain compliance with se...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...ecurity policies Acceptance Criteria: - Can configure policy to reject ALL symli...

(QB_NEW_EN)


[grammar] ~46-~46: There might be a mistake here.
Context: ...form Engineer with Legacy Infrastructure As a platform engineer with existing s...

(QB_NEW_EN)


[grammar] ~51-~51: There might be a mistake here.
Context: ...rastructure code Acceptance Criteria: - Can configure policy to allow all symlin...

(QB_NEW_EN)


[grammar] ~58-~58: There might be a mistake here.
Context: ...s ### Decision 1: Three-Policy Approach Choice: Implement three policies: `all...

(QB_NEW_EN)


[grammar] ~61-~61: There might be a mistake here.
Context: ...safe, reject_all, allow_all` Why: - Balances security with functionality - P...

(QB_NEW_EN)


[grammar] ~62-~62: There might be a mistake here.
Context: ...* - Balances security with functionality - Provides clear, understandable options -...

(QB_NEW_EN)


[grammar] ~63-~63: There might be a mistake here.
Context: ...- Provides clear, understandable options - Covers all identified use cases **Alter...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...ed use cases Alternatives Considered: - Binary on/off switch - Too limiting, doe...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ... allow_safe the default policy Why: - Provides immediate protection without br...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...s still work Alternatives Considered: - Default to allow_all - Leaves users vu...

(QB_NEW_EN)


[grammar] ~87-~87: There might be a mistake here.
Context: ...r.policy.symlinks` configuration Why: - Centralized configuration - Consistent w...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...on Why: - Centralized configuration - Consistent with Atmos patterns - Support...

(QB_NEW_EN)


[grammar] ~89-~89: There might be a mistake here.
Context: ...uration - Consistent with Atmos patterns - Supports both nested and dot notation *...

(QB_NEW_EN)


[grammar] ~92-~92: There might be a mistake here.
Context: ...dot notation Alternatives Considered: - Environment variables - Less discoverabl...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...nvironment variables - Less discoverable - Command-line flags - Requires changes to...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ... Requires changes to all vendor commands - Per-vendor-file config - Too granular fo...

(QB_NEW_EN)


[grammar] ~121-~121: There might be a mistake here.
Context: ...ring) bool ``` ### Validation Algorithm 1. Read symlink target 2. Resolve to absolu...

(QB_NEW_EN)


[grammar] ~124-~124: There might be a mistake here.
Context: ...ath 3. Check if path is within boundary: - Calculate relative path from boundary to...

(QB_NEW_EN)


[grammar] ~139-~139: There might be a mistake here.
Context: ...hase 1: Core Security Module (Completed) - [x] Create `pkg/security/symlink_validat...

(QB_NEW_EN)


[grammar] ~140-~140: There might be a mistake here.
Context: ...ecurity Module (Completed) - [x] Create pkg/security/symlink_validator.go - [x] Implement three policies - [x] Add b...

(QB_NEW_EN)


[grammar] ~141-~141: There might be a mistake here.
Context: ...dator.go` - [x] Implement three policies - [x] Add boundary validation logic - [x] ...

(QB_NEW_EN)


[grammar] ~142-~142: There might be a mistake here.
Context: ...cies - [x] Add boundary validation logic - [x] Create comprehensive unit tests ###...

(QB_NEW_EN)


[grammar] ~145-~145: There might be a mistake here.
Context: ...ts ### Phase 2: Integration (Completed) - [x] Update schema to support configurati...

(QB_NEW_EN)


[grammar] ~151-~151: There might be a mistake here.
Context: ... ### Phase 3: Documentation (Completed) - [x] Document configuration in vendor-pul...

(QB_NEW_EN)


[grammar] ~157-~157: There might be a mistake here.
Context: ...se 4: Future Enhancements (Not in scope) - [ ] Per-source policy configuration - [ ...

(QB_NEW_EN)


[grammar] ~158-~158: There might be a mistake here.
Context: ...e) - [ ] Per-source policy configuration - [ ] Symlink allowlist patterns - [ ] Met...

(QB_NEW_EN)


[grammar] ~159-~159: There might be a mistake here.
Context: ...uration - [ ] Symlink allowlist patterns - [ ] Metrics on rejected symlinks - [ ] I...

(QB_NEW_EN)


[grammar] ~160-~160: There might be a mistake here.
Context: ...terns - [ ] Metrics on rejected symlinks - [ ] Integration with security scanning t...

(QB_NEW_EN)


[grammar] ~165-~165: There might be a mistake here.
Context: ...th allow_safe or reject_all policies 2. Compatibility: Existing workflows cont...

(QB_NEW_EN)


[grammar] ~166-~166: There might be a mistake here.
Context: ...inue to function with appropriate policy 3. Performance: No measurable performance...

(QB_NEW_EN)


[grammar] ~167-~167: There might be a mistake here.
Context: ... performance impact on vendor operations 4. Adoption: Clear documentation leads to...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...tation leads to correct policy selection 5. Auditability: Security teams can track...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...ecurity Considerations ### Threat Model - Attacker: Malicious repository maintai...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ...acker**: Malicious repository maintainer - Attack Vector: Symlinks in repository ...

(QB_NEW_EN)


[grammar] ~175-~175: There might be a mistake here.
Context: ...n repository pointing to sensitive files - Impact: Unauthorized read access to sy...

(QB_NEW_EN)


[grammar] ~176-~176: There might be a mistake here.
Context: ...Unauthorized read access to system files - Mitigation: Boundary validation preven...

(QB_NEW_EN)


[grammar] ~192-~192: There might be a mistake here.
Context: ...red ## Testing Strategy ### Unit Tests - Policy parsing and validation - Boundary...

(QB_NEW_EN)


[grammar] ~193-~193: There might be a mistake here.
Context: ...it Tests - Policy parsing and validation - Boundary checking algorithm - Each polic...

(QB_NEW_EN)


[grammar] ~194-~194: There might be a mistake here.
Context: ...validation - Boundary checking algorithm - Each policy behavior - Edge cases (circu...

(QB_NEW_EN)


[grammar] ~195-~195: There might be a mistake here.
Context: ...hecking algorithm - Each policy behavior - Edge cases (circular symlinks, broken sy...

(QB_NEW_EN)


[grammar] ~198-~198: There might be a mistake here.
Context: ... broken symlinks) ### Integration Tests - CVE-2025-8959 specific attack scenarios ...

(QB_NEW_EN)


[grammar] ~199-~199: There might be a mistake here.
Context: ... CVE-2025-8959 specific attack scenarios - Policy integration with vendor operation...

(QB_NEW_EN)


[grammar] ~200-~200: There might be a mistake here.
Context: ...olicy integration with vendor operations - Cross-platform compatibility (Linux, mac...

(QB_NEW_EN)


[grammar] ~201-~201: There might be a mistake here.
Context: ...ss-platform compatibility (Linux, macOS) - Performance benchmarks ### Manual Testi...

(QB_NEW_EN)


[grammar] ~204-~204: There might be a mistake here.
Context: ...rformance benchmarks ### Manual Testing - Vendor operations with various repositor...

(QB_NEW_EN)


[grammar] ~205-~205: There might be a mistake here.
Context: ...tions with various repository structures - Configuration validation - Log output ve...

(QB_NEW_EN)


[grammar] ~206-~206: There might be a mistake here.
Context: ...ry structures - Configuration validation - Log output verification - Documentation ...

(QB_NEW_EN)


[grammar] ~207-~207: There might be a mistake here.
Context: ...ion validation - Log output verification - Documentation accuracy ## Documentation...

(QB_NEW_EN)


[grammar] ~212-~212: There might be a mistake here.
Context: ... Guide**: How to set up symlink policies 2. Security Best Practices: When to use e...

(QB_NEW_EN)


[grammar] ~213-~213: There might be a mistake here.
Context: ...est Practices**: When to use each policy 3. Migration Guide: Moving from vulnerabl...

(QB_NEW_EN)


[grammar] ~214-~214: There might be a mistake here.
Context: ...Guide**: Moving from vulnerable versions 4. Troubleshooting: Common issues and sol...

(QB_NEW_EN)


[grammar] ~215-~215: There might be a mistake here.
Context: ...eshooting**: Common issues and solutions 5. API Reference: For developers extendin...

(QB_NEW_EN)


[grammar] ~228-~228: There might be a mistake here.
Context: ...dered ### Alternative 1: Fork go-getter Rejected because: - Maintenance burden...

(QB_NEW_EN)


[grammar] ~229-~229: There might be a mistake here.
Context: ...ve 1: Fork go-getter Rejected because: - Maintenance burden - Divergence from ups...

(QB_NEW_EN)


[grammar] ~230-~230: There might be a mistake here.
Context: ...Rejected because:** - Maintenance burden - Divergence from upstream - Difficult to ...

(QB_NEW_EN)


[grammar] ~231-~231: There might be a mistake here.
Context: ...enance burden - Divergence from upstream - Difficult to track security updates ###...

(QB_NEW_EN)


[grammar] ~235-~235: There might be a mistake here.
Context: ...ent custom vendoring Rejected because: - Significant development effort - Loss of...

(QB_NEW_EN)


[grammar] ~236-~236: There might be a mistake here.
Context: ...ause:** - Significant development effort - Loss of go-getter features - Increased s...

(QB_NEW_EN)


[grammar] ~237-~237: There might be a mistake here.
Context: ...ment effort - Loss of go-getter features - Increased surface area for bugs ### Alt...

(QB_NEW_EN)


[grammar] ~241-~241: There might be a mistake here.
Context: ...le symlinks entirely Rejected because: - Breaks legitimate use cases - Not accept...

(QB_NEW_EN)


[style] ~250-~250: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...e collect telemetry on policy usage? 3. Should we integrate with external security sca...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~251-~251: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ate with external security scanners? 4. Should we add a "quarantine" mode for suspicio...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[typographical] ~251-~251: Consider using typographic quotation marks here.
Context: ...l security scanners? 4. Should we add a "quarantine" mode for suspicious symlinks? ## Refer...

(EN_QUOTES)


[grammar] ~255-~255: There might be a mistake here.
Context: ...mlinks? ## References - CVE-2025-8959 - [HashiCorp Security Advisory HCSEC-2025-2...

(QB_NEW_EN)


[grammar] ~256-~256: There might be a mistake here.
Context: ...ashiCorp Security Advisory HCSEC-2025-23](https://discuss.hashicorp.com/t/hcsec-2025-23-hashicorp-go-getter-vulnerable-to-arbitrary-read-through-symlink-attack/76242) - [go-getter v1.7.9 Release Notes](https://...

(QB_NEW_EN)


[grammar] ~257-~257: There might be a mistake here.
Context: ...76242) - go-getter v1.7.9 Release Notes - [POSIX Symlink Specification](https://pub...

(QB_NEW_EN)

CLAUDE.md

[grammar] ~19-~19: There might be a mistake here.
Context: ...requirements for features. PRDs contain: - Problem statements and goals - User stor...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... contain: - Problem statements and goals - User stories and acceptance criteria -...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...s - User stories and acceptance criteria - Design decisions with alternatives consi...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...n decisions with alternatives considered - Technical specifications - Implementatio...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...es considered - Technical specifications - Implementation plans When implementing ...

(QB_NEW_EN)


[grammar] ~24-~24: Please add a punctuation mark at the end of paragraph.
Context: ...chnical specifications - Implementation plans When implementing features, consult re...

(PUNCTUATION_PARAGRAPH_END)


[grammar] ~291-~291: There might be a mistake here.
Context: ...### Adding New Features or Major Changes 1. Check for existing PRDs in prd/ dire...

(QB_NEW_EN)


[grammar] ~293-~293: There might be a mistake here.
Context: ...cant features following the template in prd/ 3. Follow the implementation plan outlined ...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
CLAUDE.md

292-292: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ 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). (6)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Build (macos-latest, macos)
  • GitHub Check: Lint (golangci)
  • GitHub Check: Analyze (go)
  • GitHub Check: website-deploy-preview
  • GitHub Check: Summary
🔇 Additional comments (24)
prd/README.md (1)

1-63: LGTM — clear, actionable PRD workflow.

Good anchor for future features; points to the symlink PRD.

prd/vendor-symlink-security.prd.md (1)

185-189: Note versions precisely in Dependencies.

Pin exact versions in the PRD to match go.mod to avoid drift.

Please confirm go.mod uses:

  • github.com/hashicorp/go-getter v1.7.9
  • github.com/otiai10/copy v1.14.1

If different, update this section or bump go.mod accordingly.

internal/exec/copy_glob.go (3)

12-12: Good addition of the security import.

The import is properly placed and follows the project's import organization pattern.


409-414: Function signature properly extended with atmosConfig parameter.

The addition of the atmosConfig parameter enables policy-based symlink handling throughout the copy operation.


424-432: Solid implementation of policy-based symlink handling.

The code correctly retrieves the symlink policy from the configuration and creates the appropriate handler. This addresses the CVE-2025-8959 security vulnerability by allowing configurable symlink validation.

internal/exec/vendor_model.go (3)

23-23: Security import properly added.

The import follows the standard organization pattern.


350-350: Correctly passes atmosConfig to copyToTargetWithPatterns.

The parameter propagation maintains consistency with the updated function signature.


375-384: Well-implemented policy retrieval and handler creation for local packages.

The code properly fetches the symlink policy from configuration and creates the security handler. The comment on line 376 clearly documents the policy retrieval.

internal/exec/vendor_utils.go (2)

19-19: Security package import correctly added.

Follows the import organization pattern.


464-473: Clean implementation of policy-based symlink handling in copyToTarget.

The function signature properly includes atmosConfig, and the symlink policy is correctly retrieved and applied. The comment clearly indicates the policy retrieval step.

pkg/downloader/git_getter.go (1)

34-38: Good deprecation path.

Delegating removeSymlinks to ValidateSymlinks with reject_all is clear and safe.

internal/exec/vendor_component_utils.go (3)

127-131: Symlink policy wiring LGTM.

Policy retrieval and handler usage at the component copy step look correct.

Also applies to: 143-145


471-478: Good threading of atmosConfig.

Plumbing atmosConfig through installComponent ensures consistent policy application.


538-540: Mixin path: policy application looks correct.

Using the temp src as boundary for the final copy is appropriate; comment clarifies intent.

Also applies to: 549-553

pkg/security/symlink_validator.go (3)

28-52: Policy-driven handler looks good.

Clear mapping to cp.SymlinkAction with structured logs. No concerns.


146-161: ParsePolicy mapping is sound.

Covers dashed variants, defaults to allow_safe with a warning. Good.


163-175: Config fallback is correct.

Nil-safe and defaults to allow_safe when unset. LGTM.

pkg/security/symlink_validator_test.go (4)

23-49: Policy parsing tests look solid.

Good table coverage of cases and defaults.


136-153: Broken symlink behavior verified.

Matches intended semantics; no changes requested.


154-227: Handler actions per policy are correct.

Good direct assertions on cp.SymlinkAction.


229-316: Validation behavior across policies is well covered.

Nice use of cp.Copy to stage inputs; assertions are precise.

tests/vendor_symlink_security_test.go (3)

152-277: Integration flow looks good.

Covers default and explicit policies end-to-end with CreateSymlinkHandler and GetPolicyFromConfig.


279-306: Policy parsing integration mirrors unit tests.

Consistent expectations; no changes.


308-363: Copy path respects policy as intended.

Good separation of concerns and verification via Lstat/Stat.

Comment on lines +900 to 910
BasePath string `yaml:"base_path" json:"base_path" mapstructure:"base_path"`
List ListConfig `yaml:"list,omitempty" json:"list,omitempty" mapstructure:"list"`
Policy VendorPolicy `yaml:"policy,omitempty" json:"policy,omitempty" mapstructure:"policy"`
}

// VendorPolicy defines security policies for vendor operations.
type VendorPolicy struct {
// Symlinks defines how symlinks are handled during vendoring.
// Options: "allow_safe" (default), "reject_all", "allow_all"
Symlinks string `yaml:"symlinks,omitempty" json:"symlinks,omitempty" mapstructure:"symlinks"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify schema + env/flags plumbing for the new setting.

Per “Schema Updates (MANDATORY)” and env precedence rules, ensure:

  • JSON schemas include vendor.policy.symlinks with enum: allow_safe | reject_all | allow_all.
  • viper default is set to allow_safe and ATMOS_VENDOR_POLICY_SYMLINKS is bound.

Run this to confirm and locate gaps; I can follow up with patches.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching schema files for vendor policy..."
fd '1\.0\.json$' pkg/datafetcher/schema | sort
rg -n 'vendor.*policy|symlinks' pkg/datafetcher/schema -C2 || true

echo
echo "Dump vendor/package schema node if present..."
if fd 'vendor/package/1.0.json' pkg/datafetcher/schema >/dev/null; then
  jq '.properties.vendor?.properties.policy?.properties.symlinks // "MISSING"' pkg/datafetcher/schema/vendor/package/1.0.json
fi

echo
echo "Check viper defaults and env bindings for vendor.policy.symlinks..."
rg -nP 'SetDefault\(\s*"vendor\.policy\.symlinks",\s*"allow_safe"' -C2 || true
rg -nP 'BindEnv\(\s*"vendor\.policy\.symlinks"|ATMOS_VENDOR_POLICY_SYMLINKS' -C2 || true

Length of output: 1045


Add missing JSON schema and viper plumbing for vendor.policy.symlinks

  • pkg/datafetcher/schema/vendor/package/1.0.json: under .properties.vendor.properties.policy.properties, add
    "symlinks": {
      "type": "string",
      "enum": ["allow_safe","reject_all","allow_all"],
      "default": "allow_safe",
      "description": "How symlinks are handled during vendoring"
    }
  • In your config init (where other defaults/envs are bound), add
    viper.SetDefault("vendor.policy.symlinks", "allow_safe")
    viper.BindEnv("vendor.policy.symlinks", "ATMOS_VENDOR_POLICY_SYMLINKS")
🤖 Prompt for AI Agents
In pkg/schema/schema.go around lines 900 to 910, the VendorPolicy.Symlinks field
is defined but the JSON schema and viper defaults/env binding for
vendor.policy.symlinks are missing; add a JSON schema entry under
pkg/datafetcher/schema/vendor/package/1.0.json at
.properties.vendor.properties.policy.properties with type string, enum
["allow_safe","reject_all","allow_all"], default "allow_safe" and an appropriate
description, and update the app config initialization (where other viper
defaults and env bindings are set) to set the default "vendor.policy.symlinks"
to "allow_safe" and bind it to the ATMOS_VENDOR_POLICY_SYMLINKS environment
variable so runtime config and env override behave correctly.

Comment on lines +240 to +246
Or using dot notation:

```yaml
vendor:
policy.symlinks: "allow_safe"
```

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

Fix invalid YAML “dot notation” example.

YAML does not interpret policy.symlinks as a nested key; it creates a literal key with a dot and won’t bind to Vendor.Policy.Symlinks. Replace with ENV override guidance.

-Or using dot notation:
-
-```yaml
-vendor:
-  policy.symlinks: "allow_safe"
-```
+Environment variable override:
+
+```shell
+export ATMOS_VENDOR_POLICY_SYMLINKS=reject_all
+atmos vendor pull
+```
🤖 Prompt for AI Agents
In website/docs/cli/commands/vendor/vendor-pull.mdx around lines 240–246 the
example uses invalid YAML dot notation ("policy.symlinks"), which creates a
literal key instead of binding to Vendor.Policy.Symlinks; replace that snippet
with guidance to use the environment variable override instead — instruct
readers to set ATMOS_VENDOR_POLICY_SYMLINKS to the desired value (for example
"reject_all" or "allow_safe") and then run the CLI (e.g., export
ATMOS_VENDOR_POLICY_SYMLINKS=<value> && atmos vendor pull), and remove the
incorrect YAML block.

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: 4

Caution

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

⚠️ Outside diff range comments (4)
internal/exec/copy_glob.go (4)

420-422: Enforce symlink policy for local-file sources (policy bypass).

Local-file path takes the early return and calls ComponentOrMixinsCopy which currently uses cp.Copy without any OnSymlink handler, bypassing the new policy. Wire the atmosConfig through and apply the handler.

Apply:

- if sourceIsLocalFile {
-   return handleLocalFileSource(sourceDir, finalTarget)
- }
+ if sourceIsLocalFile {
+   return handleLocalFileSource(sourceDir, finalTarget, atmosConfig)
+ }

And update the function definitions below (see separate comments).


441-452: Patterned copy drops all symlinks regardless of policy.

copyDirRecursive/processDirEntry unconditionally skips symlinks, which contradicts the PR objective (preserve legitimate internal symlinks under allow_safe/allow_all). Either:

  • A) Switch this branch to cp.Copy with a Skip/Filter that enforces include/exclude and keep OnSymlink consistent, or
  • B) Teach the recursive walkers to validate symlinks with security.IsSymlinkSafe and handle them (follow or skip) per policy.

Minimal A) change here:

- if len(s.IncludedPaths) == 0 {
-   log.Debug("No inclusion patterns defined; copying entire directory recursively", "source", sourceDir, finalTargetKey, finalTarget)
-   if err := copyDirRecursive(&CopyContext{
-     SrcDir:   sourceDir,
-     DstDir:   finalTarget,
-     BaseDir:  sourceDir,
-     Excluded: s.ExcludedPaths,
-     Included: s.IncludedPaths,
-   }); err != nil {
-     return fmt.Errorf("error copying from %q to %q: %w", sourceDir, finalTarget, err)
-   }
- }
+ if len(s.IncludedPaths) == 0 {
+   log.Debug("No inclusion patterns defined; using cp.Copy with policy-aware Skip/OnSymlink", "source", sourceDir, finalTargetKey, finalTarget)
+   policy := security.GetPolicyFromConfig(atmosConfig)
+   opts := cp.Options{
+     Skip:      createPolicyAwareSkip(sourceDir, s.ExcludedPaths, nil), // see helper below.
+     OnSymlink: security.CreateSymlinkHandler(sourceDir, policy),
+   }
+   if err := cp.Copy(sourceDir, finalTarget, opts); err != nil {
+     return fmt.Errorf("error copying from %q to %q: %w", sourceDir, finalTarget, err)
+   }
+ }

Helper to add (outside this hunk):

func createPolicyAwareSkip(baseDir string, excluded, included []string) func(os.FileInfo, string, string) (bool, error) {
  return func(info os.FileInfo, src, _ string) (bool, error) {
    rel, err := filepath.Rel(baseDir, src)
    if err != nil {
      return true, nil
    }
    rel = filepath.ToSlash(rel)
    if shouldExcludePath(info, rel, excluded) {
      return true, nil
    }
    if !shouldIncludePath(info, rel, included) {
      return true, nil
    }
    return false, nil
  }
}

401-407: Propagate policy into single-file copy helper.

ComponentOrMixinsCopy should accept atmosConfig and apply OnSymlink; otherwise a symlinked source file can bypass policy.

Apply:

- func handleLocalFileSource(sourceDir, finalTarget string) error {
+ func handleLocalFileSource(sourceDir, finalTarget string, atmosConfig *schema.AtmosConfiguration) error {
   log.Debug("Local file source detected; invoking ComponentOrMixinsCopy",
     "sourceFile", sourceDir, finalTargetKey, finalTarget)
-  return ComponentOrMixinsCopy(sourceDir, finalTarget)
+  return ComponentOrMixinsCopy(sourceDir, finalTarget, atmosConfig)
 }

456-482: Apply symlink policy to file-to-file and file-to-folder copies.

cp.Copy here lacks Options, so symlink handling is uncontrolled.

Apply:

-func ComponentOrMixinsCopy(sourceFile, finalTarget string) error {
+func ComponentOrMixinsCopy(sourceFile, finalTarget string, atmosConfig *schema.AtmosConfiguration) error {
   var dest string
   if filepath.Ext(finalTarget) == "" {
     // File-to-folder copy: append the source file's base name to the directory.
     dest = filepath.Join(finalTarget, filepath.Base(sourceFile))
     log.Debug("ComponentOrMixinsCopy: file-to-folder copy", "sourceFile", sourceFile, "destination", dest)
   } else {
     // File-to-file copy: use finalTarget as is.
     dest = finalTarget
     // Create only the parent directory.
     parent := filepath.Dir(dest)
     if err := os.MkdirAll(parent, os.ModePerm); err != nil {
       log.Debug("ComponentOrMixinsCopy: error creating parent directory", "parent", parent, "error", err)
       return fmt.Errorf("creating parent directory %q: %w", parent, err)
     }
     log.Debug("ComponentOrMixinsCopy: file-to-file copy", "sourceFile", sourceFile, "destination", dest)
   }
   // Remove any existing directory at dest to avoid "is a directory" errors.
   if info, err := os.Stat(dest); err == nil && info.IsDir() {
     log.Debug("ComponentOrMixinsCopy: destination exists as directory, removing", "destination", dest)
     if err := os.RemoveAll(dest); err != nil {
       return fmt.Errorf("removing existing directory %q: %w", dest, err)
     }
   }
-  return cp.Copy(sourceFile, dest)
+  policy := security.GetPolicyFromConfig(atmosConfig)
+  base := filepath.Dir(sourceFile)
+  return cp.Copy(sourceFile, dest, cp.Options{
+    OnSymlink: security.CreateSymlinkHandler(base, policy),
+  })
 }
♻️ Duplicate comments (11)
pkg/schema/schema.go (2)

900-903: Wire default + env binding for vendor.policy.symlinks.

Ensure viper sets the default "allow_safe" and binds ATMOS_VENDOR_POLICY_SYMLINKS so precedence (flags > env > file > defaults) works as documented.

Run to confirm:

#!/bin/bash
set -euo pipefail
rg -nP 'SetDefault\(\s*"vendor\.policy\.symlinks",\s*"allow_safe"' -C2 || true
rg -nP 'BindEnv\(\s*"vendor\.policy\.symlinks"|ATMOS_VENDOR_POLICY_SYMLINKS' -C2 || true

905-909: Add JSON schema enum/default for policy; punctuation nit.

  • Add symlinks enum/default to the JSON schema so config is validated consistently.
  • End the “Options” comment with a period.

JSON schema snippet to add under .properties.vendor.properties.policy.properties:

"symlinks": {
  "type": "string",
  "enum": ["allow_safe","reject_all","allow_all"],
  "default": "allow_safe",
  "description": "How symlinks are handled during vendoring."
}

Apply this diff for the comment punctuation:

-	// Options: "allow_safe" (default), "reject_all", "allow_all"
+	// Options: "allow_safe" (default), "reject_all", "allow_all".
pkg/downloader/git_getter.go (1)

18-23: Wrap errors with context (clone + validate).

Return paths drop context; wrap with fmt.Errorf to preserve the chain and aid debugging.

Apply:

 import (
+	"fmt"
 	"net/url"

@@
 	if err := c.GetCustom(dst, url); err != nil {
-		return err
+		return fmt.Errorf("git_getter: cloning into %q failed: %w", dst, err)
 	}
@@
-	return security.ValidateSymlinks(dst, policy)
+	if err := security.ValidateSymlinks(dst, policy); err != nil {
+		return fmt.Errorf("git_getter: validating symlinks (%s) at %q failed: %w", policy, dst, err)
+	}
+	return nil

Also applies to: 31-31

website/docs/cli/commands/vendor/vendor-pull.mdx (1)

240-246: Remove invalid YAML “dot notation”; show ENV override instead.

YAML treats policy.symlinks as a literal key and won’t bind to Vendor.Policy.Symlinks. Replace with ENV example.

Apply:

-Or using dot notation:
-
-```yaml
-vendor:
-  policy.symlinks: "allow_safe"
-```
+Environment variable override:
+
+```shell
+export ATMOS_VENDOR_POLICY_SYMLINKS=reject_all
+atmos vendor pull
+```
internal/exec/vendor_component_utils.go (2)

127-135: Nice: parameter object resolves “too many args” warning.

This cleans up the argument-limit finding and improves readability.


501-521: Bug: wrong boundary for symlink validation when copying FROM local source.

OnSymlink uses baseDir=tempDir while cp.Copy reads src from p.uri. Safe links inside the source tree will be misclassified. Validate against the source boundary (directory of p.uri for files, or p.uri if it’s a directory).

Apply:

-func handlePkgTypeLocalComponent(tempDir string, p *pkgComponentVendor, atmosConfig *schema.AtmosConfiguration) error {
-  // Get the symlink policy from config
-  policy := security.GetPolicyFromConfig(atmosConfig)
-
-  copyOptions := cp.Options{
-    PreserveTimes: false,
-    PreserveOwner: false,
-    // OnSymlink specifies what to do on symlink based on security policy
-    OnSymlink: security.CreateSymlinkHandler(tempDir, policy),
-  }
+func handlePkgTypeLocalComponent(tempDir string, p *pkgComponentVendor, atmosConfig *schema.AtmosConfiguration) error {
+  // Get the symlink policy from config.
+  policy := security.GetPolicyFromConfig(atmosConfig)
+  // Boundary must be the source root, not the temp destination.
+  base := p.uri
+  if fi, err := os.Lstat(p.uri); err == nil && !fi.IsDir() {
+    base = filepath.Dir(p.uri)
+  }
+  copyOptions := cp.Options{
+    PreserveTimes: false,
+    PreserveOwner: false,
+    // OnSymlink specifies what to do on symlink based on security policy.
+    OnSymlink: security.CreateSymlinkHandler(base, policy),
+  }
prd/vendor-symlink-security.prd.md (1)

84-91: Clarify YAML vs. override key; fix punctuation.

Replace “supports both nested and dot notation” with explicit YAML-nesting and dot-path override language. Also end list items with periods.

-### Decision 3: Configuration in `atmos.yaml`
-**Choice:** Add `vendor.policy.symlinks` configuration
+### Decision 3: Configuration in `atmos.yaml`
+**Choice:** Add `vendor.policy.symlinks` configuration.
@@
-**Why:**
-- Centralized configuration
-- Consistent with Atmos patterns
-- Supports both nested and dot notation
+**Why:**
+- Centralized configuration.
+- Consistent with Atmos patterns.
+- YAML uses nested keys (e.g., `vendor: policy: symlinks:`). The dot path `vendor.policy.symlinks` is the canonical override key for environment variables and flags, not a YAML syntax option.
pkg/security/symlink_validator_test.go (1)

51-134: Add test for dir-symlink escape (escapes boundary via internal dir link).

This is the critical bypass your current IsSymlinkSafe misses (it doesn’t resolve intermediate symlinks). Add a case where an internal directory is a symlink to an external directory, then a file symlink targets a file under that dir-symlink.

@@
 	// Create external directory and file.
 	externalDir := t.TempDir()
 	externalFile := filepath.Join(externalDir, "external.txt")
 	require.NoError(t, os.WriteFile(externalFile, []byte("external"), 0o644))
+
+	// Create an internal directory symlink that points outside the boundary.
+	escapeDir := filepath.Join(tempDir, "escape_dir")
+	require.NoError(t, os.Symlink(externalDir, escapeDir))
@@
 	}{
@@
 		{
 			name:       "absolute symlink to system file",
 			linkTarget: "/etc/passwd",
 			linkPath:   filepath.Join(tempDir, "link6.txt"),
 			boundary:   tempDir,
 			expectSafe: false,
 		},
+		{
+			name:       "symlink target via internal dir symlink escaping boundary",
+			linkTarget: filepath.Join("escape_dir", "external.txt"),
+			linkPath:   filepath.Join(tempDir, "link7.txt"),
+			boundary:   tempDir,
+			expectSafe: false,
+		},
 	}
tests/vendor_symlink_security_test.go (1)

122-167: Flatten nested asserts to reduce complexity (minor).

Restructure the legit-symlink assertions to avoid nested blocks and align with the code scanning hint.

-			if tc.expectLegitimateOK {
-				assert.NoError(t, err,
-					"Legitimate symlink should work with policy %s", tc.policy)
-				// For non-reject policies, verify content was copied correctly.
-				verifyLegitimateContent(t, destLegitimate, tc.policy)
-			} else {
-				assert.True(t, os.IsNotExist(err),
-					"Legitimate symlink should be blocked with policy %s", tc.policy)
-			}
+			if !tc.expectLegitimateOK {
+				assert.True(t, os.IsNotExist(err),
+					"Legitimate symlink should be blocked with policy %s", tc.policy)
+				return
+			}
+			assert.NoError(t, err,
+				"Legitimate symlink should work with policy %s", tc.policy)
+			verifyLegitimateContent(t, destLegitimate, tc.policy)
pkg/security/symlink_validator.go (2)

58-101: Fix boundary-escape via intermediate symlinks (critical).

Current check only normalizes paths; it never resolves symlink components along the target path, so a dir symlink inside the boundary can point outside and pass the Rel check. Resolve symlinks for both boundary and target before Rel.

Apply this diff:

 func IsSymlinkSafe(symlink, boundary string) bool {
@@
-	// Clean paths to remove .. and . components.
-	cleanTarget, err := filepath.Abs(filepath.Clean(target))
-	if err != nil {
-		log.Debug("Failed to clean target path", "target", target, "error", err)
-		return false
-	}
-
-	cleanBoundary, err := filepath.Abs(filepath.Clean(boundary))
-	if err != nil {
-		log.Debug("Failed to clean boundary path", "boundary", boundary, "error", err)
-		return false
-	}
+	// Normalize first (no FS access).
+	cleanTarget := filepath.Clean(target)
+	cleanBoundary := filepath.Clean(boundary)
+
+	// Resolve symlinks to prevent intermediate-dir escapes.
+	resolvedBoundary, berr := filepath.EvalSymlinks(cleanBoundary)
+	if berr != nil {
+		// Fall back to absolute path if resolution fails (e.g., boundary not a symlink).
+		if abs, err := filepath.Abs(cleanBoundary); err == nil {
+			resolvedBoundary = abs
+		} else {
+			log.Debug("Failed to absolutize boundary path", logFieldBoundary, cleanBoundary, "error", err)
+			return false
+		}
+	}
+	resolvedTarget, terr := filepath.EvalSymlinks(cleanTarget)
+	if terr != nil {
+		// If we cannot resolve the target, treat as unsafe to be conservative.
+		log.Debug("Failed to resolve target path", "target", cleanTarget, "error", terr)
+		return false
+	}
@@
-	rel, err := filepath.Rel(cleanBoundary, cleanTarget)
+	rel, err := filepath.Rel(resolvedBoundary, resolvedTarget)
 	if err != nil {
-		log.Debug("Failed to calculate relative path", "target", cleanTarget, "boundary", cleanBoundary, "error", err)
+		log.Debug("Failed to calculate relative path", "target", resolvedTarget, logFieldBoundary, resolvedBoundary, "error", err)
 		return false
 	}
@@
-	if strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
-		log.Debug("Symlink target outside boundary", "target", cleanTarget, logFieldBoundary, cleanBoundary, "rel", rel)
+	if strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
+		log.Debug("Symlink target outside boundary", "target", resolvedTarget, logFieldBoundary, resolvedBoundary, "rel", rel)
 		return false
 	}

103-148: Wrap returned errors with context in Walk/remove.

Per repo guideline, wrap with fmt.Errorf and %w to preserve error chains and add path context.

Apply this diff:

+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	log "github.com/charmbracelet/log"
+	cp "github.com/otiai10/copy"
+
+	"github.com/cloudposse/atmos/pkg/schema"
+)
@@
-func ValidateSymlinks(root string, policy SymlinkPolicy) error {
+func ValidateSymlinks(root string, policy SymlinkPolicy) error {
 	if policy == PolicyAllowAll {
 		// Keep all symlinks (legacy behavior).
 		return nil
 	}
 
-	return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
+	walkFn := func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return fmt.Errorf("walk error at %q: %w", path, err)
+		}
@@
-		case PolicyRejectAll:
+		case PolicyRejectAll:
 			// Remove all symlinks for maximum security.
 			log.Debug("Removing symlink (reject_all policy)", "path", path)
-			return os.Remove(path)
+			if err := os.Remove(path); err != nil {
+				return fmt.Errorf("remove symlink %q: %w", path, err)
+			}
+			return nil
@@
-		case PolicyAllowSafe:
+		case PolicyAllowSafe:
 			// Validate and remove unsafe symlinks.
 			if !IsSymlinkSafe(path, root) {
 				log.Warn("Removing unsafe symlink", "path", path)
-				return os.Remove(path)
+				if err := os.Remove(path); err != nil {
+					return fmt.Errorf("remove unsafe symlink %q: %w", path, err)
+				}
+				return nil
 			}
 			log.Debug("Keeping safe symlink", "path", path)
 			return nil
@@
-		default:
+		default:
 			// For unknown policies, default to safe behavior.
 			if !IsSymlinkSafe(path, root) {
 				log.Warn("Removing unsafe symlink (unknown policy, defaulting to safe)", "path", path, "policy", policy)
-				return os.Remove(path)
+				if err := os.Remove(path); err != nil {
+					return fmt.Errorf("remove unsafe symlink %q: %w", path, err)
+				}
+				return nil
 			}
 			return nil
-		}
-	})
+		}
+	}
+	if err := filepath.Walk(root, walkFn); err != nil {
+		return fmt.Errorf("validate symlinks under %q: %w", root, err)
+	}
+	return nil
 }
🧹 Nitpick comments (12)
CLAUDE.md (2)

17-27: Tighten grammar and punctuation in the new PRD section.

Add terminal periods and remove trailing double spaces for bullets; keeps tone consistent and silences grammar linters.

Apply:

-**Important**: Check the `prd/` directory for design decisions and requirements for features. PRDs contain:
-- Problem statements and goals
-- User stories and acceptance criteria  
-- Design decisions with alternatives considered
-- Technical specifications
-- Implementation plans
-
-When implementing features, consult relevant PRDs first to understand the full context and requirements.
+**Important**: Check the `prd/` directory for design decisions and requirements for features. PRDs contain:
+- Problem statements and goals.
+- User stories and acceptance criteria.
+- Design decisions with alternatives considered.
+- Technical specifications.
+- Implementation plans.
+
+When implementing features, consult relevant PRDs first to understand the full context and requirements.

291-295: Minor style: end list items with periods; ensure fenced blocks specify a language.

  • Add periods to each step for consistency.
  • Scan nearby fenced code blocks in this section and add a language hint (e.g., bash, markdown) to satisfy MD040.

Apply:

-1. **Check for existing PRDs** in `prd/` directory for design decisions and requirements
-2. **Create a PRD** for significant features following the template in `prd/`
-3. Follow the implementation plan outlined in the relevant PRD
+1. **Check for existing PRDs** in `prd/` directory for design decisions and requirements.
+2. **Create a PRD** for significant features following the template in `prd/`.
+3. Follow the implementation plan outlined in the relevant PRD.
internal/exec/copy_glob_test.go (3)

642-644: Same nil policy concern.

Repeat the verification for this call site. If desired, pass a minimal config to exercise non-default behavior in a follow-up test.

Example:

cfg := &schema.AtmosConfiguration{} // default allow_safe
err := copyToTargetWithPatterns(srcDir, dstDir, dummy, false, cfg)

904-910: Future-proof the cp.Copy patch to tolerate options.

If cp.Copy starts being called with cp.Options (e.g., OnSymlink handler), this patch will break. Make the stub variadic-compatible.

Apply:

-patch := gomonkey.ApplyFunc(cp.Copy, func(src, dst string) error {
+patch := gomonkey.ApplyFunc(cp.Copy, func(src, dst string, opts ...cp.Options) error {
   called = true
   // For testing purposes, simulate a copy by using our copyFile function.
   return copyFile(filepath.Join(src, "file.txt"), filepath.Join(dst, "file.txt"))
 })

916-918: cp.Copy branch: confirm policy options aren’t silently ignored.

If production code passes cp.Options for symlink handling, this test still passes nil atmosConfig and won’t assert the handler is wired. Consider a dedicated test that creates a symlink and asserts behavior under reject_all vs allow_safe.

pkg/downloader/git_getter.go (1)

34-38: Deprecation shim is fine; schedule removal.

Consider adding a TODO with target version for removal to keep debt visible.

website/docs/cli/commands/vendor/vendor-pull.mdx (1)

335-337: Polish wording and punctuation.

Minor grammar/punctuation improvements in bullets.

Apply:

-- **Absolute paths**: Symlinks pointing to system files like `/etc/passwd`
-- **Boundary escapes**: Any symlink that resolves outside the vendor source directory
+- **Absolute paths**: Symlinks pointing to system files such as `/etc/passwd`.
+- **Boundary escapes**: Any symlink that resolves outside the vendor source directory.
prd/README.md (1)

24-31: Tiny wording/consistency nit.

Use parallel gerunds and keep the lead-in crisp.

Apply:

-## When to Write a PRD
+## When to write a PRD

-Create a PRD when:
+Write a PRD when:

-- Adding a significant new feature
-- Making breaking changes
-- Implementing security features
-- Changing core architecture
-- Adding new configuration options that affect users
+- Adding a significant new feature.
+- Making breaking changes.
+- Implementing security features.
+- Changing core architecture.
+- Adding new configuration options that affect users.
prd/vendor-symlink-security.prd.md (1)

179-183: Document TOCTOU risk explicitly.

Change “minimal risk” to acknowledge TOCTOU and note mitigation (validate immediately before copy; use handler that evaluates symlinks at copy-time).

-3. **Race conditions**: Symlink target could change between validation and use (minimal risk in vendor context)
+3. **Race conditions (TOCTOU)**: Symlink targets can change between validation and use. Mitigate by validating immediately before copy and using a copy-time handler that resolves symlinks during the operation.
pkg/security/symlink_validator_test.go (1)

229-316: Optional: add a ValidateSymlinks test for dir-symlink escape.

Mirror the new escape scenario here to ensure walkers also remove such links under allow_safe.

tests/vendor_symlink_security_test.go (1)

29-38: Assert on read errors for legitimate content.

Fail fast if the read fails; otherwise content check may silently skip.

-	content, err := os.ReadFile(path)
-	if err == nil {
-		assert.Equal(t, "LEGITIMATE DATA", string(content),
-			"Content should match for legitimate symlink")
-	}
+	content, err := os.ReadFile(path)
+	require.NoError(t, err, "Failed to read legitimate content")
+	assert.Equal(t, "LEGITIMATE DATA", string(content), "Content should match for legitimate symlink")
pkg/security/symlink_validator.go (1)

32-56: Pre-resolve boundary once in handler (micro perf + correctness).

Resolve baseDir (symlinks/abs) once before returning the closure to avoid repeated FS calls and handle the case where baseDir itself is a symlink.

Apply this diff:

 func CreateSymlinkHandler(baseDir string, policy SymlinkPolicy) func(string) cp.SymlinkAction {
-	return func(src string) cp.SymlinkAction {
+	// Resolve boundary once.
+	resolvedBoundary := baseDir
+	if b, err := filepath.EvalSymlinks(baseDir); err == nil {
+		resolvedBoundary = b
+	} else if abs, err := filepath.Abs(baseDir); err == nil {
+		resolvedBoundary = abs
+	}
+	return func(src string) cp.SymlinkAction {
 		switch policy {
@@
-		default:
-			if IsSymlinkSafe(src, baseDir) {
+		default:
+			if IsSymlinkSafe(src, resolvedBoundary) {
 				log.Debug("Symlink validated and allowed", logFieldSrc, src)
 				return cp.Deep
 			}
-			log.Warn("Symlink rejected - target outside boundary", logFieldSrc, src, logFieldBoundary, baseDir)
+			log.Warn("Symlink rejected - target outside boundary", logFieldSrc, src, logFieldBoundary, resolvedBoundary)
 			return cp.Skip
 		}
 	}
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

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 4c89983 and b7fd021.

📒 Files selected for processing (17)
  • CLAUDE.md (2 hunks)
  • cmd/root.go (1 hunks)
  • internal/exec/copy_glob.go (3 hunks)
  • internal/exec/copy_glob_test.go (4 hunks)
  • internal/exec/vendor_component_utils.go (6 hunks)
  • internal/exec/vendor_model.go (3 hunks)
  • internal/exec/vendor_utils.go (0 hunks)
  • pkg/downloader/git_getter.go (1 hunks)
  • pkg/hooks/store_cmd.go (1 hunks)
  • pkg/schema/schema.go (1 hunks)
  • pkg/security/symlink_validator.go (1 hunks)
  • pkg/security/symlink_validator_test.go (1 hunks)
  • prd/README.md (1 hunks)
  • prd/vendor-symlink-security.prd.md (1 hunks)
  • tests/cli_test.go (1 hunks)
  • tests/vendor_symlink_security_test.go (1 hunks)
  • website/docs/cli/commands/vendor/vendor-pull.mdx (1 hunks)
💤 Files with no reviewable changes (1)
  • internal/exec/vendor_utils.go
🧰 Additional context used
📓 Path-based instructions (10)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags
Use interfaces for external dependencies to facilitate mocking
All code must pass golangci-lint checks
Follow Go's error handling idioms
Use meaningful error messages
Wrap errors with context using fmt.Errorf("context: %w", err)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All comments must end with periods (complete sentences).
Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.
Target >80% coverage on new/changed lines and include comprehensive unit tests for new features.
Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.
Use logging only for system/debug info; structured logging without string interpolation; follow logging hierarchy per docs/logging.md.
Most text UI must go to stderr; only data/results to stdout for piping compatibility.
Prefer SDKs over shelling out to binaries; use filepath.Join, os.PathSeparator/ListSeparator; gate OS-specific logic with runtime.GOOS when unavoidable.
For non-standard execution paths, capture telemetry via pkg/telemetry (CaptureCmd or CaptureCmdString) without collecting user data.

Files:

  • pkg/hooks/store_cmd.go
  • pkg/security/symlink_validator_test.go
  • tests/cli_test.go
  • cmd/root.go
  • pkg/downloader/git_getter.go
  • internal/exec/copy_glob_test.go
  • pkg/security/symlink_validator.go
  • pkg/schema/schema.go
  • internal/exec/copy_glob.go
  • tests/vendor_symlink_security_test.go
  • internal/exec/vendor_model.go
  • internal/exec/vendor_component_utils.go
**/*_test.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for testing multiple scenarios
Include integration tests for command flows
Test CLI end-to-end when possible
Use test fixtures for complex inputs
Consider using testify/mock for creating mock implementations

**/*_test.go: Use table-driven unit tests for pure functions.
Co-locate tests alongside implementation files (use *_test.go).

Files:

  • pkg/security/symlink_validator_test.go
  • tests/cli_test.go
  • internal/exec/copy_glob_test.go
  • tests/vendor_symlink_security_test.go
pkg/**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Place unit tests for packages under pkg with *_test.go naming.

Files:

  • pkg/security/symlink_validator_test.go
website/**

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

website/**: Update website documentation in the website/ directory when adding new features
Follow the website's documentation structure and style
Keep website code in the website/ directory
Follow the existing website architecture and style
Document new features on the website
Include examples and use cases in website documentation

Files:

  • website/docs/cli/commands/vendor/vendor-pull.mdx
website/docs/cli/commands/**/**/*.mdx

📄 CodeRabbit inference engine (CLAUDE.md)

website/docs/cli/commands/**/**/*.mdx: All new commands/flags/parameters must have Docusaurus documentation under website/docs/cli/commands//.mdx.
Use definition lists

instead of tables for arguments and flags; follow existing frontmatter and section ordering (Usage → Examples → Arguments → Flags).

Files:

  • website/docs/cli/commands/vendor/vendor-pull.mdx
tests/**/*_test.go

📄 CodeRabbit inference engine (CLAUDE.md)

Place integration tests under tests with fixtures in tests/test-cases/.

Files:

  • tests/cli_test.go
  • tests/vendor_symlink_security_test.go
tests/**

📄 CodeRabbit inference engine (CLAUDE.md)

Provide integration tests for CLI commands using fixtures under tests/test-cases/.

Files:

  • tests/cli_test.go
  • tests/vendor_symlink_security_test.go
cmd/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

cmd/*.go: Implement each Cobra command in a separate file under the cmd/ directory
Use kebab-case for command-line flags
Provide comprehensive help text for all commands and flags
Include examples in command help
Provide meaningful feedback to users in command implementation
Include progress indicators for long-running operations
Provide clear error messages to users
Include troubleshooting hints when appropriate
Log detailed errors for debugging

Files:

  • cmd/root.go
cmd/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

cmd/**/*.go: For CLI commands, use //go:embed to load *_usage.md examples and utils.PrintfMarkdown() to render them.
One command per file in cmd/.
Send user prompts/status/progress and actionable errors to stderr; never use logging for UI elements.

Files:

  • cmd/root.go
cmd/root.go

📄 CodeRabbit inference engine (CLAUDE.md)

Standard Cobra commands added to RootCmd get telemetry automatically via RootCmd.ExecuteC(); no extra telemetry code needed.

Files:

  • cmd/root.go
🧠 Learnings (18)
📚 Learning: 2025-02-21T20:56:05.539Z
Learnt from: samtholiya
PR: cloudposse/atmos#1077
File: pkg/downloader/custom_github_detector.go:0-0
Timestamp: 2025-02-21T20:56:05.539Z
Learning: The `github.com/charmbracelet/log` package should be imported with the alias `log` according to the project's import alias configuration.

Applied to files:

  • pkg/hooks/store_cmd.go
  • tests/cli_test.go
  • cmd/root.go
  • pkg/security/symlink_validator.go
📚 Learning: 2025-02-21T20:56:20.761Z
Learnt from: samtholiya
PR: cloudposse/atmos#1077
File: pkg/downloader/custom_github_detector_test.go:0-0
Timestamp: 2025-02-21T20:56:20.761Z
Learning: The `github.com/charmbracelet/log` package should be imported with the alias `log`, not `clog`.

Applied to files:

  • pkg/hooks/store_cmd.go
  • tests/cli_test.go
  • cmd/root.go
  • pkg/security/symlink_validator.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*_test.go : Every new feature must include comprehensive unit tests

Applied to files:

  • pkg/security/symlink_validator_test.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Clearly describe the changes in the PR description

Applied to files:

  • prd/README.md
  • CLAUDE.md
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • website/docs/cli/commands/vendor/vendor-pull.mdx
📚 Learning: 2025-01-30T16:56:43.004Z
Learnt from: mcalhoun
PR: cloudposse/atmos#980
File: pkg/utils/log_utils.go:62-63
Timestamp: 2025-01-30T16:56:43.004Z
Learning: In pkg/utils/log_utils.go, LogTrace is intentionally redirected to LogDebug since charmbracelet logger doesn't support Trace level, maintaining backward compatibility with the original LogTrace functionality.

Applied to files:

  • tests/cli_test.go
  • cmd/root.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Wrap errors with context using `fmt.Errorf("context: %w", err)`

Applied to files:

  • pkg/downloader/git_getter.go
📚 Learning: 2025-09-02T19:59:52.081Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.081Z
Learning: Applies to **/*.go : Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.

Applied to files:

  • pkg/downloader/git_getter.go
📚 Learning: 2025-02-13T07:30:28.946Z
Learnt from: Listener430
PR: cloudposse/atmos#1061
File: internal/exec/go_getter_utils.go:74-75
Timestamp: 2025-02-13T07:30:28.946Z
Learning: In the `CustomGitDetector.Detect` method of `internal/exec/go_getter_utils.go`, verbose debug logging of raw URLs is intentionally kept for debugging purposes, despite potential credential exposure risks.

Applied to files:

  • pkg/downloader/git_getter.go
📚 Learning: 2025-02-05T11:10:51.031Z
Learnt from: mss
PR: cloudposse/atmos#1024
File: internal/exec/go_getter_utils.go:31-33
Timestamp: 2025-02-05T11:10:51.031Z
Learning: The path traversal check in `ValidateURI` function in `internal/exec/go_getter_utils.go` is intentionally kept despite potentially blocking valid Git URLs, as this validation is planned to be addressed in a separate ticket.

Applied to files:

  • pkg/downloader/git_getter.go
  • pkg/security/symlink_validator.go
  • internal/exec/vendor_component_utils.go
📚 Learning: 2025-03-21T17:35:56.827Z
Learnt from: Listener430
PR: cloudposse/atmos#984
File: internal/exec/go_getter_utils.go:234-248
Timestamp: 2025-03-21T17:35:56.827Z
Learning: When removing symlinks in Go, using os.Remove(path) is sufficient as it removes the symlink reference itself without affecting the target. filepath.Walk doesn't follow symlinks by default, so there's no need for special handling of nested symlink structures.

Applied to files:

  • pkg/downloader/git_getter.go
  • pkg/security/symlink_validator.go
📚 Learning: 2025-03-21T16:14:35.272Z
Learnt from: Listener430
PR: cloudposse/atmos#984
File: internal/exec/vendor_model.go:0-0
Timestamp: 2025-03-21T16:14:35.272Z
Learning: The function `copyToTarget` is being replaced with `copyToTargetWithPatterns` to support enhanced glob pattern matching for vendoring, but is temporarily kept for reference until the new implementation is fully confirmed.

Applied to files:

  • internal/exec/copy_glob_test.go
  • internal/exec/copy_glob.go
📚 Learning: 2025-04-04T02:03:23.676Z
Learnt from: aknysh
PR: cloudposse/atmos#1185
File: internal/exec/yaml_func_store.go:26-26
Timestamp: 2025-04-04T02:03:23.676Z
Learning: The Atmos codebase currently uses `log.Fatal` for error handling in multiple places. The maintainers are aware this isn't an ideal pattern (should only be used in main() or init() functions) and plan to address it comprehensively in a separate PR. CodeRabbit should not flag these issues or push for immediate changes until that refactoring is complete.

Applied to files:

  • pkg/security/symlink_validator.go
📚 Learning: 2025-07-05T20:59:02.914Z
Learnt from: aknysh
PR: cloudposse/atmos#1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (`_ "github.com/hairyhenderson/gomplate/v4"`) alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.

Applied to files:

  • internal/exec/copy_glob.go
  • internal/exec/vendor_model.go
  • internal/exec/vendor_component_utils.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.

Applied to files:

  • internal/exec/vendor_component_utils.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like `checkAtmosConfig` in `cmd/cmd_utils.go`, avoid suggesting refactoring to return errors instead of calling `os.Exit` if such changes would significantly increase the scope due to the need to update multiple call sites.

Applied to files:

  • internal/exec/vendor_component_utils.go
📚 Learning: 2024-12-11T18:40:12.808Z
Learnt from: Listener430
PR: cloudposse/atmos#844
File: cmd/helmfile.go:37-37
Timestamp: 2024-12-11T18:40:12.808Z
Learning: In the atmos project, `cliConfig` is initialized within the `cmd` package in `root.go` and can be used in other command files.

Applied to files:

  • internal/exec/vendor_component_utils.go
📚 Learning: 2025-08-16T23:33:07.477Z
Learnt from: aknysh
PR: cloudposse/atmos#1405
File: internal/exec/describe_dependents_test.go:651-652
Timestamp: 2025-08-16T23:33:07.477Z
Learning: In the cloudposse/atmos Go codebase, ExecuteDescribeDependents expects a pointer to AtmosConfiguration (*schema.AtmosConfiguration), so when calling it with a value returned by cfg.InitCliConfig (which returns schema.AtmosConfiguration), the address-of operator (&) is necessary: ExecuteDescribeDependents(&atmosConfig, ...).

Applied to files:

  • internal/exec/vendor_component_utils.go
🧬 Code graph analysis (7)
pkg/security/symlink_validator_test.go (1)
pkg/security/symlink_validator.go (8)
  • SymlinkPolicy (15-15)
  • PolicyAllowSafe (19-19)
  • PolicyRejectAll (22-22)
  • PolicyAllowAll (25-25)
  • ParsePolicy (152-165)
  • IsSymlinkSafe (60-101)
  • CreateSymlinkHandler (34-56)
  • ValidateSymlinks (107-148)
pkg/downloader/git_getter.go (1)
pkg/security/symlink_validator.go (4)
  • SymlinkPolicy (15-15)
  • PolicyAllowSafe (19-19)
  • ValidateSymlinks (107-148)
  • PolicyRejectAll (22-22)
pkg/security/symlink_validator.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Vendor (897-903)
internal/exec/copy_glob.go (2)
pkg/schema/schema.go (1)
  • AtmosConfiguration (25-60)
pkg/security/symlink_validator.go (2)
  • GetPolicyFromConfig (169-179)
  • CreateSymlinkHandler (34-56)
tests/vendor_symlink_security_test.go (2)
pkg/security/symlink_validator.go (7)
  • SymlinkPolicy (15-15)
  • PolicyRejectAll (22-22)
  • PolicyAllowSafe (19-19)
  • PolicyAllowAll (25-25)
  • CreateSymlinkHandler (34-56)
  • GetPolicyFromConfig (169-179)
  • ParsePolicy (152-165)
pkg/schema/schema.go (3)
  • AtmosConfiguration (25-60)
  • Vendor (897-903)
  • VendorPolicy (906-910)
internal/exec/vendor_model.go (1)
pkg/security/symlink_validator.go (2)
  • GetPolicyFromConfig (169-179)
  • CreateSymlinkHandler (34-56)
internal/exec/vendor_component_utils.go (3)
pkg/schema/schema.go (2)
  • VendorComponentSpec (622-625)
  • AtmosConfiguration (25-60)
pkg/security/symlink_validator.go (2)
  • GetPolicyFromConfig (169-179)
  • CreateSymlinkHandler (34-56)
internal/exec/file_utils.go (1)
  • SanitizeFileName (73-100)
🪛 LanguageTool
prd/README.md

[grammar] ~26-~26: There might be a mistake here.
Context: ...hen: - Adding a significant new feature - Making breaking changes - Implementing s...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...nt new feature - Making breaking changes - Implementing security features - Changin...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...changes - Implementing security features - Changing core architecture - Adding new ...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...ty features - Changing core architecture - Adding new configuration options that af...

(QB_NEW_EN)

website/docs/cli/commands/vendor/vendor-pull.mdx

[grammar] ~335-~335: There might be a mistake here.
Context: ...ymlinks using ../ to escape boundaries - Absolute paths: Symlinks pointing to s...

(QB_NEW_EN)


[grammar] ~336-~336: There might be a mistake here.
Context: ... Symlinks pointing to system files like /etc/passwd - Boundary escapes: Any symlink that res...

(QB_NEW_EN)

CLAUDE.md

[grammar] ~19-~19: There might be a mistake here.
Context: ...requirements for features. PRDs contain: - Problem statements and goals - User stor...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... contain: - Problem statements and goals - User stories and acceptance criteria -...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...s - User stories and acceptance criteria - Design decisions with alternatives consi...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...n decisions with alternatives considered - Technical specifications - Implementatio...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...es considered - Technical specifications - Implementation plans When implementing ...

(QB_NEW_EN)


[grammar] ~24-~24: Please add a punctuation mark at the end of paragraph.
Context: ...chnical specifications - Implementation plans When implementing features, consult re...

(PUNCTUATION_PARAGRAPH_END)


[grammar] ~291-~291: There might be a mistake here.
Context: ...### Adding New Features or Major Changes 1. Check for existing PRDs in prd/ dire...

(QB_NEW_EN)


[grammar] ~293-~293: There might be a mistake here.
Context: ...cant features following the template in prd/ 3. Follow the implementation plan outlined ...

(QB_NEW_EN)

prd/vendor-symlink-security.prd.md

[grammar] ~26-~26: There might be a mistake here.
Context: ...ry 1: Security-Conscious DevOps Engineer As a DevOps engineer vendoring from pu...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...** protection against malicious symlinks So that I don't accidentally expose sens...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ...s from my system Acceptance Criteria: - Default configuration blocks symlinks th...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...n ### Story 2: Enterprise Security Team As a security team member I want...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ...ty when vendoring from untrusted sources So that we maintain compliance with se...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...ecurity policies Acceptance Criteria: - Can configure policy to reject ALL symli...

(QB_NEW_EN)


[grammar] ~46-~46: There might be a mistake here.
Context: ...form Engineer with Legacy Infrastructure As a platform engineer with existing s...

(QB_NEW_EN)


[grammar] ~51-~51: There might be a mistake here.
Context: ...rastructure code Acceptance Criteria: - Can configure policy to allow all symlin...

(QB_NEW_EN)


[grammar] ~58-~58: There might be a mistake here.
Context: ...s ### Decision 1: Three-Policy Approach Choice: Implement three policies: `all...

(QB_NEW_EN)


[grammar] ~61-~61: There might be a mistake here.
Context: ...safe, reject_all, allow_all` Why: - Balances security with functionality - P...

(QB_NEW_EN)


[grammar] ~62-~62: There might be a mistake here.
Context: ...* - Balances security with functionality - Provides clear, understandable options -...

(QB_NEW_EN)


[grammar] ~63-~63: There might be a mistake here.
Context: ...- Provides clear, understandable options - Covers all identified use cases **Alter...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...ed use cases Alternatives Considered: - Binary on/off switch - Too limiting, doe...

(QB_NEW_EN)


[grammar] ~74-~74: There might be a mistake here.
Context: ... allow_safe the default policy Why: - Provides immediate protection without br...

(QB_NEW_EN)


[grammar] ~79-~79: There might be a mistake here.
Context: ...s still work Alternatives Considered: - Default to allow_all - Leaves users vu...

(QB_NEW_EN)


[grammar] ~87-~87: There might be a mistake here.
Context: ...r.policy.symlinks` configuration Why: - Centralized configuration - Consistent w...

(QB_NEW_EN)


[grammar] ~88-~88: There might be a mistake here.
Context: ...on Why: - Centralized configuration - Consistent with Atmos patterns - Support...

(QB_NEW_EN)


[grammar] ~89-~89: There might be a mistake here.
Context: ...uration - Consistent with Atmos patterns - Supports both nested and dot notation *...

(QB_NEW_EN)


[grammar] ~92-~92: There might be a mistake here.
Context: ...dot notation Alternatives Considered: - Environment variables - Less discoverabl...

(QB_NEW_EN)


[grammar] ~93-~93: There might be a mistake here.
Context: ...nvironment variables - Less discoverable - Command-line flags - Requires changes to...

(QB_NEW_EN)


[grammar] ~94-~94: There might be a mistake here.
Context: ... Requires changes to all vendor commands - Per-vendor-file config - Too granular fo...

(QB_NEW_EN)


[grammar] ~121-~121: There might be a mistake here.
Context: ...ring) bool ``` ### Validation Algorithm 1. Read symlink target 2. Resolve to absolu...

(QB_NEW_EN)


[grammar] ~124-~124: There might be a mistake here.
Context: ...ath 3. Check if path is within boundary: - Calculate relative path from boundary to...

(QB_NEW_EN)


[grammar] ~139-~139: There might be a mistake here.
Context: ...hase 1: Core Security Module (Completed) - [x] Create `pkg/security/symlink_validat...

(QB_NEW_EN)


[grammar] ~140-~140: There might be a mistake here.
Context: ...ecurity Module (Completed) - [x] Create pkg/security/symlink_validator.go - [x] Implement three policies - [x] Add b...

(QB_NEW_EN)


[grammar] ~141-~141: There might be a mistake here.
Context: ...dator.go` - [x] Implement three policies - [x] Add boundary validation logic - [x] ...

(QB_NEW_EN)


[grammar] ~142-~142: There might be a mistake here.
Context: ...cies - [x] Add boundary validation logic - [x] Create comprehensive unit tests ###...

(QB_NEW_EN)


[grammar] ~145-~145: There might be a mistake here.
Context: ...ts ### Phase 2: Integration (Completed) - [x] Update schema to support configurati...

(QB_NEW_EN)


[grammar] ~151-~151: There might be a mistake here.
Context: ... ### Phase 3: Documentation (Completed) - [x] Document configuration in vendor-pul...

(QB_NEW_EN)


[grammar] ~157-~157: There might be a mistake here.
Context: ...se 4: Future Enhancements (Not in scope) - [ ] Per-source policy configuration - [ ...

(QB_NEW_EN)


[grammar] ~158-~158: There might be a mistake here.
Context: ...e) - [ ] Per-source policy configuration - [ ] Symlink allowlist patterns - [ ] Met...

(QB_NEW_EN)


[grammar] ~159-~159: There might be a mistake here.
Context: ...uration - [ ] Symlink allowlist patterns - [ ] Metrics on rejected symlinks - [ ] I...

(QB_NEW_EN)


[grammar] ~160-~160: There might be a mistake here.
Context: ...terns - [ ] Metrics on rejected symlinks - [ ] Integration with security scanning t...

(QB_NEW_EN)


[grammar] ~165-~165: There might be a mistake here.
Context: ...th allow_safe or reject_all policies 2. Compatibility: Existing workflows cont...

(QB_NEW_EN)


[grammar] ~166-~166: There might be a mistake here.
Context: ...inue to function with appropriate policy 3. Performance: No measurable performance...

(QB_NEW_EN)


[grammar] ~167-~167: There might be a mistake here.
Context: ... performance impact on vendor operations 4. Adoption: Clear documentation leads to...

(QB_NEW_EN)


[grammar] ~168-~168: There might be a mistake here.
Context: ...tation leads to correct policy selection 5. Auditability: Security teams can track...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...ecurity Considerations ### Threat Model - Attacker: Malicious repository maintai...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ...acker**: Malicious repository maintainer - Attack Vector: Symlinks in repository ...

(QB_NEW_EN)


[grammar] ~175-~175: There might be a mistake here.
Context: ...n repository pointing to sensitive files - Impact: Unauthorized read access to sy...

(QB_NEW_EN)


[grammar] ~176-~176: There might be a mistake here.
Context: ...Unauthorized read access to system files - Mitigation: Boundary validation preven...

(QB_NEW_EN)


[grammar] ~192-~192: There might be a mistake here.
Context: ...red ## Testing Strategy ### Unit Tests - Policy parsing and validation - Boundary...

(QB_NEW_EN)


[grammar] ~193-~193: There might be a mistake here.
Context: ...it Tests - Policy parsing and validation - Boundary checking algorithm - Each polic...

(QB_NEW_EN)


[grammar] ~194-~194: There might be a mistake here.
Context: ...validation - Boundary checking algorithm - Each policy behavior - Edge cases (circu...

(QB_NEW_EN)


[grammar] ~195-~195: There might be a mistake here.
Context: ...hecking algorithm - Each policy behavior - Edge cases (circular symlinks, broken sy...

(QB_NEW_EN)


[grammar] ~198-~198: There might be a mistake here.
Context: ... broken symlinks) ### Integration Tests - CVE-2025-8959 specific attack scenarios ...

(QB_NEW_EN)


[grammar] ~199-~199: There might be a mistake here.
Context: ... CVE-2025-8959 specific attack scenarios - Policy integration with vendor operation...

(QB_NEW_EN)


[grammar] ~200-~200: There might be a mistake here.
Context: ...olicy integration with vendor operations - Cross-platform compatibility (Linux, mac...

(QB_NEW_EN)


[grammar] ~201-~201: There might be a mistake here.
Context: ...ss-platform compatibility (Linux, macOS) - Performance benchmarks ### Manual Testi...

(QB_NEW_EN)


[grammar] ~204-~204: There might be a mistake here.
Context: ...rformance benchmarks ### Manual Testing - Vendor operations with various repositor...

(QB_NEW_EN)


[grammar] ~205-~205: There might be a mistake here.
Context: ...tions with various repository structures - Configuration validation - Log output ve...

(QB_NEW_EN)


[grammar] ~206-~206: There might be a mistake here.
Context: ...ry structures - Configuration validation - Log output verification - Documentation ...

(QB_NEW_EN)


[grammar] ~207-~207: There might be a mistake here.
Context: ...ion validation - Log output verification - Documentation accuracy ## Documentation...

(QB_NEW_EN)


[grammar] ~212-~212: There might be a mistake here.
Context: ... Guide**: How to set up symlink policies 2. Security Best Practices: When to use e...

(QB_NEW_EN)


[grammar] ~213-~213: There might be a mistake here.
Context: ...est Practices**: When to use each policy 3. Migration Guide: Moving from vulnerabl...

(QB_NEW_EN)


[grammar] ~214-~214: There might be a mistake here.
Context: ...Guide**: Moving from vulnerable versions 4. Troubleshooting: Common issues and sol...

(QB_NEW_EN)


[grammar] ~215-~215: There might be a mistake here.
Context: ...eshooting**: Common issues and solutions 5. API Reference: For developers extendin...

(QB_NEW_EN)


[grammar] ~228-~228: There might be a mistake here.
Context: ...dered ### Alternative 1: Fork go-getter Rejected because: - Maintenance burden...

(QB_NEW_EN)


[grammar] ~229-~229: There might be a mistake here.
Context: ...ve 1: Fork go-getter Rejected because: - Maintenance burden - Divergence from ups...

(QB_NEW_EN)


[grammar] ~230-~230: There might be a mistake here.
Context: ...Rejected because:** - Maintenance burden - Divergence from upstream - Difficult to ...

(QB_NEW_EN)


[grammar] ~231-~231: There might be a mistake here.
Context: ...enance burden - Divergence from upstream - Difficult to track security updates ###...

(QB_NEW_EN)


[grammar] ~235-~235: There might be a mistake here.
Context: ...ent custom vendoring Rejected because: - Significant development effort - Loss of...

(QB_NEW_EN)


[grammar] ~236-~236: There might be a mistake here.
Context: ...ause:** - Significant development effort - Loss of go-getter features - Increased s...

(QB_NEW_EN)


[grammar] ~237-~237: There might be a mistake here.
Context: ...ment effort - Loss of go-getter features - Increased surface area for bugs ### Alt...

(QB_NEW_EN)


[grammar] ~241-~241: There might be a mistake here.
Context: ...le symlinks entirely Rejected because: - Breaks legitimate use cases - Not accept...

(QB_NEW_EN)


[style] ~250-~250: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...e collect telemetry on policy usage? 3. Should we integrate with external security sca...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~251-~251: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ate with external security scanners? 4. Should we add a "quarantine" mode for suspicio...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[typographical] ~251-~251: Consider using typographic quotation marks here.
Context: ...l security scanners? 4. Should we add a "quarantine" mode for suspicious symlinks? ## Refer...

(EN_QUOTES)


[grammar] ~255-~255: There might be a mistake here.
Context: ...mlinks? ## References - CVE-2025-8959 - [HashiCorp Security Advisory HCSEC-2025-2...

(QB_NEW_EN)


[grammar] ~256-~256: There might be a mistake here.
Context: ...ashiCorp Security Advisory HCSEC-2025-23](https://discuss.hashicorp.com/t/hcsec-2025-23-hashicorp-go-getter-vulnerable-to-arbitrary-read-through-symlink-attack/76242) - [go-getter v1.7.9 Release Notes](https://...

(QB_NEW_EN)


[grammar] ~257-~257: There might be a mistake here.
Context: ...76242) - go-getter v1.7.9 Release Notes - [POSIX Symlink Specification](https://pub...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
CLAUDE.md

292-292: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ 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). (25)
  • GitHub Check: [mock-linux] examples/demo-atlantis
  • GitHub Check: [mock-macos] examples/demo-context
  • GitHub Check: [mock-windows] examples/demo-context
  • GitHub Check: [mock-windows] tests/fixtures/scenarios/complete
  • GitHub Check: [mock-windows] examples/demo-vendoring
  • GitHub Check: [mock-linux] examples/demo-vendoring
  • GitHub Check: [mock-windows] examples/demo-atlantis
  • GitHub Check: [mock-windows] examples/demo-component-versions
  • GitHub Check: [mock-linux] tests/fixtures/scenarios/complete
  • GitHub Check: [mock-linux] examples/demo-component-versions
  • GitHub Check: [mock-linux] examples/demo-context
  • GitHub Check: [validate] demo-stacks
  • GitHub Check: [validate] demo-helmfile
  • GitHub Check: Acceptance Tests (ubuntu-latest, linux)
  • GitHub Check: [validate] quick-start-advanced
  • GitHub Check: [validate] demo-context
  • GitHub Check: [k3s] demo-helmfile
  • GitHub Check: [validate] quick-start-simple
  • GitHub Check: Acceptance Tests (windows-latest, windows)
  • GitHub Check: [validate] demo-localstack
  • GitHub Check: [lint] demo-context
  • GitHub Check: [localstack] demo-localstack
  • GitHub Check: [lint] Dockerfile
  • GitHub Check: [lint] quick-start-advanced
  • GitHub Check: Summary

Comment on lines +376 to 384
// Get the symlink policy from config
policy := security.GetPolicyFromConfig(atmosConfig)

// Copy from local file system
copyOptions := cp.Options{
PreserveTimes: false,
PreserveOwner: false,
OnSymlink: func(src string) cp.SymlinkAction { return cp.Deep },
OnSymlink: security.CreateSymlinkHandler(p.uri, policy),
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix boundary root when copying a single local file.

Using p.uri as the baseDir breaks boundary checks when the source is a file (base must be a directory). This can misclassify symlinks and weaken protections.

Apply:

-		// Get the symlink policy from config
-		policy := security.GetPolicyFromConfig(atmosConfig)
+		// Get the symlink policy from config.
+		policy := security.GetPolicyFromConfig(atmosConfig)
@@
-		copyOptions := cp.Options{
+		// Determine boundary root for symlink validation.
+		baseDir := p.uri
+		if p.sourceIsLocalFile {
+			baseDir = filepath.Dir(p.uri)
+		}
+		if abs, err := filepath.Abs(baseDir); err == nil {
+			baseDir = abs
+		}
+
+		copyOptions := cp.Options{
 			PreserveTimes: false,
 			PreserveOwner: false,
-			OnSymlink:     security.CreateSymlinkHandler(p.uri, policy),
+			OnSymlink:     security.CreateSymlinkHandler(baseDir, policy),
 		}

Add a test that copies a single local symlinked file where the target escapes the parent directory and confirm it is skipped under allow_safe/reject_all and allowed under allow_all.


🏁 Script executed:

#!/bin/bash
# Inspect CreateSymlinkHandler usage
rg -n -C4 'CreateSymlinkHandler' internal/exec/vendor_model.go

# Check if p.sourceIsLocalFile exists
rg -n -C2 'sourceIsLocalFile' internal/exec/vendor_model.go

# View surrounding context for copyOptions
rg -n -C5 'copyOptions' internal/exec/vendor_model.go

Length of output: 1959


🏁 Script executed:

#!/bin/bash
set -eu

# Find the CreateSymlinkHandler declaration and signature
rg -n -C5 'func CreateSymlinkHandler' -g '*.go'
rg -n -C5 'CreateSymlinkHandler' -g '*.go'

Length of output: 10010


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate symlink_validator.go
file=$(fd --hidden --strip-cwd-prefix symlink_validator.go)

# Print the CreateSymlinkHandler implementation
sed -n '1,100p' "$file"

Length of output: 3304


Fix symlink boundary for single-file copy in vendor_model.go

  • When p.sourceIsLocalFile, set baseDir = abs(filepath.Dir(p.uri)) and pass baseDir into CreateSymlinkHandler instead of p.uri.
  • Add a test that copies a local symlink pointing outside its parent directory and asserts it’s skipped under allow_safe/reject_all and followed under allow_all.
🤖 Prompt for AI Agents
In internal/exec/vendor_model.go around lines 376 to 384, when
p.sourceIsLocalFile the symlink handler is being created with p.uri which is the
full file path; compute baseDir := filepath.Abs(filepath.Dir(p.uri)) (handle
error) and pass baseDir into security.CreateSymlinkHandler instead of p.uri so
symlink boundary checks use the file's parent directory; also add unit tests
that create a local symlink pointing outside its parent directory and assert it
is skipped under allow_safe/reject_all and followed under allow_all.

Comment on lines +121 to +129
### Validation Algorithm
1. Read symlink target
2. Resolve to absolute path
3. Check if path is within boundary:
- Calculate relative path from boundary to target
- If relative path starts with `..` → unsafe
- If relative path is absolute → unsafe
- Otherwise → safe

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Validation algorithm misses dir-symlink escape (critical).

If a directory within the boundary is itself a symlink to outside, the current algorithm can incorrectly mark an escape as safe. Specify resolving intermediate symlinks (EvalSymlinks on Dir(target)) and boundary, then compare.

-1. Read symlink target
-2. Resolve to absolute path
-3. Check if path is within boundary:
-   - Calculate relative path from boundary to target
-   - If relative path starts with `..` → unsafe
-   - If relative path is absolute → unsafe
-   - Otherwise → safe
+1. Read the symlink target.
+2. Make the target absolute relative to the symlink’s directory.
+3. Resolve intermediate symlinks:
+   - Resolve Dir(target) with `filepath.EvalSymlinks` (tolerate missing leaf by joining Base(target) back).
+   - Resolve the boundary path with `filepath.EvalSymlinks`.
+4. Check containment using the resolved paths:
+   - Compute `filepath.Rel(resolvedBoundary, resolvedTarget)`.
+   - If the relative path is `..` or starts with `../` (or `..` + OS path separator) → unsafe.
+   - If the relative path is absolute → unsafe.
+   - Otherwise → safe.
📝 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
### Validation Algorithm
1. Read symlink target
2. Resolve to absolute path
3. Check if path is within boundary:
- Calculate relative path from boundary to target
- If relative path starts with `..` → unsafe
- If relative path is absolute → unsafe
- Otherwise → safe
### Validation Algorithm
1. Read the symlink target.
2. Make the target absolute relative to the symlink’s directory.
3. Resolve intermediate symlinks:
- Resolve Dir(target) with `filepath.EvalSymlinks` (tolerate missing leaf by joining Base(target) back).
- Resolve the boundary path with `filepath.EvalSymlinks`.
4. Check containment using the resolved paths:
- Compute `filepath.Rel(resolvedBoundary, resolvedTarget)`.
- If the relative path is `..` or starts with `../` (or `..` + OS path separator) → unsafe.
- If the relative path is absolute → unsafe.
- Otherwise → safe.
🧰 Tools
🪛 LanguageTool

[grammar] ~121-~121: There might be a mistake here.
Context: ...ring) bool ``` ### Validation Algorithm 1. Read symlink target 2. Resolve to absolu...

(QB_NEW_EN)


[grammar] ~124-~124: There might be a mistake here.
Context: ...ath 3. Check if path is within boundary: - Calculate relative path from boundary to...

(QB_NEW_EN)

@osterman osterman added the minor New features that do not break anything label Sep 3, 2025
osterman and others added 2 commits September 3, 2025 12:04
- Fixed symlink boundary validation in vendor_component_utils.go to use source root
- Added unit test for GetPolicyFromConfig(nil) fallback behavior
- Added error wrapping in git_getter.go to preserve error context
- Added error wrapping in ValidateSymlinks for better debugging
- Fixed PRD documentation about YAML dot-notation vs env var paths
- Fixed directory_traversal_attack test to use correct relative path
- Added environment variable support for ATMOS_VENDOR_POLICY_SYMLINKS
- Added test for intermediate directory symlink escape (skipped for now)
- Fixed broken test for relative symlink within boundary
atmosConfig.Vendor.BasePath = vendorBasePath
}

vendorPolicySymlinks := os.Getenv("ATMOS_VENDOR_POLICY_SYMLINKS")

Check failure

Code scanning / golangci-lint

use of os.Getenv forbidden because "Use viper.BindEnv for new environment variables instead of os.Getenv" Error

use of os.Getenv forbidden because "Use viper.BindEnv for new environment variables instead of os.Getenv"
// For PolicyRejectAll, all symlinks are removed.
// For PolicyAllowSafe, only unsafe symlinks are removed.
// For PolicyAllowAll, no symlinks are removed.
func ValidateSymlinks(root string, policy SymlinkPolicy) error {

Check failure

Code scanning / golangci-lint

cognitive complexity 25 of func ValidateSymlinks is high (> 20) Error

cognitive complexity 25 of func ValidateSymlinks is high (> 20)
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

♻️ Duplicate comments (2)
pkg/security/symlink_validator.go (1)

59-103: Fix boundary-escape via intermediate symlinks (resolve with EvalSymlinks).

Current check doesn’t resolve intermediate symlinks; a dir symlink inside the boundary can point outside and bypass the Rel check.

Apply:

 func IsSymlinkSafe(symlink, boundary string) bool {
@@
-	// Make paths absolute for comparison.
-	cleanTarget, err := filepath.Abs(target)
-	if err != nil {
-		log.Debug("Failed to make target absolute", "target", target, "error", err)
-		return false
-	}
-
-	cleanBoundary, err := filepath.Abs(filepath.Clean(boundary))
-	if err != nil {
-		log.Debug("Failed to clean boundary path", "boundary", boundary, "error", err)
-		return false
-	}
+	// Normalize first.
+	cleanTarget := filepath.Clean(target)
+	cleanBoundary := filepath.Clean(boundary)
+
+	// Resolve symlinks where possible to prevent intermediate-dir escapes.
+	// If resolution fails (e.g., broken target), fall back to absolute path.
+	resolvedBoundary, berr := filepath.EvalSymlinks(cleanBoundary)
+	if berr != nil {
+		if abs, err := filepath.Abs(cleanBoundary); err == nil {
+			resolvedBoundary = abs
+		} else {
+			log.Debug("Failed to absolutize boundary path", "boundary", cleanBoundary, "error", err)
+			return false
+		}
+	}
+	resolvedTarget, terr := filepath.EvalSymlinks(cleanTarget)
+	if terr != nil {
+		if abs, err := filepath.Abs(cleanTarget); err == nil {
+			resolvedTarget = abs
+		} else {
+			log.Debug("Failed to absolutize target path", "target", cleanTarget, "error", err)
+			return false
+		}
+	}
@@
-	rel, err := filepath.Rel(cleanBoundary, cleanTarget)
+	rel, err := filepath.Rel(resolvedBoundary, resolvedTarget)
 	if err != nil {
-		log.Debug("Failed to calculate relative path", "target", cleanTarget, "boundary", cleanBoundary, "error", err)
+		log.Debug("Failed to calculate relative path", "target", resolvedTarget, "boundary", resolvedBoundary, "error", err)
 		return false
 	}
@@
-	if strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
-		log.Debug("Symlink target outside boundary", "target", cleanTarget, logFieldBoundary, cleanBoundary, "rel", rel)
+	if strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
+		log.Debug("Symlink target outside boundary", "target", resolvedTarget, logFieldBoundary, resolvedBoundary, "rel", rel)
 		return false
 	}
prd/vendor-symlink-security.prd.md (1)

123-131: Document the symlink-resolution hardening (EvalSymlinks).

Update the algorithm to reflect resolution of intermediate symlinks on both target and boundary.

-### Validation Algorithm
-1. Read symlink target
-2. Resolve to absolute path
-3. Check if path is within boundary:
-   - Calculate relative path from boundary to target
-   - If relative path starts with `..` → unsafe
-   - If relative path is absolute → unsafe
-   - Otherwise → safe
+### Validation Algorithm
+1. Read symlink target.
+2. Make target absolute relative to the symlink’s directory.
+3. Resolve intermediate symlinks:
+   - Resolve the boundary path and the target path with `filepath.EvalSymlinks`. If resolution fails (e.g., broken target), fall back to the normalized absolute path.
+4. Check containment using resolved paths:
+   - Compute `filepath.Rel(resolvedBoundary, resolvedTarget)`.
+   - If the relative path is absolute or equals `..`/starts with `..` → unsafe; otherwise → safe.
🧹 Nitpick comments (3)
pkg/security/symlink_validator.go (1)

109-158: Trim cognitive complexity in ValidateSymlinks.

Minor cleanup reduces duplication and satisfies linter without behavior changes.

 func ValidateSymlinks(root string, policy SymlinkPolicy) error {
 	if policy == PolicyAllowAll {
 		// Keep all symlinks (legacy behavior).
 		return nil
 	}
 
-	return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+	handle := func(remove bool, why string, path string) error {
+		if !remove {
+			log.Debug("Keeping safe symlink", "path", path)
+			return nil
+		}
+		log.Warn(why, "path", path)
+		if err := os.Remove(path); err != nil {
+			return fmt.Errorf("removing symlink %s: %w", path, err)
+		}
+		return nil
+	}
+
+	return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
 		if err != nil {
 			return fmt.Errorf("walking %s: %w", path, err)
 		}
@@
-		switch policy {
-		case PolicyRejectAll:
-			// Remove all symlinks for maximum security.
-			log.Debug("Removing symlink (reject_all policy)", "path", path)
-			if err := os.Remove(path); err != nil {
-				return fmt.Errorf("removing symlink %s: %w", path, err)
-			}
-			return nil
-
-		case PolicyAllowSafe:
-			// Validate and remove unsafe symlinks.
-			if !IsSymlinkSafe(path, root) {
-				log.Warn("Removing unsafe symlink", "path", path)
-				if err := os.Remove(path); err != nil {
-					return fmt.Errorf("removing unsafe symlink %s: %w", path, err)
-				}
-			} else {
-				log.Debug("Keeping safe symlink", "path", path)
-			}
-			return nil
-
-		default:
-			// For unknown policies, default to safe behavior.
-			if !IsSymlinkSafe(path, root) {
-				log.Warn("Removing unsafe symlink (unknown policy, defaulting to safe)", "path", path, "policy", policy)
-				if err := os.Remove(path); err != nil {
-					return fmt.Errorf("removing unsafe symlink %s (unknown policy %s): %w", path, policy, err)
-				}
-			}
-			return nil
-		}
+		switch policy {
+		case PolicyRejectAll:
+			log.Debug("Removing symlink (reject_all policy)", "path", path)
+			return handle(true, "Removing symlink (reject_all policy)", path)
+		case PolicyAllowSafe:
+			return handle(!IsSymlinkSafe(path, root), "Removing unsafe symlink", path)
+		default:
+			return handle(!IsSymlinkSafe(path, root), "Removing unsafe symlink (unknown policy, defaulting to safe)", path)
+		}
 	})
 }
tests/vendor_symlink_security_test.go (1)

40-171: Good CVE attack coverage; add dir-symlink escape case.

Consider adding a case where a directory inside the boundary is a symlink to an external dir, and a file symlink under it points outside. This verifies the EvalSymlinks hardening.

I can draft the additional test snippet if helpful.

prd/vendor-symlink-security.prd.md (1)

293-297: Add fenced code language to satisfy markdownlint.

Label the log sample as text.

-```
+```text
 WARN Symlink rejected - target outside boundary src=/tmp/vendor/evil.tf boundary=/tmp/vendor target=/etc/passwd
 INFO Symlink validated and allowed src=/tmp/vendor/link.tf target=/tmp/vendor/shared/common.tf

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: Path: .coderabbit.yaml

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

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b7fd021f570fc1a747e9797fcb6e0fc56d8a73d2 and a4d27f8251cd2a7760470a68ec6f0b505e878870.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `internal/exec/vendor_component_utils.go` (6 hunks)
* `pkg/config/utils.go` (1 hunks)
* `pkg/downloader/git_getter.go` (1 hunks)
* `pkg/security/symlink_validator.go` (1 hunks)
* `pkg/security/symlink_validator_test.go` (1 hunks)
* `prd/vendor-symlink-security.prd.md` (1 hunks)
* `tests/vendor_symlink_security_test.go` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* pkg/security/symlink_validator_test.go
* pkg/downloader/git_getter.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (4)</summary>

<details>
<summary>**/*.go</summary>


**📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)**

> `**/*.go`: Use Viper for managing configuration, environment variables, and flags
> Use interfaces for external dependencies to facilitate mocking
> All code must pass golangci-lint checks
> Follow Go's error handling idioms
> Use meaningful error messages
> Wrap errors with context using fmt.Errorf("context: %w", err)
> Consider using a custom error type for domain-specific errors
> Follow standard Go coding style
> Use gofmt and goimports to format code
> Prefer short, descriptive variable names
> Use snake_case for environment variables
> Document all exported functions, types, and methods
> Document complex logic with inline comments
> Follow Go's documentation conventions
> Use Viper for configuration management
> Support configuration via files, environment variables, and flags
> Follow the precedence order: flags > environment variables > config file > defaults
> 
> Format Go code with `gofumpt -w .` and `goimports -w .`
> 
> `**/*.go`: All comments must end with periods (complete sentences).
> Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.
> Target >80% coverage on new/changed lines and include comprehensive unit tests for new features.
> Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.
> Use logging only for system/debug info; structured logging without string interpolation; follow logging hierarchy per docs/logging.md.
> Most text UI must go to stderr; only data/results to stdout for piping compatibility.
> Prefer SDKs over shelling out to binaries; use filepath.Join, os.PathSeparator/ListSeparator; gate OS-specific logic with runtime.GOOS when unavoidable.
> For non-standard execution paths, capture telemetry via pkg/telemetry (CaptureCmd or CaptureCmdString) without collecting user data.

Files:
- `pkg/config/utils.go`
- `internal/exec/vendor_component_utils.go`
- `tests/vendor_symlink_security_test.go`
- `pkg/security/symlink_validator.go`

</details>
<details>
<summary>**/*_test.go</summary>


**📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)**

> `**/*_test.go`: Every new feature must include comprehensive unit tests
> Test both happy paths and error conditions
> Use table-driven tests for testing multiple scenarios
> Include integration tests for command flows
> Test CLI end-to-end when possible
> Use test fixtures for complex inputs
> Consider using testify/mock for creating mock implementations
> 
> `**/*_test.go`: Use table-driven unit tests for pure functions.
> Co-locate tests alongside implementation files (use *_test.go).

Files:
- `tests/vendor_symlink_security_test.go`

</details>
<details>
<summary>tests/**/*_test.go</summary>


**📄 CodeRabbit inference engine (CLAUDE.md)**

> Place integration tests under tests with fixtures in tests/test-cases/.

Files:
- `tests/vendor_symlink_security_test.go`

</details>
<details>
<summary>tests/**</summary>


**📄 CodeRabbit inference engine (CLAUDE.md)**

> Provide integration tests for CLI commands using fixtures under tests/test-cases/.

Files:
- `tests/vendor_symlink_security_test.go`

</details>

</details><details>
<summary>🧠 Learnings (13)</summary>

<details>
<summary>📚 Learning: 2025-09-02T19:59:52.081Z</summary>

Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.081Z
Learning: Applies to **/*.go : Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.


**Applied to files:**
- `pkg/config/utils.go`

</details>
<details>
<summary>📚 Learning: 2025-08-29T20:57:35.423Z</summary>

Learnt from: osterman
PR: #1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.


**Applied to files:**
- `pkg/config/utils.go`

</details>
<details>
<summary>📚 Learning: 2025-04-23T15:02:50.246Z</summary>

Learnt from: osterman
PR: #1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to os.Getenv should be avoided. Instead, use viper.BindEnv for environment variable access. This provides a consistent approach to configuration management across the codebase.


**Applied to files:**
- `pkg/config/utils.go`

</details>
<details>
<summary>📚 Learning: 2024-12-11T18:40:12.808Z</summary>

Learnt from: Listener430
PR: #844
File: cmd/helmfile.go:37-37
Timestamp: 2024-12-11T18:40:12.808Z
Learning: In the atmos project, cliConfig is initialized within the cmd package in root.go and can be used in other command files.


**Applied to files:**
- `pkg/config/utils.go`
- `internal/exec/vendor_component_utils.go`

</details>
<details>
<summary>📚 Learning: 2024-12-02T21:26:32.337Z</summary>

Learnt from: osterman
PR: #808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like pkg/config/config.go, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.


**Applied to files:**
- `internal/exec/vendor_component_utils.go`

</details>
<details>
<summary>📚 Learning: 2025-02-05T11:10:51.031Z</summary>

Learnt from: mss
PR: #1024
File: internal/exec/go_getter_utils.go:31-33
Timestamp: 2025-02-05T11:10:51.031Z
Learning: The path traversal check in ValidateURI function in internal/exec/go_getter_utils.go is intentionally kept despite potentially blocking valid Git URLs, as this validation is planned to be addressed in a separate ticket.


**Applied to files:**
- `internal/exec/vendor_component_utils.go`
- `pkg/security/symlink_validator.go`

</details>
<details>
<summary>📚 Learning: 2025-07-05T20:59:02.914Z</summary>

Learnt from: aknysh
PR: #1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (_ "github.com/hairyhenderson/gomplate/v4") alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.


**Applied to files:**
- `internal/exec/vendor_component_utils.go`

</details>
<details>
<summary>📚 Learning: 2024-10-23T21:36:40.262Z</summary>

Learnt from: osterman
PR: #740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like checkAtmosConfig in cmd/cmd_utils.go, avoid suggesting refactoring to return errors instead of calling os.Exit if such changes would significantly increase the scope due to the need to update multiple call sites.


**Applied to files:**
- `internal/exec/vendor_component_utils.go`

</details>
<details>
<summary>📚 Learning: 2025-08-16T23:33:07.477Z</summary>

Learnt from: aknysh
PR: #1405
File: internal/exec/describe_dependents_test.go:651-652
Timestamp: 2025-08-16T23:33:07.477Z
Learning: In the cloudposse/atmos Go codebase, ExecuteDescribeDependents expects a pointer to AtmosConfiguration (*schema.AtmosConfiguration), so when calling it with a value returned by cfg.InitCliConfig (which returns schema.AtmosConfiguration), the address-of operator (&) is necessary: ExecuteDescribeDependents(&atmosConfig, ...).


**Applied to files:**
- `internal/exec/vendor_component_utils.go`

</details>
<details>
<summary>📚 Learning: 2025-02-21T20:56:05.539Z</summary>

Learnt from: samtholiya
PR: #1077
File: pkg/downloader/custom_github_detector.go:0-0
Timestamp: 2025-02-21T20:56:05.539Z
Learning: The github.com/charmbracelet/log package should be imported with the alias log according to the project's import alias configuration.


**Applied to files:**
- `pkg/security/symlink_validator.go`

</details>
<details>
<summary>📚 Learning: 2025-02-21T20:56:20.761Z</summary>

Learnt from: samtholiya
PR: #1077
File: pkg/downloader/custom_github_detector_test.go:0-0
Timestamp: 2025-02-21T20:56:20.761Z
Learning: The github.com/charmbracelet/log package should be imported with the alias log, not clog.


**Applied to files:**
- `pkg/security/symlink_validator.go`

</details>
<details>
<summary>📚 Learning: 2025-04-04T02:03:23.676Z</summary>

Learnt from: aknysh
PR: #1185
File: internal/exec/yaml_func_store.go:26-26
Timestamp: 2025-04-04T02:03:23.676Z
Learning: The Atmos codebase currently uses log.Fatal for error handling in multiple places. The maintainers are aware this isn't an ideal pattern (should only be used in main() or init() functions) and plan to address it comprehensively in a separate PR. CodeRabbit should not flag these issues or push for immediate changes until that refactoring is complete.


**Applied to files:**
- `pkg/security/symlink_validator.go`

</details>
<details>
<summary>📚 Learning: 2025-03-21T17:35:56.827Z</summary>

Learnt from: Listener430
PR: #984
File: internal/exec/go_getter_utils.go:234-248
Timestamp: 2025-03-21T17:35:56.827Z
Learning: When removing symlinks in Go, using os.Remove(path) is sufficient as it removes the symlink reference itself without affecting the target. filepath.Walk doesn't follow symlinks by default, so there's no need for special handling of nested symlink structures.


**Applied to files:**
- `pkg/security/symlink_validator.go`

</details>

</details><details>
<summary>🧬 Code graph analysis (4)</summary>

<details>
<summary>pkg/config/utils.go (1)</summary><blockquote>

<details>
<summary>pkg/schema/schema.go (1)</summary>

* `Vendor` (897-903)

</details>

</blockquote></details>
<details>
<summary>internal/exec/vendor_component_utils.go (3)</summary><blockquote>

<details>
<summary>pkg/schema/schema.go (2)</summary>

* `VendorComponentSpec` (622-625)
* `AtmosConfiguration` (25-60)

</details>
<details>
<summary>pkg/security/symlink_validator.go (2)</summary>

* `GetPolicyFromConfig` (179-189)
* `CreateSymlinkHandler` (35-57)

</details>
<details>
<summary>internal/exec/file_utils.go (1)</summary>

* `SanitizeFileName` (73-100)

</details>

</blockquote></details>
<details>
<summary>tests/vendor_symlink_security_test.go (2)</summary><blockquote>

<details>
<summary>pkg/security/symlink_validator.go (7)</summary>

* `SymlinkPolicy` (16-16)
* `PolicyRejectAll` (23-23)
* `PolicyAllowSafe` (20-20)
* `PolicyAllowAll` (26-26)
* `CreateSymlinkHandler` (35-57)
* `GetPolicyFromConfig` (179-189)
* `ParsePolicy` (162-175)

</details>
<details>
<summary>pkg/schema/schema.go (3)</summary>

* `AtmosConfiguration` (25-60)
* `Vendor` (897-903)
* `VendorPolicy` (906-910)

</details>

</blockquote></details>
<details>
<summary>pkg/security/symlink_validator.go (1)</summary><blockquote>

<details>
<summary>pkg/schema/schema.go (2)</summary>

* `AtmosConfiguration` (25-60)
* `Vendor` (897-903)

</details>

</blockquote></details>

</details><details>
<summary>🪛 GitHub Check: golangci-lint</summary>

<details>
<summary>pkg/config/utils.go</summary>

[failure] 194-194: 
use of `os.Getenv` forbidden because "Use `viper.BindEnv` for new environment variables instead of `os.Getenv`"

</details>
<details>
<summary>pkg/security/symlink_validator.go</summary>

[failure] 109-109: 
cognitive complexity 25 of func `ValidateSymlinks` is high (> 20)

</details>

</details>
<details>
<summary>🪛 LanguageTool</summary>

<details>
<summary>prd/vendor-symlink-security.prd.md</summary>

[grammar] ~26-~26: There might be a mistake here.
Context: ...ry 1: Security-Conscious DevOps Engineer **As a** DevOps engineer vendoring from pu...

(QB_NEW_EN)

---

[grammar] ~28-~28: There might be a mistake here.
Context: ...** protection against malicious symlinks   **So that** I don't accidentally expose sens...

(QB_NEW_EN)

---

[grammar] ~31-~31: There might be a mistake here.
Context: ...s from my system  **Acceptance Criteria:** - Default configuration blocks symlinks th...

(QB_NEW_EN)

---

[grammar] ~36-~36: There might be a mistake here.
Context: ...n  ### Story 2: Enterprise Security Team **As a** security team member   **I want**...

(QB_NEW_EN)

---

[grammar] ~38-~38: There might be a mistake here.
Context: ...ty when vendoring from untrusted sources   **So that** we maintain compliance with se...

(QB_NEW_EN)

---

[grammar] ~41-~41: There might be a mistake here.
Context: ...ecurity policies  **Acceptance Criteria:** - Can configure policy to reject ALL symli...

(QB_NEW_EN)

---

[grammar] ~46-~46: There might be a mistake here.
Context: ...form Engineer with Legacy Infrastructure **As a** platform engineer with existing s...

(QB_NEW_EN)

---

[grammar] ~51-~51: There might be a mistake here.
Context: ...rastructure code  **Acceptance Criteria:** - Can configure policy to allow all symlin...

(QB_NEW_EN)

---

[grammar] ~58-~58: There might be a mistake here.
Context: ...s  ### Decision 1: Three-Policy Approach **Choice:** Implement three policies: `all...

(QB_NEW_EN)

---

[grammar] ~61-~61: There might be a mistake here.
Context: ...safe`, `reject_all`, `allow_all`  **Why:** - Balances security with functionality - P...

(QB_NEW_EN)

---

[grammar] ~62-~62: There might be a mistake here.
Context: ...* - Balances security with functionality - Provides clear, understandable options -...

(QB_NEW_EN)

---

[grammar] ~63-~63: There might be a mistake here.
Context: ...- Provides clear, understandable options - Covers all identified use cases  **Alter...

(QB_NEW_EN)

---

[grammar] ~66-~66: There might be a mistake here.
Context: ...ed use cases  **Alternatives Considered:** - Binary on/off switch - Too limiting, doe...

(QB_NEW_EN)

---

[grammar] ~74-~74: There might be a mistake here.
Context: ... `allow_safe` the default policy  **Why:** - Provides immediate protection without br...

(QB_NEW_EN)

---

[grammar] ~79-~79: There might be a mistake here.
Context: ...s still work  **Alternatives Considered:** - Default to `allow_all` - Leaves users vu...

(QB_NEW_EN)

---

[grammar] ~87-~87: There might be a mistake here.
Context: ...r.policy.symlinks` configuration  **Why:** - Centralized configuration - Consistent w...

(QB_NEW_EN)

---

[grammar] ~88-~88: There might be a mistake here.
Context: ...on  **Why:** - Centralized configuration - Consistent with Atmos patterns - Uses st...

(QB_NEW_EN)

---

[grammar] ~89-~89: There might be a mistake here.
Context: ...uration - Consistent with Atmos patterns - Uses standard YAML nested structure  **N...

(QB_NEW_EN)

---

[grammar] ~94-~94: There might be a mistake here.
Context: ...ntax option.  **Alternatives Considered:** - Environment variables - Less discoverabl...

(QB_NEW_EN)

---

[grammar] ~95-~95: There might be a mistake here.
Context: ...nvironment variables - Less discoverable - Command-line flags - Requires changes to...

(QB_NEW_EN)

---

[grammar] ~96-~96: There might be a mistake here.
Context: ... Requires changes to all vendor commands - Per-vendor-file config - Too granular fo...

(QB_NEW_EN)

---

[grammar] ~123-~123: There might be a mistake here.
Context: ...ring) bool ```  ### Validation Algorithm 1. Read symlink target 2. Resolve to absolu...

(QB_NEW_EN)

---

[grammar] ~126-~126: There might be a mistake here.
Context: ...ath 3. Check if path is within boundary:    - Calculate relative path from boundary to...

(QB_NEW_EN)

---

[grammar] ~141-~141: There might be a mistake here.
Context: ...hase 1: Core Security Module (Completed) - [x] Create `pkg/security/symlink_validat...

(QB_NEW_EN)

---

[grammar] ~142-~142: There might be a mistake here.
Context: ...ecurity Module (Completed) - [x] Create `pkg/security/symlink_validator.go` - [x] Implement three policies - [x] Add b...

(QB_NEW_EN)

---

[grammar] ~143-~143: There might be a mistake here.
Context: ...dator.go` - [x] Implement three policies - [x] Add boundary validation logic - [x] ...

(QB_NEW_EN)

---

[grammar] ~144-~144: There might be a mistake here.
Context: ...cies - [x] Add boundary validation logic - [x] Create comprehensive unit tests  ###...

(QB_NEW_EN)

---

[grammar] ~147-~147: There might be a mistake here.
Context: ...ts  ### Phase 2: Integration (Completed) - [x] Update schema to support configurati...

(QB_NEW_EN)

---

[grammar] ~153-~153: There might be a mistake here.
Context: ...  ### Phase 3: Documentation (Completed) - [x] Document configuration in vendor-pul...

(QB_NEW_EN)

---

[grammar] ~159-~159: There might be a mistake here.
Context: ...se 4: Future Enhancements (Not in scope) - [ ] Per-source policy configuration - [ ...

(QB_NEW_EN)

---

[grammar] ~160-~160: There might be a mistake here.
Context: ...e) - [ ] Per-source policy configuration - [ ] Symlink allowlist patterns - [ ] Met...

(QB_NEW_EN)

---

[grammar] ~161-~161: There might be a mistake here.
Context: ...uration - [ ] Symlink allowlist patterns - [ ] Metrics on rejected symlinks - [ ] I...

(QB_NEW_EN)

---

[grammar] ~162-~162: There might be a mistake here.
Context: ...terns - [ ] Metrics on rejected symlinks - [ ] Integration with security scanning t...

(QB_NEW_EN)

---

[grammar] ~167-~167: There might be a mistake here.
Context: ...th `allow_safe` or `reject_all` policies 2. **Compatibility**: Existing workflows cont...

(QB_NEW_EN)

---

[grammar] ~168-~168: There might be a mistake here.
Context: ...inue to function with appropriate policy 3. **Performance**: No measurable performance...

(QB_NEW_EN)

---

[grammar] ~169-~169: There might be a mistake here.
Context: ... performance impact on vendor operations 4. **Adoption**: Clear documentation leads to...

(QB_NEW_EN)

---

[grammar] ~170-~170: There might be a mistake here.
Context: ...tation leads to correct policy selection 5. **Auditability**: Security teams can track...

(QB_NEW_EN)

---

[grammar] ~175-~175: There might be a mistake here.
Context: ...ecurity Considerations  ### Threat Model - **Attacker**: Malicious repository maintai...

(QB_NEW_EN)

---

[grammar] ~176-~176: There might be a mistake here.
Context: ...acker**: Malicious repository maintainer - **Attack Vector**: Symlinks in repository ...

(QB_NEW_EN)

---

[grammar] ~177-~177: There might be a mistake here.
Context: ...n repository pointing to sensitive files - **Impact**: Unauthorized read access to sy...

(QB_NEW_EN)

---

[grammar] ~178-~178: There might be a mistake here.
Context: ...Unauthorized read access to system files - **Mitigation**: Boundary validation preven...

(QB_NEW_EN)

---

[grammar] ~194-~194: There might be a mistake here.
Context: ...red  ## Testing Strategy  ### Unit Tests - Policy parsing and validation - Boundary...

(QB_NEW_EN)

---

[grammar] ~195-~195: There might be a mistake here.
Context: ...it Tests - Policy parsing and validation - Boundary checking algorithm - Each polic...

(QB_NEW_EN)

---

[grammar] ~196-~196: There might be a mistake here.
Context: ...validation - Boundary checking algorithm - Each policy behavior - Edge cases (circu...

(QB_NEW_EN)

---

[grammar] ~197-~197: There might be a mistake here.
Context: ...hecking algorithm - Each policy behavior - Edge cases (circular symlinks, broken sy...

(QB_NEW_EN)

---

[grammar] ~200-~200: There might be a mistake here.
Context: ... broken symlinks)  ### Integration Tests - CVE-2025-8959 specific attack scenarios ...

(QB_NEW_EN)

---

[grammar] ~201-~201: There might be a mistake here.
Context: ... CVE-2025-8959 specific attack scenarios - Policy integration with vendor operation...

(QB_NEW_EN)

---

[grammar] ~202-~202: There might be a mistake here.
Context: ...olicy integration with vendor operations - Cross-platform compatibility (Linux, mac...

(QB_NEW_EN)

---

[grammar] ~203-~203: There might be a mistake here.
Context: ...ss-platform compatibility (Linux, macOS) - Performance benchmarks  ### Manual Testi...

(QB_NEW_EN)

---

[grammar] ~206-~206: There might be a mistake here.
Context: ...rformance benchmarks  ### Manual Testing - Vendor operations with various repositor...

(QB_NEW_EN)

---

[grammar] ~207-~207: There might be a mistake here.
Context: ...tions with various repository structures - Configuration validation - Log output ve...

(QB_NEW_EN)

---

[grammar] ~208-~208: There might be a mistake here.
Context: ...ry structures - Configuration validation - Log output verification - Documentation ...

(QB_NEW_EN)

---

[grammar] ~209-~209: There might be a mistake here.
Context: ...ion validation - Log output verification - Documentation accuracy  ## Documentation...

(QB_NEW_EN)

---

[grammar] ~214-~214: There might be a mistake here.
Context: ... Guide**: How to set up symlink policies 2. **Security Best Practices**: When to use e...

(QB_NEW_EN)

---

[grammar] ~215-~215: There might be a mistake here.
Context: ...est Practices**: When to use each policy 3. **Migration Guide**: Moving from vulnerabl...

(QB_NEW_EN)

---

[grammar] ~216-~216: There might be a mistake here.
Context: ...Guide**: Moving from vulnerable versions 4. **Troubleshooting**: Common issues and sol...

(QB_NEW_EN)

---

[grammar] ~217-~217: There might be a mistake here.
Context: ...eshooting**: Common issues and solutions 5. **API Reference**: For developers extendin...

(QB_NEW_EN)

---

[grammar] ~230-~230: There might be a mistake here.
Context: ...dered  ### Alternative 1: Fork go-getter **Rejected because:** - Maintenance burden...

(QB_NEW_EN)

---

[grammar] ~231-~231: There might be a mistake here.
Context: ...ve 1: Fork go-getter **Rejected because:** - Maintenance burden - Divergence from ups...

(QB_NEW_EN)

---

[grammar] ~232-~232: There might be a mistake here.
Context: ...Rejected because:** - Maintenance burden - Divergence from upstream - Difficult to ...

(QB_NEW_EN)

---

[grammar] ~233-~233: There might be a mistake here.
Context: ...enance burden - Divergence from upstream - Difficult to track security updates  ###...

(QB_NEW_EN)

---

[grammar] ~237-~237: There might be a mistake here.
Context: ...ent custom vendoring **Rejected because:** - Significant development effort - Loss of...

(QB_NEW_EN)

---

[grammar] ~238-~238: There might be a mistake here.
Context: ...ause:** - Significant development effort - Loss of go-getter features - Increased s...

(QB_NEW_EN)

---

[grammar] ~239-~239: There might be a mistake here.
Context: ...ment effort - Loss of go-getter features - Increased surface area for bugs  ### Alt...

(QB_NEW_EN)

---

[grammar] ~243-~243: There might be a mistake here.
Context: ...le symlinks entirely **Rejected because:** - Breaks legitimate use cases - Not accept...

(QB_NEW_EN)

---

[style] ~252-~252: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...e collect telemetry on policy usage? 3. Should we integrate with external security sca...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

---

[style] ~253-~253: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ate with external security scanners? 4. Should we add a "quarantine" mode for suspicio...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

---

[typographical] ~253-~253: Consider using typographic quotation marks here.
Context: ...l security scanners? 4. Should we add a "quarantine" mode for suspicious symlinks?  ## Refer...

(EN_QUOTES)

---

[grammar] ~257-~257: There might be a mistake here.
Context: ...mlinks?  ## References  - [CVE-2025-8959](https://nvd.nist.gov/vuln/detail/CVE-2025-8959) - [HashiCorp Security Advisory HCSEC-2025-2...

(QB_NEW_EN)

---

[grammar] ~258-~258: There might be a mistake here.
Context: ...ashiCorp Security Advisory HCSEC-2025-23](https://discuss.hashicorp.com/t/hcsec-2025-23-hashicorp-go-getter-vulnerable-to-arbitrary-read-through-symlink-attack/76242) - [go-getter v1.7.9 Release Notes](https://...

(QB_NEW_EN)

---

[grammar] ~259-~259: There might be a mistake here.
Context: ...76242) - [go-getter v1.7.9 Release Notes](https://github.com/hashicorp/go-getter/releases/tag/v1.7.9) - [POSIX Symlink Specification](https://pub...

(QB_NEW_EN)

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>prd/vendor-symlink-security.prd.md</summary>

294-294: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

</details>

<details>
<summary>⏰ 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). (2)</summary>

* GitHub Check: Build (windows-latest, windows)
* GitHub Check: Summary

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>pkg/security/symlink_validator.go (1)</summary><blockquote>

`33-57`: **Policy-driven cp.OnSymlink handler looks solid.**

Clear mapping: reject_all→Skip, allow_all→Deep, allow_safe→Deep if validated. Logs are helpful. No changes needed.

</blockquote></details>
<details>
<summary>internal/exec/vendor_component_utils.go (3)</summary><blockquote>

`137-156`: **Correct boundary for destination copy (use source tempDir).**

Using tempDir as the validation boundary while copying to the component path is correct and prevents false negatives.

---

`502-513`: **Local source copy validates against source root (p.uri) — good.**

This fixes the earlier misclassification risk for local components.

---

`557-572`: **Mixin copy uses tempDir boundary — consistent with component path.**

Policy application is consistent across flows. LGTM.

</blockquote></details>
<details>
<summary>tests/vendor_symlink_security_test.go (1)</summary><blockquote>

`200-282`: **Policy integration test reads well and exercises all modes.**

Covers defaulting, explicit policies, and assertions on internal/external links. Nice.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment on lines +194 to +199
vendorPolicySymlinks := os.Getenv("ATMOS_VENDOR_POLICY_SYMLINKS")
if len(vendorPolicySymlinks) > 0 {
log.Debug(foundEnvVarMessage, "ATMOS_VENDOR_POLICY_SYMLINKS", vendorPolicySymlinks)
atmosConfig.Vendor.Policy.Symlinks = vendorPolicySymlinks
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Do not introduce new os.Getenv; bind and read via Viper.

This violates the repo rule and fails golangci-lint. Use viper.BindEnv and viper.GetString for ATMOS_VENDOR_POLICY_SYMLINKS, then set atmosConfig from that value.

Apply this diff within this hunk:

-	vendorPolicySymlinks := os.Getenv("ATMOS_VENDOR_POLICY_SYMLINKS")
-	if len(vendorPolicySymlinks) > 0 {
-		log.Debug(foundEnvVarMessage, "ATMOS_VENDOR_POLICY_SYMLINKS", vendorPolicySymlinks)
-		atmosConfig.Vendor.Policy.Symlinks = vendorPolicySymlinks
-	}
+	// Read via Viper (ENV override should be bound in load.go).
+	vendorPolicySymlinks := viper.GetString("vendor.policy.symlinks")
+	if vendorPolicySymlinks != "" {
+		log.Debug(foundEnvVarMessage, "ATMOS_VENDOR_POLICY_SYMLINKS", vendorPolicySymlinks)
+		atmosConfig.Vendor.Policy.Symlinks = vendorPolicySymlinks
+	}

Add the import in this file:

import (
    // ...
    "github.com/spf13/viper"
)

And bind the ENV once (outside this file) in pkg/config/load.go using the project’s helper:

// Somewhere in setEnv(...) alongside other bindings.
bindEnv(v, "vendor.policy.symlinks", "ATMOS_VENDOR_POLICY_SYMLINKS")
🧰 Tools
🪛 GitHub Check: golangci-lint

[failure] 194-194:
use of os.Getenv forbidden because "Use viper.BindEnv for new environment variables instead of os.Getenv"

🤖 Prompt for AI Agents
In pkg/config/utils.go around lines 194 to 199, replace the direct os.Getenv
usage with Viper: import "github.com/spf13/viper" in this file, remove os.Getenv
call and instead read the value via viper.GetString("vendor.policy.symlinks")
and set atmosConfig.Vendor.Policy.Symlinks from that result if it's non-empty;
do not call viper.BindEnv here (the review asks to bind the env key once in
pkg/config/load.go using bindEnv(v, "vendor.policy.symlinks",
"ATMOS_VENDOR_POLICY_SYMLINKS")), so ensure only the GetString read is used in
this file and remove the os package dependency if no longer needed.

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)
CLAUDE.md (2)

17-27: PRD section: trim stray spaces and add discoverability links.

  • Remove the trailing double-space after “acceptance criteria”.
  • Consider adding relative links to prd/README.md (overview) and the PRD template to speed up navigation.
- - User stories and acceptance criteria  
+ - User stories and acceptance criteria

309-313: Make the “New Features/Major Changes” checklist enforce schema/docs/tests.

Add explicit steps so changes don’t miss required updates (aligns with Testing Guidelines and our past learning on unit/integration tests).

 ### Adding New Features or Major Changes
 1. **Check for existing PRDs** in `prd/` directory for design decisions and requirements
 2. **Create a PRD** for significant features following the template in `prd/`
 3. Follow the implementation plan outlined in the relevant PRD
+4. Update all affected schemas (see “Schema Updates (MANDATORY)”).
+5. Add unit tests and, for CLI changes, integration tests using `tests/` fixtures.
+6. Update Docusaurus docs for any new/changed flags/config (see “Documentation Requirements”).
📜 Review details

Configuration used: Path: .coderabbit.yaml

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 a4d27f8 and c3c1f0e.

📒 Files selected for processing (3)
  • CLAUDE.md (2 hunks)
  • internal/exec/copy_glob_test.go (4 hunks)
  • tests/cli_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/exec/copy_glob_test.go
  • tests/cli_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-05T14:40:05.107Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T14:40:05.107Z
Learning: Applies to **/*_test.go : New features must include comprehensive unit tests, and CLI features require integration tests using tests/ fixtures.

Applied to files:

  • CLAUDE.md
🪛 LanguageTool
CLAUDE.md

[grammar] ~19-~19: There might be a mistake here.
Context: ...requirements for features. PRDs contain: - Problem statements and goals - User stor...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... contain: - Problem statements and goals - User stories and acceptance criteria -...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...s - User stories and acceptance criteria - Design decisions with alternatives consi...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...n decisions with alternatives considered - Technical specifications - Implementatio...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...es considered - Technical specifications - Implementation plans When implementing ...

(QB_NEW_EN)


[grammar] ~24-~24: Please add a punctuation mark at the end of paragraph.
Context: ...chnical specifications - Implementation plans When implementing features, consult re...

(PUNCTUATION_PARAGRAPH_END)


[grammar] ~309-~309: There might be a mistake here.
Context: ...### Adding New Features or Major Changes 1. Check for existing PRDs in prd/ dire...

(QB_NEW_EN)


[grammar] ~311-~311: There might be a mistake here.
Context: ...cant features following the template in prd/ 3. Follow the implementation plan outlined ...

(QB_NEW_EN)

⏰ 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). (6)
  • GitHub Check: Build (windows-latest, windows)
  • GitHub Check: Build (macos-latest, macos)
  • GitHub Check: Analyze (go)
  • GitHub Check: Lint (golangci)
  • GitHub Check: website-deploy-preview
  • GitHub Check: Summary

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor New features that do not break anything size/xl Extra large size PR triage Needs triage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant