Skip to content

feat(age): support age encrypted env vars in mise.toml files#6463

Merged
jdx merged 38 commits intomainfrom
feat/age-encryption-experimental
Sep 28, 2025
Merged

feat(age): support age encrypted env vars in mise.toml files#6463
jdx merged 38 commits intomainfrom
feat/age-encryption-experimental

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Sep 28, 2025

Summary

Adds the foundational support for encrypting environment variables using age encryption. This is the first PR in a series that will implement full age encryption support for mise set and environment variable resolution.

Features

  • 🔐 New agecrypt module for age-based encryption/decryption
  • 🔑 Support for both x25519 and SSH recipients
  • 🔍 Automatic identity discovery from multiple sources
  • 📦 Storage format: age64:zstd:v1:<base64> for encrypted values
  • ⚙️ Settings for configuring age identity files and strict mode

Implementation Details

  • Added age crate dependency with SSH support
  • Created comprehensive encryption/decryption helpers in src/task/agecrypt.rs
  • Integrated with existing settings system
  • Compatible with SOPS age.txt file location for easy migration
  • Includes zstd compression for efficient storage

Settings

New settings.age configuration (all marked [experimental]):

  • identity_files: List of age identity files for decryption
  • key_file: Primary age private key file (defaults to ~/.config/mise/age.txt)
  • ssh_identity_files: List of SSH identity files for decryption
  • strict: Whether to fail on decryption errors (default false)

Default Key Discovery

When no explicit recipients are provided, the system will try:

  1. settings.age.key_file (if configured)
  2. ~/.config/mise/age.txt (SOPS-compatible fallback)
  3. Default SSH keys: ~/.ssh/id_ed25519, ~/.ssh/id_rsa

Tests

✅ Unit tests included for:

  • x25519 round-trip encryption/decryption
  • Age prefix detection (age64:zstd:v1:)
  • Recipient parsing (both age and SSH formats)

Next Steps (Future PRs)

  • Add CLI flags to mise set command (--age-encrypt, --age-recipient, etc.)
  • Implement encryption logic in mise set
  • Integrate decryption into env resolution pipeline
  • Add E2E tests
  • Documentation updates

Notes

  • All functionality is marked as [experimental]
  • The module is currently not connected to any commands (no dead code warnings are expected)
  • This PR establishes the foundation - actual user-facing features will come in follow-up PRs

🤖 Generated with Claude Code


Note

Adds experimental age encryption for env vars (inline in mise.toml) with decryption in env resolution, new CLI set flags, settings, schemas, docs, and tests.

  • Core/Config:
    • Introduce src/agecrypt.rs (age encrypt/decrypt, x25519/SSH recipients, zstd compression, identity discovery).
    • Add EnvDirective::Age with parsing/templating; default redaction; resolve-time decryption; make EnvDirectiveOptions.redact optional.
    • Extend mise_toml to write age directives; update snapshots.
    • Wire module in src/main.rs.
  • CLI:
    • Enhance mise set with --prompt, --age-encrypt, --age-recipient, --age-ssh-recipient, --age-key-file; decrypt when reading values; support age writes.
    • Update usage spec and Fig completions.
  • Settings/Schemas:
    • Add [settings.age] (identity_files, key_file, ssh_identity_files).
    • Extend schema/mise*.json and mise-task.json to support env age forms and new settings.
  • Docs:
    • Restructure Secrets docs; add pages for direct age and sops; update nav and set CLI docs; fix secrets links.
  • Tests:
    • Add E2E test_env_age_encryption and unit tests for age round-trips/recipient parsing.
  • Deps:
    • Add age crate (with SSH), related crypto deps; list age in Cargo.toml and Cargo.lock.

Written by Cursor Bugbot for commit ec92504. This will update automatically on new commits. Configure here.

Adds the foundational support for encrypting environment variables using age encryption.
This is marked as experimental and provides the core encryption/decryption functionality
that will be integrated into `mise set` and env resolution.

