Silent undefined from k6/experimental/secrets.get() masks missing credentials until request failure
0 reputation · 04 Sept 2023, 10:25 UTC
0 reputation · 04 Sept 2023, 10:25 UTC
The k6/experimental/secrets module (introduced in v0.35) reads a local JSON file at VU initialization and exposes secrets.get(name). When a requested key is absent, the function returns undefined without throwing or logging a warning.
This silent failure propagates into HTTP calls as malformed headers or query parameters, producing generic 401/400 responses that are difficult to trace back to a missing secret. In distributed execution, the secrets file must exist on every load-generator node, and a missing file on any single agent yields the same undefined behavior without a clear diagnostic.
--env suffer from the same silent undefined access via __ENV.k6/experimental/secrets will gain a strict mode or explicit error for missing keys?k6-operator deployments verify secret propagation across all pods before the main test starts?31775 reputation · 04 Sept 2023, 12:30 UTC
The short answer: there is no built-in strict mode, so the standard fix is a thin wrapper that throws on undefined, called in the init context so the run aborts before any iteration starts. That converts a late, confusing 401 into an immediate, named failure.
Because secrets.get(name) returns undefined silently, centralize all access behind a helper:
import secrets from 'k6/experimental/secrets';
function requireSecret(name) {
const v = secrets.get(name);
if (v === undefined) {
throw new Error(`Missing required secret: ${name}`);
}
return v;
}
// Evaluated once per VU in the init context — a throw here aborts the run.
export const API_TOKEN = requireSecret('api_token');
export const TENANT_ID = requireSecret('tenant_id');
Throwing in the init context stops the entire run immediately, which is exactly what you want for missing credentials. If you prefer an explicit abort with a custom message over an exception, do the same check in setup() and call exec.test.abort('missing secret: api_token'). Apply the identical wrapper to __ENV lookups, since --env variables fail silently the same way — one validated config module covering both sources keeps the behavior consistent.
One caveat: a bug in the validation helper itself will also abort every run, so keep it trivial and unit-test the secrets file shape in CI (e.g., a JSON schema check) rather than adding logic to the wrapper.
The module is explicitly experimental, and there is no confirmed public commitment that it will gain a strict mode or throw-on-missing behavior. Treat any expectation of that as unverified. The practical implication is to pin your k6 version, isolate all secret access behind your own wrapper (as above), and check the k6 release notes and GitHub repository when upgrading — if the API changes, you only fix one call site. Do not build scripts that depend on the current return-undefined semantics staying stable.
In distributed runs the secrets file must exist and be identical on every runner pod; a node-local file is the usual source of the "works on one agent, undefined on another" failure. The common pattern is:
Because the module is experimental, confirm behavior on your installed k6: call secrets.get() for a deliberately absent key and log the result; then add the wrapper, omit a key, and confirm the run aborts at startup with your message instead of failing later with 401/400. That five-minute check removes any doubt about version-specific semantics.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.