Credential injection
Let code in a microVM call an authenticated API without the API key ever entering the microVM, by adding headers to its outbound HTTPS requests.
Credential injection adds the headers you configure to a microVM's outbound HTTPS requests to the domains you name. Use it to let an AI agent's code call OpenAI, GitHub or your own API while the key stays out of the microVM: no environment variable, file or process inside it can read the value.
Add credentials when you create a microVM
List the rules in network.inject. Each rule names a domain and the headers to set on every request to it:
import { MicroVM } from '@gigadrive/sdk';
const vm = await MicroVM.create({
image: 'python3.13',
network: {
egress: 'allow',
inject: [
{ domain: 'api.openai.com', headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` } },
{ domain: '*.github.com', headers: { Authorization: `token ${process.env.GITHUB_TOKEN}` } },
],
},
});
const { stdout } = await vm.run('curl -s https://api.openai.com/v1/models');The key is read from your own environment and sent to Gigadrive Network; the command in the microVM sends no key.
Code in the microVM then calls the API without a key, and the request arrives with the header set:
curl https://api.openai.com/v1/modelsThe response never shows a value. It lists each rule's domain and lowercase header names:
{
"network": {
"egress": "allow",
"inject": [
{ "domain": "api.openai.com", "headers": ["authorization"] },
{ "domain": "*.github.com", "headers": ["authorization"] }
]
}
}Audit log entries for the microVM show the same redacted form.
How requests are matched
- A rule applies to every HTTPS request on port 443 to a name it covers. Its headers replace any header of the same name that the code in the microVM sent.
domainfollows the rules of a domain entry innetwork.allow:*.github.comcovers every subdomain ofgithub.combut notgithub.comitself. IPv4 networks are not accepted.- When several rules cover a name, the most specific wins: an exact name over a wildcard, and a longer wildcard over a shorter one.
- A rule's domain is reachable even when
network.allowdoes not list it. - Plain HTTP to a rule's domain is refused, so a credential never travels unencrypted. Other ports are not covered.
Requests reach the destination over HTTP/1.1, which every HTTP client supports.
Trusting the connection
The microVM's HTTPS connections to a rule's domain end at Gigadrive Network, which adds the headers and opens its own verified connection to the destination. For that, every microVM trusts its own certificate authority, created for that microVM alone. It is added to the system certificate bundles and saved as /etc/gigadrive/egress-ca.pem, and these environment variables point to it unless your image or your command already sets them:
| Variable | Used by |
|---|---|
NODE_EXTRA_CA_CERTS | Node.js |
DENO_CERT | Deno |
SSL_CERT_FILE | OpenSSL-based clients, Python httpx |
REQUESTS_CA_BUNDLE | Python requests |
A program that ships its own fixed set of root certificates, such as Java with its bundled cacerts, fails the TLS handshake with a rule's domain. Add /etc/gigadrive/egress-ca.pem to its trust store. Its connections to every other destination are unaffected.
A program that resolves names without the system resolver, for example over DNS over HTTPS or by connecting to a hardcoded address, bypasses injection. Its requests go out without the headers.
The destination receives the credential and can send it back in a response, where code in the microVM can read it. Configure credentials only for services you trust not to echo them.
Change credentials while it runs
PATCH /microvms/{id}/network takes inject too. Because the values are never returned, the call treats inject differently from egress and allow:
| Body | Effect on the rules |
|---|---|
No inject field | Kept as they are |
"inject": [...] | Replaced by the new list |
"inject": [] | Removed |
"egress": "deny" without inject | Removed, and not restored by a later allow |
"egress": "deny" with a non-empty inject | Refused with 422 and invalid_network |
To rotate a key, send the full list with the new value:
import { MicroVM } from '@gigadrive/sdk';
const vm = await MicroVM.get('0199a7c4-5b1e-7c3d-9f20-333333333333');
await vm.updateNetwork({
egress: 'allow',
inject: [{ domain: 'api.openai.com', headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` } }],
});Leave out inject to keep the rules, and pass inject: [] to remove them.
The new rules apply to a running microVM before the call answers, and to a paused one when it resumes. A microVM started before credential injection was available answers 409 with microvm_egress_inject_unsupported; stop it and create a new one.
Snapshots
A microVM created from a snapshot gets only the rules in its own create request, and its own certificate authority. It never inherits the rules of the microVM the snapshot was taken from. A snapshot taken before credential injection was available cannot start a microVM with rules: create that microVM from an image or a newer snapshot.
Limits
| Limit | Value |
|---|---|
| Rules per microVM | 8, each with a different domain |
| Headers per rule | 1 to 8 |
| Header name | An HTTP header name of at most 64 characters |
| Header value | 1 to 8192 bytes of visible ASCII, spaces and tabs, with no leading or trailing space |
| All names and values together | 32 KiB |
The service refuses with 400 and invalid_request:
- the headers
Host,Content-Length,Transfer-Encoding,Connection,Upgrade,Keep-Alive,TE,Trailerand anyProxy-*header; - a wildcard over a public suffix that anyone can register names under, such as
*.co.uk,*.vercel.appor*.github.io. Name the exact domain, such asmyproject.supabase.co, instead.
inject together with "egress": "deny" answers 422 with invalid_network. These limits are the same on every plan, and no error message repeats a header value.
Related
- Ports and previews covers
egressandallow. - Scopes lists the scope each call needs: creating a microVM and changing its network need
network:microvms:write.
