This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git
The following commit(s) were added to refs/heads/main by this push:
new b59be74 fix(vulnogram): stderr routing, double-warning, UTF-8 read,
strict package regex (#241)
b59be74 is described below
commit b59be74c1124cded2e7e4840717bf978fde4eee4
Author: André Ahlert <[email protected]>
AuthorDate: Wed May 20 05:17:16 2026 -0300
fix(vulnogram): stderr routing, double-warning, UTF-8 read, strict package
regex (#241)
oauth-api/check.py: route 'error: ...' results to stderr regardless of
--quiet so callers piping 2>/dev/null get a clean channel and --quiet
invocations still surface the failure reason. Previous code only emitted
to stderr under --quiet via a ternary and otherwise printed to stdout.
oauth-api/credentials.py: collapse chmod-failure path into try/except/else
so a successful chmod no longer double-warns on subsequent stat check.
generate-cve-json/cve_json.py:
- load_config: read with encoding='utf-8' to avoid locale-default decode
on systems where LANG is unset; matches the UTF-8 we write elsewhere.
- check_advisory_for_metadata: drop fullmatch-or-match fallback. PACKAGE_RE
is already anchored with a trailing $; the 'or match' branch would only
fire if a future edit removed the anchor, silently downgrading validation.
Tests added in oauth-api/tests/test_check.py:
- test_unknown_error_writes_to_stderr_not_stdout
- test_unknown_error_quiet_still_writes_to_stderr
---
.../src/generate_cve_json/cve_json.py | 9 ++++--
.../vulnogram/oauth-api/src/vulnogram_api/check.py | 33 ++++++++++++----------
.../oauth-api/src/vulnogram_api/credentials.py | 33 ++++++++++++++++------
tools/vulnogram/oauth-api/tests/test_check.py | 29 +++++++++++++++++++
4 files changed, 78 insertions(+), 26 deletions(-)
diff --git
a/tools/vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py
b/tools/vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py
index 79a57de..8944570 100644
--- a/tools/vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py
+++ b/tools/vulnogram/generate-cve-json/src/generate_cve_json/cve_json.py
@@ -155,7 +155,7 @@ def _load_config(config_path: Path | str | None = None) ->
dict:
f" at {_DEFAULT_CONFIG_RELPATH} relative to the cwd.\n"
f" Schema: see the generate-cve-json package README."
)
- return tomllib.loads(path.read_text())
+ return tomllib.loads(path.read_text(encoding="utf-8"))
def _set_config_path(config_path: Path | str) -> None:
@@ -704,7 +704,12 @@ def _product_for_package(
return overrides[package_name]
if package_name == TOP_LEVEL_NAME:
return TOP_LEVEL_PRODUCT
- match = PACKAGE_RE.fullmatch(package_name) or
PACKAGE_RE.match(package_name)
+ # Anchor at both ends: a partial match (`match`) can capture an
+ # arbitrary suffix into the `project` group for non-standard
+ # package strings, producing a fabricated product name. If the
+ # package name does not fully match the configured pattern we
+ # fall through and treat it as opaque.
+ match = PACKAGE_RE.fullmatch(package_name)
if match is not None:
project_dir = (match.groupdict().get("project") or "").strip()
if project_dir:
diff --git a/tools/vulnogram/oauth-api/src/vulnogram_api/check.py
b/tools/vulnogram/oauth-api/src/vulnogram_api/check.py
index 3c9847d..9bdf833 100644
--- a/tools/vulnogram/oauth-api/src/vulnogram_api/check.py
+++ b/tools/vulnogram/oauth-api/src/vulnogram_api/check.py
@@ -98,22 +98,25 @@ def main(argv: list[str] | None = None) -> int:
session = Session.load(creds_path)
result = probe(session, section=args.section)
- if not args.quiet:
- # Keep the first line a bare `valid` / `expired` / etc. so callers
- # that exact-match the result token still parse cleanly (per the
- # parse rules in `.claude/skills/security-issue-sync/SKILL.md`
- # Step 5b). When a from-address is on file, surface it on a
- # second line for audit-trail visibility — *"did I capture the
- # cookie from the right @apache.org login?"*.
- print(result)
- if result == "valid" and session.from_address:
- print(f"logged in as {session.from_address}")
- if result == "valid":
- return 0
- if result == "expired":
- return 1
+ if result in ("valid", "expired"):
+ if not args.quiet:
+ # Keep the first line a bare `valid` / `expired` so callers
+ # that exact-match the result token still parse cleanly
+ # (per the parse rules in
+ # `.claude/skills/security-issue-sync/SKILL.md` Step 5b).
+ # When a from-address is on file, surface it on a second
+ # line for audit-trail visibility — *"did I capture the
+ # cookie from the right @apache.org login?"*.
+ print(result)
+ if result == "valid" and session.from_address:
+ print(f"logged in as {session.from_address}")
+ return 0 if result == "valid" else 1
+
# Anything else — network errors, unexpected HTTP status, etc.
- print(result, file=sys.stderr) if args.quiet else None
+ # Errors always go to stderr regardless of `--quiet` so callers
+ # that pipe `2>/dev/null` get a clean channel and `--quiet`
+ # invocations still see *why* the exit code is 3.
+ print(result, file=sys.stderr)
return 3
diff --git a/tools/vulnogram/oauth-api/src/vulnogram_api/credentials.py
b/tools/vulnogram/oauth-api/src/vulnogram_api/credentials.py
index fa335fa..71625fd 100644
--- a/tools/vulnogram/oauth-api/src/vulnogram_api/credentials.py
+++ b/tools/vulnogram/oauth-api/src/vulnogram_api/credentials.py
@@ -121,15 +121,30 @@ def write_session_atomic(
try:
out_path.parent.chmod(0o700)
except OSError as e:
- print(f"Warning: could not chmod 700 {out_path.parent}: {e}",
file=sys.stderr)
- parent_mode = out_path.parent.stat().st_mode & 0o777
- if parent_mode & 0o077:
- print(
- f"Warning: {out_path.parent} mode is {oct(parent_mode)} — "
- f"session cookie may be readable by other users on this host. "
- f"Consider moving --out to a directory you control.",
- file=sys.stderr,
- )
+ # chmod failed (NFS / AFS / unowned dir on macOS / Windows
+ # ACLs). Emit a single warning that captures both the failure
+ # and the resulting open mode, rather than the two-warning
+ # cascade that the previous code produced.
+ parent_mode = out_path.parent.stat().st_mode & 0o777
+ if parent_mode & 0o077:
+ print(
+ f"Warning: could not chmod 700 {out_path.parent}: {e}. "
+ f"Current mode is {oct(parent_mode)} — session cookie "
+ f"may be readable by other users on this host. "
+ f"Consider moving --out to a directory you control.",
+ file=sys.stderr,
+ )
+ else:
+ # chmod succeeded; sanity-check the resulting mode in case a
+ # concurrent writer / umask race left it open anyway.
+ parent_mode = out_path.parent.stat().st_mode & 0o777
+ if parent_mode & 0o077:
+ print(
+ f"Warning: {out_path.parent} mode is {oct(parent_mode)} "
+ f"after chmod 700 — possible race or filesystem "
+ f"override. Session cookie may be readable by others.",
+ file=sys.stderr,
+ )
payload = (
json.dumps(
diff --git a/tools/vulnogram/oauth-api/tests/test_check.py
b/tools/vulnogram/oauth-api/tests/test_check.py
index 058770a..aeec061 100644
--- a/tools/vulnogram/oauth-api/tests/test_check.py
+++ b/tools/vulnogram/oauth-api/tests/test_check.py
@@ -85,3 +85,32 @@ def test_unknown_error_returns_3(tmp_path, monkeypatch,
capsys):
monkeypatch.setattr(check, "probe", lambda *a, **kw: "error: connection
refused")
rc = check.main([])
assert rc == 3
+
+
+def test_unknown_error_writes_to_stderr_not_stdout(tmp_path, monkeypatch,
capsys):
+ """Error results must go to stderr so `2>/dev/null` gives a clean channel.
+
+ Regression: the previous code printed every result to stdout in
+ non-quiet mode and only emitted to stderr under ``--quiet`` (and
+ only via a ternary that swallowed it the rest of the time).
+ """
+ creds = _write_session(tmp_path / "session.json")
+ monkeypatch.setenv("VULNOGRAM_SESSION", str(creds))
+ monkeypatch.setattr(check, "probe", lambda *a, **kw: "error: connection
refused")
+ rc = check.main([])
+ captured = capsys.readouterr()
+ assert rc == 3
+ assert "error: connection refused" in captured.err
+ assert "error: connection refused" not in captured.out
+
+
+def test_unknown_error_quiet_still_writes_to_stderr(tmp_path, monkeypatch,
capsys):
+ """Under ``--quiet``, stdout stays empty but stderr still surfaces the
reason."""
+ creds = _write_session(tmp_path / "session.json")
+ monkeypatch.setenv("VULNOGRAM_SESSION", str(creds))
+ monkeypatch.setattr(check, "probe", lambda *a, **kw: "error: timeout")
+ rc = check.main(["--quiet"])
+ captured = capsys.readouterr()
+ assert rc == 3
+ assert captured.out == ""
+ assert "error: timeout" in captured.err