grafana/k6 · error

key (k) is required

Error message

key (k) is required

What it means

When importing a symmetric (octet, kty='oct') key from JWK via crypto.subtle.importKey, k6 unmarshals it into symmetricJWK and validates it (internal/js/modules/k6/webcrypto/jwk.go:37). The `k` member holds the base64url-encoded key material; if it is missing or an empty string, validate() returns "key (k) is required". (A wrong kty produces the separate "invalid key type" error.)

Source

Thrown at internal/js/modules/k6/webcrypto/jwk.go:43

// Set sets a key-value pair in the JWK.
func (jwk *JsonWebKey) Set(key string, value any) {
	(*jwk)[key] = value
}

// symmetricJWK represents a symmetric JWK key.
// It is used to unmarshal symmetric keys from JWK format.
type symmetricJWK struct {
	Kty string `json:"kty"`
	K   string `json:"k"`
}

func (jwk *symmetricJWK) validate() error {
	if jwk.Kty != JWKOctKeyType {
		return fmt.Errorf("invalid key type: %s", jwk.Kty)
	}

	if jwk.K == "" {
		return errors.New("key (k) is required")
	}

	return nil
}

// extractSymmetricJWK extracts the symmetric key from a given JWK key (JSON data).
func extractSymmetricJWK(jsonKeyData []byte) ([]byte, error) {
	sk := symmetricJWK{}
	if err := json.Unmarshal(jsonKeyData, &sk); err != nil {
		return nil, fmt.Errorf("failed to parse symmetric JWK: %w", err)
	}

	if err := sk.validate(); err != nil {
		return nil, fmt.Errorf("invalid symmetric JWK: %w", err)
	}

	skBytes, err := base64URLDecode(sk.K)
	if err != nil {

View on GitHub (pinned to 93accf6570)

Solutions

  1. Include the base64url-encoded key material: { kty: 'oct', alg: 'A128GCM', k: base64urlKey }
  2. Verify programmatically that the JWK has a non-empty k before calling importKey
  3. Check how the JWK was serialized — a mismatched property name (e.g. 'key') silently drops k

Example fix

// before
const jwk = { kty: 'oct', alg: 'A256GCM' }; // no key material
const key = await crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, true, ['encrypt']);

// after (base64url of the raw 32 bytes, no padding)
const jwk = { kty: 'oct', alg: 'A256GCM', k: 'RxS6T4LZf0p1O2n3M4q5R6s7T8u9V0w1X2y3Z4a5B6c' };
const key = await crypto.subtle.importKey('jwk', jwk, { name: 'AES-GCM' }, true, ['encrypt']);
Defensive patterns

Strategy: validation

Validate before calling

const parsed = typeof jwk === 'string' ? JSON.parse(jwk) : jwk;
if (parsed.kty === 'oct' && (typeof parsed.k !== 'string' || parsed.k.length === 0)) {
  throw new Error('symmetric JWK is missing the k (key material) member');
}

Type guard

const isValidSymmetricJWK = (j) => j.kty === 'oct' && typeof j.k === 'string' && j.k.length > 0;

Try / catch

try {
  key = await crypto.subtle.importKey('jwk', jwk, alg, true, usages);
} catch (e) {
  if (String(e.message).includes('key (k) is required')) throw new Error('JWK has no key material — check how it was built/serialized');
  throw e;
}

Prevention

When it happens

Trigger: `crypto.subtle.importKey('jwk', { kty: 'oct', alg: 'A256GCM' }, ...)` — a JWK with no `k` field or `k: ''`. Common with hand-written or template JWKs where only the algorithm identifier was filled in.

Common situations: Building JWKs from configuration that forgot the key material; copying JWK examples and replacing alg but not k; string-building JSON where the k value ends up empty; secret-management code that injects the key under a different property name.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/e93f841fc41abdce. Report an issue: GitHub.