{"record":{"id":"1aedb893ab65fded","repo":"instructure/canvas-lms","slug":"message-tool-proxy-validator","errorCode":null,"errorMessage":"#{message}","messagePattern":"#\\{message\\}","errorType":"validation","errorClass":"Lti::Errors::InvalidToolProxyError","httpStatus":400,"severity":"error","filePath":"lib/lti/tool_proxy_validator.rb","lineNumber":53,"sourceCode":"        hash.merge!(invalid_service_errors)\n        hash.merge!(invalid_security_contract_errors)\n        hash.merge!(invalid_security_profile_errors)\n        hash.merge!(restricted_security_profile_errors)\n      end\n    end\n\n    def validate!\n      if errors.present?\n        messages = []\n        messages << \"Invalid Capabilities\" if errors[:invalid_capabilities].present?\n        messages << \"Invalid Services\" if errors[:invalid_services].present?\n        messages << \"Invalid SecurityContract\" if errors[:invalid_security_contract].present?\n        messages << \"Invalid SecurityProfiles\" if errors[:invalid_security_profiles].present?\n        messages << \"Restricted SecurityProfiles\" if errors[:restricted_security_profiles].present?\n        last_message = messages.pop if messages.size > 1\n        message = messages.join(\", \")\n        message + \" and #{last_message}\" if last_message\n        raise Lti::Errors::InvalidToolProxyError.new message, errors\n      end\n    end\n\n    private\n\n    def invalid_capability_errors\n      messages = {}\n      if validator.errors[:invalid_message_handlers]\n        messages[:invalid_capabilities] = validator.errors[:invalid_message_handlers][:resource_handlers].map do |rh|\n          rh[:messages].map do |message|\n            message[:invalid_capabilities] || message[:invalid_parameters].pluck(:variable)\n          end\n        end.flatten\n      end\n      messages\n    end\n\n    def invalid_service_errors","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/lti/tool_proxy_validator.rb#L35-L71","documentation":"Lti::ToolProxyValidator#validate! accumulates error symbols (invalid capabilities, capabilities not allowed, invalid security contract/profiles, restricted security profiles) and, if any exist, raises Lti::Errors::InvalidToolProxyError with a joined human-readable message (e.g. 'Invalid Capabilities and Invalid SecurityContract') plus the errors hash. This guards tool proxy registration payloads against TC policy violations.","triggerScenarios":"Registering/validating a tool proxy (POST of the tool proxy JSON) whose capabilities, security profiles, or security contract fail any of the validator checks — wrong enabled_capabilities, unsupported security profile, or a security contract not matching Canvas requirements.","commonSituations":"Tool vendors shipping proxy JSON referencing capabilities Canvas does not allow; missing/wrong security profiles (e.g. not declaring SharedSecret or split secret correctly); registration payloads generated against a different LTI spec version.","solutions":["Read the raised message and errors hash; fix each named section (capabilities, security contract, security profiles) in the tool proxy JSON.","Only request capabilities Canvas allows for the placement/tool type; remove unsupported ones.","Declare valid security profiles (e.g. HalfSharedSecret/SharedSecret with correct services and actions).","Re-submit the registration with the corrected payload."],"exampleFix":"// before\n{ \"enabled_capability\": [\"vnd.instructure.some.unsupported.cap\"], ... }\n// after\n{ \"enabled_capability\": [\"basic-lti-launch-request\", \"Security.splitSecret\"], ... }","handlingStrategy":"validation","validationCode":"validator = Lti::ToolProxyValidator.new(tp_json, context)\nunless validator.errors.empty?\n  raise \"invalid tool proxy: #{validator.errors.keys.join(', ')}\"\nend","typeGuard":"def valid_security_profiles?(tp_json)\n  required = %w[SecurityProfile_1.0]\n  (tp_json['security_profile'] && required.any? { |r| tp_json['security_profile']['security_profile_name']&.include?(r) })\nend","tryCatchPattern":"begin\n  tp_service.process_tool_proxy_form\nrescue Lti::Errors::InvalidToolProxyError => e\n  render json: { errors: e.errors }, status: :bad_request\nend","preventionTips":["Validate tool proxy JSON against the LTI 2.0 spec before registration.","Only enable capabilities Canvas supports for the placement.","Keep vendor registration payloads under version control and test in beta first."],"tags":["lti","tool-proxy","validation","registration"],"backgroundTag":"schema-validation-failed","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"}