langgenius / dify

Dify is an open-source LLM app development platform. Dify's intuitive interface combines AI workflow, RAG pipeline, agent capabilities, model management, observability features and more, letting you quickly go from prototype to production.
https://dify.ai
Other
45.53k stars 6.4k forks source link

Embeding chatbot with iframe will expose the url #7742

Closed taowang1993 closed 1 week ago

taowang1993 commented 2 weeks ago

Self Checks

Dify version

0.7.2

Cloud or Self Hosted

Cloud

Steps to reproduce

If I embed this chatbot, technical users can just inspect the code and find the url. Then, they can bypass paywall and use the chatbot. How to prevent this?

✔️ Expected Behavior

No response

❌ Actual Behavior

No response

dosubot[bot] commented 2 weeks ago

I found a similar open issue: Embedding Chatbot Will Expose URL, which was last updated on August 27, 2024 [1].

To prevent technical users from inspecting the code and finding the URL when embedding a chatbot with an iframe, you can obfuscate the URL and use server-side validation to ensure that only authorized users can access the chatbot. Here is a basic approach to achieve this:

  1. Obfuscate the URL: Encode the URL or use a token that is validated server-side.
  2. Server-side Validation: Ensure that the server validates the token or session to confirm that the user has access rights.

Here is an example of how you can modify the existing code to include these steps:

Step 1: Obfuscate the URL

You can use a function to encode the URL or generate a token that will be validated on the server.

async function getObfuscatedUrl(token, inputs) {
  const params = new URLSearchParams(inputs);
  const baseUrl = `https://${config.isDev ? "dev." : ""}udify.app`;
  const url = `${baseUrl}/chatbot/${token}?${params}`;

  // Encode the URL
  const encodedUrl = btoa(url);
  return encodedUrl;
}

Step 2: Modify the embedChatbot function to use the obfuscated URL

async function embedChatbot() {
  if (!config || !config.token) {
    console.error(`${configKey} is empty or token is not provided`);
    return;
  }

  const inputs = await getCompressedInputsFromConfig();
  const obfuscatedUrl = await getObfuscatedUrl(config.token, inputs);

  // Use the obfuscated URL
  const iframeUrl = `https://your-server.com/validate?url=${obfuscatedUrl}`;

  function createIframe() {
    const iframe = document.createElement("iframe");
    iframe.allow = "fullscreen;microphone";
    iframe.title = "dify chatbot bubble window";
    iframe.id = iframeId;
    iframe.src = iframeUrl;
    iframe.style.cssText = `
      border: none; position: fixed; flex-direction: column; justify-content: space-between;
      box-shadow: rgba(150, 150, 150, 0.2) 0px 10px 30px 0px, rgba(150, 150, 150, 0.2) 0px 0px 0px 1px;
      bottom: 5rem; right: 1rem; width: 24rem; max-width: calc(100vw - 2rem); height: 40rem;
      max-height: calc(100vh - 6rem); border-radius: 0.75rem; display: flex; z-index: 2147483647;
      overflow: hidden; left: unset; background-color: #F3F4F6;
    `;

    document.body.appendChild(iframe);
  }

  createButton();
}

Step 3: Server-side Validation

On your server, you should have an endpoint that validates the obfuscated URL and returns the actual URL if the user is authorized.

from flask import Flask, request, jsonify
import base64

app = Flask(__name__)

@app.route('/validate', methods=['GET'])
def validate():
    obfuscated_url = request.args.get('url')
    if not obfuscated_url:
        return jsonify({"error": "Invalid request"}), 400

    # Decode the URL
    decoded_url = base64.b64decode(obfuscated_url).decode('utf-8')

    # Perform your validation logic here
    if user_is_authorized():
        return jsonify({"url": decoded_url})
    else:
        return jsonify({"error": "Unauthorized"}), 403

def user_is_authorized():
    # Implement your authorization logic here
    return True

if __name__ == '__main__':
    app.run()

Additionally, you can use the Dify platform's built-in capabilities to manage the embedding of chatbots securely. Dify provides various embedding options such as iframe, scripts, and Chrome plugins, which can be customized and secured as needed. Here is an example of how you can use Dify's embedding options:

const OPTION_MAP = {
  iframe: {
    getContent: (url: string, token: string) =>
      `<iframe
 src="${url}/chatbot/${token}"
 style="width: 100%; height: 100%; min-height: 700px"
 frameborder="0"
 allow="microphone">
</iframe>`,
  },
  scripts: {
    getContent: (url: string, token: string, primaryColor: string, isTestEnv?: boolean) =>
      `<script>
 window.difyChatbotConfig = {
  token: '${token}'${isTestEnv
  ? `,
  isDev: true`
  : ''}${IS_CE_EDITION
  ? `,
  baseUrl: '${url}'`
  : ''}
 }
</script>
<script
 src="${url}/embed.min.js"
 id="${token}"
 defer>
</script>
<style>
  #dify-chatbot-bubble-button {
    background-color: ${primaryColor} !important;
  }
</style>`,
  },
  chromePlugin: {
    getContent: (url: string, token: string) => `ChatBot URL: ${url}/chatbot/${token}`,
  },
}

By following these steps and leveraging Dify's features, you can help prevent technical users from easily inspecting the code and finding the URL, thus avoiding bypassing the paywall [2][3].

To continue talking to Dosu, mention @dosu.

crazywoola commented 1 week ago

This is an expected behavior.

taowang1993 commented 1 week ago

Is it possible to configure CORS to only allow my own domain to embed the chatbot?

If others find out my chatbot url, they can embed it on their website and consume my tokens.

taowang1993 commented 1 week ago

This is an expected behavior.

Is this going to be fixed in near future? I don't want others embed my chatbot.

GoulartNogueira commented 1 week ago

It will probably not be "fixed" as it's not "broken". That's how iframe works, not an issue with Dify.

If you want to protect your chatbot, I suggest one of 2 ways:

  1. Deploy your application with another approach other than iframe.
  2. Keep using iframe, but use some security method inside dify to make sure the user is authenticated. For example: Create a key, set a lifetime to it, store it in your backend. Inject it into the iframe with window.difyChatbotConfig. Then inside your bot, before calling any LLM you must always call an API to check if the key is valid.

Either way, you must pick some kind of complexity. I'd recommend the first.

taowang1993 commented 1 week ago

Flowise has this secure approach of embedding chatbot.

<flowise-fullchatbot></flowise-fullchatbot>
<script type="module">
    import Chatbot from "https://cdn.jsdelivr.net/npm/flowise-embed/dist/web.js"
    Chatbot.init({
        chatflowid: "f5adc8af-d9e3-4735-af79-886f237",
        apiHost: "https://taowang1993-flowise.hf.space",
    })
</script>

Then, you can set up Allowed Domains for embedding your chatbot. So, only your website can embed your chatbot.

image

I wish Dify has this as well. I would love to make contributions, but I am not very technical.