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

Reply via email to