{"record":{"id":"d5245fd0647b9750","repo":"gchq/CyberChef","slug":"letter-duplicated-in-the-key","errorCode":null,"errorMessage":"Letter duplicated in the key","messagePattern":"Letter duplicated in the key","errorType":"validation","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/lib/LS47.mjs","lineNumber":108,"sourceCode":"}\n\n/**\n * Checks the key is a valid key.\n *\n * @param {string} key\n */\nfunction checkKey(key) {\n    if (key.length !== letters.length)\n        throw new OperationError(\"Wrong key size\");\n    const counts = new Array();\n    for (let i = 0; i < letters.length; i++)\n        counts[letters.charAt(i)] = 0;\n    for (const elem of letters) {\n        if (letters.indexOf(elem) === -1)\n            throw new OperationError(\"Letter \" + elem + \" not in LS47\");\n        counts[elem]++;\n        if (counts[elem] > 1)\n            throw new OperationError(\"Letter duplicated in the key\");\n    }\n}\n\n/**\n * Finds the position of a letter in they key.\n *\n * @param {letter} key\n * @param {string} letter\n * @returns {object}\n */\nfunction findPos (key, letter) {\n    const index = key.indexOf(letter);\n    if (index >= 0 && index < 49)\n        return [Math.floor(index/7), index%7];\n    throw new OperationError(\"Letter \" + letter + \" is not in the key\");\n}\n\n/**","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/lib/LS47.mjs#L90-L126","documentation":"LS47 keys must be permutations: each of the 49 alphabet characters must appear exactly once. checkKey() intends to detect duplicates via a per-character count map. NOTE: like error 101, the loop iterates `letters` rather than `key`, so counts[elem] is incremented exactly once per alphabet character and never exceeds 1; this branch is effectively unreachable in the current code.","triggerScenarios":"Intent: calling encrypt/decrypt with a 49-char key that repeats a character (e.g. two 'a's) and therefore omits another. In practice the current loop never trips this guard.","commonSituations":"Hand-crafted key with a typo duplicating a letter; key derived from a faulty source; key edited in a text editor without unique-character verification. The masking bug lets invalid keys through to produce wrong output instead of an error.","solutions":["Always derive keys via deriveKey(password), which produces a correct permutation.","Validate manually with new Set(key).size === 49 && key.length === 49 before calling encrypt/decrypt.","Report the upstream bug: change `for (const elem of letters)` to `for (const elem of key)` so duplicates are detected."],"exampleFix":"// before (buggy - iterates `letters`, never detects duplicates in key)\nfor (const elem of letters) {\n    counts[elem]++;\n    if (counts[elem] > 1) throw new OperationError(\"Letter duplicated in the key\");\n}\n\n// after\nfor (const elem of key) {\n    counts[elem]++;\n    if (counts[elem] > 1) throw new OperationError(\"Letter duplicated in the key\");\n}","handlingStrategy":"validation","validationCode":"function isLS47Permutation(key) {\n  return key.length === 49 && new Set(key).size === 49;\n}","typeGuard":"function isLS47PermutationKey(x): x is string {\n  return typeof x === 'string' && x.length === 49 && new Set(x).size === 49;\n}","tryCatchPattern":null,"preventionTips":["Always use deriveKey() to obtain a valid permutation.","Verify new Set(key).size === 49 yourself because the in-library guard is currently a no-op (upstream bug).","When editing a key manually, replace characters rather than inserting duplicates.","Persist keys as opaque blobs rather than re-typing them."],"tags":["cryptography","ls47","validation","upstream-bug"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}