2026-03-10 03:59:35 -04:00
|
|
|
#!/usr/bin/env python3
|
2026-03-11 23:15:07 -04:00
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
2026-03-10 03:59:35 -04:00
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
import getpass
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
|
|
from script.todo.todo_i18n import t
|
|
|
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-09-03 23:42:00 -04:00
|
|
|
# DEUX blocs, et c'est le point : tkinter ne sert QU'au sélecteur de fichier
|
|
|
|
|
# quand aucun chemin n'est configuré. Réunis dans un seul `try`, l'absence de
|
|
|
|
|
# tkinter mettait aussi `PyKeePass` à None — et le coffre devenait impossible
|
|
|
|
|
# à ouvrir sur toute machine sans interface graphique, chemin et mot de passe
|
|
|
|
|
# configurés ou non. C'est-à-dire sur tous les serveurs.
|
2026-03-10 03:59:35 -04:00
|
|
|
try:
|
|
|
|
|
from pykeepass import PyKeePass
|
2026-08-10 03:10:50 -04:00
|
|
|
from pykeepass.exceptions import CredentialsError
|
2026-03-10 03:59:35 -04:00
|
|
|
except ModuleNotFoundError:
|
|
|
|
|
PyKeePass = None
|
|
|
|
|
|
2026-08-10 03:10:50 -04:00
|
|
|
class CredentialsError(Exception):
|
|
|
|
|
"""Jamais levée ici : sans pykeepass, `get_kdbx` sort avant d'ouvrir
|
|
|
|
|
quoi que ce soit. Définie pour que le `except` reste écrivable."""
|
|
|
|
|
|
2026-03-10 03:59:35 -04:00
|
|
|
|
2026-09-03 23:42:00 -04:00
|
|
|
try:
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
from tkinter import filedialog
|
|
|
|
|
except ModuleNotFoundError:
|
|
|
|
|
tk = None
|
|
|
|
|
filedialog = None
|
|
|
|
|
|
|
|
|
|
|
2026-03-10 03:59:35 -04:00
|
|
|
class KdbxManager:
|
2026-03-10 04:05:04 -04:00
|
|
|
def __init__(self, config_file) -> None:
|
2026-03-10 03:59:35 -04:00
|
|
|
self._config_file = config_file
|
|
|
|
|
self._kdbx = None
|
|
|
|
|
|
2026-08-10 03:10:50 -04:00
|
|
|
def get_kdbx(self, attempts: int = 3):
|
2026-03-10 03:59:35 -04:00
|
|
|
if self._kdbx:
|
|
|
|
|
return self._kdbx
|
|
|
|
|
|
2026-03-10 04:05:04 -04:00
|
|
|
kdbx_file_path = self._config_file.get_config_value(["kdbx", "path"])
|
2026-03-10 03:59:35 -04:00
|
|
|
if not kdbx_file_path:
|
|
|
|
|
if tk is None:
|
|
|
|
|
_logger.error("tkinter is not available")
|
|
|
|
|
return None
|
|
|
|
|
root = tk.Tk()
|
|
|
|
|
root.withdraw()
|
|
|
|
|
kdbx_file_path = filedialog.askopenfilename(
|
|
|
|
|
title="Select a File",
|
|
|
|
|
filetypes=(("KeepassX files", "*.kdbx"),),
|
|
|
|
|
)
|
|
|
|
|
if not kdbx_file_path:
|
|
|
|
|
_logger.error(
|
|
|
|
|
"KDBX is not configured, please fill"
|
|
|
|
|
f" {self._config_file.CONFIG_FILE}"
|
|
|
|
|
)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
if PyKeePass is None:
|
|
|
|
|
_logger.error("pykeepass is not installed")
|
|
|
|
|
return None
|
|
|
|
|
|
2026-08-10 03:10:50 -04:00
|
|
|
kdbx_password = self._config_file.get_config_value(
|
|
|
|
|
["kdbx", "password"]
|
|
|
|
|
)
|
|
|
|
|
if kdbx_password:
|
|
|
|
|
# Mot de passe pris dans la configuration : personne à qui
|
|
|
|
|
# redemander, mais il peut être faux — le dire au lieu de
|
|
|
|
|
# laisser remonter une trace de la bibliothèque.
|
|
|
|
|
try:
|
|
|
|
|
self._kdbx = PyKeePass(kdbx_file_path, password=kdbx_password)
|
|
|
|
|
except CredentialsError:
|
|
|
|
|
print(t("kdbx_wrong_password"))
|
|
|
|
|
return None
|
|
|
|
|
return self._kdbx
|
|
|
|
|
|
|
|
|
|
# Saisie interactive. Un mot de passe refusé est le cas NORMAL ici,
|
|
|
|
|
# pas une panne : la bibliothèque lève `CredentialsError` et, sans
|
|
|
|
|
# ce rattrapage, la trace remontait jusqu'à tuer le CLI. On nomme
|
|
|
|
|
# aussi le coffre — l'invite ne disait pas DE QUOI elle parlait.
|
2026-09-03 23:42:18 -04:00
|
|
|
# `flush` : l'invite de getpass part vers le terminal, ce `print`
|
|
|
|
|
# vers la sortie standard — qui est un TUBE quand le menu nous lance.
|
|
|
|
|
# Sans vidage, on lisait « Mot de passe du coffre : Coffre KeePass :
|
|
|
|
|
# /chemin » — la question avant ce dont elle parle.
|
|
|
|
|
print(f"{t('kdbx_vault_is')} {kdbx_file_path}", flush=True)
|
2026-08-10 03:10:50 -04:00
|
|
|
for _ in range(attempts):
|
|
|
|
|
password = getpass.getpass(prompt=t("kdbx_ask_password"))
|
|
|
|
|
if not password:
|
|
|
|
|
print(t("kdbx_give_up"))
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
self._kdbx = PyKeePass(kdbx_file_path, password=password)
|
|
|
|
|
except CredentialsError:
|
|
|
|
|
print(t("kdbx_wrong_password"))
|
|
|
|
|
continue
|
|
|
|
|
return self._kdbx
|
|
|
|
|
print(t("kdbx_give_up"))
|
|
|
|
|
return None
|
2026-03-10 03:59:35 -04:00
|
|
|
|
[ADD] vpn : cinq pilotes, secrets en coffre, diagnostic étagé
Le dépôt n'avait aucun moyen de monter un tunnel VPN ni de dire pourquoi il
refuse de monter. Cinq technologies libres, un pilote chacune, derrière un
`vpn.py` qui monte, démonte et diagnostique.
Ce qui n'est pas secret — hôte, utilisateur, routes, MTU — vit dans une
configuration JSON lisible ; clés pré-partagées et mots de passe vivent dans
un coffre KeePassXC. Un profil se montre et se partage sans donner de quoi
monter le tunnel. Les secrets s'écrivent en tmpfs sous 0700, jamais sur un
disque persistant. Le diagnostic part du noyau et remonte, pour que la
première ligne fausse soit la cause et non une conséquence.
Vérifié : 138 tests, dont le rendu de chaque fichier généré.
--- EN ---
The repository had no way to raise a VPN tunnel, nor to say why one refuses
to come up. Five free technologies, one driver each, behind a `vpn.py` that
raises, tears down and diagnoses.
What is not secret — host, user, routes, MTU — lives in readable JSON
configuration; pre-shared keys and passwords live in a KeePassXC vault. A
profile can be shown and shared without handing over the means to raise the
tunnel. Secrets are written to tmpfs at 0700, never to a persistent disk.
Diagnosis starts at the kernel and climbs, so the first false line is the
cause and not a consequence.
Checked: 138 tests, including the rendering of every generated file.
Assisted-by: Claude Opus 5
2026-09-03 23:42:49 -04:00
|
|
|
def adopt(self, kdbx) -> None:
|
|
|
|
|
"""Prend pour la session une base DÉJÀ ouverte.
|
|
|
|
|
|
|
|
|
|
Sert au moment où le coffre vient d'être CRÉÉ : `create_database`
|
|
|
|
|
rend la base ouverte, et sans cela le mot de passe maître serait
|
|
|
|
|
redemandé dans la seconde qui suit — à quelqu'un qui vient de le
|
|
|
|
|
taper deux fois.
|
|
|
|
|
"""
|
|
|
|
|
self._kdbx = kdbx
|
|
|
|
|
|
2026-03-10 04:05:04 -04:00
|
|
|
def get_extra_command_user(
|
|
|
|
|
self, kdbx_key: str | list | None
|
[FIX] security: the KeePass password leaves the command line too
Same exposure as the master password, same fix. kdbx_manager put the Odoo
password straight into the web_login command; /proc/<pid>/cmdline is
readable by every user on the machine, and no downstream filter reaches
that.
The command now carries the NAME of an environment variable, never the
value. One name per entry, because several credentials go out in a single
"parallel" call and a single variable could not tell them apart.
get_extra_command_user therefore returns (fragments, variables), and the
two call sites hand the variables to exec_command_live, which already
merged an environment.
Two things found on the way. web_login re-sent config.default_password_auth
when it retried after dismissing a modal, ignoring whatever the caller had
passed -- the retry silently fell back to "admin". And install_forgejo
printed the admin password back to the terminal, hence into the install log
and any CI capture; its own header already documents the default.
A test pins the guarantee: the fragment must not contain the password.
--- FR ---
Même exposition que pour le mot de passe maître, même correctif.
kdbx_manager mettait le mot de passe Odoo directement dans la commande
web_login ; /proc/<pid>/cmdline est lisible par tout utilisateur de la
machine, et aucun filtre en aval ne l'atteint.
La commande porte désormais le NOM d'une variable d'environnement, jamais
la valeur. Un nom par entrée, car plusieurs identifiants partent dans un
seul appel « parallel » et une variable unique ne saurait les distinguer.
get_extra_command_user rend donc (fragments, variables), et les deux
appelants confient les variables à exec_command_live, qui fusionnait déjà
un environnement.
Deux trouvailles en chemin. web_login renvoyait config.default_password_auth
à la reprise après une modale, ignorant ce que l'appelant avait fourni — la
reprise retombait en silence sur « admin ». Et install_forgejo réaffichait
le mot de passe administrateur, donc dans le journal d'installation et
toute capture de CI ; son propre en-tête documente déjà le défaut.
Un test verrouille la garantie : le fragment ne doit pas porter le secret.
Assisted-by: Claude Opus 5
2026-08-23 00:25:03 -04:00
|
|
|
) -> tuple[str | list, dict]:
|
|
|
|
|
"""(fragments de commande, variables d'environnement à poser).
|
|
|
|
|
|
|
|
|
|
Le mot de passe ne rejoint PAS la ligne de commande : seul le NOM
|
|
|
|
|
d'une variable y figure. /proc/<pid>/cmdline est lisible par tout
|
|
|
|
|
utilisateur de la machine, /proc/<pid>/environ par son seul
|
|
|
|
|
propriétaire — et un mot de passe KeePass n'a rien à faire dans la
|
|
|
|
|
liste des processus.
|
|
|
|
|
|
|
|
|
|
Un nom par entrée : plusieurs identifiants partent dans UNE seule
|
|
|
|
|
commande « parallel », donc une variable unique ne suffirait pas.
|
|
|
|
|
"""
|
2026-03-10 03:59:35 -04:00
|
|
|
values = []
|
[FIX] security: the KeePass password leaves the command line too
Same exposure as the master password, same fix. kdbx_manager put the Odoo
password straight into the web_login command; /proc/<pid>/cmdline is
readable by every user on the machine, and no downstream filter reaches
that.
The command now carries the NAME of an environment variable, never the
value. One name per entry, because several credentials go out in a single
"parallel" call and a single variable could not tell them apart.
get_extra_command_user therefore returns (fragments, variables), and the
two call sites hand the variables to exec_command_live, which already
merged an environment.
Two things found on the way. web_login re-sent config.default_password_auth
when it retried after dismissing a modal, ignoring whatever the caller had
passed -- the retry silently fell back to "admin". And install_forgejo
printed the admin password back to the terminal, hence into the install log
and any CI capture; its own header already documents the default.
A test pins the guarantee: the fragment must not contain the password.
--- FR ---
Même exposition que pour le mot de passe maître, même correctif.
kdbx_manager mettait le mot de passe Odoo directement dans la commande
web_login ; /proc/<pid>/cmdline est lisible par tout utilisateur de la
machine, et aucun filtre en aval ne l'atteint.
La commande porte désormais le NOM d'une variable d'environnement, jamais
la valeur. Un nom par entrée, car plusieurs identifiants partent dans un
seul appel « parallel » et une variable unique ne saurait les distinguer.
get_extra_command_user rend donc (fragments, variables), et les deux
appelants confient les variables à exec_command_live, qui fusionnait déjà
un environnement.
Deux trouvailles en chemin. web_login renvoyait config.default_password_auth
à la reprise après une modale, ignorant ce que l'appelant avait fourni — la
reprise retombait en silence sur « admin ». Et install_forgejo réaffichait
le mot de passe administrateur, donc dans le journal d'installation et
toute capture de CI ; son propre en-tête documente déjà le défaut.
Un test verrouille la garantie : le fragment ne doit pas porter le secret.
Assisted-by: Claude Opus 5
2026-08-23 00:25:03 -04:00
|
|
|
env = {}
|
2026-03-10 03:59:35 -04:00
|
|
|
if kdbx_key:
|
|
|
|
|
kp = self.get_kdbx()
|
|
|
|
|
if not kp:
|
[FIX] security: the KeePass password leaves the command line too
Same exposure as the master password, same fix. kdbx_manager put the Odoo
password straight into the web_login command; /proc/<pid>/cmdline is
readable by every user on the machine, and no downstream filter reaches
that.
The command now carries the NAME of an environment variable, never the
value. One name per entry, because several credentials go out in a single
"parallel" call and a single variable could not tell them apart.
get_extra_command_user therefore returns (fragments, variables), and the
two call sites hand the variables to exec_command_live, which already
merged an environment.
Two things found on the way. web_login re-sent config.default_password_auth
when it retried after dismissing a modal, ignoring whatever the caller had
passed -- the retry silently fell back to "admin". And install_forgejo
printed the admin password back to the terminal, hence into the install log
and any CI capture; its own header already documents the default.
A test pins the guarantee: the fragment must not contain the password.
--- FR ---
Même exposition que pour le mot de passe maître, même correctif.
kdbx_manager mettait le mot de passe Odoo directement dans la commande
web_login ; /proc/<pid>/cmdline est lisible par tout utilisateur de la
machine, et aucun filtre en aval ne l'atteint.
La commande porte désormais le NOM d'une variable d'environnement, jamais
la valeur. Un nom par entrée, car plusieurs identifiants partent dans un
seul appel « parallel » et une variable unique ne saurait les distinguer.
get_extra_command_user rend donc (fragments, variables), et les deux
appelants confient les variables à exec_command_live, qui fusionnait déjà
un environnement.
Deux trouvailles en chemin. web_login renvoyait config.default_password_auth
à la reprise après une modale, ignorant ce que l'appelant avait fourni — la
reprise retombait en silence sur « admin ». Et install_forgejo réaffichait
le mot de passe administrateur, donc dans le journal d'installation et
toute capture de CI ; son propre en-tête documente déjà le défaut.
Un test verrouille la garantie : le fragment ne doit pas porter le secret.
Assisted-by: Claude Opus 5
2026-08-23 00:25:03 -04:00
|
|
|
return "", {}
|
2026-03-10 03:59:35 -04:00
|
|
|
if type(kdbx_key) is not list:
|
|
|
|
|
kdbx_keys = [kdbx_key]
|
|
|
|
|
else:
|
|
|
|
|
kdbx_keys = kdbx_key
|
|
|
|
|
for key in kdbx_keys:
|
|
|
|
|
entry = kp.find_entries_by_title(key, first=True)
|
|
|
|
|
try:
|
|
|
|
|
odoo_user = entry.username
|
|
|
|
|
except AttributeError:
|
2026-03-10 04:05:04 -04:00
|
|
|
_logger.error(f"Cannot find username from keys {key}")
|
2026-03-10 03:59:35 -04:00
|
|
|
try:
|
|
|
|
|
odoo_password = entry.password
|
|
|
|
|
except AttributeError:
|
2026-03-10 04:05:04 -04:00
|
|
|
_logger.error(f"Cannot find password from keys {key}")
|
[FIX] security: the KeePass password leaves the command line too
Same exposure as the master password, same fix. kdbx_manager put the Odoo
password straight into the web_login command; /proc/<pid>/cmdline is
readable by every user on the machine, and no downstream filter reaches
that.
The command now carries the NAME of an environment variable, never the
value. One name per entry, because several credentials go out in a single
"parallel" call and a single variable could not tell them apart.
get_extra_command_user therefore returns (fragments, variables), and the
two call sites hand the variables to exec_command_live, which already
merged an environment.
Two things found on the way. web_login re-sent config.default_password_auth
when it retried after dismissing a modal, ignoring whatever the caller had
passed -- the retry silently fell back to "admin". And install_forgejo
printed the admin password back to the terminal, hence into the install log
and any CI capture; its own header already documents the default.
A test pins the guarantee: the fragment must not contain the password.
--- FR ---
Même exposition que pour le mot de passe maître, même correctif.
kdbx_manager mettait le mot de passe Odoo directement dans la commande
web_login ; /proc/<pid>/cmdline est lisible par tout utilisateur de la
machine, et aucun filtre en aval ne l'atteint.
La commande porte désormais le NOM d'une variable d'environnement, jamais
la valeur. Un nom par entrée, car plusieurs identifiants partent dans un
seul appel « parallel » et une variable unique ne saurait les distinguer.
get_extra_command_user rend donc (fragments, variables), et les deux
appelants confient les variables à exec_command_live, qui fusionnait déjà
un environnement.
Deux trouvailles en chemin. web_login renvoyait config.default_password_auth
à la reprise après une modale, ignorant ce que l'appelant avait fourni — la
reprise retombait en silence sur « admin ». Et install_forgejo réaffichait
le mot de passe administrateur, donc dans le journal d'installation et
toute capture de CI ; son propre en-tête documente déjà le défaut.
Un test verrouille la garantie : le fragment ne doit pas porter le secret.
Assisted-by: Claude Opus 5
2026-08-23 00:25:03 -04:00
|
|
|
var = f"EL_WEB_LOGIN_PWD_{len(values)}"
|
|
|
|
|
env[var] = odoo_password
|
2026-03-10 03:59:35 -04:00
|
|
|
values.append(
|
|
|
|
|
" --default_email_auth"
|
[FIX] security: the KeePass password leaves the command line too
Same exposure as the master password, same fix. kdbx_manager put the Odoo
password straight into the web_login command; /proc/<pid>/cmdline is
readable by every user on the machine, and no downstream filter reaches
that.
The command now carries the NAME of an environment variable, never the
value. One name per entry, because several credentials go out in a single
"parallel" call and a single variable could not tell them apart.
get_extra_command_user therefore returns (fragments, variables), and the
two call sites hand the variables to exec_command_live, which already
merged an environment.
Two things found on the way. web_login re-sent config.default_password_auth
when it retried after dismissing a modal, ignoring whatever the caller had
passed -- the retry silently fell back to "admin". And install_forgejo
printed the admin password back to the terminal, hence into the install log
and any CI capture; its own header already documents the default.
A test pins the guarantee: the fragment must not contain the password.
--- FR ---
Même exposition que pour le mot de passe maître, même correctif.
kdbx_manager mettait le mot de passe Odoo directement dans la commande
web_login ; /proc/<pid>/cmdline est lisible par tout utilisateur de la
machine, et aucun filtre en aval ne l'atteint.
La commande porte désormais le NOM d'une variable d'environnement, jamais
la valeur. Un nom par entrée, car plusieurs identifiants partent dans un
seul appel « parallel » et une variable unique ne saurait les distinguer.
get_extra_command_user rend donc (fragments, variables), et les deux
appelants confient les variables à exec_command_live, qui fusionnait déjà
un environnement.
Deux trouvailles en chemin. web_login renvoyait config.default_password_auth
à la reprise après une modale, ignorant ce que l'appelant avait fourni — la
reprise retombait en silence sur « admin ». Et install_forgejo réaffichait
le mot de passe administrateur, donc dans le journal d'installation et
toute capture de CI ; son propre en-tête documente déjà le défaut.
Un test verrouille la garantie : le fragment ne doit pas porter le secret.
Assisted-by: Claude Opus 5
2026-08-23 00:25:03 -04:00
|
|
|
f" {odoo_user} --default_password_auth_env {var}"
|
2026-03-10 03:59:35 -04:00
|
|
|
)
|
|
|
|
|
if len(values) == 0:
|
[FIX] security: the KeePass password leaves the command line too
Same exposure as the master password, same fix. kdbx_manager put the Odoo
password straight into the web_login command; /proc/<pid>/cmdline is
readable by every user on the machine, and no downstream filter reaches
that.
The command now carries the NAME of an environment variable, never the
value. One name per entry, because several credentials go out in a single
"parallel" call and a single variable could not tell them apart.
get_extra_command_user therefore returns (fragments, variables), and the
two call sites hand the variables to exec_command_live, which already
merged an environment.
Two things found on the way. web_login re-sent config.default_password_auth
when it retried after dismissing a modal, ignoring whatever the caller had
passed -- the retry silently fell back to "admin". And install_forgejo
printed the admin password back to the terminal, hence into the install log
and any CI capture; its own header already documents the default.
A test pins the guarantee: the fragment must not contain the password.
--- FR ---
Même exposition que pour le mot de passe maître, même correctif.
kdbx_manager mettait le mot de passe Odoo directement dans la commande
web_login ; /proc/<pid>/cmdline est lisible par tout utilisateur de la
machine, et aucun filtre en aval ne l'atteint.
La commande porte désormais le NOM d'une variable d'environnement, jamais
la valeur. Un nom par entrée, car plusieurs identifiants partent dans un
seul appel « parallel » et une variable unique ne saurait les distinguer.
get_extra_command_user rend donc (fragments, variables), et les deux
appelants confient les variables à exec_command_live, qui fusionnait déjà
un environnement.
Deux trouvailles en chemin. web_login renvoyait config.default_password_auth
à la reprise après une modale, ignorant ce que l'appelant avait fourni — la
reprise retombait en silence sur « admin ». Et install_forgejo réaffichait
le mot de passe administrateur, donc dans le journal d'installation et
toute capture de CI ; son propre en-tête documente déjà le défaut.
Un test verrouille la garantie : le fragment ne doit pas porter le secret.
Assisted-by: Claude Opus 5
2026-08-23 00:25:03 -04:00
|
|
|
return "", {}
|
2026-03-10 03:59:35 -04:00
|
|
|
elif len(values) == 1:
|
[FIX] security: the KeePass password leaves the command line too
Same exposure as the master password, same fix. kdbx_manager put the Odoo
password straight into the web_login command; /proc/<pid>/cmdline is
readable by every user on the machine, and no downstream filter reaches
that.
The command now carries the NAME of an environment variable, never the
value. One name per entry, because several credentials go out in a single
"parallel" call and a single variable could not tell them apart.
get_extra_command_user therefore returns (fragments, variables), and the
two call sites hand the variables to exec_command_live, which already
merged an environment.
Two things found on the way. web_login re-sent config.default_password_auth
when it retried after dismissing a modal, ignoring whatever the caller had
passed -- the retry silently fell back to "admin". And install_forgejo
printed the admin password back to the terminal, hence into the install log
and any CI capture; its own header already documents the default.
A test pins the guarantee: the fragment must not contain the password.
--- FR ---
Même exposition que pour le mot de passe maître, même correctif.
kdbx_manager mettait le mot de passe Odoo directement dans la commande
web_login ; /proc/<pid>/cmdline est lisible par tout utilisateur de la
machine, et aucun filtre en aval ne l'atteint.
La commande porte désormais le NOM d'une variable d'environnement, jamais
la valeur. Un nom par entrée, car plusieurs identifiants partent dans un
seul appel « parallel » et une variable unique ne saurait les distinguer.
get_extra_command_user rend donc (fragments, variables), et les deux
appelants confient les variables à exec_command_live, qui fusionnait déjà
un environnement.
Deux trouvailles en chemin. web_login renvoyait config.default_password_auth
à la reprise après une modale, ignorant ce que l'appelant avait fourni — la
reprise retombait en silence sur « admin ». Et install_forgejo réaffichait
le mot de passe administrateur, donc dans le journal d'installation et
toute capture de CI ; son propre en-tête documente déjà le défaut.
Un test verrouille la garantie : le fragment ne doit pas porter le secret.
Assisted-by: Claude Opus 5
2026-08-23 00:25:03 -04:00
|
|
|
return values[0], env
|
|
|
|
|
return values, env
|