Skip to content

fix(executor)!: give input artifacts without mode a default of 0644. Fixes #14792 - #16620

Open
anneheartrecord wants to merge 2 commits into
argoproj:mainfrom
anneheartrecord:fix/artifact-default-mode
Open

fix(executor)!: give input artifacts without mode a default of 0644. Fixes #14792#16620
anneheartrecord wants to merge 2 commits into
argoproj:mainfrom
anneheartrecord:fix/artifact-default-mode

Conversation

@anneheartrecord

@anneheartrecord anneheartrecord commented Aug 10, 2026

Copy link
Copy Markdown

Fixes #14792

Motivation

An artifact repository generally cannot store file permissions, so an input artifact only kept the permissions it was created with if it happened to be archived. A tarball restores the modes recorded in its headers, so a file saved as 0644 came back as 0644; the same file saved with archive: none: {} came back as 0600, because there is nowhere to record the mode. Some repositories (http, for example) have nowhere to record it at all.

That made an artifact's permissions a property of the template that produced it rather than of the template consuming it, which is what the issue reports.

This takes @Joibel's suggestion from the issue: rather than restoring metadata from the archive, give an artifact that does not set mode a well known set of defaults.

Modifications

chmodDefault applies 0644 to an artifact's files and 0755 to its directories, for artifacts that do not set mode.

  • It covers the whole artifact rather than just its root, because an artifact stored with archive: none can be a directory too (loadS3Artifact falls back to GetDirectory). recurseMode is deliberately not consulted — it selects how an explicit mode is applied, and this is the default for its absence.
  • Directories get 0755 rather than 0644 so that they can still be entered.
  • Symlinks are skipped. os.Chmod follows them and untar preserves the symlinks within an artifact, so chmoding one could change the permissions of a file outside the artifact. The existing chmod helper does follow them, but it only runs when mode is set explicitly, and that seemed worth not inheriting into a default.
  • An explicit mode still wins, and artifacts loaded by an artifact plugin keep their existing 0666 default. That branch already has a deliberate default, so there is no inconsistency there to fix — happy to fold it in if you would rather it were 0644 as well.

The default started as the more restrictive 0600/0700 (owner-only), but TestArtifactsSuite/TestOutputOnInputPlugin caught a real problem with that: an artifact-plugin sidecar reads a saved output through its own mount of the main container's filesystem, and isn't guaranteed to run as the same user as the container that loaded the input artifact. Owner-only permissions made that cross-container read fail with permission denied. 0644/0755 stays world-readable, which fixes that case, while still restricting writes to the owner.

Verification

TestChmodDefault covers a single file (the reported case), a directory tree, a symlink pointing outside the artifact, and a dangling symlink as the artifact itself. TestArtifactsSuite/TestOutputOnInputPlugin covers the cross-container artifact-plugin read. go test ./workflow/executor/..., go vet ./workflow/executor/..., and the full E2E suite pass.

Documentation

docs/upgrading.md documents this as a breaking change under v4.1, with the two cases most likely to need an explicit mode: a file that has to stay executable, and a container that needs to write to an artifact loaded by a different user. It also notes that when an artifact's path falls inside a volume you mounted yourself the artifact is loaded into that volume, so on a persistentVolumeClaim the new permissions outlive the pod.

docs/walk-through/artifacts.md mentions the default in its mode section.

AI

This PR was written with Claude — code, tests and docs — and reviewed with it before submitting. The details were checked against this repository rather than taken from the model: that untar creates real symlinks, that archive: none can produce a directory via GetDirectory, the 0755 YAML octal convention used in the docs example, and (after the amend below) that an artifact-plugin sidecar can run as a different user than the container that loaded the artifact.

Summary by CodeRabbit

  • Enhancements

    • Standardized permissions for input artifacts without explicitly configured modes: files use 0644 and directories use 0755.
    • Preserved explicitly configured permissions and existing symlink behavior.
    • Plugin-provided artifacts receive writable file permissions by default.
  • Documentation

    • Added upgrade guidance covering permission changes, executable files, mounted volumes, and persistent storage.
    • Documented artifact permission defaults and repository preservation considerations.

Artifact repositories generally cannot store file permissions, so an
artifact only kept the permissions it was created with if it was
archived: a tarball restores the modes recorded in its headers, while an
artifact stored with `archive: none` arrives with whatever mode its
driver created the files with. The same artifact therefore landed with
different permissions depending on how the template that produced it
chose to store it.

Give an artifact that does not set `mode` a well known set of
permissions instead: 0600 for its files and 0700 for its directories,
which have to stay traversable. This covers the whole artifact, as an
artifact stored as-is can be a directory too.

Symlinks are skipped, since os.Chmod follows them and untar preserves
the symlinks within an artifact, so following one could change the
permissions of a file outside it.

Signed-off-by: Charles Cheng <chengxisheng777@gmail.com>
…0700

An artifact-plugin sidecar reads a saved output through its own mount of
the main container's filesystem, and is not guaranteed to run as the same
user as the container that loaded the input artifact. A 0600 default made
that read fail with permission denied, caught by
TestArtifactsSuite/TestOutputOnInputPlugin. 0644/0755 stays readable
across containers while still restricting writes to the owner.

Also rewrites the mode-selection if/else-if/else as a switch to satisfy
golangci-lint's gocritic ifElseChain check.

Signed-off-by: Charles Cheng <chengxisheng777@gmail.com>
@anneheartrecord
anneheartrecord force-pushed the fix/artifact-default-mode branch from cb50bf3 to f58ca22 Compare August 11, 2026 04:55
@Joibel

Joibel commented Aug 11, 2026

Copy link
Copy Markdown
Member

