grafana/k6 · error

failed to parse symmetric JWK: %w

Error message

failed to parse symmetric JWK: %w

What it means

Thrown by k6 WebCrypto when the JWK data passed to importKey('jwk', ...) for a symmetric algorithm cannot be parsed as JSON. extractSymmetricJWK (internal/js/modules/k6/webcrypto/jwk.go:50-54) runs json.Unmarshal on the serialized key data and wraps the encoding/json error verbatim (%w), so the tail of the message shows the exact JSON syntax problem (unexpected token, truncated document, etc.).

Source

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

}

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 {
		return nil, fmt.Errorf("failed to decode symmetric key: %w", err)
	}

	return skBytes, nil
}

// exportSymmetricJWK exports a symmetric key as a map of JWK key parameters.
func exportSymmetricJWK(key *CryptoKey) (*JsonWebKey, error) {
	rawKey, ok := key.handle.([]byte)
	if !ok {

View on GitHub (pinned to 93accf6570)

Solutions

  1. Pass an actual JWK object (or a string that parses to one): { kty: 'oct', k: base64urlSecret }.
  2. If the source is a JSON file/response, JSON.parse it once before handing it to importKey.
  3. If all you have is a raw secret, import it as 'raw' format with the base64url-decoded bytes instead of jwk.
  4. Read the wrapped JSON error to find the offending token position.

Example fix

// before
await crypto.subtle.importKey('jwk', __ENV.SECRET_B64, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);

// after
await crypto.subtle.importKey('raw', base64urlToBytes(__ENV.SECRET_B64), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
Defensive patterns

Strategy: validation

Validate before calling

function parseJwk(maybeJson) {
  const obj = typeof maybeJson === 'string' ? JSON.parse(maybeJson) : maybeJson;
  if (obj == null || typeof obj !== 'object') throw new Error('JWK input is not an object');
  return obj;
}

Type guard

const looksLikeJwk = (v) => { try { const o = typeof v === 'string' ? JSON.parse(v) : v; return !!o && typeof o === 'object' && 'kty' in o; } catch { return false; } };

Try / catch

try { await crypto.subtle.importKey('jwk', keyData, alg, false, usages); } catch (e) { if (/failed to parse symmetric JWK/.test(e.message)) { const parsed = JSON.parse(keyData); return crypto.subtle.importKey('jwk', parsed, alg, false, usages); } throw e; }

Prevention

When it happens

Trigger: Passing a raw base64 secret, a PEM string, or a double-stringified JSON string instead of a JWK object; a JWK JSON file with a trailing comma or truncation; passing the result of JSON.stringify twice so the parser sees a string document where an object is expected.

Common situations: Copy-pasting an opaque shared secret from a vault into the jwk parameter; fetch() of a key endpoint returning text that was never JSON.parse'd; hand-editing a JWK fixture and breaking syntax.

Understand the failure class

Related errors


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