Files
takana/scripts/test-atuq-boveda-coherente.py
T
sergioandsergio 6f0ba974c7 feat(atuq): la boveda de la suite guarda las contrasenas, y el gestor de Gecko se aparta
La decima extension de atuq. Ofrece la credencial que corresponde al sitio
abierto y la pone en el formulario, pero NO puede sacar una contrasena de la
boveda por su cuenta: pregunta cuales corresponden —y eso contesta titulo y
usuario, nada mas— y la contrasena sale por otro verbo que pregunta en el
escritorio antes de contestar. Si la extension queda comprometida, o si una
pagina consigue hablarle, lo que obtiene es la lista de titulos de los sitios
que coinciden con su propia direccion.

La direccion siempre la pone el chrome y nunca la pagina. Lo unico que el
trozo que corre dentro del sitio aporta es que HAY un formulario y lo que el
usuario tipeo; si pudiera decir de quien es la pagina, podria decir que es el
banco.

Y la extension no decide nada: quien sabe que credencial va en que sitio es
la funcion del otro lado del cable. Ponerlo en JS seria reimplementar el
original y, peor, poner en la pagina la decision de a quien se le entrega una
contrasena.

EL GESTOR DE GECKO SE APAGA, pero como valor de arranque y no como politica.
Dos gestores peleando por el mismo campo es la peor experiencia que puede
tener alguien que solo quiere entrar a su correo: el sitio se rellena dos
veces, o ninguna, y no hay forma de saber cual tiene la buena. De fabrica
manda la boveda; el que prefiera el de Gecko lo prende y listo. Prohibirselo
seria decidir por el, que es lo que este fichero dice en su cabecera que atuq
no quiere ser.

Y un guardian nuevo, que corre en un segundo y sin construir nada. Una
extension esta enchufada en CUATRO lugares distintos y ninguno da error si
falta: el identificador que ella declara, la politica que la instala, el
permiso para hablarle al host, y las preferencias que la acompanan. Si falta
la politica no se instala. Si falta el permiso se instala y se conecta a
nada, en silencio, y la insignia no aparece nunca. Si faltan las preferencias
los dos gestores se pelean. Ninguno de los tres se ve como un error: se ven
como "no anda". El guardian los mira todos y trae su control negativo, que le
saca el permiso del host y exige que lo detecte.

No reemplaza al guardian de metal —servidor, navegador real, login real, el
dialogo a la vista—: lo precede, y ahorra descubrir construyendo que faltaba
un renglon en un JSON.

Los cinco verbos del otro lado ya estan en tawasuyu (bc6903f9e), con trece
tests que leen el cable.
2026-09-15 10:08:20 -04:00

