{"record":{"id":"392382d2b2bdf4bc","repo":"gchq/CyberChef","slug":"negative-costs-are-not-allowed","errorCode":null,"errorMessage":"Negative costs are not allowed.","messagePattern":"Negative costs are not allowed\\.","errorType":"exception","errorClass":"OperationError","httpStatus":null,"severity":"error","filePath":"src/core/operations/LevenshteinDistance.mjs","lineNumber":63,"sourceCode":"                type: \"number\",\n                value: 1\n            },\n        ];\n    }\n\n    /**\n     * @param {string} input\n     * @param {Object[]} args\n     * @returns {number}\n     */\n    run(input, args) {\n        const [delim, insCost, delCost, subCost] = args;\n        const samples = input.split(delim);\n        if (samples.length !== 2) {\n            throw new OperationError(\"Incorrect number of samples. Check your input and/or delimiter.\");\n        }\n        if (insCost < 0 || delCost < 0 || subCost < 0) {\n            throw new OperationError(\"Negative costs are not allowed.\");\n        }\n        const src = samples[0], dest = samples[1];\n        let currentCost = new Array(src.length + 1);\n        let nextCost = new Array(src.length + 1);\n        for (let i = 0; i < currentCost.length; i++) {\n            currentCost[i] = delCost * i;\n        }\n        for (let i = 0; i < dest.length; i++) {\n            const destc = dest.charAt(i);\n            nextCost[0] = currentCost[0] + insCost;\n            for (let j = 0; j < src.length; j++) {\n                let candidate;\n                // insertion\n                let optCost = currentCost[j + 1] + insCost;\n                // deletion\n                candidate = nextCost[j] + delCost;\n                if (candidate < optCost) optCost = candidate;\n                // substitution or matched character","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/gchq/CyberChef/blob/4290ea753912378913b1f3f54e0fc5720afeda5d/src/core/operations/LevenshteinDistance.mjs#L45-L81","documentation":"Thrown by the Levenshtein Distance operation when any of the Insertion, Deletion, or Substitution cost arguments is negative. The dynamic-programming algorithm relies on non-negative weights to guarantee a meaningful minimum edit cost; negative weights would make the optimum ill-defined. The guard is on line 62-64 of src/core/operations/LevenshteinDistance.mjs.","triggerScenarios":"Calling LevenshteinDistance.run(input, args) where insCost, delCost, or subCost (args[1], args[2], args[3]) is less than 0. Defaults are all 1; a manually entered negative value (e.g. -1) triggers the error.","commonSituations":"A typo entering '-1' instead of '1'; attempting to 'reward' certain edits with negative weights (not supported); a UI/recipe that computed costs from user data without clamping; copying a recipe whose number field lost its sign.","solutions":["Set Insertion, Deletion, and Substitution costs to 0 or a positive integer (0 is allowed and yields a 'free' edit).","If costs are computed dynamically, clamp them with `Math.max(0, value)` before passing them as arguments.","Validate the three cost arguments against `value >= 0 && Number.isFinite(value)` before invoking the operation."],"exampleFix":"// before: args = [\"\\n\", 1, -1, 1]                  -> throws 'Negative costs are not allowed.'\n// after:  args = [\"\\n\", 1, 1, 1] (or 0 allowed)    -> ok","handlingStrategy":"validation","validationCode":"function validateCosts(insCost, delCost, subCost) {\n  for (const [name, v] of [[\"insCost\", insCost], [\"delCost\", delCost], [\"subCost\", subCost]]) {\n    if (!Number.isFinite(v) || v < 0) {\n      throw new Error(`${name} must be a finite number >= 0 (got ${v})`);\n    }\n  }\n}\n// then: validateCosts(args[1], args[2], args[3]);","typeGuard":"function isNonNegativeCost(v) {\n  return typeof v === \"number\" && Number.isFinite(v) && v >= 0;\n}","tryCatchPattern":"try {\n  const result = levenshtein.run(input, args);\n} catch (err) {\n  if (err instanceof OperationError && /Negative costs/.test(err.message)) {\n    args = [args[0], Math.max(0, args[1]), Math.max(0, args[2]), Math.max(0, args[3])];\n    // retry with clamped costs\n  } else {\n    throw err;\n  }\n}","preventionTips":["Clamp externally computed costs with Math.max(0, value) before passing them in.","Treat edit costs as non-negative integers by convention in your recipe/UI.","Validate all three cost fields against `Number.isFinite(v) && v >= 0` before run()."],"tags":["levenshtein","argument-validation","edit-distance","numeric-range"],"backgroundTag":null,"analyzedSha":"4290ea753912378913b1f3f54e0fc5720afeda5d","analyzedAt":"2026-08-13T06:05:50.210Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}