## Key Features
- New `agecrypt` module for age-based encryption/decryption
- Support for both x25519 and SSH recipients
- Automatic identity discovery from multiple sources
- Storage format: `age64:zstd:v1:<base64>` for encrypted values
- Settings for configuring age identity files and strict mode

## Implementation Details
- Added age crate dependency with SSH support
- Created comprehensive encryption/decryption helpers
- Integrated with existing settings system
- Compatible with SOPS age.txt file location

## Settings
New `settings.age` configuration:
- `identity_files`: List of age identity files for decryption
- `key_file`: Primary age private key file (defaults to ~/.config/mise/age.txt)
- `ssh_identity_files`: List of SSH identity files for decryption
- `strict`: Whether to fail on decryption errors (default false)

## Tests
- Unit tests for x25519 round-trip encryption/decryption
- Tests for prefix detection and recipient parsing
- All tests passing

Next steps will integrate this into the CLI commands and env resolution pipeline.

[experimental]

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 28, 2025 15:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Adds foundational support for age encryption to encrypt environment variables, introducing the core encryption/decryption infrastructure without exposing user-facing commands yet. This establishes the groundwork for future PRs that will integrate age encryption into mise set and environment variable resolution.

  • Implements complete age encryption/decryption workflow with x25519 and SSH recipient support
  • Adds configuration settings for age identity management and encryption behavior
  • Creates SOPS-compatible storage format and automatic key discovery mechanism

Reviewed Changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/task/mod.rs Adds agecrypt module declaration
src/task/agecrypt.rs Core age encryption/decryption implementation with identity loading and recipient parsing
settings.toml Defines experimental age configuration settings for identity files and encryption behavior
schema/mise.json Updates JSON schema to include age configuration properties
Cargo.toml Adds age crate dependency with SSH support and related utilities

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread src/agecrypt.rs Outdated
Comment on lines +69 to +71
debug!(
"[experimental] No age identities found, returning ciphertext in non-strict mode"
);
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

The debug! macro is used without importing it. Add use log::debug; or use tracing::debug; at the top of the file, or use the appropriate logging macro from the crate's logging framework.

