Skip to content

Add SIP for environment variable isolation#3400

Open
tschneidereit wants to merge 1 commit intospinframework:mainfrom
tschneidereit:env-isolator-sip
Open

Add SIP for environment variable isolation#3400
tschneidereit wants to merge 1 commit intospinframework:mainfrom
tschneidereit:env-isolator-sip

Conversation

@tschneidereit
Copy link
Contributor

@tschneidereit tschneidereit commented Feb 16, 2026

This SIP proposes a new component isolation mechanism for environment variables, allowing components to have their own isolated view of the environment. This is achieved by synthesizing an isolator component applying filtering to expose only environment variables with a specified prefix to each component in a composition.

Rendered

tschneidereit added a commit to tschneidereit/spin that referenced this pull request Feb 16, 2026
This implements the SIP introduced in spinframework#3400.

Note: I made extensive use of GitHub Copilot in implementing this, but made sure that I fully understand the entire implementation. Additionally, I ensured that everything is thoroughly documented and tested, since some of the implementation is very subtle and low-level.

In particular, the isolation itself is implemented by synthesizing a new component containing two, also synthesized, core modules, plus a wrapper component for each target component. This code makes substantial use of the `wasm-encoder` crate to generate the necessary WebAssembly components and modules, and the `wasmparser` crate to validate the generated components.

By comparison, the integration into Spin is delightfully simple: manifest loading and composition are the only two pieces that needed updating, with everything else not needing to have any awareness of the isolation at all.
tschneidereit added a commit to tschneidereit/spin that referenced this pull request Feb 16, 2026
This implements the SIP introduced in spinframework#3400.

Note: I made extensive use of GitHub Copilot in implementing this, but made sure that I fully understand the entire implementation. Additionally, I ensured that everything is thoroughly documented and tested, since some of the implementation is very subtle and low-level.

In particular, the isolation itself is implemented by synthesizing a new component containing two, also synthesized, core modules, plus a wrapper component for each target component. This code makes substantial use of the `wasm-encoder` crate to generate the necessary WebAssembly components and modules, and the `wasmparser` crate to validate the generated components.

By comparison, the integration into Spin is delightfully simple: manifest loading and composition are the only two pieces that needed updating, with everything else not needing to have any awareness of the isolation at all.

Signed-off-by: Till Schneidereit <till@tillschneidereit.net>
This SIP proposes a new component isolation mechanism for environment variables, allowing components to have their own isolated view of the environment. This is achieved by synthesizing an isolator component applying filtering to expose only environment variables with a specified prefix to each component in a composition.

Signed-off-by: Till Schneidereit <till@tillschneidereit.net>
@tschneidereit
Copy link
Contributor Author

Oh, one very important thing I forgot to mention: I'm not entirely sure myself whether this should be accepted as-is. Perhaps the better thing to do would be to address component isolation more comprehensively: instead of doing env vars now, and something else later, we'd make one big change applying isolation to everything where it makes sense. That'd include things like Spin's own variables interface, outbound allow lists for sockets & http, kv store buckets, db interfaces, etc.

The downside is that that is quite substantially more work, and requires a lot more design decisions than are needed just for env vars. I think a lot of the isolation approach here would still apply, and many considerations would stay the same. But certainly not all would, and we'd all but certainly need to make pretty intrusive changes to interfaces implemented in factors, as opposed to just having to change manifest loading and component composition.

Copy link
Collaborator

@itowlson itowlson left a comment

Choose a reason for hiding this comment

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

Looks good - thanks for noodling through all the details on this! I had a few thoughts, mostly about UI, compatibility (which turned out to be covered later!), and a couple of things that I was unclear on. But generally yep this looks splendid!

|---|---|
| `main` | `MAIN_` |
| `my-service` | `MY_SERVICE_` |
| `acme:lib/helper` | `HELPER_` |
Copy link
Collaborator

Choose a reason for hiding this comment

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

HELPER_ or ACME_LIB_HELPER_?

Also I believe we allow "import all" deps so the dependency could be acme:lib (check with Brian though).

Copy link
Collaborator

Choose a reason for hiding this comment

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

^ is correct: acme:lib is an acceptable form

