Una pequeña aplicación para macOS que tiene el permiso de leer carpetas protegidas para que los programas que leen su salida no tengan que tenerlo.
El Acceso total al disco (Acceso completo al disco en Latinoamérica) es el ajuste de privacidad de macOS que permite a un programa leer las carpetas que macOS protege, como la biblioteca de Notas de voz.
Algunas carpetas de un Mac están protegidas. Un programa que lee una de ellas necesita el Acceso total al disco. Ese ajuste no es un permiso sobre una sola carpeta. Abre todo lo que macOS protege de una vez.
La necesidad suele ser una sola carpeta. Un programa que quiera leer la biblioteca de Notas de voz necesitaría el Acceso total al disco para hacerlo directamente. Concedido a ese programa, el permiso llega también a cada intérprete o entorno de ejecución que cargue, a cada paquete instalado junto a él y a lo que esos paquetes carguen después. Nada de eso pidió el permiso.
paranco es lo más pequeño que conviene conceder. Lee una carpeta por ruta y escribe en una carpeta normal. Al programa que consume los archivos no se le concede nada, y sigue funcionando como antes.
Dos cosas que hace macOS y que dieron forma a esta herramienta.
Ambas se midieron. La primera explica por qué paranco es una aplicación y no un script. La segunda explica por qué distingue una carpeta vacía de una carpeta rechazada.
Primer hallazgo
Un acceso se atribuye a quien lo pidió.
macOS atribuye un acceso al proceso que lo pidió. Si controlas Finder o System Events desde una terminal, lo que se comprueba es el permiso de la terminal, no el de ellos. Un permiso no se puede tomar prestado controlando algo que lo tenga.
Por eso el titular tiene que ser una aplicación con identidad propia, iniciada por launchd o por una persona. Esa es Paranco.app. Su agente ejecuta el ejecutable propio de la aplicación con --agent, así que una sola concesión sirve a la ventana y al agente. La línea de comandos ejecuta el mismo núcleo desde una terminal, así que se le atribuye el permiso de la terminal. En una carpeta protegida, paranco check responde que está rechazada incluso después de conceder el permiso a la app. Eso es macOS atribuyendo bien el acceso.
Segundo hallazgo
Un rechazo que llega como carpeta vacía.
Si se le pregunta a través de System Events, una carpeta protegida no dice que no. Dice que está vacía.
16
Una carpeta normal con dieciséis archivos, consultada a través de System Events. El número que tiene.
0
La biblioteca de Notas de voz, consultada del mismo modo desde un proceso sin el permiso. No es un error ni un rechazo. Cero.
Cero es una respuesta plausible para una biblioteca, y por eso es la más engañosa. Así que cada carpeta que paranco comprueba recibe una de tres respuestas, y un rechazo lleva la frase que una persona necesita.
Respuesta
Qué significa
legible
La carpeta se puede listar y el recuento es real. Cero aquí es un cero real.
rechazada
La carpeta existe, y macOS no deja que este proceso la liste. El mensaje indica la carpeta y el ajuste que hay que cambiar.
ausente
No hay ninguna carpeta en esa ruta.
macOS deja a un programa preguntar si existe una carpeta protegida. Lo que se rechaza es el listado, y por eso los tres casos se pueden separar.
Una lista, no un archivo, decide qué se puede leer.
Cuatro hechos explican el diseño: qué es la lista, qué debe ser un destino, qué hace la copia y qué puede seguir haciendo un programa que se ejecute como tú.
La lista está compilada dentro
Una ruta nombra su origen por identificador, nunca por ruta. El identificador se resuelve solo contra Source.builtIn, una lista escrita en el código fuente. Hoy tiene una entrada, voice-memos. Nada en el equipo puede añadirle entradas: ni un archivo, ni la ventana, ni la línea de comandos. Añadir una carpeta significa editar el código y recompilar. Con una firma ad hoc, recompilar también significa volver a conceder el Acceso total al disco, de modo que quien revisa ve que se añade la carpeta y una persona vuelve a dar su consentimiento.
Un destino se comprueba antes de usarlo
Tiene que ser una carpeta real dentro de tu carpeta personal, fuera de ~/Library, no dentro de ningún origen, y en el propio disco de este Mac y no en uno de red o extraíble. Ningún enlace simbólico puede aparecer en su ruta, ni tampoco ningún componente . o ... Todo esto se decide antes de escribir un solo byte.
La copia no toma atajos
Cada archivo se escribe en un archivo nuevo, creado con O_EXCL y O_NOFOLLOW, con modo 0600, sin bit de ejecución y sin copiar atributos. Después se renombra para dejarlo en su sitio. Lo que ya haya con ese nombre, incluido un enlace simbólico, lo reemplaza el renombrado y nunca se sigue.
Lo que una escritura indebida todavía no puede hacer
Un programa que se ejecute como tú todavía puede escribir routes.json. Nada en el sistema de archivos lo impide. Lo que no puede hacer es nombrar un origen que esta compilación no incluya en la lista, ni dar una ruta como origen. Puede apuntar una ruta a otra carpeta de tu carpeta personal, pero solo a una que supere las mismas comprobaciones de destino. El límite es la lista de orígenes y no el archivo.
paranco no tiene código de red. El código fuente no contiene ninguna sesión de URL, ningún socket ni ninguna comprobación de actualizaciones. No escribe nada en un origen. Mail, Mensajes y Safari no están en la lista, y no se añadirán.
Rutas, y un agente que las ejecuta.
Una ruta nombra un origen por identificador, una carpeta de destino y los archivos que incluye. No hay forma de decir «todo lo que hay bajo Library». El programa tiene un permiso que podría leer cualquier cosa, y lo usa para leer una sola cosa.
name cómo se llama la ruta
sourceID un identificador de la lista compilada, p. ej. "voice-memos"
destination una carpeta normal, creada si no existe
extensions en minúsculas, sin el punto; vacío significa la lista del propio origen
enabled una ruta desactivada se conserva, pero nunca se ejecuta
Ejecutar una ruta
Un archivo con el mismo nombre y el mismo tamaño ya está en el destino, y no se toca.
Un archivo cuyo tamaño difiere se vuelve a copiar. Eso ocurre cuando una grabación todavía se estaba escribiendo la primera vez que se vio.
Un archivo cuyo tamaño cambia entre dos comprobaciones, separadas dos segundos, todavía se está escribiendo. Se omite hasta la siguiente ejecución.
Una ruta lleva los archivos normales del nivel superior de su origen. Omite los archivos ocultos y no entra en las subcarpetas.
El destino es el registro. Lo que hay allí es lo que se ha sacado, así que no hay una base de datos que pueda desincronizarse con la carpeta.
El agente
Un agente de launchd ejecuta cada ruta activada cada cinco minutos por defecto, y una vez al cargarse. Ejecuta el ejecutable propio de la aplicación con --agent. Cada ruta se ejecuta una vez, y el proceso termina. Imprime una línea solo cuando algo se ha copiado, ha fallado o ha sido rechazado. Esa salida va a agent.log, junto a routes.json.
El agente se instala desde la ventana, no desde la línea de comandos. El permiso pertenece al paquete de la aplicación, y la ventana es ese paquete.
El archivo de rutas
Las rutas están en ~/Library/Application Support/paranco/routes.json. Es un archivo JSON, así que se puede leer, comparar y editar con herramientas normales. Una ruta añadida en la ventana es la misma ruta que ven el agente y la línea de comandos. Un programa que lea este archivo debe seguir las notas de integración (en inglés).
Instalación
Necesitas macOS 14 o posterior, y una cadena de herramientas Swift 6 (Xcode 16 o las Command Line Tools correspondientes). No hay nada que descargar: la aplicación se compila desde el repositorio con un solo script.
git clone https://github.com/nerln/paranco
cd paranco
./build.sh
Eso deja Paranco.app en la carpeta del repositorio. La primera compilación genera también el icono, con make-icon.sh.
1
Concede el Acceso total al disco
En Ajustes del Sistema (Configuración del Sistema en Latinoamérica), abre Privacidad y seguridad, luego Acceso total al disco (Acceso completo al disco en Latinoamérica), y añade Paranco.app. Ningún programa que lea los archivos copiados lo necesita. Es el único permiso que necesitan sus rutas. Otros programas pueden necesitarlo para su propio trabajo, por ejemplo el servicio de registro de paratia, que es un programa aparte.
2
Abre la aplicación
open Paranco.app
3
Añade una ruta
Elige un origen de la lista, hoy Notas de voz, y una carpeta de destino propia, por ejemplo ~/Recordings. La ventana comprueba el origen y dice cuál de las tres respuestas ha recibido.
4
Instala el agente
Instálalo desde la ventana. A partir de entonces, el destino se llena solo en cada ejecución.
La línea de comandos
Compílalo con swift build -c release --product paranco. Eso deja el binario en .build/release/paranco. Los comandos siguientes usan paranco para ese binario.
Ejecutada desde una terminal, la línea de comandos usa el permiso de la terminal. Por eso un origen protegido aparece como rechazado allí, aunque se haya concedido el permiso a Paranco.app. Para rutas desde carpetas protegidas, usa la ventana o el agente. Instalar el agente no tiene forma de línea de comandos, a propósito.
swift test ejecuta las pruebas del núcleo. Cubren la copia, la regla de «ya está», el origen sin tocar, las comprobaciones del destino y la diferencia entre una carpeta rechazada y una vacía.
Límites
Estos límites también están en el README del proyecto (en inglés). Léelos antes de confiar en una garantía.
Solo macOS, por naturaleza. El permiso y las carpetas protegidas son de macOS.
Hoy hay un solo origen en la lista, la biblioteca de Notas de voz. Cualquier otra ruta hacia ella se puede escribir con paranco add o en el archivo. Un origen nuevo es un cambio de código.
El paquete tiene una firma ad hoc. Cada recompilación es una aplicación nueva para la lista de privacidad, así que hay que volver a conceder el Acceso total al disco después de cada compilación.
Todavía no hay Developer ID, así que no hay descarga notarizada. Se compila desde el repositorio.
Desde una terminal, la línea de comandos usa el permiso de la terminal. No puede leer un origen protegido, aunque se haya concedido acceso a la aplicación.
Un destino no debe ser un volumen cifrado mientras ese volumen está cerrado, por ejemplo una carpeta que protege paratia. La ruta es entonces una carpeta vacía en tu disco, y los archivos que se sacan allí se escriben en claro. paranco no puede ver los volúmenes cifrados.
Un destino es una carpeta normal. Cualquier programa que se ejecute como tú puede leerla. macOS pide su propio permiso para Escritorio, Documentos y Descargas.
La familia
paranco es uno de dos pequeños programas del mismo autor. Ambos funcionan en el mismo Mac. Ninguno llama al otro, y paranco no lee los archivos de paratia.
paratia mantiene cifradas las carpetas que eliges, de modo que un agente de programación no pueda leerlas mientras están bloqueadas, y guarda un registro de quién abre o cambia los archivos que vigilas. También bloquea a los agentes mientras una carpeta está abierta, y cubre a dos agentes que juntos hacen lo que ninguno puede hacer solo.
paranco tiene el único permiso que copia archivos fuera de una carpeta protegida, así que ningún programa que lea las copias tiene que tenerlo. Copia en una sola dirección, siguiendo las rutas que configuras, y sus orígenes están escritos en el programa y no en un archivo.
Cada programa tiene su propia entrada en la lista de macOS, y cada uno necesita su propio permiso.
Programa
Para qué se usa el Acceso total al disco
Tras una nueva compilación
paratia Paratia.app
Leer eventos del kernel a través de su servicio de registro
Quitar la entrada, añadirla de nuevo y reinstalar el servicio
paranco Paranco.app
Leer la carpeta de Notas de voz
Quitar la entrada y añadir Paranco.app de nuevo
Son dos entradas separadas. El permiso de paranco no hace innecesario el de paratia.
Los dos programas comparten un archivo, routes.json. paratia puede leerlo sin cambiarlo, mediante su comando paratia registro rotte-paranco, en las versiones que incluyen ese comando. El comando propone vigilar las carpetas en las que escriben las rutas, así puedes ver quién abre los archivos copiados. Sin paratia, paranco no cambia.
Ninguno de los dos programas necesita al otro para funcionar.
Para leer más
README (en inglés)Cómo funciona paranco, su modelo de seguridad y los pasos de instalación.
paratiaEl programa hermano: carpetas cifradas para agentes de programación, y un registro de accesos.
paratia en GitHubSu código fuente y los pasos de instalación. GPL-3.0.
Sobre el nombre
Un paranco es un aparejo: poleas y una cuerda para levantar una carga. El símbolo es una rueda, una cuerda y una carga. Junto a él, paratia es un mamparo estanco, un tabique que atraviesa el barco. Ambos son términos náuticos.