{"record":{"id":"93b5f3a02dacf24e","repo":"instructure/canvas-lms","slug":"signature-doesn-t-match","errorCode":null,"errorMessage":"signature doesn't match.","messagePattern":"signature doesn't match\\.","errorType":"exception","errorClass":"HmacHelper::Error","httpStatus":null,"severity":"error","filePath":"app/helpers/hmac_helper.rb","lineNumber":24,"sourceCode":"# This file is part of Canvas.\n#\n# Canvas is free software: you can redistribute it and/or modify it under\n# the terms of the GNU Affero General Public License as published by the Free\n# Software Foundation, version 3 of the License.\n#\n# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY\n# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\n# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n\nmodule HmacHelper\n  # returns parsed json after verification\n  def extract_blob(hmac, json, expected_values = {})\n    unless Canvas::Security.verify_hmac_sha1(hmac, json)\n      raise Error, \"signature doesn't match.\"\n    end\n\n    blob = JSON.parse(json)\n\n    expected_values.each do |k, v|\n      raise Error, \"invalid value for #{k}\" if blob[k] != v\n    end\n\n    blob\n  end\n\n  class Error < StandardError; end\nend\n","sourceCodeStart":6,"sourceCodeEnd":38,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/hmac_helper.rb#L6-L38","documentation":"HmacHelper#extract_blob verifies an HMAC-SHA1 signature over a JSON payload before parsing. If Canvas::Security.verify_hmac_sha1(hmac, json) fails, the payload was tampered with, truncated, or signed with a different secret, so it raises Error \"signature doesn't match.\".","triggerScenarios":"Passing a hmac/json pair where json was modified after signing, the hmac was URL-decoded/encoded differently, the canvas signing secret rotated between signing and verification, or the hmac belongs to a different payload.","commonSituations":"Deep-link/sessionless-launch URLs whose query params were edited; secrets.env differing between app nodes; proxy rewriting the encoded payload; copy-paste dropping trailing '=' padding.","solutions":["Regenerate the signed URL/payload — do not hand-edit signed json.","Confirm the same Canvas security secret is configured on the verifying node (config/canvas_security.yml or equivalent env).","Ensure the hmac and json strings are passed raw (no extra URL decode) exactly as received.","Log a diff of received json vs expected payload to spot tampering or encoding drift."],"exampleFix":"// before\nblob = extract_blob(params[:hmac].tr(' ', '+'), params[:json].gsub('%20', ' '))\n// after\nblob = extract_blob(params[:hmac], params[:json]) # pass raw values untouched","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  blob = HmacHelper.extract_blob(hmac, json, expected)\nrescue HmacHelper::Error => e\n  Rails.logger.warn(\"hmac blob rejected: #{e.message}\")\n  return head :bad_request\nend","preventionTips":["Never modify signed json or hmac between signing and verification","Keep signing secrets identical across all app nodes","Pass query params through raw without extra URL decoding","Regenerate signed URLs rather than editing them"],"tags":["hmac","security","signature-verification"],"backgroundTag":"checksum-mismatch","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}