Skip to content

feat(components): protect .env files in the operations API#1527

Merged
kriszyp merged 5 commits into
mainfrom
feat/protect-env-files
Jul 1, 2026
Merged

feat(components): protect .env files in the operations API#1527
kriszyp merged 5 commits into
mainfrom
feat/protect-env-files

Conversation

@dawsontoth

@dawsontoth dawsontoth commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What & why

Component .env files are exposed verbatim through the operations API: get_component_file returns the raw bytes (secrets and all) and get_components lists them, so anyone browsing a component in the editor/Studio can read every secret value.

This adds editor-facing, smoke-and-mirrors protection on the read path, plus merge-preserving write operations — enough to prevent accidental disclosure, not a real security boundary (you can still console.log(process.env) at runtime). The runtime loader resources/loadEnv.ts is deliberately untouched, so config.yaml/components still load the real values.

New operations (all super_user)

Operation Behavior
get_env_keys Returns key names only ({ file, keys, size, mtime }), never values
set_env_value Upsert one (key+value) or many (values:{}) — merges in place, preserving other keys, comments and formatting; creates the file if absent
delete_env_value Remove one (key) or many (keys:[]), leaving the rest intact

Modified operations

  • get_component_file on a protected .env{ protected: true, keys, message } where message is a value-free KEY=******** rendering instead of raw secrets.
  • get_components → tags protected .env entries with protected: true, so clients can detect them.

set_component_file is intentionally not restricted: it writes .env files verbatim like any other component file. A client (the Studio UI) detects a protected .env via the protected flag / get_env_keys and decides how to edit — the merge-preserving set_env_value, or a full set_component_file write if the user wants it. (An earlier revision hard-blocked set_component_file on .env; that block was removed — see the "allow set_component_file to write .env files" commit.)

Notes / decisions

  • Read masking, not write blocking. Protection is on display (get_component_file / get_components never reveal values) and on the convenience of safe edits (set_env_value / delete_env_value). Whole-file writes stay open; clients decide.
  • Template files (.env.example, .env.sample, .env.template, case-insensitive) are not secret and are exempted from masking/flagging — read verbatim.
  • .env matching is case-insensitive so a stray .ENV isn't masked-around (favors over-protection on case-insensitive filesystems).
  • Core parsing/serialisation lives in a new pure module utility/envFile.ts, built around dotenv 16's exact parse semantics so written values round-trip and reported keys match what the runtime loads. Values that need it are quoted (a value containing both ' and " is unrepresentable via dotenv round-trip and is rejected rather than silently corrupted).
  • Env-key charset locked to [\w.-] to prevent key injection (e.g. a key with a newline writing extra assignments).
  • Not exposed via MCP by default — the new get_* / set_* names fall outside the default read-only MCP allow-list, matching the existing posture for get_component_file.

Tests

Plain-assert suites (no sinon/rewire):

  • unitTests/utility/envFile.test.js — pure parse / mask / upsert / remove logic + round-trips through dotenv.parse (proves the runtime still reads the real value)
  • unitTests/components/envOperationsValidation.test.js — validator shape, xor logic, key-injection rejection
  • unitTests/components/envOperations.test.js — handlers end-to-end against a temp components root (masking, merge-preserving set / delete, template pass-through, and set_component_file writing a .env verbatim)

oxlint, prettier, and tsc pass; the env suites (65 tests) are green.

🤖 Generated with Claude Code


Related PRs — env-secret encryption

One enc:v1: client contract across tiers (public key + envelope; values encrypted before they leave the client):

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces new operations (get_env_keys, set_env_value, and delete_env_value) to securely manage .env files, including value masking for protected environment files and validation schemas. The reviewer identified three critical issues: first, the setEnvValue and deleteEnvValue operations in operation_authorization.ts are missing their API name mapping, which will block them for users with restricted operation allowlists; second, getEnvKeysValidator lacks project name validation, exposing a potential path traversal vulnerability; and third, sensitive secrets from set_env_value requests are at risk of being leaked in plain text within the operation logs because value and values are not excluded from logging.

Comment thread utility/operation_authorization.ts Outdated
Comment thread components/operationsValidation.js
Comment thread server/serverHelpers/serverUtilities.ts
Comment thread utility/envFile.ts Outdated
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

Comment thread utility/envFile.ts Outdated
Comment thread utility/envFile.ts

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are we going to add the ability to manage env values through Fabric (bypassing op API for security) and an op API mechanism?

@dawsontoth

Copy link
Copy Markdown
Contributor Author

