Skip to content

feat(agent): make output file permissions configurable - #342

Open
alekc wants to merge 3 commits into
Infisical:mainfrom
alekc:feat/issue-339-agent-file-permissions
Open

feat(agent): make output file permissions configurable#342
alekc wants to merge 3 commits into
Infisical:mainfrom
alekc:feat/issue-339-agent-file-permissions

Conversation

@alekc

@alekc alekc commented Jul 30, 2026

Copy link
Copy Markdown

Description 📣

Fixes #339.

The agent writes rendered secret templates with whatever os.Create gives it, so on a normal umask they land 0644, and Template.Config carries only polling-interval and execute, so there is nothing to set. That makes the agent awkward to adopt anywhere it replaces something that wrote 0600.

The execute hook is not a workaround. A chmod 600 there is valid and runs under sh -c, but the hook is guarded:

if !firstRun && execCommand != "" {

So it is skipped on the first render and only fires once an etag change causes a re-render. The file is world-readable from the moment it is created until the first rotation.

This adds a permission field to the template config and to the file sink config, spelled to match the one certificates[].file-output.* already accepts:

sinks:
  - type: file
    config:
      path: /out/access-token
      permission: "0600"
templates:
  - source-path: /etc/infisical/dotenv.tmpl
    destination-path: /out/app/app.env
    config:
      polling-interval: 60s
      permission: "0600"

The sink is included because WriteTokenToFiles had the same hardcoded 0644 on content more valuable than a rendered template.

Implementation notes:

  • Defaults are unchanged. An unset permission keeps the umask-derived mode for templates and 0644 for sinks, so no existing deployment changes behaviour on upgrade. I did not default templates to 0600 even though the content argues for it, because the agent commonly runs as root while the consuming application reads the file as a different user, and tightening the default breaks those setups with no error from the agent. Glad to change it if you would rather have 0600.

  • Values are validated in parseAgentConfigWithMode, so a malformed mode stops the agent at startup rather than falling back to a looser one at first render, which would be invisible until somebody stats the file:

    templates[0].config.permission: invalid file permission "600x": expected an octal mode such as "0600"
    
  • The octal parser is promoted out of WriteCertificateFiles into a package-level parseFileMode, so there is one implementation rather than two. Two deliberate changes to certificate behaviour come with that: modes above 0777 are now rejected, where the previous strconv.ParseInt accepted them and would set setuid on a private key, and a malformed value now logs a warning before falling back to 0600 instead of being silently swallowed. Unset and valid values behave exactly as before.

No new dependencies. The agent config reference lives on infisical.com/docs rather than in this repo, so the new option is undocumented there; happy to open a docs PR if you point me at the right place.

Type ✨

  • Bug fix
  • New feature
  • Improvement
  • Breaking change
  • Documentation

Tests 🛠️

Added packages/cmd/agent_file_permission_test.go, 20 cases, tagged //go:build unix because file modes are a POSIX concept and os.Chmod on Windows only toggles the read-only bit. It covers octal parsing including every rejection case, WriteBytesToFile and WriteTemplateToFile against both a new and an already-existing file, and config parsing for valid, malformed and omitted values.

The already-existing-file cases are the ones that matter. They are umask-independent, so they isolate the chmod as the mechanism instead of passing by accident on whatever umask the runner has. The omitted-permission cases compare against a reference file created with plain os.Create in the same temp dir, which pins "default unchanged" to something stronger than a hardcoded 0644.

To reproduce:

go build ./...
go test -vet=off -count=1 ./packages/cmd/... -run 'TestParseFileMode|TestWriteBytesToFile|TestWriteTemplateToFile|TestParseAgentConfig' -v
gofmt -l packages/cmd/agent.go packages/cmd/agent_file_permission_test.go

-vet=off is needed because go vet ./packages/cmd/ already fails on main with four non-constant format string findings in run.go, unrelated to this change. I left them alone, and confirmed this change adds none by diffing vet output with and without the patch. Happy to fix those separately if useful.

Two things I want to be straight about. The create-with-mode behaviour above is not unit tested and I do not think it can be: once the call returns, create-with-mode and create-then-chmod are indistinguishable by stat, and observing the intermediate state means racing the writer, so that property rests on the open flags. And I have not run the agent against a live instance, so the verification here is the unit tests plus a build; TestWriteTemplateToFileAppliesConfiguredPermission drives the same WriteTemplateToFile path the agent uses per render, but not the surrounding fetch-and-render loop. I can add an e2e/agent case if you want one, though that suite needs a composed instance and currently only covers certificates.


Rendered secret templates were written with a bare os.Create, so they landed 0644 under a
normal umask with no way to change it. The execute hook is not a workaround for this: it is
guarded by `if !firstRun`, so a chmod there is skipped on the very first render and the file
stays world-readable until an etag change forces a re-render.

Add a `permission` field to the template config and to the file sink config, spelled to match
the one certificates already accept. The access token sink had the same hardcoded 0644 on
higher-value content, so it is covered too.

Values are validated while the agent config is parsed, so a typo aborts startup instead of
silently falling back to a looser mode at the first render.

Defaults are unchanged. An unset permission keeps the umask-derived mode for templates and
0644 for sinks, so no existing deployment changes behaviour on upgrade.

Files are opened with the target mode rather than created at 0666 and chmod'ed afterwards,
which would leave a window where the path is world-readable. Permissions are checked at
open(2), so a descriptor obtained during that window would survive the chmod. The chmod is
still applied, to restore bits the umask stripped and to tighten a file that already existed.

The octal parser is promoted out of WriteCertificateFiles so there is one implementation
rather than two. It now rejects modes above 0777, which the certificate-only version accepted,
and logs a warning on a malformed value instead of silently falling back to 0600.

Fixes: Infisical#339
Signed-off-by: Alexander Chernov <alexander@chernov.it>
@infisical-cla-app

infisical-cla-app Bot commented Jul 30, 2026

Copy link
Copy Markdown

CLA satisfied. All contributors have signed the current CLA. The cla/signed check is passing.

Comment thread packages/cmd/agent.go Outdated
@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds configurable octal permissions for agent token sinks and rendered templates while preserving existing defaults.

  • Validates configured modes during agent startup.
  • Applies permissions before truncating existing output files.
  • Reuses the mode parser for certificate outputs.
  • Adds Unix-focused tests for parsing, writes, failure handling, and configuration loading.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/cmd/agent.go Adds validated file-mode configuration and safely applies it before truncating and writing agent outputs.
packages/cmd/agent_file_permission_test.go Covers mode parsing, existing-file handling, chmod failures, unchanged defaults, and config validation.
agent-config.yaml Demonstrates optional 0600 permissions for token sinks and rendered templates.

Reviews (2): Last reviewed commit: "slight change to the documentation comme..." | Re-trigger Greptile

alekc added 2 commits July 30, 2026 20:49
Truncating before the chmod destroyed the old content when the destination was writable but not
owned by the agent, so the file is now truncated only once the mode is known to be correct.

Refs: Infisical#342
Signed-off-by: Alexander Chernov <alexander@chernov.it>
Signed-off-by: Alexander Chernov <alexander@chernov.it>
@alekc

alekc commented Aug 2, 2026

Copy link
Copy Markdown
Author

@greptile the issue has been solved, update summary

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.

Agent renders secret templates world-readable (0644) with no way to set the mode

1 participant