Copilot uses AI. Check for mistakes.
Comment thread src/agecrypt.rs Outdated
if Settings::get().age.strict {
return Err(eyre!("[experimental] Failed to decrypt: {}", e));
} else {
debug!("[experimental] Failed to decrypt in non-strict mode: {}", e);
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

The debug! macro is used without importing it. Add use log::debug; or use tracing::debug; at the top of the file, or use the appropriate logging macro from the crate's logging framework.

Copilot uses AI. Check for mistakes.
Comment thread src/agecrypt.rs
Comment on lines +249 to +252
debug!(
"[experimental] Failed to read identity file {:?}: {}",
path, e
);
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

The debug! macro is used without importing it. Add use log::debug; or use tracing::debug; at the top of the file, or use the appropriate logging macro from the crate's logging framework.

Copilot uses AI. Check for mistakes.
Comment thread src/agecrypt.rs
Comment on lines +271 to +274
debug!(
"[experimental] Failed to parse SSH identity from {:?}: {}",
path, e
);
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

The debug! macro is used without importing it. Add use log::debug; or use tracing::debug; at the top of the file, or use the appropriate logging macro from the crate's logging framework.

Copilot uses AI. Check for mistakes.
Comment thread src/agecrypt.rs
Comment on lines +279 to +282
debug!(
"[experimental] Failed to read SSH identity file {:?}: {}",
path, e
);
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

The debug! macro is used without importing it. Add use log::debug; or use tracing::debug; at the top of the file, or use the appropriate logging macro from the crate's logging framework.

Copilot uses AI. Check for mistakes.
cursor[bot]

This comment was marked as outdated.

@jdx jdx changed the title feat(env): add experimental age encryption support foundation feat(age): support age encrypted env vars in mise.toml files Sep 28, 2025
jdx and others added 3 commits September 28, 2025 10:34
- Add --age-encrypt and related flags to mise set command
- Implement encryption in mise set with recipient collection
- Add decryption to env resolution pipeline
- Fix Send trait issues with Identity types
- Support MISE_AGE_KEY for raw secret keys
- Ensure encrypted values are auto-redacted

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Use age64:v1: prefix for uncompressed values (<1KB)
- Use age64:zstd:v1: prefix for compressed values (>1KB)
- Add separate tests for small and large value encryption
- Support decryption of both formats

This reduces overhead for small secrets while still providing
compression benefits for larger values.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
cursor[bot]

This comment was marked as outdated.

autofix-ci Bot and others added 3 commits September 28, 2025 15:54
- Add comprehensive E2E tests for age encryption functionality
- Test small vs large value compression behavior
- Test multiple recipients, SSH keys, and key files
- Add extensive documentation in environments/secrets.md
- Update CLI help text with age encryption examples
- Document differences from sops and use cases

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
The age.identity_files and age.ssh_identity_files paths were not being
expanded with replace_path() for tilde expansion and environment variables,
unlike age.key_file. This inconsistency meant identity files specified
with unexpanded paths (e.g., ~/age.txt or $HOME/age.txt) would not be found.

Now all age identity file paths are consistently expanded using replace_path().

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
cursor[bot]

This comment was marked as outdated.

jdx and others added 2 commits September 28, 2025 11:09
…mpt support

- Move recipient collection outside encryption loop to avoid repeated I/O
- Change encrypt_value to accept &[Recipients] instead of taking ownership
- Add --prompt flag for interactive environment variable input
- Mask prompted input when --age-encrypt is used for security
- Simplify age encryption examples in help text

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
cursor[bot]

This comment was marked as outdated.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Sep 28, 2025

Hyperfine Performance

mise x -- echo

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2025.9.22 x -- echo 21.2 ± 0.4 20.4 23.2 1.00 ± 0.03
mise x -- echo 21.2 ± 0.4 20.4 25.2 1.00

mise env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2025.9.22 env 20.4 ± 0.5 19.7 25.4 1.00
mise env 20.7 ± 0.4 19.8 22.8 1.01 ± 0.03

mise hook-env

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2025.9.22 hook-env 20.1 ± 0.4 19.3 22.3 1.00
mise hook-env 20.3 ± 0.5 19.4 26.5 1.01 ± 0.03

mise ls

Command Mean [ms] Min [ms] Max [ms] Relative
mise-2025.9.22 ls 18.1 ± 0.4 17.4 22.5 1.00
mise ls 18.5 ± 0.4 17.6 20.2 1.02 ± 0.03

xtasks/test/perf

Command mise-2025.9.22 mise Variance
install (cached) 174ms ✅ 109ms +59%
ls (cached) 66ms 66ms +0%
bin-paths (cached) 73ms 73ms +0%
task-ls (cached) 502ms 499ms +0%

✅ Performance improvement: install cached is 59%

jdx and others added 10 commits September 28, 2025 11:35
Changes the storage format from `FOO="age64:zstd:v1:<base64>"` to
`FOO={age = {value = "<base64>", format = "zstd"}}` for better structure and extensibility.

## Key Changes
- **New TOML format**: Use inline tables instead of prefixed strings
- **Age directive**: Added `EnvDirective::Age` variant for typed handling
- **Format enum**: `AgeFormat::Raw` (default) and `AgeFormat::Zstd` for compression
- **Improved decryption**: Direct detection of age directives vs string parsing
- **Module reorganization**: Moved agecrypt from `src/task/` to `src/agecrypt.rs`

## TOML Format Examples
```toml
[env]
# Small value (uncompressed)
API_KEY = { age = { value = "YWdlLWVuY3J5cHRpb24...", format = "raw" } }

# Large value (compressed)
DATABASE_URL = { age = { value = "KLUv/QBYuS4A...", format = "zstd" } }
```

## Implementation Details
- Added `update_env_age()` method for TOML serialization
- Updated CLI logic to use `create_age_directive()`
- Fixed decryption in `mise set KEY` commands
- Maintained backward compatibility with old string format
- Added proper error handling and fallback behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Enhances e2e tests to verify age-encrypted environment variables work correctly with mise env in addition to mise set.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Age encryption should always fail when decryption keys are unavailable for better security.

- Remove `age.strict` setting from settings.toml
- Remove all strict mode conditionals from agecrypt.rs
- Always return errors when no identities found or decryption fails
- Update e2e tests to expect strict behavior (failures instead of fallbacks)

- 🔒 **More secure**: No silent fallbacks to showing encrypted values
- 🎯 **Simpler**: One behavior path instead of two modes
- 🧪 **Experimental**: Strict behavior is appropriate for experimental features
- ⚡ **Fail-fast**: Immediate feedback when keys are missing

The age encryption feature now consistently fails when keys are unavailable,
providing clear error messages instead of potentially exposing encrypted data.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix clippy manual_strip warnings by using strip_prefix()
- Remove unused encrypt_value() function (now using create_age_directive)
- Clean up code formatting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add Settings.ensure_experimental() checks for age encryption in CLI and env directive processing
- Fix test compilation errors by updating to use new create_age_directive/decrypt_age_directive APIs
- Ensure age encryption is properly gated behind experimental setting
- Apply formatting fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add MISE_EXPERIMENTAL=true environment variable to all mise commands in the
age encryption e2e test to ensure experimental age encryption functionality
is properly enabled during testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
cursor[bot]

This comment was marked as outdated.

- Add Deserialize derive to EnvDirective enum to enable reading age-encrypted values from TOML
- Move experimental check from directive processing to actual decryption for better UX
- Remove unused Settings import to fix compiler warning

This fixes the TOML parsing issue where age-encrypted values couldn't be read back
from config files. The experimental check is now more targeted - it only triggers
when actually decrypting age values, not when simply loading config that contains them.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@jdx jdx force-pushed the feat/age-encryption-experimental branch from d7dd901 to 8092f0a Compare September 28, 2025 17:50
Fixes inconsistent handling of age-encrypted value decryption failures
between EnvDirective::Val and EnvDirective::Age. Both directive types
now fail consistently when decryption fails, rather than having
different fallback behaviors.

Changes:
- EnvDirective::Val: Remove graceful fallback, fail on decryption error
- EnvDirective::Age: Remove graceful fallback, fail on decryption error
- mise set: Fix logic to properly detect local config files and fail
  on decryption errors when explicitly requesting variable values
- mise set: Use decrypt_age_directive for Age directives instead of
  decrypt_value to handle the correct data format

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
cursor[bot]

This comment was marked as outdated.

jdx and others added 10 commits September 28, 2025 13:29
…sing

Fixes inconsistent handling of age-encrypted value decryption failures between EnvDirective::Val and EnvDirective::Age directive types.

## Key Issues Fixed

### 1. TOML Parsing for Age Directives
- Fixed serde deserialization conflict in Val enum where Age variant wasn't being parsed correctly
- Reordered enum variants to prioritize Age parsing and removed conflicting flatten annotations
- Resolves "data did not match any variant of untagged enum Val" errors

### 2. Config Path Detection in mise set
- Fixed config path detection logic to properly find local config files during e2e tests
- Added explicit checks for mise.toml and .mise.toml in current directory
- Ensures mise set can find age-encrypted values in local config files

### 3. Decryption Error Consistency
- Both EnvDirective::Val and EnvDirective::Age now fail consistently when decryption fails
- Removed graceful fallback behavior that was causing inconsistent runtime behavior
- Ensures predictable error handling across directive types

## Test Results
- E2E test now passes most assertions (previously 100% failure rate)
- mise set command works correctly with age-encrypted values
- mise env command works with single age-encrypted values
- Both raw and zstd compression formats working correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…mand

Fixes two bugs in the environment variable retrieval logic:

1. **Double iteration bug**: The get() method was calling config.env_entries()
   twice when retrieving age-encrypted values, causing unnecessary performance
   overhead. Fixed by capturing env_entries once and reusing it.

2. **Age format mismatch**: When retrieving EnvDirective::Age values, the code
   was incorrectly using agecrypt::decrypt_value() which expects prefixed format
   (age64:zstd:v1:) on raw base64 ciphertext, causing "age encryption prefix not
   found" errors. Fixed by using agecrypt::decrypt_age_directive() for
   EnvDirective::Age and agecrypt::decrypt_value() only for old prefixed formats.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixes test failure in multi-recipient age encryption by creating a clean
environment for testing the second recipient key. Previously the test was
mixing single-recipient and multi-recipient variables in the same environment,
causing the second key to fail when trying to decrypt variables that were
encrypted with only the first key.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add support for simplified age format: `{age = "value"}` vs `{age = {value = "...", format = "zstd"}}`
- Omit `format = "raw"` when it's the default format
- Use simplified format when only value is provided
- Enhanced TOML parsing with AgeSimple/AgeComplex variants to handle both formats
- Updated JSON schema to support both age format variants
- Added comprehensive e2e test for age format consistency and redaction
- Fixed documentation dead links by adding trailing slashes
- Verified age-encrypted values are properly redacted in task execution

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Resolved conflict in src/cli/set.rs by keeping age imports and adding new ConfigPathOptions/resolve_target_config_path imports
- Resolved conflict in e2e/cli/test_set_use_consistency by keeping age encryption tests
- Fixed unused import and formatting issues
- Make EnvDirectiveOptions.redact nullable to distinguish explicit vs default values
- Age-encrypted values now default to redacted for security unless explicitly set to redact = false
- Reorder TOML parsing variants so AgeWithOptions matches before AgeSimple
- Add comprehensive e2e test for age encryption redaction behavior
- Support both simple age format {age = "value"} and options format {age = "value", redact = false}
- Ensure proper security-by-default while allowing explicit non-redaction when needed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ield

- Update test snapshots to reflect Option<bool> type for redact field
- Fix venv.rs compilation errors by changing redact: false to redact: Some(false)
- All unit tests now passing with no clippy warnings
cursor[bot]

This comment was marked as outdated.

Replace direct age-keygen calls with 'mise x age -- age-keygen' to ensure
age is available in CI environments where it may not be installed globally
cursor[bot]

This comment was marked as outdated.

jdx added 2 commits September 28, 2025 15:28
- Use 'mise x age' instead of direct age-keygen to ensure age is available in CI
- Work around circular dependency when mise.toml contains encrypted values
  by temporarily moving config file when generating second age key
- Remove redundant nested match in EnvDirective::Age resolution logic
  that contained unreachable dead code
…pted values

Previously, when age decryption failed, the code would fall back to displaying
the encrypted value, which is a security issue. Now all decryption failures
properly return an error with bail!() to prevent exposing encrypted data.
cursor[bot]

This comment was marked as outdated.

jdx added 3 commits September 28, 2025 15:43
- Refactor get() method to use config.env_with_sources() for global config
  which already handles all decryption centrally
- Extract decrypt_value_if_needed() helper to reduce duplication in local
  config handling
- Remove manual decryption logic from run() method, use Config's centralized
  env loading instead
- Clean up unused imports and variables
- Removed support for deprecated age64:* string encryption format
- Removed is_age_encrypted() and decrypt_value() functions from agecrypt.rs
- Removed AgeSimple TOML variant, consolidated to AgeWithOptions
- Simplified age format now uses AgeWithOptions with default options
- All age directives now consistently use the same parsing path

This simplifies the codebase by removing legacy support for the old
format while maintaining backward compatibility for the simplified
{age = "value"} syntax through AgeWithOptions with default options.
Resolved conflict in mise.lock by keeping both linux-x64 and macos-arm64
platforms for the bun tool.
@jdx jdx merged commit 57a8355 into main Sep 28, 2025
26 checks passed
@jdx jdx deleted the feat/age-encryption-experimental branch September 28, 2025 22:00
@jdx jdx mentioned this pull request Sep 28, 2025
jdx added a commit that referenced this pull request Sep 29, 2025
### 📦 Registry

- add ggshield by @TyceHerrman in
[#6435](#6435)
- add jaq by @TyceHerrman in
[#6434](#6434)

### 🚀 Features

- **(age)** support age encrypted env vars in mise.toml files by @jdx in
[#6463](#6463)

### 🐛 Bug Fixes

- **(vfox)** integrate `parse_legacy_file` into backend by @malept in
[#6471](#6471)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Bumps to 2025.9.24 with age‑encrypted env var support, registry
additions (ggshield, jaq, patterner), vfox fix, and lockfile/tooling
updates.
> 
> - **Release**
> - Bump version to `2025.9.24` across `Cargo.toml`, `Cargo.lock`,
`default.nix`, `packaging/rpm/mise.spec`, `README.md`, and shell
completions; update `CHANGELOG.md`.
> - **Features**
>   - Support age‑encrypted env vars in `mise.toml`.
> - **Registry**
> - Add `ggshield`, `jaq` entries; add `tailor-platform/patterner` in
`crates/aqua-registry/.../patterner/registry.yaml`.
> - **Bug Fixes**
>   - `(vfox)` integrate `parse_legacy_file` into backend.
> - **Dependencies/Tooling**
> - Update `mise.lock`: `usage-cli` → 2.3.2, `hk` → 1.15.7, `sops` →
3.11.0.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
0b0708b. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: mise-en-dev <release@mise.jdx.dev>
jdx pushed a commit that referenced this pull request Apr 11, 2026
## Summary
- add missing task sandbox fields to the mise task schemas
- add top-level `env_file`/`dotenv`/`env_path` schema entries and mark
them deprecated as legacy shortcuts
- allow env age directive options, tighten complex age option nesting,
and cover the schema fixture
- keep sandbox fields in a task-only schema overlay so they validate for
`[tasks.*]` but do not leak into `[task_templates.*]`, whose Rust type
does not deserialize/apply them

## Context
- The renderer builds both task and `task_template` schemas from
`task_props`. The new `taskOnlyProps` overlay is for fields accepted by
`Task` but not `TaskTemplate`; this mirrors the existing task-only
treatment for `extends`.
- `env_file`, `dotenv`, and `env_path` are still accepted by current
serde parsing, but they are legacy top-level shortcuts.
#1361 marked `env_file`/`env_path`
deprecated in favor of `env.mise.file`/`env.mise.path`;
#1519 later rewrote env parsing and kept
accepting `env_file`, alias `dotenv`, and `env_path` without a runtime
deprecation warning. The schema keeps them valid but marks them
deprecated to point users at `[env] _.file` / `_.path`.
- Task sandbox config fields were introduced with process sandboxing in
#8845; `allow_env` wildcard semantics
were later expanded in #8974.
- Age env directives, including flattened `EnvDirectiveOptions` on age
values, were introduced in #6463. This
PR now mirrors the Rust variants more closely: top-level age options are
allowed with `age = "..."`, while complex `age = { value = ... }`
options must be nested inside the `age` object.

## Verification
- `bun xtasks/render/schema.ts`
- `jq empty schema/mise.json schema/mise-task.json schema/miserc.json`
- `git diff --check`
- `mise run test:e2e e2e/config/test_schema_tombi`
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