The code and the description differ on what permissions are set on the artifacts. (0600 in the descripiton, but 0644 in the code). I prefer the more restrictive option, but understand there might be reasons why your AI didn't do that

@anneheartrecord
anneheartrecord marked this pull request as ready for review August 12, 2026 12:55
@anneheartrecord
anneheartrecord requested review from a team as code owners August 12, 2026 12:55
@anneheartrecord

Copy link
Copy Markdown
Author

Good catch, thanks — the description was stale, not intentional. Original submission used 0600/0700, but TestArtifactsSuite/TestOutputOnInputPlugin caught a real problem with that: an artifact-plugin sidecar reads a saved output through its own mount of the main container's filesystem, and isn't guaranteed to run as the same user as the container that loaded the input artifact. Owner-only permissions made that cross-container read fail with permission denied, so I amended the code (and docs/upgrading.md/docs/walk-through/artifacts.md) to 0644/0755 — I just missed updating the PR description to match. Fixed that now. 0644 is the more restrictive option compatible with that sidecar case; happy to revisit if there's a way to keep 0600 that I'm missing.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Input artifacts without explicit modes now use 0644 for files and 0755 for directories. Explicit modes remain authoritative. Plugin artifacts retain their 0666 default. Symlinks are not modified.

Changes

Artifact permission normalization

Layer / File(s) Summary
Default permission application
workflow/executor/executor.go, workflow/executor/executor_test.go, docs/upgrading.md, docs/walk-through/artifacts.md
The executor applies default modes to artifact files and directories while preserving explicit modes and plugin defaults. Tests cover files, directory trees, external symlink targets, and dangling symlinks. Documentation describes the new defaults and persistence behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes normalize unspecified input artifact permissions after loading, which addresses inconsistent permissions between archived and non-archived artifacts in [#14792].
Out of Scope Changes check ✅ Passed The code, tests, and documentation changes directly support consistent default permissions for input artifacts and remain within [#14792].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely describes the main executor change and uses a conventional commit format.
Description check ✅ Passed The description covers the issue, motivation, modifications, verification, documentation, and AI use with relevant implementation details.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with 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.

❤️ Share

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

@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 `@workflow/executor/executor_test.go`:
- Around line 494-501: Update the file fixture near chmodDefault and the
external-target fixture to be created with mode 0600 instead of their expected
final modes. Keep the standalone-file assertion verifying chmodDefault changes
it to 0644, and assert the external target remains 0600 after processing so
no-op and symlink-following implementations fail.
🪄 Autofix

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 Plus

Run ID: f6306741-8285-48e9-a29a-045005a14984

📥 Commits

Reviewing files that changed from the base of the PR and between 47a7a08 and f58ca22.

📒 Files selected for processing (4)
  • docs/upgrading.md
  • docs/walk-through/artifacts.md
  • workflow/executor/executor.go
  • workflow/executor/executor_test.go

Comment on lines +494 to +501
artPath := filepath.Join(t.TempDir(), "myfile.txt")
require.NoError(t, os.WriteFile(artPath, []byte("test content\n"), 0o644))

require.NoError(t, chmodDefault(artPath))

info, err := os.Stat(artPath)
require.NoError(t, err)
assert.Equal(t, "-rw-r--r--", info.Mode().String())

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use non-default modes in these test fixtures.

Line 495 starts the file at its expected final mode. A no-op implementation passes this test.

Line 528 starts the external target at its expected final mode. An implementation that follows the symlink and chmods the target also passes this test.

Set both fixtures to 0600. Assert that the standalone file changes to 0644 and that the external target remains 0600.

Proposed test adjustment
- require.NoError(t, os.WriteFile(artPath, []byte("test content\n"), 0o644))
+ require.NoError(t, os.WriteFile(artPath, []byte("test content\n"), 0o600))
...
- require.NoError(t, os.WriteFile(outside, []byte("not part of the artifact\n"), 0o644))
+ require.NoError(t, os.WriteFile(outside, []byte("not part of the artifact\n"), 0o600))
...
- assert.Equal(t, "-rw-r--r--", info.Mode().String())
+ assert.Equal(t, "-rw-------", info.Mode().String())

Also applies to: 527-537

🤖 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 `@workflow/executor/executor_test.go` around lines 494 - 501, Update the file
fixture near chmodDefault and the external-target fixture to be created with
mode 0600 instead of their expected final modes. Keep the standalone-file
assertion verifying chmodDefault changes it to 0644, and assert the external
target remains 0600 after processing so no-op and symlink-following
implementations fail.

@anneheartrecord anneheartrecord changed the title fix(executor)!: give input artifacts without mode a default of 0600. Fixes #14792 fix(executor)!: give input artifacts without mode a default of 0644. Fixes #14792 Aug 18, 2026
@anneheartrecord

Copy link
Copy Markdown
Author

@Joibel I fixed the title as well — it still read 0600 while the code and the description say 0644/0755.

On the restrictive option: 0600/0700 is what I submitted first, but TestArtifactsSuite/TestOutputOnInputPlugin fails with it. An artifact-plugin sidecar reads the saved output through its own mount of the main container's filesystem and isn't guaranteed to run as the same user as the container that loaded the input artifact, so owner-only permissions turn that cross-container read into a permission denied. If you would rather keep 0600 for the ordinary load path, I can scope the default that way and leave the artifact-plugin path on its existing 0666. Otherwise this is out of draft with CI green and ready for another look.

@anneheartrecord

Copy link
Copy Markdown
Author

@Joibel any preference here? If the plugin-mount test constraint makes 0644 acceptable I'll leave it as is; if you'd rather keep 0600 for the ordinary load path with the plugin case scoped separately, I can rework it that way — small change either way.

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.

Mounted S3 artifact permissions are not consistent when disabling archivation

2 participants