{"record":{"id":"e4e3d054e1338118","repo":"instructure/canvas-lms","slug":"sourcedid-invalid","errorCode":"sourcedid_invalid","errorMessage":"Invalid sourcedid","messagePattern":"Invalid sourcedid","errorType":"error_code","errorClass":"BasicLTI::Errors::InvalidSourceId","httpStatus":null,"severity":"error","filePath":"lib/basic_lti/sourcedid.rb","lineNumber":65,"sourceCode":"        assignment_id: assignment.id,\n        user_id: user.id,\n      }\n    end\n    private :jwt_payload\n\n    def validate!\n      raise Errors::InvalidSourceId.new(\"Course is invalid\", :course_invalid) unless course\n      raise Errors::InvalidSourceId.new(\"User is no longer in course\", :user_not_in_course) unless user\n      raise Errors::InvalidSourceId.new(\"Assignment is invalid\", :assignment_invalid) unless assignment\n\n      tag = assignment.external_tool_tag\n      raise Errors::InvalidSourceId.new(\"Assignment is no longer associated with this tool\", :assignment_tool_mismatch) unless tag &&\n                                                                                                                               (tool.matches_url?(tag.url, match_queries_exactly: false) || tool.matches_tool_domain?(tag.url)) &&\n                                                                                                                               tool.workflow_state != \"deleted\"\n    end\n\n    def self.load!(sourcedid_string)\n      raise Errors::InvalidSourceId.new(\"Invalid sourcedid\", :sourcedid_invalid) if sourcedid_string.blank?\n\n      token = load_from_legacy_sourcedid!(sourcedid_string) ||\n              token_from_sourcedid!(sourcedid_string)\n\n      tool = Lti::ToolFinder.find_by(id: token[:tool_id])\n      course = Course.active.find_by(id: token[:course_id])\n      if course\n        user = course.student_enrollments.active.find_by(user_id: token[:user_id])&.user\n        assignment = course.assignments.active.find_by(id: token[:assignment_id])\n      end\n\n      sourcedid = new(tool, course, assignment, user)\n      sourcedid.validate!\n      sourcedid\n    end\n\n    def self.load_from_legacy_sourcedid!(sourcedid)\n      token = nil","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/basic_lti/sourcedid.rb#L47-L83","documentation":"Sourcedid.load! raises Errors::InvalidSourceId with :sourcedid_invalid when the sourcedid string is blank, or (via token_from_sourcedid!) when the encrypted JWT payload cannot be parsed as a valid format (JSON::JWT::InvalidFormat rescued and re-raised). It is the generic 'this lis_result_sourcedid is not a decodable token' error.","triggerScenarios":"Calling Sourcedid.load!(nil), load!(\"\") , or passing a string that is not base64-encoded encrypted JWT data (e.g. an HTML-escaped, truncated, or legacy-corrupted value) to token_from_sourcedid!.","commonSituations":"Tool stores lis_result_sourcedid in a column/cookie that truncated it; sending the sourcedid URL-encoded/unescaped incorrectly; passing an old OAuth-signed sourcedid through the JWT path after a format migration.","solutions":["Check the sourcedid is present and unmodified (no truncation, no double URL-encoding) before calling load!.","Re-launch the tool to obtain a fresh sourcedid.","If legacy sourcedids are in play, confirm load_from_legacy_sourcedid! path applies (SOURCE_ID_REGEX match) — legacy format is tried first.","Log the received value safely (length/prefix only) to detect truncation or escaping corruption."],"exampleFix":"// before\nsourcedid = BasicLti::Sourcedid.load!(params[:lis_result_sourcedid])\n// after\nsd = params[:lis_result_sourcedid].to_s\nif sd.blank? || sd.length < 50\n  return render_error(\"missing or malformed sourcedid\")\nend\nsourcedid = BasicLti::Sourcedid.load!(sd)","handlingStrategy":"validation","validationCode":"# ruby\nsd = params[:lis_result_sourcedid].to_s\nreturn error(\"missing sourcedid\") if sd.blank?","typeGuard":null,"tryCatchPattern":"begin\n  BasicLti::Sourcedid.load!(sd)\nrescue BasicLti::Sourcedid::Errors::InvalidSourceId => e\n  render_bad_request(\"invalid sourcedid\") if e.error_code == :sourcedid_invalid\nend","preventionTips":["Treat sourcedids as opaque; never truncate, escape, or re-encode them.","Use adequately sized storage columns (TEXT) for sourcedid values.","Reject blank sourcedids at the controller layer with a clear 400."],"tags":["lti","jwt","malformed-token"],"backgroundTag":"invalid-argument-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"}