{"record":{"id":"4f9351a0a85bb762","repo":"instructure/canvas-lms","slug":"tool-invalid-is","errorCode":"tool_invalid","errorMessage":"Tool is invalid","messagePattern":"Tool is invalid","errorType":"error_code","errorClass":"BasicLTI::Errors::InvalidSourceId","httpStatus":null,"severity":"error","filePath":"lib/basic_lti/sourcedid.rb","lineNumber":87,"sourceCode":"\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\n      md = sourcedid.match(SOURCE_ID_REGEX)\n      if md\n        tool = Lti::ToolFinder.find_by(id: md[1])\n        raise Errors::InvalidSourceId.new(\"Tool is invalid\", :tool_invalid) unless tool\n\n        new_encoding = [md[1], md[2], md[3], md[4]].join(\"-\")\n        raise Errors::InvalidSourceId.new(\"Invalid signature\", :signature_invalid) unless Canvas::Security\n                                                                                          .verify_hmac_sha1(md[5], new_encoding, key: tool.shard.settings[:encryption_key])\n\n        token = { tool_id: md[1].to_i, course_id: md[2], assignment_id: md[3], user_id: md[4] }\n      end\n      token\n    end\n\n    def self.token_from_sourcedid!(sourcedid)\n      Canvas::Security.decrypt_encrypted_jwt(\n        Canvas::Security.base64_decode(sourcedid),\n        signing_secret,\n        encryption_secret\n      )\n    rescue JSON::JWT::InvalidFormat\n      raise Errors::InvalidSourceId.new(\"Invalid sourcedid\", :sourcedid_invalid)","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/basic_lti/sourcedid.rb#L69-L105","documentation":"Sourcedid.load_from_legacy_sourcedid! raises Errors::InvalidSourceId with :tool_invalid when a legacy-format sourcedid matches SOURCE_ID_REGEX but the tool id embedded in it (md[1]) does not resolve via Lti::ToolFinder.find_by. The legacy sourcedid references a tool that no longer exists, so its signature cannot even be verified.","triggerScenarios":"Loading a legacy OAuth-signed sourcedid whose first segment is a tool id that was deleted, or from a different shard than the one being queried, or with a corrupted id segment.","commonSituations":"Years-old sourcedids stored by external gradebooks referencing tools removed from Canvas; sharding/multi-tenant setups where find_by runs on the wrong shard; manual construction of sourcedid strings in tests/scripts.","solutions":["Re-launch the tool so the client receives a fresh (JWT-based) sourcedid.","Confirm the tool still exists and check Lti::ToolFinder.find_by(id: tool_id) on the correct shard.","Purge stale legacy sourcedids from your data store.","Verify the sourcedid string was not corrupted so md[1] parses to the right id."],"exampleFix":"// before\ntool_id = sourcedid.split('-').first\n# assumes tool exists\n// after\nraise \"stale sourcedid: tool gone\" unless Lti::ToolFinder.find_by(id: tool_id)","handlingStrategy":"try-catch","validationCode":"# ruby\ntool_id = sourcedid.scan(/\\A(\\d+)-/).flatten.first\nLti::ToolFinder.find_by(id: tool_id).present?","typeGuard":null,"tryCatchPattern":"begin\n  BasicLti::Sourcedid.load!(sourcedid)\nrescue BasicLti::Sourcedid::Errors::InvalidSourceId => e\n  invalidate_legacy_sourcedid if e.error_code == :tool_invalid\nend","preventionTips":["Migrate off legacy sourcedids to JWT-based ones.","Verify tools exist on the correct shard before cross-environment operations.","Purge stored sourcedids when their tool is decommissioned."],"tags":["lti","legacy-sourcedid","tool-deleted"],"backgroundTag":"entity-not-found","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"}