From 79b97bf08b785651eb712810636d6d310594daff Mon Sep 17 00:00:00 2001 From: Micah Villmow <4211002+mvillmow@users.noreply.github.com> Date: Tue, 12 May 2026 19:53:35 -0700 Subject: [PATCH] security(promtail): redact secrets from syslog pipeline (#190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the same five replace stages used by the hermes and nats scrape jobs (bearer, token=, key=, secret=, password=) to the syslog job. Host-level syslog may contain credentials if system services log them, so the same redaction guarantees must hold. Adds tests/test_configs.py::test_redaction_enabled_jobs_have_secret_patterns, which iterates over the set {syslog, hermes, nats} and asserts each carries all five patterns — preventing future regressions where a new redaction- enabled job ships without the required stages. Closes #190 Co-Authored-By: Claude Opus 4.7 (1M context) --- configs/promtail.yml | 16 ++++++++++++++++ tests/test_configs.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/configs/promtail.yml b/configs/promtail.yml index 7467c3f..82467ca 100644 --- a/configs/promtail.yml +++ b/configs/promtail.yml @@ -20,6 +20,22 @@ scrape_configs: job: syslog host: ${PROMTAIL_HOST_LABEL:-${HOSTNAME}} __path__: /var/log/syslog + pipeline_stages: + - replace: + expression: '(?i)(bearer\s+)[A-Za-z0-9._\-]+' + replace: '${1}' + - replace: + expression: '(?i)(token[=:]\s*)[A-Za-z0-9._\-]+' + replace: '${1}' + - replace: + expression: '(?i)(key[=:]\s*)[A-Za-z0-9._\-]+' + replace: '${1}' + - replace: + expression: '(?i)(secret[=:]\s*)[A-Za-z0-9._\-]+' + replace: '${1}' + - replace: + expression: '(?i)(password[=:]\s*)\S+' + replace: '${1}' # Tail Hermes application log - job_name: hermes diff --git a/tests/test_configs.py b/tests/test_configs.py index a0516a8..d677542 100644 --- a/tests/test_configs.py +++ b/tests/test_configs.py @@ -143,6 +143,34 @@ def test_syslog_host_label_uses_env_var(self): raw = (CONFIGS_DIR / "promtail.yml").read_text() assert "HOSTNAME" in raw, "host label must reference ${HOSTNAME} for portability" + def test_redaction_enabled_jobs_have_secret_patterns(self): + """All jobs that read host/app logs containing user-supplied data must + redact the same five secret patterns (bearer, token, key, secret, + password). Adding a new such job without redaction is a security + regression — extend REDACTION_ENABLED_JOBS below. + + Issue #190: extended to syslog (host-level logs may contain secrets + if system services log credentials). + """ + REDACTION_ENABLED_JOBS = {"syslog", "hermes", "nats"} + REQUIRED_PATTERNS = ("bearer", "token", "key", "secret", "password") + + jobs_by_name = { + j["job_name"]: j for j in self.config["scrape_configs"] + } + for job_name in REDACTION_ENABLED_JOBS: + assert job_name in jobs_by_name, f"expected scrape job {job_name!r}" + stages = jobs_by_name[job_name].get("pipeline_stages") + assert stages, f"{job_name!r} must have pipeline_stages for redaction" + joined = " ".join( + str(s.get("replace", {}).get("expression", "")) for s in stages + ).lower() + for pat in REQUIRED_PATTERNS: + assert pat in joined, ( + f"{job_name!r} pipeline_stages missing redaction for " + f"{pat!r}; got expressions: {joined!r}" + ) + class TestGrafanaDatasourcesConfig(unittest.TestCase): def setUp(self):