-
-
Notifications
You must be signed in to change notification settings - Fork 133
fix: implement symlink security for vendor operations (CVE-2025-8959) #1446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
## 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]>
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. |
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
- 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.
📝 WalkthroughWalkthroughIntroduces 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this 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.
📒 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 listsinstead 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 > defaultsFormat Go code with
gofumpt -w .
andgoimports -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.
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"` | ||
} |
There was a problem hiding this comment.
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.
Or using dot notation: | ||
|
||
```yaml | ||
vendor: | ||
policy.symlinks: "allow_safe" | ||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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 nilAlso 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 toVendor.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.
📒 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 > defaultsFormat Go code with
gofumpt -w .
andgoimports -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 listsinstead 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
// 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), | ||
} |
There was a problem hiding this comment.
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.
### 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 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
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.
### 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)
- 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
// 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
There was a problem hiding this 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 toos.Getenv
should be avoided. Instead, useviper.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 thecmd
package inroot.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 likepkg/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 inValidateURI
function ininternal/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 likecheckAtmosConfig
incmd/cmd_utils.go
, avoid suggesting refactoring to return errors instead of callingos.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: Thegithub.1485827954.workers.dev/charmbracelet/log
package should be imported with the aliaslog
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: Thegithub.1485827954.workers.dev/charmbracelet/log
package should be imported with the aliaslog
, notclog
.**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 useslog.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 -->
vendorPolicySymlinks := os.Getenv("ATMOS_VENDOR_POLICY_SYMLINKS") | ||
if len(vendorPolicySymlinks) > 0 { | ||
log.Debug(foundEnvVarMessage, "ATMOS_VENDOR_POLICY_SYMLINKS", vendorPolicySymlinks) | ||
atmosConfig.Vendor.Policy.Symlinks = vendorPolicySymlinks | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
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.
There was a problem hiding this 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.
📒 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
what
allow_safe
(default),reject_all
, andallow_all
why
references
prd/vendor-symlink-security.prd.md
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
Security Module (
pkg/security/symlink_validator.go
)allow_safe
,reject_all
,allow_all
Configuration (
vendor.policy.symlinks
in atmos.yaml)Git Operations (
pkg/downloader/git_getter.go
)Comprehensive Testing
Documentation
Security Impact
allow_safe
policy immediately protects users from CVE-2025-8959allow_all
for legacy behavior if neededreject_all
available for high-security environmentsTest Results
All tests pass, including specific tests for:
allow_safe
🤖 Generated with Claude Code
Co-Authored-By: Claude [email protected]
Summary by CodeRabbit
New Features
Documentation
Tests