{"record":{"id":"ff958005f94a6952","repo":"instructure/canvas-lms","slug":"the-toolproxy-security-contract-doesn-t-include-service","errorCode":null,"errorMessage":"The ToolProxy security contract doesn't include #{service_names.join(\", or \")}","messagePattern":"The ToolProxy security contract doesn't include #(.+?)","errorType":"exception","errorClass":"Lti::OAuth2::InvalidTokenError","httpStatus":401,"severity":"error","filePath":"app/controllers/lti/ims/access_token_helper.rb","lineNumber":62,"sourceCode":"    end\n  end\n\n  def oauth2_request?\n    pattern = /^Bearer /\n    header = request.headers[\"Authorization\"]\n    header&.match?(pattern)\n  end\n\n  def tool_proxy\n    @_tool_proxy ||= Lti::ToolProxy.find_by(guid: access_token.sub)\n  end\n\n  def validate_services!(tool_proxy)\n    ims_tp = ::IMS::LTI::Models::ToolProxy.from_json(tool_proxy.raw_data)\n    service_names = [*lti2_service_name]\n    service = ims_tp.security_contract.tool_services.find(\n      lambda do\n        raise Lti::OAuth2::InvalidTokenError,\n              \"The ToolProxy security contract doesn't include #{service_names.join(\", or \")}\"\n      end\n    ) do |s|\n      service_names.include? s.service.split(\":\").last.split(\"#\").last\n    end\n    unless service.actions.map(&:downcase).include? request.method.downcase\n      msg = \"#{s.service.split(\":\").last.split(\"#\").last}.#{request.method} not included in ToolProxy security Contract\"\n      raise Lti::OAuth2::InvalidTokenError, msg\n    end\n  end\n\n  def developer_key\n    @_developer_key ||= access_token && begin\n      tp = Lti::ToolProxy.find_by(guid: access_token.sub)\n      if tp.present?\n        raise Lti::OAuth2::InvalidTokenError, \"Tool Proxy is not active\" if tp.workflow_state != \"active\"\n\n        validate_services!(tp)","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/controllers/lti/ims/access_token_helper.rb#L44-L80","documentation":"Guard in Lti::IMS::AccessTokenHelper#validate_services!: the LTI 2 ToolProxy's security contract lists no service matching the service names required by the endpoint being called (lti2_service_name), so Lti::OAuth2::InvalidTokenError is raised — the tool was never registered for this REST service.","triggerScenarios":"Thrown at app/controllers/lti/ims/access_token_helper.rb:62 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Re-register/re-deploy the ToolProxy with the required service (e.g. ToolProxy.custom or Result service) in its security_contract.tool_services","Call an endpoint matching a service the tool is actually authorized for"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"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"}