"""Lee un secreto del gestor de contrasenas `pass` (passwordstore.org). Ejecuta `pass show ` como subproceso (lista de args, nunca shell=True) y devuelve la linea solicitada del secreto. Por convencion de pass, la primera linea es la contrasena y las lineas siguientes son metadata (usuario, URL, notas, etc.). El valor devuelto es sensible: esta funcion NUNCA lo logea. El caller es responsable de tratarlo como secreto (no imprimirlo, no persistirlo en claro). """ import subprocess def pass_get_secret(path: str, *, line: int = 1, timeout_s: float = 10.0) -> dict: """Lee una linea de un secreto del password store (pass). Args: path: ruta del secreto dentro del store (p.ej. "gitea/dataforge-git-token"). Es el argumento que recibiria `pass show `. line: numero de linea a devolver, 1-indexed. line=1 (default) es la primera linea = la contrasena por convencion de pass. line=N devuelve la linea N para metadata multilinea. timeout_s: timeout del subproceso en segundos. Returns: Dict. En exito: ``{"status": "ok", "value": str}`` con la linea pedida sin el salto de linea final. En error (sin lanzar): ``{"status": "error", "error": str}`` para: pass no instalado, entry inexistente / fallo de pass (returncode != 0), o linea fuera de rango. """ try: proc = subprocess.run( ["pass", "show", path], capture_output=True, text=True, timeout=timeout_s, ) except FileNotFoundError: return {"status": "error", "error": "pass not installed"} except subprocess.TimeoutExpired: return {"status": "error", "error": f"pass timed out after {timeout_s}s"} if proc.returncode != 0: return {"status": "error", "error": (proc.stderr or "").strip()} # `pass show` termina con un salto de linea; splitlines lo absorbe. lines = proc.stdout.splitlines() if line < 1 or line > len(lines): return {"status": "error", "error": f"line {line} out of range"} return {"status": "ok", "value": lines[line - 1]}