Tapping ChatGPT without handshakes
Creating a Client for accessing ChatGPT simple conversation model anonymously
It was Saturday noon that I sat down with an empty and relaxed mind, after waking up a bit late than usual and completing the chores. I had a curiosity popping in my head for a couple of days as to how OpenAI had made the use of ChatGPT anonymous to the world.
The Twitter post from Simon Wilson about scrapping ChatGPT made me wonder if is it possible to do that now? since ChatGPT access is made anonymous.
I opened an incognito mode and to my surprise, it was not a joke despite being an announcement made on April 1st. ChatGPT was accessible without having to create an account or authenticate into the application. Users won't be able to store or share conversations, that’s the point of being anonymous right? But the major catch in it is that the conversation will be used to train the model. This made me wonder, is the data from the authenticated and authorized users of ChatGPT not used to train itself, or is it just condolence? Who knows, but hey ChatGPT without login that’s a big deal.
I opened the network tab and the first request I saw was to the `chat.openai.com` as expected. This was the request that initiates the authentication and validation of the request, basically sending some necessary cookies like CSRF tokens, API-specific tokens, etc. These cookies are necessary for the subsequent requests in the actual prompting and sending of the messages.
This was not giving out any response as such but was setting important cookies via the response, so this was just like initializing the ChatGPT client request.
Then on the browser, I sent a prompt to the ChatGPT application as usual, after some digging in the request and response in the network tab, I was able to find the endpoint that sent and received the generated message to the given prompt.
The request was to the /backend-anon/conversation
endpoint as a POST request with a specific structure. This looked easy to get a script for getting a quick response from GPT.
I quickly started with a simple Python script which looked like this:
import requests
url = 'https://chat.openai.com/backend-anon/conversation'
headers = {
'accept': 'text/event-stream',
'accept-language': 'en-GB,en;q=0.8',
'content-type': 'application/json',
'cookie': '*****',
'oai-language': 'en-US',
'openai-sentinel-chat-requirements-token': '*****',
'origin': 'https://chat.openai.com',
'referer': 'https://chat.openai.com/',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
}
data = {
"action": "next",
"messages": [{
"id": "aaa2975f-214f-4b93-ac55-77aa21aeced5",
"author": {"role": "user"},
"content": {"content_type": "text", "parts": ["hi"]},
"metadata": {}
}],
"parent_message_id": "aaa1ee93-2920-4208-a067-599c1d544b7c",
"model": "text-davinci-002-render-sha",
"timezone_offset_min": -330,
"history_and_training_disabled": True,
"conversation_mode": {"kind": "primary_assistant"},
"force_paragen": False,
"force_paragen_model_slug": "",
"force_nulligen": False,
"force_rate_limit": False,
}
response = requests.post(url, headers=headers, json=data)
print(response.text)
And sure enough, this script was a good starting point to validate my idea of creating a ChatGPT anonymous client, it worked for a few minutes and then it stopped because I was hitting too much time on the same conversation message-id as well as the cookies used are the same too.
So, this needed to be fixed, because this was the hacky solution just the MVP for the initial idea and ticking the feasibility of creating it.
Next up was to configure the client itself to set the message IDs and cookies from an actual request to their backend.
I added the first request to the `chat.openai.com` and created a client for sending the request, by doing that I could store the cookies set from the initial request to be carried to the request for the prompt i.e. `/backend-anon/conversation` endpoint.
However, I found that I also needed to set the header `openai-sentinel-chat-requirements-token` dynamically for the chat specifically. Sentinal tokens would be a way for OpenAI API to flag a request as authentic or non-authorized. They are generated each time for a new chat, so these are obtained from a new request to the `/backend-anon/sentinel/chat-requirements` endpoint.
This needed two more requests, one to get the cookies for authorizing the user-agent and the other for authenticating as a genuine request from ChatGPT with the help of sentinel tokens.
So the final script looked something like this:
The first request to set cookies from the base endpoint of ChatGPT
import requests
import json
import uuid
BASE_URL = "https://chat.openai.com"
# First request to get the cookies
headers = {
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
}
request_client = requests.session()
response = request_client.get(BASE_URL, headers=headers)
set_cookie = response.cookies.get_dict()
# format the cookies to be sent in the headers of subsequent requests
cookies = "; ".join([f"{key}={value}" for key, value in set_cookie.items()])
The cookies set from this API include the following:
__cf_bm
_cfuvid
__Host-next-auth.csrf-token
__Secure-next-auth.callback-url
__cflb
The second request is to get the sentinel token for the chat.
# second request to get the OpenAI's Sentinel token
chat_req_url = f"{BASE_URL}/backend-anon/sentinel/chat-requirements"
headers = {
"accept": "text/event-stream",
"accept-language": "en-GB,en;q=0.8",
"content-type": "application/json",
"oai-device-id": set_cookie.get("oai-did"),
"oai-language": "en-US",
"origin": "https://chat.openai.com",
"referer": "https://chat.openai.com/",
"user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
}
chat_req_res = request_client.post(chat_req_url, headers=headers)
chat_req_token = ""
if chat_req_res.status_code == 200:
# grab the sentinel token
chat_req_token = chat_req_res.json().get("token")
The third Request is for sending the prompt for generating the message.
# third request to get the response to the prompt
url = f"{BASE_URL}/backend-anon/conversation"
headers = {
**headers,
"openai-sentinel-chat-requirements-token": chat_req_token,
}
parent_id = str(uuid.uuid4())
msg_id = str(uuid.uuid4())
prompt = input("Prompt: ")
data = {
"action": "next",
"messages": [
{
"id": f"{msg_id}",
"author": {"role": "user"},
"content": {"content_type": "text", "parts": [f"{prompt}"]},
"metadata": {},
}
],
"parent_message_id": f"{parent_id}",
"model": "text-davinci-002-render-sha",
"timezone_offset_min": -330,
"history_and_training_disabled": True,
"conversation_mode": {"kind": "primary_assistant"},
"force_paragen": False,
"force_paragen_model_slug": "",
"force_nulligen": False,
"force_rate_limit": False,
}
response = request_client.post(url, headers=headers, json=data)
data = response.text
print(response.status_code)
msgs = data.split("\ndata:")
if len(msgs) > 1:
resp = json.loads(msgs[-2])
# grab the first text content in the message
print(resp["message"]["content"]["parts"][0])
This was the gist of how I came up with this solution for creating an anonymous client for ChatGPT, it has a Python package/CLI as the name anonymous-chatgpt.
You can observe, the parameter for the prompt request body (3rd request) as `history_and_training_disabled` which should disable and share the data from this conversation to train the model itself, but who knows what happens really? I have set it to `False` but it might still pass the data for training and track the data, that’s a possibility and it might be just a parameter without a distinct else condition. Just like we have a joke for cookies, if enabled track data, else track data.
Here is the demo:
Links:
Anonymous ChatGPT GitHub Repository
Python Package - PyPI
Hope you enjoyed this first stroll kind of a post, just exploring the unexplored ideas and thinking through the intuitions. Thanks for reading.
Have a nice day, Happy Coding and dreaming :)