144 lines
5.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""¿La bóveda está enchufada ENTERA, o a medias? (SDD-BOVEDA §8, D7)
python3 scripts/test-atuq-boveda-coherente.py
python3 scripts/test-atuq-boveda-coherente.py --negative-control
── QUÉ MIRA, Y POR QUÉ ESTE GUARDIÁN EXISTE APARTE ───────────────────────────────────────────
Una extensión de `atuq` está enchufada en CUATRO lugares distintos, y ninguno da error si falta:
manifest.json el id que la extensión declara
distribution/policies.json la política que la instala
native-messaging/*.json el permiso para hablarle al host
atuq.cfg las preferencias que la acompañan
Si falta la política, la extensión no se instala y el usuario ve un navegador sin la función. Si
falta el permiso del host, la extensión se instala y **se conecta a nada** —`connectNative` falla
en silencio y la insignia no aparece nunca—. Si faltan las preferencias, la bóveda y el gestor de
Gecko se pelean por el mismo campo. Ninguno de los tres se ve como un error: se ven como «no anda».
Éste NO reemplaza al guardián de metal (servidor HTTP, navegador real, login real, el diálogo de
consentimiento a la vista). Lo precede: mide lo que se puede medir sin construir nada, en un
segundo, y ahorra descubrir en metal un renglón que faltaba en un JSON.
`--negative-control` saca el permiso del host de una copia en memoria y exige que esto lo detecte.
Sin él, un guardián que siempre dice que sí se vería idéntico a uno que funciona.
"""
import json
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ATUQ = os.path.join(ROOT, "recipes/atuq")
ID = "boveda@atuq.tawasuyu"
# Las preferencias de D7: de fábrica manda la bóveda, pero el usuario puede volver atrás.
PREFS = ["signon.rememberSignons", "signon.autofillForms"]
fallas = []
def mal(msg):
fallas.append(msg)
print(f" ✗ {msg}")
def bien(msg):
print(f" ✓ {msg}")
def leer(p):
with open(os.path.join(ATUQ, p), encoding="utf-8") as f:
return f.read()
def revisar(permisos_host):
# 1. La extensión existe y declara el id que todos los demás nombran.
try:
m = json.loads(leer("extensions/boveda/manifest.json"))
except FileNotFoundError:
mal("no existe extensions/boveda/manifest.json")
return
declarado = m.get("browser_specific_settings", {}).get("gecko", {}).get("id")
if declarado != ID:
mal(f"el manifest declara {declarado!r} y el resto nombra {ID!r}")
else:
bien(f"la extensión declara {ID}")
# 2. Los permisos que necesita para poder hacer su trabajo.
for p in ("nativeMessaging", "tabs"):
if p not in m.get("permissions", []):
mal(f"le falta el permiso {p!r}: sin eso no puede {'hablarle al host' if p == 'nativeMessaging' else 'saber qué página mira el usuario'}")
if not m.get("content_scripts"):
mal("sin content script no puede rellenar ningún campo")
if "background" not in m:
mal("sin fondo no hay quién hable con el host")
# 3. Los archivos que el manifest promete.
for rel in [m.get("background", {}).get("scripts", []), *[c.get("js", []) for c in m.get("content_scripts", [])]]:
for f in rel:
if not os.path.exists(os.path.join(ATUQ, "extensions/boveda", f)):
mal(f"el manifest nombra {f} y el archivo no está")
# 4. La política que la instala.
pol = json.loads(leer("distribution/policies.json"))
ext = pol.get("policies", {}).get("ExtensionSettings", {})
if ID not in ext:
mal("no está en ExtensionSettings: el navegador no la va a instalar")
elif ext[ID].get("installation_mode") != "normal_installed":
mal(f"se instala en modo {ext[ID].get('installation_mode')!r}, que no la deja activada")
else:
bien("la política la instala")
# 5. El permiso para hablarle al host. Éste es el que falla en SILENCIO.
if ID not in permisos_host:
mal("no está en allowed_extensions del host: se instala y no se conecta a nada")
else:
bien("el host la deja hablar")
# 6. Las preferencias de D7, y que sean `defaultPref` y no `lockPref`.
cfg = leer("atuq.cfg")
for p in PREFS:
if not re.search(rf'defaultPref\("{re.escape(p)}"', cfg):
mal(f"falta defaultPref({p!r}): el gestor de Gecko va a pelear por el mismo campo")
if re.search(rf'lockPref\("{re.escape(p)}"', cfg):
mal(f"{p} está como lockPref: eso decide POR el usuario, y atuq no hace eso")
if not fallas:
bien("las preferencias están, y como valores de arranque y no como cárcel")
# 7. Y lo que la extensión NO tiene que hacer: decidir ella qué credencial va.
fondo = leer("extensions/boveda/fondo.js")
if "sender.tab.url" not in fondo:
mal("el fondo no toma la dirección del chrome: una página podría decir que es el banco")
else:
bien("la dirección la pone el navegador, no la página")
def main():
control = "--negative-control" in sys.argv
host = json.loads(leer("native-messaging/puriy_costura.json"))
permisos = list(host.get("allowed_extensions", []))
if control:
print("⚠ control negativo: se le saca el permiso del host")
permisos = [x for x in permisos if x != ID]
print(f"── la bóveda en atuq ──")
revisar(permisos)
if control:
if any("allowed_extensions" in f for f in fallas):
print("\n✓ el control negativo lo detectó")
return 0
print("\n✗ el control negativo NO lo detectó: este guardián no mide nada")
return 1
if fallas:
print(f"\n{len(fallas)} problema(s)")
return 1
print("\n✓ la bóveda está enchufada entera")
return 0
if __name__ == "__main__":
raise SystemExit(main())