A raíz de anteriores artículos como Acelera tu desarrollo en Spring Boot con GitHub Copilot y Explorando la colaboración en pair programming y GitHub Copilot para testing, mucha gente se pregunta qué información envía copilot a GitHub. Tomando como inspiración el debug de los prompts del artículo Integrando ChatGPT en DBeaver y observando la cantidad de gente trazando el flujo de información que envía copilot a través de un proxy empresarial (en su caso BurpSuite proxy), en este artículo se propone el uso de un proxy open source sencillo de usar como es mitmproxy para poder husmear que está pasando desde un punto de vista global y no tan centrado en la seguridad.
0. Índice de contenidos.
- Introducción.
- Entorno.
- Arrancando y usando mitmproxy.
- Configurando el entorno de desarrollo.
- Viendo resultados.
- Conclusiones.
- Referencias.
1. Introducción.
Según la wikipedia, «El anglicismo proxy (plural proxies) o servidor proxy, adaptado al español como proxi en una red informática, es un servidor —programa o dispositivo—, que hace de intermediario en las peticiones de recursos que realiza un cliente (A) a otro servidor (C).»
Con el uso de tecnologías como los asistentes virtuales, mucha gente se pregunta qué información viaja en el prompt hacia servidores externos. Usar herramientas como por ejemplo los proxys, nos es útil para ver el trasiego de peticiones entre cliente y servidor. Con un proxy, también podemos depurar otras aplicaciones para buscar fallos. Por ejemplo, si lo ponemos entre nuestro navegador y el servidor, veríamos todas las peticiones que se están realizando (aunque en este caso sería más recomendable empezar usando las herramientas para desarrolladores del propio navegador).
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 16′ (2,3 GHz 8-Core Intel Core i9 16 GB 2667 MHz DDR4).
- Sistema Operativo: Mac OS Ventura 13.3.1
- Visual Studio Code Version: 1.78.2 (Universal) con GitHub Copilot v1.86.82
- mitmproxy 9.0.1
3. Arrancando y usando mitmproxy.
Una de las cosas que más nos ha gustado de mitmproxy es su sencillez. Hasta en su definición es sencilla.
En su web se define como un proxy HTTPS interactivo gratuito y de código abierto. Ni más ni menos, justo lo que necesitamos.
También nos da las siguientes características:
- Una interfaz por línea de comandos
- Una interfaz web (será la que usemos nosotros)
- Un api en python
Por lo que puede ser un gran aliado en muchas ocasiones.
En su pagina, nos dan varias opciones a la hora de instalarlo. Bien sea con un ejecutable de windows, binarios linux, brew en osx, docker u otras alternativas
Nosotros vamos a usar Docker porque nos va a servir en cualquier plataforma y no dejamos nada instalado.
Para ejecutar el contenedor docker con la interfaz web lanzamos la siguiente línea en la terminal:
docker run --rm -it -p 8080:8080 -p 127.0.0.1:8081:8081 mitmproxy/mitmproxy:9.0.1 mitmweb --web-host 0.0.0.0
Para que el proxy funcione necesitamos exponer un puerto, que en nuestro caso vamos a hacer que coincida con el que usa por defecto. También necesitamos el puerto 8081 para la interfaz web. La interfaz web vamos a hacer que solo sea accesible en local. Por último, le pasamos en el entrypoint mitmweb –web-host 0.0.0.0 para que arranque la interfaz web y se quede a la escucha.
Tenemos el siguiente resultado:
[11:08:57.031] HTTP(S) proxy listening at *:8080.
[11:08:57.033] Web server listening at http://0.0.0.0:8081/
[11:08:57.357] No web browser found. Please open a browser and point it to http://0.0.0.0:8081/
Como vemos, nos dice que no hay ningún navegador apuntando al puerto 8081. Pues vamos a abrir en el navegador la url http://0.0.0.0:8081/
Al abrir el navegador vemos lo siguiente: una pantalla que nos da la bienvenida al proxy diciendo que configuremos el cliente para que use el proxy en el puerto 8080 en localhost y arriba unas barras de búsqueda.
4. Configurando el entorno de desarrollo.
Ahora que tenemos el servidor levantado, tenemos que decirle a las aplicaciones que lo usen. En nuestro caso vamos a configurar el entorno de desarrollo VS Code para que lo use.
En osx, en visual studio code nos vamos a Code -> Settings … -> Settings
En la barra de búsqueda, buscamos proxy, lo seleccionamos y lo configuramos.
Lo importante es que:
- En la URL tengamos http://localhost:8080
- Desactivar la casilla de Proxy Strict SSL
Por último reiniciamos el IDE para que aplique los cambios.
5. Viendo resultados.
Como proyecto de ejemplo vamos a usar la guía de springboot en la rama 2.7.
Una vez que reiniciamos, si tenemos la terminal abierta, veremos muchos mensajes de conexión y desconexión. No nos vamos a centrar en esos mensajes ni en verlos en la terminal, vamos a la interfaz web y vemos todas las peticiones:
De momento no tenemos nada de copilot. Cuando empezamos a escribir en el editor var message =
:
package com.example.springboot;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping(path = "/", produces = "application/json")
public ResponseEntity index() {
var message =
return new ResponseEntity("Greetings from Spring Boot!", HttpStatus.OK);
}
}
Vemos una petición post
a https://copilot-proxy.githubusercontent.com/v1/engines/copilot-codex/completions
:
En las cabeceras vemos que la autorización es de tipo bearer
y que tiene un request id
. También se identifica la máquina y la sesión de VS Code. Luego nos dicen la organización dentro de openai, el intent
y el user agent
. Por último tenemos las versiones de VS Code y del plugin de copilot.
- authorization:
- x-request-id:
- openai-organization: github-copilot
- vscode-sessionid:
- vscode-machineid:
- editor-version: vscode/1.78.2
- editor-plugin-version: copilot/1.86.82
- openai-intent: copilot-ghost
- content-type: application/json
- user-agent: GithubCopilot/1.86.82
{
"extra": {
"language": "java",
"next_indent": 8,
"prompt_tokens": 134,
"suffix_tokens": 20,
"trim_by_indentation": true
},
"max_tokens": 500,
"n": 1,
"nwo": "repositorio",
"prompt": "// Path: gs-spring-boot-boot-2.7/initial/src/main/java/com/example/springboot/HelloController.javanpackage com.example.springboot;nnimport org.springframework.http.HttpStatus;nimport org.springframework.http.ResponseEntity;nimport org.springframework.web.bind.annotation.GetMapping;nimport org.springframework.web.bind.annotation.RestController;nn@RestControllernpublic class HelloController {nn @GetMapping(path = "/", produces = "application/json")n public ResponseEntity index() {n",
"stop": [
"nnn"
],
"stream": true,
"suffix": "return new ResponseEntity("Greetings from Spring Boot!", HttpStatus.OK);n }nn}n",
"temperature": 0,
"top_p": 1
}
En la respuesta vemos que solo da espacios en blanco que es lo que tocaría.
data: {"id":"cmpl-7KSyA3V6FPmAAh4IHl1sgR8eKGJUe","model":"cushman-ml","created":1685112274,"choices":[{"text":"","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyA3V6FPmAAh4IHl1sgR8eKGJUe","model":"cushman-ml","created":1685112274,"choices":[{"text":" ","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyA3V6FPmAAh4IHl1sgR8eKGJUe","model":"cushman-ml","created":1685112274,"choices":[{"text":" ","index":0,"finish_reason":"stop","logprobs":null}]}
data: [DONE]
Pero si cogemos la siguiente petición que hace,
{
"extra": {
"language": "java",
"next_indent": 8,
"prompt_tokens": 135,
"suffix_tokens": 20,
"trim_by_indentation": true
},
"max_tokens": 500,
"n": 1,
"nwo": "repositorio",
"prompt": "// Path: gs-spring-boot-boot-2.7/initial/src/main/java/com/example/springboot/HelloController.javanpackage com.example.springboot;nnimport org.springframework.http.HttpStatus;nimport org.springframework.http.ResponseEntity;nimport org.springframework.web.bind.annotation.GetMapping;nimport org.springframework.web.bind.annotation.RestController;nn@RestControllernpublic class HelloController {nn @GetMapping(path = "/", produces = "application/json")n public ResponseEntity index() {n va",
"stop": [
"nnn"
],
"stream": true,
"suffix": "return new ResponseEntity("Greetings from Spring Boot!", HttpStatus.OK);n }nn}n",
"temperature": 0,
"top_p": 1
}
La respuesta es la siguiente:
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":"r","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":" message","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":" =","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":" \\"","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":"Greetings","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":" from","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":" Spring","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":" Boot","index":0,"finish_reason":null,"logprobs":null}]}
data: {"id":"cmpl-7KSyGXBPtWwZOCjpYTg7fDt69sMDy","model":"cushman-ml","created":1685112280,"choices":[{"text":"!\\";","index":0,"finish_reason":"stop","logprobs":null}]}
data: [DONE]
Vemos que en la request en el prompt le dice el nombre del fichero, y las líneas que están por arriba. En el suffix le da las líneas que están por debajo. El campo nwo
es el repositorio de git. Como vemos el prompt es menos elaborado que en otros artículos que hemos visto de LLM donde le podemos dar más contexto y tener una conversación. Con el plugin, depende de lo que tengamos arriba y abajo del código, la respuesta será mejor o peor. También observamos que la temperatura es 0, por lo que va a ser poco creativo y, en teoría, predictivo, pero haciendo pruebas hemos visto que da respuestas distintas.
Al final la recomendación que nos ha dado es poner "Greetings from Spring Boot!"
.
Como veíamos en Prompt Engineering para desarrolladores… y no tan desarrolladores, una de las ventajas de usar un bot conversacional es que puedes ir mejorando los prompts, cosa que no ocurre con el plugin.
Viendo lo que envía el plugin, podemos intentar ponérselo algo más fácil, pero mas que el plugin adaptarse a nosotros, seriamos nosotros los que nos estaríamos adaptando al plugin.
6. Conclusiones.
- Es interesante tener en nuestro radar herramientas como un proxy, ya que nos puede ayudar al desarrollo, no solo como herramienta típicamente de infraestructura.
- Hemos visto las peticiones que hace copilot y la información que envía. Dependiendo del proyecto puede que tenga información sensible que no queremos exponer. No es lo mismo estar haciendo un hola mundo en nuestra cuenta de GitHub, que un proyecto profesional. Para tener una mejor configuración, podemos ver como configurarlo. La idea de este artículo es tener la herramienta para poder ver las peticiones y que cada uno tome sus decisiones y conclusiones.
- Observando cómo son los prompts generados por el plugin de copilot, ahora somos capaces de ver por qué a veces sus respuestas no son muy óptimas y nos ayuda a entender mejor su uso.