{"record":{"id":"db5a753178664b17","repo":"grafana/k6","slug":"invalid-symmetric-jwk-w","errorCode":null,"errorMessage":"invalid symmetric JWK: %w","messagePattern":"invalid symmetric JWK: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/webcrypto/jwk.go","lineNumber":57,"sourceCode":"\t\treturn fmt.Errorf(\"invalid key type: %s\", jwk.Kty)\n\t}\n\n\tif jwk.K == \"\" {\n\t\treturn errors.New(\"key (k) is required\")\n\t}\n\n\treturn nil\n}\n\n// extractSymmetricJWK extracts the symmetric key from a given JWK key (JSON data).\nfunc extractSymmetricJWK(jsonKeyData []byte) ([]byte, error) {\n\tsk := symmetricJWK{}\n\tif err := json.Unmarshal(jsonKeyData, &sk); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse symmetric JWK: %w\", err)\n\t}\n\n\tif err := sk.validate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid symmetric JWK: %w\", err)\n\t}\n\n\tskBytes, err := base64URLDecode(sk.K)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode symmetric key: %w\", err)\n\t}\n\n\treturn skBytes, nil\n}\n\n// exportSymmetricJWK exports a symmetric key as a map of JWK key parameters.\nfunc exportSymmetricJWK(key *CryptoKey) (*JsonWebKey, error) {\n\trawKey, ok := key.handle.([]byte)\n\tif !ok {\n\t\treturn nil, errors.New(\"key's handle isn't a byte slice\")\n\t}\n\n\t// wrap result into the object that is expected to be returned","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/webcrypto/jwk.go#L39-L75","documentation":"Umbrella error thrown by k6 WebCrypto when a symmetric JWK fails semantic validation after parsing. extractSymmetricJWK (internal/js/modules/k6/webcrypto/jwk.go:56-58) wraps symmetricJWK.validate's error with 'invalid symmetric JWK: %w'; the inner error is either 'invalid key type: <kty>' (kty is not 'oct') or 'key (k) is required' (the k field is missing/empty). The message therefore nests the real cause after the colon.","triggerScenarios":"importKey('jwk', { kty: 'oct' }, { name: 'AES-GCM' }) — no k field; importKey('jwk', { k: '...', kty: 'RSA' }, HMAC) — wrong kty; a JWK where the k field name is capitalized or misspelled ('K') so it unmarshals as empty.","commonSituations":"Minimal hand-written JWK fixtures missing the secret; field-name casing errors from manual transcription; a key-exchange endpoint returning metadata-only JWKs without secret material.","solutions":["Read the nested cause: if it says 'key (k) is required', add the base64url-encoded secret as k.","If it says 'invalid key type', see that kty is exactly 'oct' (lowercase).","Use correct JSON field names: kty, k — both lowercase."],"exampleFix":"// before\nawait crypto.subtle.importKey('jwk', { kty: 'oct' }, { name: 'AES-GCM' }, false, ['encrypt']);\n\n// after\nawait crypto.subtle.importKey('jwk', { kty: 'oct', k: base64urlSecret }, { name: 'AES-GCM' }, false, ['encrypt']);","handlingStrategy":"validation","validationCode":"function assertValidOctJwk(jwk) {\n  if (jwk.kty !== 'oct') throw new Error(`expected kty 'oct', got '${jwk.kty}'`);\n  if (!jwk.k || typeof jwk.k !== 'string') throw new Error('oct JWK requires a non-empty k field');\n}","typeGuard":"const isValidOctJwk = (jwk) => jwk != null && jwk.kty === 'oct' && typeof jwk.k === 'string' && jwk.k.length > 0;","tryCatchPattern":null,"preventionTips":["Read the nested cause after 'invalid symmetric JWK:' to branch between kty and k fixes.","Use lowercase field names kty/k exactly as the RFC defines them."],"tags":["webcrypto","jwk","import-key","validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}