Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c47325ef14 | ||
|
|
0ff6e687b6 |
@@ -42,6 +42,13 @@ bloquent donc pas le déploiement. Il ne valide ni les valeurs ni les permission
|
|||||||
du fichier d'environnement. Pour analyser seulement le contenu indexé avant un
|
du fichier d'environnement. Pour analyser seulement le contenu indexé avant un
|
||||||
commit, utilisez `scripts/check_secrets.py --staged`.
|
commit, utilisez `scripts/check_secrets.py --staged`.
|
||||||
|
|
||||||
|
Dans une URL, le contrôle exempte uniquement les placeholders documentaires
|
||||||
|
explicitement reconnus : `{token}`/`{jeton}` (et les quelques noms documentaires
|
||||||
|
équivalents), leur forme `<token>`, les suites de trois points ou les caractères
|
||||||
|
de masquage `•`/`…`. Les valeurs entre accolades ou chevrons au contenu arbitraire,
|
||||||
|
les astérisques et toute valeur réelle restent signalés. Un même paramètre URL est
|
||||||
|
contrôlé même si un autre paramètre sensible de la ligne est déjà un placeholder.
|
||||||
|
|
||||||
Le dry-run autorise les lectures nécessaires à la simulation, mais n'applique aucune sortie
|
Le dry-run autorise les lectures nécessaires à la simulation, mais n'applique aucune sortie
|
||||||
CalDAV/XMPP et ne modifie aucun état local persistant. L'état RSS reste limité à la mémoire du
|
CalDAV/XMPP et ne modifie aucun état local persistant. L'état RSS reste limité à la mémoire du
|
||||||
processus. Le mode `PRONOTE_AUTH_MODE=qr_token` est incompatible avec cette garantie : la commande
|
processus. Le mode `PRONOTE_AUTH_MODE=qr_token` est incompatible avec cette garantie : la commande
|
||||||
|
|||||||
@@ -37,6 +37,15 @@ _URL_SECRET_RE = re.compile(
|
|||||||
r"(?ix)[?&](?:api[_-]?key|access[_-]?token|auth(?:orization)?|icalsecurise|password|secret|token)"
|
r"(?ix)[?&](?:api[_-]?key|access[_-]?token|auth(?:orization)?|icalsecurise|password|secret|token)"
|
||||||
r"=([^&#\s]{3,})"
|
r"=([^&#\s]{3,})"
|
||||||
)
|
)
|
||||||
|
_URL_PLACEHOLDER_RE = re.compile(
|
||||||
|
r"(?ix)^(?:"
|
||||||
|
r"\{(?:token|jeton|secret|valeur|value|param(?:etre)?|version|prenom|"
|
||||||
|
r"etablissement|hote|host)\}|"
|
||||||
|
r"<(?:token|jeton|secret|valeur|value|param(?:etre)?|version|prenom|"
|
||||||
|
r"etablissement|hote|host)>|"
|
||||||
|
r"\.{3,}|[•…]+"
|
||||||
|
r")$"
|
||||||
|
)
|
||||||
_EXTRA_NAMES = frozenset({"pronote_sync"})
|
_EXTRA_NAMES = frozenset({"pronote_sync"})
|
||||||
|
|
||||||
|
|
||||||
@@ -185,7 +194,11 @@ def find_secrets(
|
|||||||
)
|
)
|
||||||
if is_literal_secret:
|
if is_literal_secret:
|
||||||
findings.append(SecretFinding(relative_path, number, "affectation-litterale"))
|
findings.append(SecretFinding(relative_path, number, "affectation-litterale"))
|
||||||
if _URL_SECRET_RE.search(line):
|
has_real_url_secret = any(
|
||||||
|
not _URL_PLACEHOLDER_RE.fullmatch(match.group(1))
|
||||||
|
for match in _URL_SECRET_RE.finditer(line)
|
||||||
|
)
|
||||||
|
if has_real_url_secret:
|
||||||
findings.append(SecretFinding(relative_path, number, "parametre-url"))
|
findings.append(SecretFinding(relative_path, number, "parametre-url"))
|
||||||
return sorted(findings, key=lambda finding: (str(finding.path), finding.line, finding.rule))
|
return sorted(findings, key=lambda finding: (str(finding.path), finding.line, finding.rule))
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,75 @@ def test_main_detects_sensitive_url_parameter(
|
|||||||
assert sentinel not in output
|
assert sentinel not in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_ignores_documentation_url_placeholders(
|
||||||
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
||||||
|
) -> None:
|
||||||
|
"""Ignore les marqueurs de remplacement utilisés dans une documentation.
|
||||||
|
|
||||||
|
:param secret_checker: Module du script sous test.
|
||||||
|
:param tmp_path: Répertoire temporaire représentant un dépôt.
|
||||||
|
:param capsys: Fixture de capture de sortie.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
(tmp_path / "guide.md").write_text(
|
||||||
|
"\n".join(
|
||||||
|
(
|
||||||
|
"https://example.invalid/?icalsecurise={jeton}",
|
||||||
|
"https://example.invalid/?icalsecurise=••••••••",
|
||||||
|
"https://example.invalid/?icalsecurise=<token>",
|
||||||
|
"https://example.invalid/?icalsecurise=...",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
+ "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert secret_checker.main([], root=tmp_path) == 0
|
||||||
|
assert "OK:" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_detects_a_real_url_secret_after_a_placeholder(
|
||||||
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
||||||
|
) -> None:
|
||||||
|
"""Détecte un secret réel placé après un placeholder sur la même URL.
|
||||||
|
|
||||||
|
:param secret_checker: Module du script sous test.
|
||||||
|
:param tmp_path: Répertoire temporaire représentant un dépôt.
|
||||||
|
:param capsys: Fixture de capture de sortie.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
sentinel = "m14-url-after-placeholder-sentinel"
|
||||||
|
(tmp_path / "guide.md").write_text(
|
||||||
|
f"https://example.invalid/?token={{jeton}}&api_key={sentinel}\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert secret_checker.main([], root=tmp_path) == 1
|
||||||
|
output = capsys.readouterr().out
|
||||||
|
assert "guide.md:1 (parametre-url)" in output
|
||||||
|
assert sentinel not in output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("value", ["<ghp_…>", "***"])
|
||||||
|
def test_main_rejects_ambiguous_url_placeholders(
|
||||||
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str], value: str
|
||||||
|
) -> None:
|
||||||
|
"""Refuse les valeurs qui ne sont pas des placeholders documentaires fermés.
|
||||||
|
|
||||||
|
:param secret_checker: Module du script sous test.
|
||||||
|
:param tmp_path: Répertoire temporaire représentant un dépôt.
|
||||||
|
:param capsys: Fixture de capture de sortie.
|
||||||
|
:param value: Valeur ambiguë à ne pas exempter.
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
(tmp_path / "guide.md").write_text(
|
||||||
|
f"https://example.invalid/?token={value}\n", encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert secret_checker.main([], root=tmp_path) == 1
|
||||||
|
assert "guide.md:1 (parametre-url)" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
def test_staged_mode_inspects_only_paths_provided_by_git(
|
def test_staged_mode_inspects_only_paths_provided_by_git(
|
||||||
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
secret_checker: ModuleType, tmp_path: Path, capsys: CaptureFixture[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user