Skip to content

immutable gateway: skip ..-prefixed dirs in artifact walker#2640

Merged
O-sura merged 4 commits into
wso2:mainfrom
tricktron:immutable-mode-mount-dir
Jul 16, 2026
Merged

immutable gateway: skip ..-prefixed dirs in artifact walker#2640
O-sura merged 4 commits into
wso2:mainfrom
tricktron:immutable-mode-mount-dir

Conversation

@tricktron

Copy link
Copy Markdown
Contributor

Purpose

Kubernetes ConfigMap volume mounts create ..data and ..TIMESTAMP symlink directories inside the mount path. The immutable gateway artifact walker descended into these, processing each artifact twice and producing a conflict error that prevented the controller from starting.

Goals

Ensure LoadArtifacts discovers each artifact file exactly once when the artifacts directory is a Kubernetes ConfigMap mount.

Approach

  • Extracted collectArtifacts(dir string) ([]string, error) from LoadArtifacts. A pure function that walks a directory and returns YAML/JSON file paths, skipping directories whose name starts with .. via fs.SkipDir.
  • Wired collectArtifacts into LoadArtifacts, replacing the inline filepath.WalkDir callback.

The core check:

if d.IsDir() {
    if strings.HasPrefix(d.Name(), "..") {
        return fs.SkipDir
    }
    return nil
}

User stories

As an operator mounting a ConfigMap volume at the artifacts directory, I want the immutable gateway loader to ignore Kubernetes' internal ..-prefixed directories so that each artifact is loaded exactly once and the controller starts successfully.

Documentation

N/A. No user-facing configuration changes. The fix is transparent to operators.

Automation tests

  • Unit tests
    • TestCollectArtifacts_ConfigMapMountYieldsFileOnce: creates a real ConfigMap mount layout with symlinks, asserts the artifact is found exactly once via the top-level symlink.
    • TestCollectArtifacts_DescendsIntoNonDotDotDirs: table-driven. Verifies nested subdirectories (rest-apis/) and single-dot directories (.hidden/) are not skipped.
  • Integration tests
    • N/A. The fix is in a pure function with no external dependencies.

Security checks

Test environment

  • Go
  • Linux

@CLAassistant

CLAassistant commented Jul 13, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

LoadArtifacts now collects filesystem paths before reading and classifying artifacts. Recursive collection skips dot-prefixed entries and preserves symlink paths, while unsupported extensions are warned and skipped. Tests cover ConfigMap-style mounts, nested symlinked directories, and hidden directories.

Changes

Artifact discovery refactor

Layer / File(s) Summary
Collect artifact paths
gateway/gateway-controller/pkg/immutable/loader.go, gateway/gateway-controller/pkg/immutable/loader_test.go
collectArtifacts recursively gathers file paths with os.ReadDir, skips dot-prefixed entries, and is tested against ConfigMap-style symlinks, nested directories, and hidden directories.
Integrate path collection into loading
gateway/gateway-controller/pkg/immutable/loader.go
LoadArtifacts reads and parses collected paths, maps YAML/JSON extensions to content types, warns on unsupported extensions, buckets artifacts into three passes, and aggregates applyArtifact errors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: renuka-fernando, malinthaprasan, tgtshanika

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the immutable gateway artifact-walker fix and the ConfigMap .. directory skipping behavior.
Description check ✅ Passed The PR description covers purpose, goals, approach, tests, security, and environment, but omits the optional Samples and Related PRs sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


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

❤️ Share

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

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 13, 2026
@renuka-fernando renuka-fernando self-assigned this Jul 14, 2026
@O-sura

O-sura commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Hi @tricktron, Could you please rebase your branch on the latest main, resolve any conflicts if needed, and update your PR? That should fix these workflow failures. Thanks!

@tricktron
tricktron force-pushed the immutable-mode-mount-dir branch from 5df20c1 to 5216575 Compare July 14, 2026 20:17
@tricktron

tricktron commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Hi @tricktron, Could you please rebase your branch on the latest main, resolve any conflicts if needed, and update your PR? That should fix these workflow failures. Thanks!

@O-sura Rebased and updated.

@O-sura O-sura left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The fix correctly handles the flat configMap layout but it introduces a regression for artifacts stored in nested directories. With this new change it skips ..* directories to avoid duplicate processing and relies on filepath.WalkDir, which does not follow directory symlinks.

This works for flat ConfigMap mounts, but fails for nested paths (e.g. rest-apis/petstore.yaml) because the timestamped directory is skipped, and WalkDir does not traverse directory symlinks.

/artifacts/
├── ..2026_07_13_10_00_00.123/       ← real dir
│   └── rest-apis/petstore.yaml
├── ..data     ──► ..2026_07_13_.../  ← symlink to current revision
└── rest-apis  ──► ..data/rest-apis   ← top-level entry is a symlink to a DIRECTORY

