2026-02-13 01:27:38 -05:00
|
|
|
#!/usr/bin/env python3
|
2026-03-11 23:15:07 -04:00
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
2026-02-13 01:27:38 -05:00
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
[FIX] script: différer les annotations, la 12 tourne en Python 3.7
La migration lance ses outils avec le venv de la version Odoo courante.
Au premier palier c'est celui de la 12, en 3.7, où « dict | None » (3.10)
et « tuple[str, str] » (3.9) sont ÉVALUÉS au chargement du module. La
restauration du zip mourait donc sur un TypeError avant d'avoir rien
fait, dans execute.py — importé par db_restore.py.
`from __future__ import annotations` existe depuis 3.7 et le dépôt s'en
sert déjà dans treize fichiers. Un test le vérifie maintenant sur toute
la fermeture d'imports des outils que le pilote lance, points d'entrée
lus dans le pilote pour que le script ajouté demain soit couvert.
--- EN ---
The migration runs its tools with the venv of the current Odoo version.
At the first step that is 12's, on 3.7, where « dict | None » (3.10) and
« tuple[str, str] » (3.9) are EVALUATED when the module loads. Restoring
the zip therefore died on a TypeError before doing anything at all, in
execute.py — imported by db_restore.py.
`from __future__ import annotations` has existed since 3.7 and the repo
already uses it in thirteen files. A test now checks the whole import
closure of the tools the driver launches, with the entry points read
from the driver so tomorrow's script is covered too.
Assisted-by: Claude Opus 5
2026-08-23 03:13:58 -04:00
|
|
|
# Annotations différées : la migration charge ce module sous le Python
|
|
|
|
|
# d'Odoo 12 — 3.7 — où « dict | None » et « tuple[str, str] » n'existent
|
|
|
|
|
# pas encore. Sans ceci, l'annotation est ÉVALUÉE au chargement et la
|
|
|
|
|
# migration meurt sur un TypeError avant d'avoir rien fait.
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
[FIX] script execute : afficher l'invite sans attendre le saut de ligne
« readline » ne rend la main qu'au saut de ligne : une invite qui n'en
porte pas restait retenue jusqu'à la ligne suivante, écrite après la
réponse. Toute question « [o/N] » d'une commande du menu se voyait donc
après coup, et l'on y répondait à l'aveugle. La lecture se fait par blocs,
le reliquat part à l'écran aussitôt et stdout est vidé ; un décodeur
incrémental protège les accents coupés entre deux lectures.
Vérifié : invite visible avant la frappe, sans double affichage ; 13 cas de
non-régression ; 4546 tests, seuls les 9 échecs préexistants de qemu_proxmox.
--- EN ---
`readline` only returns at a newline: a prompt carrying none stayed held
back until the next line, written after the answer. Every "[o/N]" question
asked by a menu command was therefore seen after the fact, and answered
blind. Reading now happens in chunks, the trailing fragment reaches the
screen at once and stdout is flushed; an incremental decoder protects
accented characters split across two reads.
Checked: prompt visible before typing, with no double display; 13 regression
cases; 4546 tests, only the 9 pre-existing qemu_proxmox failures.
Assisted-by: Claude Opus 5
2026-09-04 00:24:09 -04:00
|
|
|
import codecs
|
2026-02-13 01:27:38 -05:00
|
|
|
import datetime
|
|
|
|
|
import logging
|
|
|
|
|
import os
|
[FIX] execute: redact the secrets before printing a command
CodeQL flagged five clear-text logging alerts here, and they are real:
todo.py and kdbx_manager.py build « --default_password_auth '<KeePass
password>' » and db_restore.py « --master_password=… », while this file
printed the command before and after every run and logged it on error. The
« if "password" in command » guard covered one branch out of six.
Redaction sits at the display point, not at construction: six outputs here
against commands built all over the repository. Only the value goes, never
the option name, and the RETURNED command stays clear — « [1] redo the
command » needs it. Verified that PreferredAuthentications=password survives.
--- FR ---
CodeQL signalait ici cinq journalisations en clair, et elles sont réelles :
todo.py et kdbx_manager.py construisent « --default_password_auth '<mot de
passe KeePass>' » et db_restore.py « --master_password=… », tandis que ce
fichier affichait la commande avant et après chaque exécution et la
journalisait en erreur. Le garde « if "password" in command » couvrait une
branche sur six.
Le caviardage est au point d'affichage, non à la construction : six sorties
ici, contre des commandes bâties partout dans le dépôt. Seule la valeur
part, jamais le nom de l'option, et la commande RENVOYÉE reste en clair —
« [1] refaire la commande » en dépend. Vérifié que
PreferredAuthentications=password reste intact.
Assisted-by: Claude Opus 5
2026-08-07 03:35:20 -04:00
|
|
|
import re
|
2026-02-13 01:27:38 -05:00
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
|
2026-02-14 05:53:31 -05:00
|
|
|
try:
|
|
|
|
|
import humanize
|
2026-02-19 12:27:35 -05:00
|
|
|
except ModuleNotFoundError as e:
|
2026-02-14 05:53:31 -05:00
|
|
|
humanize = None
|
2026-02-13 01:27:38 -05:00
|
|
|
|
2026-03-10 03:06:07 -04:00
|
|
|
VENV_ERPLIBRE = ".venv.erplibre"
|
2026-02-13 01:27:38 -05:00
|
|
|
|
[FIX] execute: redact the secrets before printing a command
CodeQL flagged five clear-text logging alerts here, and they are real:
todo.py and kdbx_manager.py build « --default_password_auth '<KeePass
password>' » and db_restore.py « --master_password=… », while this file
printed the command before and after every run and logged it on error. The
« if "password" in command » guard covered one branch out of six.
Redaction sits at the display point, not at construction: six outputs here
against commands built all over the repository. Only the value goes, never
the option name, and the RETURNED command stays clear — « [1] redo the
command » needs it. Verified that PreferredAuthentications=password survives.
--- FR ---
CodeQL signalait ici cinq journalisations en clair, et elles sont réelles :
todo.py et kdbx_manager.py construisent « --default_password_auth '<mot de
passe KeePass>' » et db_restore.py « --master_password=… », tandis que ce
fichier affichait la commande avant et après chaque exécution et la
journalisait en erreur. Le garde « if "password" in command » couvrait une
branche sur six.
Le caviardage est au point d'affichage, non à la construction : six sorties
ici, contre des commandes bâties partout dans le dépôt. Seule la valeur
part, jamais le nom de l'option, et la commande RENVOYÉE reste en clair —
« [1] refaire la commande » en dépend. Vérifié que
PreferredAuthentications=password reste intact.
Assisted-by: Claude Opus 5
2026-08-07 03:35:20 -04:00
|
|
|
# Une commande construite ailleurs peut porter un secret en clair : todo.py et
|
[FIX] security: keep the master password out of the command line
Redacting what we print treats the symptom. The password was still an
argument, and /proc/<pid>/cmdline is readable by EVERY user on the
machine for as long as the command runs -- an exposure no filter reaches.
It now travels in MASTER_PWD. The probe sets it on the child it spawns;
once accepted it is placed in this process's environment, so every later
call inherits it without argv ever carrying it. /proc/<pid>/environ is
readable only by its owner.
Worth knowing for anyone reading the old code: the option was appended to
every invocation, but odoo's db command reads it in the drop branch
alone. list, restore and clone were carrying a secret they never used.
The redaction stays. It is the last line, not the first, and other
options still put secrets on command lines.
--- FR ---
Caviarder ce qu'on affiche traite le symptôme. Le mot de passe restait un
argument, et /proc/<pid>/cmdline est lisible par TOUT utilisateur de la
machine tant que la commande tourne — une exposition qu'aucun filtre
n'atteint.
Il voyage désormais dans MASTER_PWD. La sonde le pose sur l'enfant
qu'elle lance ; une fois accepté, il est placé dans l'environnement de ce
processus, si bien que tous les appels suivants en héritent sans qu'argv
le porte jamais. /proc/<pid>/environ n'est lisible que par son
propriétaire.
À savoir pour qui relit l'ancien code : l'option était ajoutée à chaque
invocation, alors que la commande db d'odoo ne la lit que dans la branche
drop. list, restore et clone portaient un secret dont ils ne faisaient
rien.
Le caviardage reste. Il est le dernier rempart, pas le premier, et
d'autres options mettent encore des secrets sur des lignes de commande.
Assisted-by: Claude Opus 5
2026-08-23 00:13:10 -04:00
|
|
|
# kdbx_manager.py y mettent « --default_password_auth '<mot de passe KeePass>' ».
|
|
|
|
|
# Cette commande est affichée avant et après l'exécution, et journalisée en
|
|
|
|
|
# erreur : le secret finissait donc dans le terminal, dans les journaux et dans
|
|
|
|
|
# toute sortie CI qui les capture.
|
|
|
|
|
#
|
|
|
|
|
# Ce filtre reste le dernier rempart, pas le premier : un secret n'a rien à
|
|
|
|
|
# faire sur argv, que /proc/<pid>/cmdline expose à tout utilisateur de la
|
|
|
|
|
# machine et qu'aucun caviardage n'atteint. db_restore.py est passé à
|
|
|
|
|
# MASTER_PWD dans l'environnement pour cette raison.
|
[FIX] execute: redact the secrets before printing a command
CodeQL flagged five clear-text logging alerts here, and they are real:
todo.py and kdbx_manager.py build « --default_password_auth '<KeePass
password>' » and db_restore.py « --master_password=… », while this file
printed the command before and after every run and logged it on error. The
« if "password" in command » guard covered one branch out of six.
Redaction sits at the display point, not at construction: six outputs here
against commands built all over the repository. Only the value goes, never
the option name, and the RETURNED command stays clear — « [1] redo the
command » needs it. Verified that PreferredAuthentications=password survives.
--- FR ---
CodeQL signalait ici cinq journalisations en clair, et elles sont réelles :
todo.py et kdbx_manager.py construisent « --default_password_auth '<mot de
passe KeePass>' » et db_restore.py « --master_password=… », tandis que ce
fichier affichait la commande avant et après chaque exécution et la
journalisait en erreur. Le garde « if "password" in command » couvrait une
branche sur six.
Le caviardage est au point d'affichage, non à la construction : six sorties
ici, contre des commandes bâties partout dans le dépôt. Seule la valeur
part, jamais le nom de l'option, et la commande RENVOYÉE reste en clair —
« [1] refaire la commande » en dépend. Vérifié que
PreferredAuthentications=password reste intact.
Assisted-by: Claude Opus 5
2026-08-07 03:35:20 -04:00
|
|
|
#
|
|
|
|
|
# On caviarde la VALEUR, jamais le nom de l'option : la commande reste lisible et
|
|
|
|
|
# reproductible, il ne manque que ce qui ne doit pas être lu.
|
|
|
|
|
_SECRET_OPTION = re.compile(
|
|
|
|
|
r"(?P<opt>--?[\w-]*"
|
|
|
|
|
r"(?:password|passwd|pwd|secret|token|api[-_]?key)[\w-]*"
|
|
|
|
|
r"(?:\s+|=))"
|
|
|
|
|
r"(?P<val>'[^']*'|\"[^\"]*\"|\S+)",
|
|
|
|
|
re.IGNORECASE,
|
|
|
|
|
)
|
|
|
|
|
_SECRET_ENV = re.compile(
|
|
|
|
|
r"(?P<var>\b\w*(?:PASSWORD|PASSWD|SECRET|TOKEN)\w*=)"
|
|
|
|
|
r"(?P<val>'[^']*'|\"[^\"]*\"|\S+)"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def redact_secrets(text):
|
|
|
|
|
"""Remplace la valeur des options et variables porteuses de secret.
|
|
|
|
|
|
|
|
|
|
Appliqué à CHAQUE affichage d'une commande. Filtrer au point d'affichage
|
|
|
|
|
plutôt qu'à la construction est ce qui rend la garantie tenable : il n'y a
|
|
|
|
|
qu'une poignée de sorties ici, alors que les commandes se construisent
|
|
|
|
|
partout dans le dépôt.
|
|
|
|
|
"""
|
|
|
|
|
if not text:
|
|
|
|
|
return text
|
|
|
|
|
text = _SECRET_OPTION.sub(lambda m: m.group("opt") + "'***'", text)
|
|
|
|
|
return _SECRET_ENV.sub(lambda m: m.group("var") + "'***'", text)
|
|
|
|
|
|
|
|
|
|
|
2026-02-13 01:27:38 -05:00
|
|
|
new_path = os.path.normpath(
|
|
|
|
|
os.path.join(os.path.dirname(__file__), "..", "..")
|
|
|
|
|
)
|
|
|
|
|
sys.path.append(new_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logging.basicConfig(
|
|
|
|
|
format=(
|
|
|
|
|
"%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d]"
|
|
|
|
|
" %(message)s"
|
|
|
|
|
),
|
|
|
|
|
datefmt="%Y-%m-%d:%H:%M:%S",
|
|
|
|
|
level=logging.INFO,
|
|
|
|
|
)
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Execute:
|
2026-03-10 04:07:38 -04:00
|
|
|
def __init__(self) -> None:
|
|
|
|
|
self.cmd_source_erplibre: str = ""
|
|
|
|
|
self.cmd_source_default: str = ""
|
2026-02-13 01:27:38 -05:00
|
|
|
exec_path_gnome_terminal = shutil.which("gnome-terminal")
|
|
|
|
|
if exec_path_gnome_terminal:
|
|
|
|
|
self.cmd_source_erplibre = (
|
|
|
|
|
f"gnome-terminal -- bash -c 'source"
|
2026-03-10 03:06:07 -04:00
|
|
|
f" ./{VENV_ERPLIBRE}/bin/activate;%s'"
|
2026-02-13 01:27:38 -05:00
|
|
|
)
|
|
|
|
|
self.cmd_source_default = "gnome-terminal -- bash -c '" f"%s'"
|
|
|
|
|
else:
|
|
|
|
|
exec_path_tell = shutil.which("osascript")
|
|
|
|
|
if exec_path_tell:
|
|
|
|
|
self.cmd_source_erplibre = (
|
|
|
|
|
"osascript -e 'tell application \"Terminal\"'"
|
|
|
|
|
)
|
|
|
|
|
self.cmd_source_erplibre += " -e 'tell application \"System Events\" to keystroke \"t\" using {command down}' -e 'delay 0.1' -e 'do script \""
|
2026-03-10 03:06:07 -04:00
|
|
|
self.cmd_source_erplibre += f"cd {os.getcwd()}; source ./{VENV_ERPLIBRE}/bin/activate; %s\" in front window'"
|
2026-02-13 01:27:38 -05:00
|
|
|
self.cmd_source_erplibre += " -e 'end tell'"
|
|
|
|
|
else:
|
|
|
|
|
self.cmd_source_erplibre = (
|
2026-03-10 03:06:07 -04:00
|
|
|
f"source ./{VENV_ERPLIBRE}/bin/activate;%s"
|
2026-02-13 01:27:38 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def exec_command_live(
|
|
|
|
|
self,
|
2026-03-10 04:07:38 -04:00
|
|
|
command: str,
|
|
|
|
|
source_erplibre: bool = True,
|
|
|
|
|
quiet: bool = False,
|
|
|
|
|
single_source_erplibre: bool = False,
|
|
|
|
|
new_window: bool = False,
|
|
|
|
|
single_source_odoo: bool = False,
|
|
|
|
|
source_odoo: str = "",
|
|
|
|
|
new_env: dict | None = None,
|
|
|
|
|
return_status_and_command: bool = False,
|
|
|
|
|
return_status_and_output: bool = False,
|
|
|
|
|
return_status_and_output_and_command: bool = False,
|
|
|
|
|
) -> (
|
|
|
|
|
int
|
|
|
|
|
| tuple[int, str]
|
|
|
|
|
| tuple[int, list[str]]
|
|
|
|
|
| tuple[int, str, list[str]]
|
2026-02-13 01:27:38 -05:00
|
|
|
):
|
|
|
|
|
"""
|
2026-03-10 03:45:11 -04:00
|
|
|
Execute a command and display its output live.
|
2026-02-13 01:27:38 -05:00
|
|
|
|
|
|
|
|
Args:
|
2026-03-10 03:45:11 -04:00
|
|
|
command (str): The command to execute.
|
2026-02-13 01:27:38 -05:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
my_env = os.environ.copy()
|
|
|
|
|
if new_env:
|
|
|
|
|
my_env.update(new_env)
|
|
|
|
|
|
|
|
|
|
process_start_time = time.time()
|
2026-03-10 03:06:07 -04:00
|
|
|
exit_code = None
|
2026-02-13 01:27:38 -05:00
|
|
|
if source_erplibre:
|
2026-03-10 03:06:07 -04:00
|
|
|
# command = f"source ./{VENV_ERPLIBRE}/bin/activate && " + command
|
2026-02-13 01:27:38 -05:00
|
|
|
# cmd = (
|
|
|
|
|
# f"gnome-terminal --tab -- bash -c 'source"
|
2026-03-10 03:06:07 -04:00
|
|
|
# f" ./{VENV_ERPLIBRE}/bin/activate;{command}'"
|
2026-02-13 01:27:38 -05:00
|
|
|
# )
|
2026-03-10 03:06:07 -04:00
|
|
|
command = self.cmd_source_erplibre % command
|
|
|
|
|
# os.system(f"./script/terminal/open_terminal.sh {command}")
|
2026-02-13 01:27:38 -05:00
|
|
|
elif single_source_erplibre:
|
2026-03-10 04:07:38 -04:00
|
|
|
command = f"source ./{VENV_ERPLIBRE}/bin/activate && %s" % command
|
2026-02-13 01:27:38 -05:00
|
|
|
elif single_source_odoo:
|
|
|
|
|
if not source_odoo and os.path.exists("./.erplibre-version"):
|
|
|
|
|
with open("./.erplibre-version") as f:
|
|
|
|
|
source_odoo = f.read()
|
|
|
|
|
if not source_odoo:
|
|
|
|
|
_logger.error(
|
[FIX] execute: redact the secrets before printing a command
CodeQL flagged five clear-text logging alerts here, and they are real:
todo.py and kdbx_manager.py build « --default_password_auth '<KeePass
password>' » and db_restore.py « --master_password=… », while this file
printed the command before and after every run and logged it on error. The
« if "password" in command » guard covered one branch out of six.
Redaction sits at the display point, not at construction: six outputs here
against commands built all over the repository. Only the value goes, never
the option name, and the RETURNED command stays clear — « [1] redo the
command » needs it. Verified that PreferredAuthentications=password survives.
--- FR ---
CodeQL signalait ici cinq journalisations en clair, et elles sont réelles :
todo.py et kdbx_manager.py construisent « --default_password_auth '<mot de
passe KeePass>' » et db_restore.py « --master_password=… », tandis que ce
fichier affichait la commande avant et après chaque exécution et la
journalisait en erreur. Le garde « if "password" in command » couvrait une
branche sur six.
Le caviardage est au point d'affichage, non à la construction : six sorties
ici, contre des commandes bâties partout dans le dépôt. Seule la valeur
part, jamais le nom de l'option, et la commande RENVOYÉE reste en clair —
« [1] refaire la commande » en dépend. Vérifié que
PreferredAuthentications=password reste intact.
Assisted-by: Claude Opus 5
2026-08-07 03:35:20 -04:00
|
|
|
"You cannot execute Odoo command if no version is"
|
|
|
|
|
f" installed. Command : {redact_secrets(command)}"
|
2026-02-13 01:27:38 -05:00
|
|
|
)
|
[ADD] migration: see and repair the website COW views
A copy-on-write view freezes the module view it came from; the module moves
on and the upgrade dies hours later on a missing anchor. These tools
predict, snapshot, diff, neutralize and reset them. The migration screen
gains the real state of each step, replay from any of them, and statistics.
--- FR ---
Une vue copy-on-write fige la vue de module dont elle vient ; le module
évolue et la mise à niveau meurt des heures plus tard sur un point
d'ancrage absent. Ces outils les prévoient, photographient, comparent,
neutralisent et réinitialisent. L'écran de migration gagne l'état réel de
chaque étape, la reprise depuis n'importe laquelle, et des statistiques.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
|
|
|
# Return the SAME shape the caller asked for. A bare int here
|
|
|
|
|
# made callers doing « status, cmd = exec_command_live(...) »
|
|
|
|
|
# crash with ValueError instead of seeing the failure.
|
|
|
|
|
if return_status_and_output_and_command:
|
|
|
|
|
return 1, command, []
|
|
|
|
|
if return_status_and_command:
|
|
|
|
|
return 1, command
|
|
|
|
|
if return_status_and_output:
|
|
|
|
|
return 1, []
|
|
|
|
|
return 1
|
2026-03-10 04:07:38 -04:00
|
|
|
command = f"source ./.venv.{source_odoo}/bin/activate && {command}"
|
2026-02-13 04:07:02 -05:00
|
|
|
if new_window and self.cmd_source_default:
|
2026-03-10 03:06:07 -04:00
|
|
|
command = self.cmd_source_default % command
|
2026-02-13 01:27:38 -05:00
|
|
|
|
|
|
|
|
if not quiet:
|
2026-03-12 05:20:32 -04:00
|
|
|
print("🏠 ⬇ Execute command :\n")
|
[FIX] execute: redact the secrets before printing a command
CodeQL flagged five clear-text logging alerts here, and they are real:
todo.py and kdbx_manager.py build « --default_password_auth '<KeePass
password>' » and db_restore.py « --master_password=… », while this file
printed the command before and after every run and logged it on error. The
« if "password" in command » guard covered one branch out of six.
Redaction sits at the display point, not at construction: six outputs here
against commands built all over the repository. Only the value goes, never
the option name, and the RETURNED command stays clear — « [1] redo the
command » needs it. Verified that PreferredAuthentications=password survives.
--- FR ---
CodeQL signalait ici cinq journalisations en clair, et elles sont réelles :
todo.py et kdbx_manager.py construisent « --default_password_auth '<mot de
passe KeePass>' » et db_restore.py « --master_password=… », tandis que ce
fichier affichait la commande avant et après chaque exécution et la
journalisait en erreur. Le garde « if "password" in command » couvrait une
branche sur six.
Le caviardage est au point d'affichage, non à la construction : six sorties
ici, contre des commandes bâties partout dans le dépôt. Seule la valeur
part, jamais le nom de l'option, et la commande RENVOYÉE reste en clair —
« [1] refaire la commande » en dépend. Vérifié que
PreferredAuthentications=password reste intact.
Assisted-by: Claude Opus 5
2026-08-07 03:35:20 -04:00
|
|
|
print(redact_secrets(command))
|
2026-03-10 03:06:07 -04:00
|
|
|
output_lines = []
|
2026-02-13 01:27:38 -05:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
process = subprocess.Popen(
|
2026-03-10 03:06:07 -04:00
|
|
|
command,
|
2026-02-13 01:27:38 -05:00
|
|
|
shell=True,
|
|
|
|
|
executable="/bin/bash",
|
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
|
stderr=subprocess.STDOUT,
|
[FIX] script execute : afficher l'invite sans attendre le saut de ligne
« readline » ne rend la main qu'au saut de ligne : une invite qui n'en
porte pas restait retenue jusqu'à la ligne suivante, écrite après la
réponse. Toute question « [o/N] » d'une commande du menu se voyait donc
après coup, et l'on y répondait à l'aveugle. La lecture se fait par blocs,
le reliquat part à l'écran aussitôt et stdout est vidé ; un décodeur
incrémental protège les accents coupés entre deux lectures.
Vérifié : invite visible avant la frappe, sans double affichage ; 13 cas de
non-régression ; 4546 tests, seuls les 9 échecs préexistants de qemu_proxmox.
--- EN ---
`readline` only returns at a newline: a prompt carrying none stayed held
back until the next line, written after the answer. Every "[o/N]" question
asked by a menu command was therefore seen after the fact, and answered
blind. Reading now happens in chunks, the trailing fragment reaches the
screen at once and stdout is flushed; an incremental decoder protects
accented characters split across two reads.
Checked: prompt visible before typing, with no double display; 13 regression
cases; 4546 tests, only the 9 pre-existing qemu_proxmox failures.
Assisted-by: Claude Opus 5
2026-09-04 00:24:09 -04:00
|
|
|
# Octets bruts, SANS tampon. « readline » attendait le saut de
|
|
|
|
|
# ligne pour rendre la main : une invite qui n'en porte pas —
|
|
|
|
|
# « Continuer ? [o/N] » — restait donc invisible jusqu'à ce que
|
|
|
|
|
# la réponse soit déjà tapée. La question s'affichait APRÈS la
|
|
|
|
|
# réponse, et l'on répondait à l'aveugle.
|
|
|
|
|
bufsize=0,
|
2026-02-13 01:27:38 -05:00
|
|
|
env=my_env,
|
|
|
|
|
)
|
|
|
|
|
|
[ADD] migration: keep the logs on disk, one file per step
The state screen showed « no tool has run yet » after closing and
reopening. It was reading the progression file, and that file is archived
and reset when a migration restarts — so everything it knew vanished at
the exact moment one wants to understand why the restart was needed.
Each step now has its own log file, appended never overwritten, and the
failures and tool verdicts go to an append-only JSONL beside them. Both
outlive the progression, and the screen reads disk first, memory second,
without counting the overlap twice.
The command output itself is captured where every line already passes,
in the executor's read loop: the terminal still shows it live and nothing
about the run changes. What goes through the real terminal cannot be
captured — a pipe there makes full screens refuse, that lesson is paid —
so those keep at least their command and their exit code.
--- FR ---
[ADD] migration : garder les journaux sur disque, un fichier par étape
L'écran d'état affichait « aucun outil n'a encore tourné » après une
fermeture. Il lisait le fichier de progression, or celui-ci est archivé
puis remis à zéro quand on recommence : tout ce qu'il savait disparaissait
au moment précis où l'on cherche pourquoi il a fallu recommencer.
Chaque étape a désormais son fichier, en ajout et jamais en écrasement, et
les échecs comme les verdicts d'outils vont dans un JSONL à côté. Les deux
survivent à la progression, et l'écran lit le disque d'abord.
La sortie des commandes est captée là où chaque ligne passe déjà, dans la
boucle de lecture de l'exécuteur : le terminal la montre toujours en
direct. Ce qui passe par le vrai terminal n'est pas captable — un tube y
ferait renoncer les pleins écrans — et garde au moins son verdict.
Assisted-by: Claude Opus 5
2026-08-19 04:32:25 -04:00
|
|
|
sink = getattr(self, "log_sink", None)
|
[FIX] script execute : afficher l'invite sans attendre le saut de ligne
« readline » ne rend la main qu'au saut de ligne : une invite qui n'en
porte pas restait retenue jusqu'à la ligne suivante, écrite après la
réponse. Toute question « [o/N] » d'une commande du menu se voyait donc
après coup, et l'on y répondait à l'aveugle. La lecture se fait par blocs,
le reliquat part à l'écran aussitôt et stdout est vidé ; un décodeur
incrémental protège les accents coupés entre deux lectures.
Vérifié : invite visible avant la frappe, sans double affichage ; 13 cas de
non-régression ; 4546 tests, seuls les 9 échecs préexistants de qemu_proxmox.
--- EN ---
`readline` only returns at a newline: a prompt carrying none stayed held
back until the next line, written after the answer. Every "[o/N]" question
asked by a menu command was therefore seen after the fact, and answered
blind. Reading now happens in chunks, the trailing fragment reaches the
screen at once and stdout is flushed; an incremental decoder protects
accented characters split across two reads.
Checked: prompt visible before typing, with no double display; 13 regression
cases; 4546 tests, only the 9 pre-existing qemu_proxmox failures.
Assisted-by: Claude Opus 5
2026-09-04 00:24:09 -04:00
|
|
|
# Le tube porte des octets, et une lecture peut couper un caractère
|
|
|
|
|
# accentué ou un emoji en deux. Le décodeur incrémental garde le
|
|
|
|
|
# morceau incomplet en attente au lieu de rendre un « ? ».
|
|
|
|
|
decoder = codecs.getincrementaldecoder("utf-8")("replace")
|
|
|
|
|
fd = process.stdout.fileno()
|
|
|
|
|
# « pending » est la ligne en cours, pas encore terminée ; « shown »
|
|
|
|
|
# compte ce qui en a déjà été envoyé au terminal, pour ne jamais
|
|
|
|
|
# afficher deux fois le même morceau d'invite quand la ligne finit
|
|
|
|
|
# par se terminer.
|
|
|
|
|
pending = ""
|
|
|
|
|
shown = 0
|
|
|
|
|
|
|
|
|
|
def retenir(ligne):
|
|
|
|
|
"""Journaliser et retenir une ligne complète, caviardée."""
|
|
|
|
|
nonlocal sink
|
|
|
|
|
# La sortie du sous-processus passe par le MÊME filtre que la
|
|
|
|
|
# commande : un outil qui réaffiche ses propres arguments
|
[FIX] security: redact the master password from every output
CodeQL raised seven high-severity alerts on this branch. Three were real,
and the same secret was behind all of them: the Odoo master password,
which db_restore appends to its command line as soon as the database
declares one.
The command itself was printed raw -- "print(arg)" -- so the password
reached stdout, and any terminal capture with it. The probe output was
logged raw too, and a refused attempt echoes the command it tried.
Wider than that: the runner filtered the command it was about to run, but
not what came back. A tool that reprints its own arguments -- "set -x", a
traceback, odoo_bin.sh -- put the secret straight back into the terminal
AND into the log file the sink writes. Every subprocess line now goes
through the same filter as the command.
The four remaining alerts sit on expressions already wrapped in
redact_secrets(). CodeQL does not cross re.sub, so it cannot see the
barrier; the mitigation is real and they are false positives.
--- FR ---
CodeQL a levé sept alertes de sévérité haute sur cette branche. Trois
étaient réelles, et le même secret était derrière : le mot de passe maître
d'Odoo, que db_restore ajoute à sa ligne de commande dès que la base en
exige un.
La commande elle-même était imprimée telle quelle — « print(arg) » — donc
le mot de passe atteignait la sortie standard, et toute capture de
terminal avec elle. La sortie de la sonde était journalisée brute
également, et un essai refusé réaffiche la commande tentée.
Plus large : le lanceur filtrait la commande qu'il allait exécuter, mais
pas ce qui en revenait. Un outil qui réaffiche ses propres arguments —
« set -x », une trace, odoo_bin.sh — remettait le secret dans le terminal
ET dans le fichier de journal. Chaque ligne du sous-processus passe
désormais par le même filtre que la commande.
Les quatre alertes restantes portent sur des expressions déjà entourées de
redact_secrets(). CodeQL ne franchit pas re.sub et ne voit donc pas la
barrière ; la mitigation est réelle, ce sont des faux positifs.
Assisted-by: Claude Opus 5
2026-08-22 23:39:00 -04:00
|
|
|
# (« set -x », une trace, odoo_bin.sh) y remettrait le secret
|
[FIX] script execute : afficher l'invite sans attendre le saut de ligne
« readline » ne rend la main qu'au saut de ligne : une invite qui n'en
porte pas restait retenue jusqu'à la ligne suivante, écrite après la
réponse. Toute question « [o/N] » d'une commande du menu se voyait donc
après coup, et l'on y répondait à l'aveugle. La lecture se fait par blocs,
le reliquat part à l'écran aussitôt et stdout est vidé ; un décodeur
incrémental protège les accents coupés entre deux lectures.
Vérifié : invite visible avant la frappe, sans double affichage ; 13 cas de
non-régression ; 4546 tests, seuls les 9 échecs préexistants de qemu_proxmox.
--- EN ---
`readline` only returns at a newline: a prompt carrying none stayed held
back until the next line, written after the answer. Every "[o/N]" question
asked by a menu command was therefore seen after the fact, and answered
blind. Reading now happens in chunks, the trailing fragment reaches the
screen at once and stdout is flushed; an incremental decoder protects
accented characters split across two reads.
Checked: prompt visible before typing, with no double display; 13 regression
cases; 4546 tests, only the 9 pre-existing qemu_proxmox failures.
Assisted-by: Claude Opus 5
2026-09-04 00:24:09 -04:00
|
|
|
# que l'affichage de la commande venait d'écarter.
|
|
|
|
|
clean = redact_secrets(ligne)
|
[ADD] migration: keep the logs on disk, one file per step
The state screen showed « no tool has run yet » after closing and
reopening. It was reading the progression file, and that file is archived
and reset when a migration restarts — so everything it knew vanished at
the exact moment one wants to understand why the restart was needed.
Each step now has its own log file, appended never overwritten, and the
failures and tool verdicts go to an append-only JSONL beside them. Both
outlive the progression, and the screen reads disk first, memory second,
without counting the overlap twice.
The command output itself is captured where every line already passes,
in the executor's read loop: the terminal still shows it live and nothing
about the run changes. What goes through the real terminal cannot be
captured — a pipe there makes full screens refuse, that lesson is paid —
so those keep at least their command and their exit code.
--- FR ---
[ADD] migration : garder les journaux sur disque, un fichier par étape
L'écran d'état affichait « aucun outil n'a encore tourné » après une
fermeture. Il lisait le fichier de progression, or celui-ci est archivé
puis remis à zéro quand on recommence : tout ce qu'il savait disparaissait
au moment précis où l'on cherche pourquoi il a fallu recommencer.
Chaque étape a désormais son fichier, en ajout et jamais en écrasement, et
les échecs comme les verdicts d'outils vont dans un JSONL à côté. Les deux
survivent à la progression, et l'écran lit le disque d'abord.
La sortie des commandes est captée là où chaque ligne passe déjà, dans la
boucle de lecture de l'exécuteur : le terminal la montre toujours en
direct. Ce qui passe par le vrai terminal n'est pas captable — un tube y
ferait renoncer les pleins écrans — et garde au moins son verdict.
Assisted-by: Claude Opus 5
2026-08-19 04:32:25 -04:00
|
|
|
if sink:
|
|
|
|
|
# Chaque ligne passe DÉJÀ ici : c'est le seul endroit où
|
|
|
|
|
# journaliser sans rien changer à ce que le terminal
|
|
|
|
|
# montre. Une erreur d'écriture ne doit jamais faire
|
|
|
|
|
# échouer la commande qu'on est en train de suivre.
|
|
|
|
|
try:
|
[FIX] script execute : afficher l'invite sans attendre le saut de ligne
« readline » ne rend la main qu'au saut de ligne : une invite qui n'en
porte pas restait retenue jusqu'à la ligne suivante, écrite après la
réponse. Toute question « [o/N] » d'une commande du menu se voyait donc
après coup, et l'on y répondait à l'aveugle. La lecture se fait par blocs,
le reliquat part à l'écran aussitôt et stdout est vidé ; un décodeur
incrémental protège les accents coupés entre deux lectures.
Vérifié : invite visible avant la frappe, sans double affichage ; 13 cas de
non-régression ; 4546 tests, seuls les 9 échecs préexistants de qemu_proxmox.
--- EN ---
`readline` only returns at a newline: a prompt carrying none stayed held
back until the next line, written after the answer. Every "[o/N]" question
asked by a menu command was therefore seen after the fact, and answered
blind. Reading now happens in chunks, the trailing fragment reaches the
screen at once and stdout is flushed; an incremental decoder protects
accented characters split across two reads.
Checked: prompt visible before typing, with no double display; 13 regression
cases; 4546 tests, only the 9 pre-existing qemu_proxmox failures.
Assisted-by: Claude Opus 5
2026-09-04 00:24:09 -04:00
|
|
|
sink.write(clean)
|
[ADD] migration: keep the logs on disk, one file per step
The state screen showed « no tool has run yet » after closing and
reopening. It was reading the progression file, and that file is archived
and reset when a migration restarts — so everything it knew vanished at
the exact moment one wants to understand why the restart was needed.
Each step now has its own log file, appended never overwritten, and the
failures and tool verdicts go to an append-only JSONL beside them. Both
outlive the progression, and the screen reads disk first, memory second,
without counting the overlap twice.
The command output itself is captured where every line already passes,
in the executor's read loop: the terminal still shows it live and nothing
about the run changes. What goes through the real terminal cannot be
captured — a pipe there makes full screens refuse, that lesson is paid —
so those keep at least their command and their exit code.
--- FR ---
[ADD] migration : garder les journaux sur disque, un fichier par étape
L'écran d'état affichait « aucun outil n'a encore tourné » après une
fermeture. Il lisait le fichier de progression, or celui-ci est archivé
puis remis à zéro quand on recommence : tout ce qu'il savait disparaissait
au moment précis où l'on cherche pourquoi il a fallu recommencer.
Chaque étape a désormais son fichier, en ajout et jamais en écrasement, et
les échecs comme les verdicts d'outils vont dans un JSONL à côté. Les deux
survivent à la progression, et l'écran lit le disque d'abord.
La sortie des commandes est captée là où chaque ligne passe déjà, dans la
boucle de lecture de l'exécuteur : le terminal la montre toujours en
direct. Ce qui passe par le vrai terminal n'est pas captable — un tube y
ferait renoncer les pleins écrans — et garde au moins son verdict.
Assisted-by: Claude Opus 5
2026-08-19 04:32:25 -04:00
|
|
|
except Exception:
|
|
|
|
|
sink = None
|
2026-02-13 01:27:38 -05:00
|
|
|
if (
|
|
|
|
|
return_status_and_output
|
|
|
|
|
or return_status_and_output_and_command
|
|
|
|
|
):
|
2026-02-13 03:46:27 -05:00
|
|
|
# Remove last \n char
|
2026-03-10 03:06:07 -04:00
|
|
|
output_lines.append(
|
[FIX] script execute : afficher l'invite sans attendre le saut de ligne
« readline » ne rend la main qu'au saut de ligne : une invite qui n'en
porte pas restait retenue jusqu'à la ligne suivante, écrite après la
réponse. Toute question « [o/N] » d'une commande du menu se voyait donc
après coup, et l'on y répondait à l'aveugle. La lecture se fait par blocs,
le reliquat part à l'écran aussitôt et stdout est vidé ; un décodeur
incrémental protège les accents coupés entre deux lectures.
Vérifié : invite visible avant la frappe, sans double affichage ; 13 cas de
non-régression ; 4546 tests, seuls les 9 échecs préexistants de qemu_proxmox.
--- EN ---
`readline` only returns at a newline: a prompt carrying none stayed held
back until the next line, written after the answer. Every "[o/N]" question
asked by a menu command was therefore seen after the fact, and answered
blind. Reading now happens in chunks, the trailing fragment reaches the
screen at once and stdout is flushed; an incremental decoder protects
accented characters split across two reads.
Checked: prompt visible before typing, with no double display; 13 regression
cases; 4546 tests, only the 9 pre-existing qemu_proxmox failures.
Assisted-by: Claude Opus 5
2026-09-04 00:24:09 -04:00
|
|
|
clean.removesuffix("\r\n")
|
2026-02-13 03:46:27 -05:00
|
|
|
.removesuffix("\n")
|
|
|
|
|
.removesuffix("\r")
|
|
|
|
|
)
|
2026-02-13 01:27:38 -05:00
|
|
|
|
[FIX] script execute : afficher l'invite sans attendre le saut de ligne
« readline » ne rend la main qu'au saut de ligne : une invite qui n'en
porte pas restait retenue jusqu'à la ligne suivante, écrite après la
réponse. Toute question « [o/N] » d'une commande du menu se voyait donc
après coup, et l'on y répondait à l'aveugle. La lecture se fait par blocs,
le reliquat part à l'écran aussitôt et stdout est vidé ; un décodeur
incrémental protège les accents coupés entre deux lectures.
Vérifié : invite visible avant la frappe, sans double affichage ; 13 cas de
non-régression ; 4546 tests, seuls les 9 échecs préexistants de qemu_proxmox.
--- EN ---
`readline` only returns at a newline: a prompt carrying none stayed held
back until the next line, written after the answer. Every "[o/N]" question
asked by a menu command was therefore seen after the fact, and answered
blind. Reading now happens in chunks, the trailing fragment reaches the
screen at once and stdout is flushed; an incremental decoder protects
accented characters split across two reads.
Checked: prompt visible before typing, with no double display; 13 regression
cases; 4546 tests, only the 9 pre-existing qemu_proxmox failures.
Assisted-by: Claude Opus 5
2026-09-04 00:24:09 -04:00
|
|
|
while True:
|
|
|
|
|
chunk = os.read(fd, 65536)
|
|
|
|
|
if not chunk:
|
|
|
|
|
break
|
|
|
|
|
pending += decoder.decode(chunk)
|
|
|
|
|
while True:
|
|
|
|
|
coupe = pending.find("\n")
|
|
|
|
|
if coupe < 0:
|
|
|
|
|
break
|
|
|
|
|
ligne = pending[: coupe + 1]
|
|
|
|
|
pending = pending[coupe + 1 :]
|
|
|
|
|
if not quiet:
|
|
|
|
|
print(redact_secrets(ligne[shown:]), end="")
|
|
|
|
|
shown = 0
|
|
|
|
|
retenir(ligne)
|
|
|
|
|
if not quiet:
|
|
|
|
|
if len(pending) > shown:
|
|
|
|
|
# Le reliquat sans saut de ligne EST l'invite : la
|
|
|
|
|
# montrer tout de suite, avant que la commande ne se
|
|
|
|
|
# bloque sur la lecture de la réponse.
|
|
|
|
|
print(redact_secrets(pending[shown:]), end="")
|
|
|
|
|
shown = len(pending)
|
|
|
|
|
# Sans vidage explicite, cette invite resterait dans le
|
|
|
|
|
# tampon de Python : second endroit où la question se
|
|
|
|
|
# perdait, la sortie n'étant vidée qu'au saut de ligne.
|
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
|
|
pending += decoder.decode(b"", True)
|
|
|
|
|
if pending:
|
|
|
|
|
if not quiet:
|
|
|
|
|
print(redact_secrets(pending[shown:]), end="")
|
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
retenir(pending)
|
|
|
|
|
|
2026-03-10 03:45:11 -04:00
|
|
|
process.wait()
|
2026-03-10 03:06:07 -04:00
|
|
|
exit_code = process.returncode
|
2026-02-13 01:27:38 -05:00
|
|
|
if process.returncode != 0 and not quiet:
|
2026-03-10 04:07:38 -04:00
|
|
|
print("Command returned error code:" f" {process.returncode}")
|
2026-02-13 01:27:38 -05:00
|
|
|
|
[ADD] migration: see and repair the website COW views
A copy-on-write view freezes the module view it came from; the module moves
on and the upgrade dies hours later on a missing anchor. These tools
predict, snapshot, diff, neutralize and reset them. The migration screen
gains the real state of each step, replay from any of them, and statistics.
--- FR ---
Une vue copy-on-write fige la vue de module dont elle vient ; le module
évolue et la mise à niveau meurt des heures plus tard sur un point
d'ancrage absent. Ces outils les prévoient, photographient, comparent,
neutralisent et réinitialisent. L'écran de migration gagne l'état réel de
chaque étape, la reprise depuis n'importe laquelle, et des statistiques.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
|
|
|
# An exception MUST report a failure. exit_code stays None otherwise,
|
|
|
|
|
# and None is falsy: callers testing « if not status: » would mark the
|
|
|
|
|
# step as done, and « if status and wait_at_error » would skip the error
|
|
|
|
|
# prompt. A crashed command was therefore recorded as a success.
|
2026-02-13 01:27:38 -05:00
|
|
|
except FileNotFoundError:
|
[ADD] migration: see and repair the website COW views
A copy-on-write view freezes the module view it came from; the module moves
on and the upgrade dies hours later on a missing anchor. These tools
predict, snapshot, diff, neutralize and reset them. The migration screen
gains the real state of each step, replay from any of them, and statistics.
--- FR ---
Une vue copy-on-write fige la vue de module dont elle vient ; le module
évolue et la mise à niveau meurt des heures plus tard sur un point
d'ancrage absent. Ces outils les prévoient, photographient, comparent,
neutralisent et réinitialisent. L'écran de migration gagne l'état réel de
chaque étape, la reprise depuis n'importe laquelle, et des statistiques.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
|
|
|
exit_code = 1
|
2026-02-13 01:27:38 -05:00
|
|
|
if not quiet:
|
[FIX] execute: redact the secrets before printing a command
CodeQL flagged five clear-text logging alerts here, and they are real:
todo.py and kdbx_manager.py build « --default_password_auth '<KeePass
password>' » and db_restore.py « --master_password=… », while this file
printed the command before and after every run and logged it on error. The
« if "password" in command » guard covered one branch out of six.
Redaction sits at the display point, not at construction: six outputs here
against commands built all over the repository. Only the value goes, never
the option name, and the RETURNED command stays clear — « [1] redo the
command » needs it. Verified that PreferredAuthentications=password survives.
--- FR ---
CodeQL signalait ici cinq journalisations en clair, et elles sont réelles :
todo.py et kdbx_manager.py construisent « --default_password_auth '<mot de
passe KeePass>' » et db_restore.py « --master_password=… », tandis que ce
fichier affichait la commande avant et après chaque exécution et la
journalisait en erreur. Le garde « if "password" in command » couvrait une
branche sur six.
Le caviardage est au point d'affichage, non à la construction : six sorties
ici, contre des commandes bâties partout dans le dépôt. Seule la valeur
part, jamais le nom de l'option, et la commande RENVOYÉE reste en clair —
« [1] refaire la commande » en dépend. Vérifié que
PreferredAuthentications=password reste intact.
Assisted-by: Claude Opus 5
2026-08-07 03:35:20 -04:00
|
|
|
print(f"Error: Command '{redact_secrets(command)}' not found.")
|
2026-02-13 01:27:38 -05:00
|
|
|
except Exception as e:
|
[ADD] migration: see and repair the website COW views
A copy-on-write view freezes the module view it came from; the module moves
on and the upgrade dies hours later on a missing anchor. These tools
predict, snapshot, diff, neutralize and reset them. The migration screen
gains the real state of each step, replay from any of them, and statistics.
--- FR ---
Une vue copy-on-write fige la vue de module dont elle vient ; le module
évolue et la mise à niveau meurt des heures plus tard sur un point
d'ancrage absent. Ces outils les prévoient, photographient, comparent,
neutralisent et réinitialisent. L'écran de migration gagne l'état réel de
chaque étape, la reprise depuis n'importe laquelle, et des statistiques.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
|
|
|
exit_code = 1
|
2026-02-13 01:27:38 -05:00
|
|
|
if not quiet:
|
[FIX] execute: redact the secrets before printing a command
CodeQL flagged five clear-text logging alerts here, and they are real:
todo.py and kdbx_manager.py build « --default_password_auth '<KeePass
password>' » and db_restore.py « --master_password=… », while this file
printed the command before and after every run and logged it on error. The
« if "password" in command » guard covered one branch out of six.
Redaction sits at the display point, not at construction: six outputs here
against commands built all over the repository. Only the value goes, never
the option name, and the RETURNED command stays clear — « [1] redo the
command » needs it. Verified that PreferredAuthentications=password survives.
--- FR ---
CodeQL signalait ici cinq journalisations en clair, et elles sont réelles :
todo.py et kdbx_manager.py construisent « --default_password_auth '<mot de
passe KeePass>' » et db_restore.py « --master_password=… », tandis que ce
fichier affichait la commande avant et après chaque exécution et la
journalisait en erreur. Le garde « if "password" in command » couvrait une
branche sur six.
Le caviardage est au point d'affichage, non à la construction : six sorties
ici, contre des commandes bâties partout dans le dépôt. Seule la valeur
part, jamais le nom de l'option, et la commande RENVOYÉE reste en clair —
« [1] refaire la commande » en dépend. Vérifié que
PreferredAuthentications=password reste intact.
Assisted-by: Claude Opus 5
2026-08-07 03:35:20 -04:00
|
|
|
print(f"An error occurred: {redact_secrets(str(e))}")
|
2026-02-13 01:27:38 -05:00
|
|
|
process_end_time = time.time()
|
|
|
|
|
duration_sec = process_end_time - process_start_time
|
|
|
|
|
if humanize:
|
|
|
|
|
duration_delta = datetime.timedelta(seconds=duration_sec)
|
2026-03-10 03:45:11 -04:00
|
|
|
human_time = humanize.precisedelta(duration_delta)
|
2026-02-13 01:27:38 -05:00
|
|
|
if not quiet:
|
2026-03-12 05:20:32 -04:00
|
|
|
print(f"🏠 ⬆ Executed ({human_time}) :\n")
|
2026-02-13 01:27:38 -05:00
|
|
|
else:
|
|
|
|
|
if not quiet:
|
2026-03-12 05:20:32 -04:00
|
|
|
print(f"🏠 ⬆ Executed ({duration_sec:.2f} sec.) :\n")
|
2026-02-13 01:27:38 -05:00
|
|
|
if not quiet:
|
[FIX] execute: redact the secrets before printing a command
CodeQL flagged five clear-text logging alerts here, and they are real:
todo.py and kdbx_manager.py build « --default_password_auth '<KeePass
password>' » and db_restore.py « --master_password=… », while this file
printed the command before and after every run and logged it on error. The
« if "password" in command » guard covered one branch out of six.
Redaction sits at the display point, not at construction: six outputs here
against commands built all over the repository. Only the value goes, never
the option name, and the RETURNED command stays clear — « [1] redo the
command » needs it. Verified that PreferredAuthentications=password survives.
--- FR ---
CodeQL signalait ici cinq journalisations en clair, et elles sont réelles :
todo.py et kdbx_manager.py construisent « --default_password_auth '<mot de
passe KeePass>' » et db_restore.py « --master_password=… », tandis que ce
fichier affichait la commande avant et après chaque exécution et la
journalisait en erreur. Le garde « if "password" in command » couvrait une
branche sur six.
Le caviardage est au point d'affichage, non à la construction : six sorties
ici, contre des commandes bâties partout dans le dépôt. Seule la valeur
part, jamais le nom de l'option, et la commande RENVOYÉE reste en clair —
« [1] refaire la commande » en dépend. Vérifié que
PreferredAuthentications=password reste intact.
Assisted-by: Claude Opus 5
2026-08-07 03:35:20 -04:00
|
|
|
print(redact_secrets(command))
|
2026-02-13 01:27:38 -05:00
|
|
|
print()
|
|
|
|
|
if return_status_and_output_and_command:
|
2026-03-10 03:06:07 -04:00
|
|
|
return exit_code, command, output_lines
|
2026-02-13 01:27:38 -05:00
|
|
|
if return_status_and_command:
|
2026-03-10 03:06:07 -04:00
|
|
|
return exit_code, command
|
2026-02-13 01:27:38 -05:00
|
|
|
if return_status_and_output:
|
2026-03-10 03:06:07 -04:00
|
|
|
return exit_code, output_lines
|
|
|
|
|
return exit_code
|