HELPER_HELPER_SETTING=42
```

**NOTE**: Dynamically provided environment variables, e.g. those added using `spin up -e SOME=var` aren't automatically prefixed, and as a result not visible to any component, unless they already have the right prefix. E.g. to expose `SOME` to the `main` component, it has to be changed to `MAIN_SOME`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems like a breaking change.

The current behaviour is that spin up -e SOME=var makes SOME appear in all components of the application. The proposed new behaviour would require separate THING_SOME for each component.

We should consider instead having Spin auto-munge -e variables so that they are applied to all components.

(If we do intentionally want to make -e selective - which should probably be a separate discussion - I'd look to decouple the external representation from the internal one. At the moment, we can change the internal representation as we see fit: I'd be very cautious about making it contractual.)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Another potential woe:

Imagine two components, alice and bob. Both import carol.

With the current scheme, the syntax for setting environment variables on carol would be spin up -e CAROL_WHATEVER=thingy.

But there are two carols in play. We don't want to couple them together. So we need further disambiguation at the CLI. (Of course this is not an issue for the internal representation, as it's scoped to a particular composition).


The result is that each component sees only its own environment variables, with unprefixed keys, as if those were the only variables that existed.

The isolator also passes through `get-arguments` and `initial-cwd` without modification, since these are process-level values that do not benefit from per-component filtering.
Copy link
Collaborator

Choose a reason for hiding this comment

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

My first reaction is "why am I letting Semi-Trusted Steve scope out MY argument list" but I may be misunderstanding - is the idea that I can still block access via the normal "no inherit config" mechanism, but if I grant access then there is no point customising? If so then no worries.

HELPER_HELPER_SETTING=42
```

**NOTE**: Dynamically provided environment variables, e.g. those added using `spin up -e SOME=var` aren't automatically prefixed, and as a result not visible to any component, unless they already have the right prefix. E.g. to expose `SOME` to the `main` component, it has to be changed to `MAIN_SOME`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

As the isolator is only in play for components with at least one dependency, this suggests that SOME=var will work for components without dependencies? I'm not enthusiastic about having to guess which components have dependencies and customise syntax for them alone.


### Activation rule

Environment variable isolation is activated **automatically** for components with at least one dependency. When active, **all** components in the composition are isolated — including the main component. A component with no declared `environment` will see an empty environment when isolation is active.
Copy link
Collaborator

Choose a reason for hiding this comment

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

A component with no declared environment will see an empty environment when isolation is active.

This is the same as the current behaviour right? I am not quite sure what you are seeking to highlight here.


### Prefix is not user-configurable

The prefix is derived deterministically from the component or dependency name. Exposing the prefix to users would add configuration surface for an implementation detail that has no user-visible effect (components never see the prefix). Keeping it internal simplifies the UX and prevents misconfiguration.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree it should be user-configurable but the stuff about spin up -e gives me the impression that it does have a user-visible effect. (But as mentioned above I think we should avoid it having a user-visible effect.)


### Automatic activation

Environment isolation is activated implicitly when any component in a composition imports `wasi:cli/environment`, without an opt-out. This choice follows from the goal to make safe behavior the default or even enforced, but could be weakened by providing an opt-out.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah I think care is needed here. Currently dependencies_inherit_configuration = true gives a dep access to all the composition's EVs. It's not clear to me what the semantics are after this change.

The combinations (and my guess at behaviour) are:

  1. "inherit = false" and no env section(s) - no EVs for dep (isolator in play but blocking everything)
  2. "inherit = false" and yes env section(s) - only specified EVs, isolator in play
  3. "inherit = true" and no env secition - ??? all inherited EVs as current ???
  4. "inherit = true" and yes env secition - ??? all inherited EVs and all dep EVs??? ???

But I am not quite sure what you envision for scenarios 3 and 4. If we change scenario 3 then it's breaking (which I'm not opposed to, we just need to plan and communicate).


### Backward compatibility

There are two compatibility-breaking aspects to this change:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oops, should have read ahead before leaving those comments!


There are two compatibility-breaking aspects to this change:
1. The main component's `environment` isn't visible to dependencies anymore, so existing applications that assume it is won't work as expected.
2. Environment variables provided dynamically, e.g. using the `spin up -e` need to be prefixed to be exposed to specific components.
Copy link
Collaborator

Choose a reason for hiding this comment

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

As noted above I do not love this one especially if the way we do it couples the internal representation to the UI.


## Future possibilities

- **Template expressions in environment.** The `environment` field currently accepts only static strings. A future enhancement could allow Spin template expressions (e.g. `{{ my_variable }}`), leveraging the existing variables/expressions system for dynamic configuration.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This feature is completely independent of isolation though isn't it?

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.

3 participants