(In Kubernetes' atomic writer layout, the only real copy of the file exists inside the timestamped ..2026_* directory, while the exposed directory (rest-apis) is a symlink)

This will return zero artifacts without any error even though the file is there. Hence we need to consider this scenario as well

@tricktron

Copy link
Copy Markdown
Contributor Author

@O-sura Good catch. I'll add a test to reproduce it and try to think about a solution.

@tricktron

Copy link
Copy Markdown
Contributor Author

@O-sura, Ok I added the test and fixed it by replacing filepath.WalkDir with os.ReadDir + os.Stat to handle Kubernetes ConfigMap atomic-writer mounts.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
gateway/gateway-controller/pkg/immutable/loader.go (1)

93-103: 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

Enforce path containment check before reading files.

As per coding guidelines, any file path must be resolved to its final absolute path and verified to stay strictly within the intended root directory before calling os.ReadFile or any other read operation.

Since collectArtifacts follows symlinks, failing to enforce containment allows a crafted symlink to escape the ArtifactsDir, resulting in arbitrary file reads during startup.

To prevent partial prefix matches and resolve symlinked root directories (common in container mounts), evaluate symlinks on both the ArtifactsDir and the file path before asserting containment.

🛡️ Proposed fix to add containment check
 	artifactPaths, err := collectArtifacts(g.cfg.ArtifactsDir)
 	if err != nil {
 		return err
 	}
 
+	finalRoot, err := filepath.EvalSymlinks(g.cfg.ArtifactsDir)
+	if err != nil {
+		return fmt.Errorf("failed to eval symlinks for artifacts dir: %w", err)
+	}
+	absRoot, err := filepath.Abs(finalRoot)
+	if err != nil {
+		return fmt.Errorf("failed to get absolute root path: %w", err)
+	}
+	if !strings.HasSuffix(absRoot, string(filepath.Separator)) {
+		absRoot += string(filepath.Separator)
+	}
+
 	for _, path := range artifactPaths {
+		finalPath, err := filepath.EvalSymlinks(path)
+		if err != nil {
+			return fmt.Errorf("failed to eval symlinks for %s: %w", path, err)
+		}
+		absPath, err := filepath.Abs(finalPath)
+		if err != nil {
+			return fmt.Errorf("failed to get absolute path for %s: %w", finalPath, err)
+		}
+		if !strings.HasPrefix(absPath, absRoot) {
+			return fmt.Errorf("artifact path escapes root directory: %s", absPath)
+		}
+
 		ext := strings.ToLower(filepath.Ext(path))
-		data, err := os.ReadFile(path)
+		data, err := os.ReadFile(absPath)
 		if err != nil {
-			return fmt.Errorf("failed to read artifact %s: %w", path, err)
+			return fmt.Errorf("failed to read artifact %s: %w", absPath, err)
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gateway/gateway-controller/pkg/immutable/loader.go` around lines 93 - 103,
Before os.ReadFile in the artifact loop, resolve symlinks for both
g.cfg.ArtifactsDir and each path, then convert them to absolute paths and verify
the file path is strictly contained within the resolved artifacts root without
relying on string-prefix matching. Reject and return an error for paths outside
the root, and read only the validated resolved path while preserving the
existing artifact processing in collectArtifacts.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gateway/gateway-controller/pkg/immutable/loader.go`:
- Line 228: Update collectArtifacts to append only files with YAML or JSON
extensions, excluding README.md and other non-artifact files before
LoadArtifacts processes them. Apply the extension check immediately before the
paths append operation and preserve recursive artifact discovery for supported
files.

---

Outside diff comments:
In `@gateway/gateway-controller/pkg/immutable/loader.go`:
- Around line 93-103: Before os.ReadFile in the artifact loop, resolve symlinks
for both g.cfg.ArtifactsDir and each path, then convert them to absolute paths
and verify the file path is strictly contained within the resolved artifacts
root without relying on string-prefix matching. Reject and return an error for
paths outside the root, and read only the validated resolved path while
preserving the existing artifact processing in collectArtifacts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3eb51671-3cf7-4957-ab92-665ca3fda846

📥 Commits

Reviewing files that changed from the base of the PR and between 5216575 and cd80ffb.

📒 Files selected for processing (2)
  • gateway/gateway-controller/pkg/immutable/loader.go
  • gateway/gateway-controller/pkg/immutable/loader_test.go

Comment thread gateway/gateway-controller/pkg/immutable/loader.go
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 15, 2026
@tricktron
tricktron force-pushed the immutable-mode-mount-dir branch from cd80ffb to dab6e7a Compare July 15, 2026 09:40
@tricktron
tricktron requested a review from O-sura July 15, 2026 09:59
Replace filepath.WalkDir with os.ReadDir + os.Stat to handle
Kubernetes ConfigMap atomic-writer mounts. Dot-prefixed entries
(..data, ..TIMESTAMP) are skipped; os.Stat follows symlinks so
user-visible entries resolve transparently. This matches the
pattern used by the kubelet itself (filepath.Glob with [^.]*).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@gateway/gateway-controller/pkg/immutable/loader.go`:
- Around line 100-116: Enforce symlink-aware root containment in LoadArtifacts
and collectArtifacts: resolve g.cfg.ArtifactsDir to an absolute evaluated path,
then resolve each candidate path before any os.Stat, directory recursion, or
os.ReadFile call and require it to remain strictly under the root using a
trailing-separator prefix check. Reuse the resolved root throughout traversal
and return an error or skip the path consistently when containment fails, while
preserving existing artifact filtering and read-error behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ef5bffc9-837e-471a-8d62-e2ca3f382064

📥 Commits

Reviewing files that changed from the base of the PR and between cd80ffb and 35f8b86.

📒 Files selected for processing (2)
  • gateway/gateway-controller/pkg/immutable/loader.go
  • gateway/gateway-controller/pkg/immutable/loader_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • gateway/gateway-controller/pkg/immutable/loader_test.go

Comment thread gateway/gateway-controller/pkg/immutable/loader.go
@tricktron
tricktron requested a review from O-sura July 15, 2026 18:22
@O-sura
O-sura merged commit 467b5a9 into wso2:main Jul 16, 2026
12 checks passed
@renuka-fernando

Copy link
Copy Markdown
Contributor

This PR fixes #2719

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants