{"record":{"id":"10815a93be3aa837","repo":"instructure/canvas-lms","slug":"attachment-verifier-token-invalid-verifier","errorCode":null,"errorMessage":"Attachment verifier token invalid: #{verifier}","messagePattern":"Attachment verifier token invalid: #(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/models/attachments/verification.rb","lineNumber":91,"sourceCode":"  # Returns nil if the verifier could not be decoded for whatever reason, and returns\n  # a Hash of the body contents if it can.\n  def decode_verifier(verifier)\n    begin\n      body = CanvasSecurity.decode_jwt(verifier)\n      if body[:id] != attachment.global_id\n        InstStatsd::Statsd.distributed_increment(\"attachments.token_verifier_id_mismatch\")\n        Rails.logger.warn(\"Attachment verifier token id mismatch. token id: #{body[:id]}, attachment id: #{attachment.global_id}, token: #{verifier}\")\n        return nil\n      end\n\n      InstStatsd::Statsd.distributed_increment(\"attachments.token_verifier_success\")\n    rescue CanvasSecurity::TokenExpired\n      InstStatsd::Statsd.distributed_increment(\"attachments.token_verifier_expired\")\n      Rails.logger.warn(\"Attachment verifier token expired: #{verifier}\")\n      return nil\n    rescue CanvasSecurity::InvalidToken\n      InstStatsd::Statsd.distributed_increment(\"attachments.token_verifier_invalid\")\n      Rails.logger.warn(\"Attachment verifier token invalid: #{verifier}\")\n      return nil\n    end\n\n    body\n  end\n\n  # Decodes a verifier and checks the user of the verifier has permission to access\n  # the attachment.\n  #\n  # @param verifier (String) - The verifier\n  # @param permission (Symbol) - Either :read or :download\n  #\n  # Returns a boolean\n  def valid_verifier_for_permission?(verifier, permission, root_account, session = {}, request: nil, files_domain: false)\n    return false unless verifier.is_a?(String)\n\n    # Support for legacy verifiers.\n    # if we have a uuid-sized verifier that doesn't match, see whether it matches a related attachment","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/attachments/verification.rb#L73-L109","documentation":"CanvasSecurity.decode_jwt raises CanvasSecurity::InvalidToken when the verifier JWT fails signature or format validation — it is malformed, tampered with, or signed with a different secret. decode_verifier logs this warning, increments attachments.token_verifier_invalid, and returns nil, so the attachment request is denied.","triggerScenarios":"A verifier string that is truncated, altered, forged, or signed by a different Canvas instance/secret key is passed to a file URL.","commonSituations":"URL truncation by email clients or chat apps breaking the JWT; a rotated CanvasSecurity secret invalidating old tokens; cross-environment token reuse (staging URL token used in production); manual URL editing.","solutions":["Regenerate the file URL from the source Canvas instance so the verifier is signed with the current secret","Check the URL was not truncated/modified in transit (JWTs are long; some mailers wrap or cut them)","If secret rotation caused mass invalidation, coordinate TTL so tokens issued pre-rotation expire before enforcement","Compare environments: a verifier only validates against the instance whose signing secret issued it"],"exampleFix":"// before\nverifier = params[:verifier][0, 40] # truncated token\n// after\nverifier = params[:verifier] # pass the full token unchanged","handlingStrategy":"validation","validationCode":"def well_formed_verifier?(verifier)\n  verifier.is_a?(String) && verifier.count('.') == 2 && verifier.length > 40\nend","typeGuard":"def decodable_jwt?(token)\n  token.is_a?(String) && token.split('.').length == 3\nend","tryCatchPattern":"begin\n  body = CanvasSecurity.decode_jwt(verifier)\nrescue CanvasSecurity::InvalidToken\n  Rails.logger.warn(\"invalid verifier, regenerating\")\n  nil\nend","preventionTips":["Never truncate or rewrite JWTs in links (email wrapping, URL shorteners)","Use one signing secret per environment; never copy tokens across environments","Rotate CanvasSecurity secrets with awareness of outstanding token TTLs","Escape file URLs properly so tokens aren't mangled in HTML/emails"],"tags":["jwt","attachments","invalid-token","security"],"backgroundTag":"invalid-identifier-format","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"}