Two questions:

  1. What do you think about the shape of the operations?
  2. Do we want this feature for non-Fabric clusters too?

@dawsontoth

Copy link
Copy Markdown
Contributor Author

@kriszyp I'd also say that, since we already support .env files as a part of components, this PR is a good enhancement for the platform. It protects the files a bit, and lets us build out a nice UX that will feel good for people. That same web-side component can drive what you're talking about with secrets set at a higher level that have real protections. So we could abandon #1528 if that's not the direction you want us to go, and proceed with this PR.

@dawsontoth dawsontoth marked this pull request as ready for review June 30, 2026 16:39
@dawsontoth

Copy link
Copy Markdown
Contributor Author

@kriszyp I removed the set_component_file protections, I can draft a Studio PR for this too so we can visualize it better if you'd like.

dawsontoth and others added 5 commits June 30, 2026 15:03
Add editor-facing protection for component .env files so secrets aren't
accidentally revealed or clobbered through the operations API, while the
runtime (resources/loadEnv.ts, untouched) still loads the real values.

New operations (all super_user):
- get_env_keys: returns key names only, never values
- set_env_value: upsert one (key+value) or many (values{}), merge-preserving
- delete_env_value: remove one (key) or many (keys[]), merge-preserving

Modified operations:
- get_component_file: returns a masked, value-free body + key list for a
  protected .env file
- set_component_file: refuses protected .env files (would clobber other
  secrets / write back masked placeholders); points at set_env_value
- get_components: flags protected .env entries with protected: true

Template files (.env.example/.sample/.template, case-insensitive) are not
secret and are exempted from all protection. .env matching is
case-insensitive so a stray .ENV can't slip through unprotected.

Core parsing/serialisation lives in a new pure utility/envFile.ts, built
around dotenv 16's exact parse semantics so written values round-trip and
reported keys match what the runtime loads. Env-key charset is locked to
[\w.-] to prevent key injection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- operation_authorization: register set_env_value / delete_env_value with
  their snake_case api_name so roles with an `operations` allowlist can
  actually permit them (otherwise opApiName resolved to the camelCase
  function name and was always denied)
- operationsValidation: validate get_env_keys `project` against
  PROJECT_FILE_NAME_REGEX to prevent `..` path traversal when joined to the
  components root in resolveEnvFilePath
- serverUtilities: exclude `value`/`values` from the operation-log body so
  secrets passed to set_env_value aren't written to logs in plain text

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dotenv treats single-quoted values literally, so a value ending in `\`
(e.g. a Windows path `'C:\Users\name\'`) closes at that quote. closesQuote
was skipping the char after a backslash for all quote types, so it missed
the closing quote of such a value and mis-read following keys as
continuation lines — corrupting them in upsertEnvValues/removeEnvKeys.
Restrict backslash-escaping to double/backtick quotes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dotenv v16 does no backslash expansion inside backtick-quoted values (same
as single quotes), so a backtick value ending in `\` closes at that quote.
Exclude backticks (not just single quotes) from backslash-escaping in
closesQuote so following keys aren't mis-read as continuation lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the set_component_file block on .env files. We aren't worried about
clobbering: the Studio UI (the one client) will detect the protected-file
mechanism (the `protected` flag, plus get_env_keys / masked get_component_file)
and handle .env editing, while power users can still write .env files directly
via set_component_file if they choose.

Read-side protection is unchanged: get_component_file still masks values and
get_components still flags `.env` entries `protected: true`, so clients can
detect them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dawsontoth dawsontoth force-pushed the feat/protect-env-files branch from ff86a50 to adf8731 Compare June 30, 2026 19:04
@kriszyp

kriszyp commented Jul 1, 2026

Copy link
Copy Markdown
Member

What do you think about the shape of the operations?

This looks good.

Do we want this feature for non-Fabric clusters too?

Yes, I think that makes sense.

So we could abandon #1528 if that's not the direction you want us to go, and proceed with this PR.

Why? I really liked #1528 and am already trying to build other PRs on it (using its security mechanism), why close it?

@kriszyp kriszyp added this to the v5.2 milestone Jul 1, 2026

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks good. Note, that I think this should go in 5.2 (and I assigned that milestone).

@kriszyp kriszyp merged commit 793468a into main Jul 1, 2026
47 checks passed
@kriszyp kriszyp deleted the feat/protect-env-files branch July 1, 2026 23:01
@kriszyp kriszyp restored the feat/protect-env-files branch July 1, 2026 23:02
@kriszyp kriszyp deleted the feat/protect-env-files branch July 1, 2026 23:03
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.

2 participants