{"record":{"id":"eb9ab90fca94d5d2","repo":"grafana/k6","slug":"failed-to-decode-symmetric-key-w","errorCode":null,"errorMessage":"failed to decode symmetric key: %w","messagePattern":"failed to decode symmetric key: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/webcrypto/jwk.go","lineNumber":62,"sourceCode":"\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\n\texported := &JsonWebKey{}\n\n\texported.Set(\"k\", base64URLEncode(rawKey))\n\texported.Set(\"kty\", JWKOctKeyType)\n\texported.Set(\"ext\", key.Extractable)","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/webcrypto/jwk.go#L44-L80","documentation":"Thrown by k6 WebCrypto when the k field of a symmetric JWK is not valid base64url encoding. After validation succeeds, extractSymmetricJWK (internal/js/modules/k6/webcrypto/jwk.go:60-63) runs base64URLDecode on k; standard base64 characters ('+', '/'), bad padding, or non-alphabet characters fail and the underlying decoding error is wrapped with %w.","triggerScenarios":"k contains standard-base64 ('+'/'/' characters) instead of base64url ('-'/'_'); k has trailing '=' padding that violates the strict decoder; k includes whitespace/newlines from being wrapped in a certificate-style layout; k is hex-encoded rather than base64.","commonSituations":"Secrets copied from JWT headers or vaults that emit standard base64; line-wrapped base64 pasted from terminals; secrets stored as hex by another system.","solutions":["Convert standard base64 to base64url: replace '+' with '-', '/' with '_', and strip '=' padding (or use k6's encoding/base64: Base64.encodings.Base64Url without padding).","Ensure no whitespace or newlines inside k.","If the secret is hex, decode hex to bytes and re-encode as base64url before putting it in the JWK."],"exampleFix":"// before\nconst k = 'a+b/cd=='; // standard base64 with padding\nawait crypto.subtle.importKey('jwk', { kty: 'oct', k }, alg, false, ['encrypt']);\n\n// after\nimport encoding from 'k6/encoding';\nconst k = encoding.b64encode(encoding.b64decode('a+b/cd==', 'std'), 'url').replace(/=+$/, '');\nawait crypto.subtle.importKey('jwk', { kty: 'oct', k }, alg, false, ['encrypt']);","handlingStrategy":"validation","validationCode":"function toBase64url(b64) {\n  return b64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\nconst k = toBase64url(rawSecretB64); // then { kty: 'oct', k }","typeGuard":"const isBase64Url = (s) => typeof s === 'string' && /^[A-Za-z0-9_-]+$/.test(s);","tryCatchPattern":null,"preventionTips":["Store secrets pre-encoded as unpadded base64url in environments.","Strip whitespace/newlines from pasted key material before use."],"tags":["webcrypto","jwk","base64url","import-key"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}