instructure/canvas-lms · error · Lti::Errors::InvalidToolProxyError

#

Error message

#{message}

What it means

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.

Solutions

  1. Read the raised message and errors hash; fix each named section (capabilities, security contract, security profiles) in the tool proxy JSON.
  2. Only request capabilities Canvas allows for the placement/tool type; remove unsupported ones.
  3. Declare valid security profiles (e.g. HalfSharedSecret/SharedSecret with correct services and actions).
  4. Re-submit the registration with the corrected payload.

Example fix

// before
{ "enabled_capability": ["vnd.instructure.some.unsupported.cap"], ... }
// after
{ "enabled_capability": ["basic-lti-launch-request", "Security.splitSecret"], ... }
Defensive patterns

Strategy: validation

Validate before calling

validator = Lti::ToolProxyValidator.new(tp_json, context)
unless validator.errors.empty?
  raise "invalid tool proxy: #{validator.errors.keys.join(', ')}"
end

Type guard

def valid_security_profiles?(tp_json)
  required = %w[SecurityProfile_1.0]
  (tp_json['security_profile'] && required.any? { |r| tp_json['security_profile']['security_profile_name']&.include?(r) })
end

Try / catch

begin
  tp_service.process_tool_proxy_form
rescue Lti::Errors::InvalidToolProxyError => e
  render json: { errors: e.errors }, status: :bad_request
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/1aedb893ab65fded. Report an issue: GitHub.

Appendix: source

Thrown at lib/lti/tool_proxy_validator.rb:53

        hash.merge!(invalid_service_errors)
        hash.merge!(invalid_security_contract_errors)
        hash.merge!(invalid_security_profile_errors)
        hash.merge!(restricted_security_profile_errors)
      end
    end

    def validate!
      if errors.present?
        messages = []
        messages << "Invalid Capabilities" if errors[:invalid_capabilities].present?
        messages << "Invalid Services" if errors[:invalid_services].present?
        messages << "Invalid SecurityContract" if errors[:invalid_security_contract].present?
        messages << "Invalid SecurityProfiles" if errors[:invalid_security_profiles].present?
        messages << "Restricted SecurityProfiles" if errors[:restricted_security_profiles].present?
        last_message = messages.pop if messages.size > 1
        message = messages.join(", ")
        message + " and #{last_message}" if last_message
        raise Lti::Errors::InvalidToolProxyError.new message, errors
      end
    end

    private

    def invalid_capability_errors
      messages = {}
      if validator.errors[:invalid_message_handlers]
        messages[:invalid_capabilities] = validator.errors[:invalid_message_handlers][:resource_handlers].map do |rh|
          rh[:messages].map do |message|
            message[:invalid_capabilities] || message[:invalid_parameters].pluck(:variable)
          end
        end.flatten
      end
      messages
    end

    def invalid_service_errors

View on GitHub (pinned to 1c9f0bb801)