{"record":{"id":"c0b0189813586389","repo":"grafana/k6","slug":"failed-to-parse-symmetric-jwk-w","errorCode":null,"errorMessage":"failed to parse symmetric JWK: %w","messagePattern":"failed to parse symmetric JWK: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/webcrypto/jwk.go","lineNumber":53,"sourceCode":"}\n\nfunc (jwk *symmetricJWK) validate() error {\n\tif jwk.Kty != JWKOctKeyType {\n\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 {","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/webcrypto/jwk.go#L35-L71","documentation":"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.).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass an actual JWK object (or a string that parses to one): { kty: 'oct', k: base64urlSecret }.","If the source is a JSON file/response, JSON.parse it once before handing it to importKey.","If all you have is a raw secret, import it as 'raw' format with the base64url-decoded bytes instead of jwk.","Read the wrapped JSON error to find the offending token position."],"exampleFix":"// before\nawait crypto.subtle.importKey('jwk', __ENV.SECRET_B64, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);\n\n// after\nawait crypto.subtle.importKey('raw', base64urlToBytes(__ENV.SECRET_B64), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);","handlingStrategy":"validation","validationCode":"function parseJwk(maybeJson) {\n  const obj = typeof maybeJson === 'string' ? JSON.parse(maybeJson) : maybeJson;\n  if (obj == null || typeof obj !== 'object') throw new Error('JWK input is not an object');\n  return obj;\n}","typeGuard":"const looksLikeJwk = (v) => { try { const o = typeof v === 'string' ? JSON.parse(v) : v; return !!o && typeof o === 'object' && 'kty' in o; } catch { return false; } };","tryCatchPattern":"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; }","preventionTips":["JSON.parse fetched key material exactly once before importKey.","For raw secrets, use 'raw' format rather than fabricating JWK JSON."],"tags":["webcrypto","jwk","json","import-key"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}