Compare commits

...

2 Commits

Author SHA1 Message Date
Molecule AI Dev Engineer A (Kimi) 7eb8dde3da style: ruff cleanup — F401, F541, F841, E741 in 18 files
Auto-fixes + manual renames for ambiguous variable names.
All checks now pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 20:22:03 +00:00
Molecule AI Dev Engineer A (Kimi) 54b6ce0d57 fix(channels): make discord token-leak test deterministic (RCA #1763 Finding 2)
Block internal-flavored paths / Block forbidden paths (pull_request) Waiting to run
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
CI / Python Lint & Test (pull_request) Waiting to run
CI / all-required (pull_request) Waiting to run
CI / Detect changes (pull_request) Waiting to run
E2E API Smoke Test / detect-changes (pull_request) Waiting to run
E2E Chat / detect-changes (pull_request) Waiting to run
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Waiting to run
Handlers Postgres Integration / detect-changes (pull_request) Waiting to run
Harness Replays / detect-changes (pull_request) Waiting to run
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Waiting to run
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Waiting to run
lint-required-no-paths / lint-required-no-paths (pull_request) Waiting to run
Secret scan / Scan diff for credential-shaped strings (pull_request) Waiting to run
gate-check-v3 / gate-check (pull_request) Waiting to run
qa-review / approved (pull_request) Waiting to run
security-review / approved (pull_request) Waiting to run
sop-checklist / all-items-acked (pull_request) Waiting to run
sop-checklist / review-refire (pull_request) Waiting to run
sop-tier-check / tier-check (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 8s
audit-force-merge / audit (pull_request) Waiting to run
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Has been cancelled
E2E API Smoke Test / E2E API Smoke Test (pull_request) Has been cancelled
CI / Platform (Go) (pull_request) Has been cancelled
CI / Canvas (Next.js) (pull_request) Has been cancelled
CI / Shellcheck (E2E scripts) (pull_request) Has been cancelled
CI / Canvas Deploy Reminder (pull_request) Has been cancelled
E2E Chat / E2E Chat (pull_request) Has been cancelled
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Has been cancelled
Harness Replays / Harness Replays (pull_request) Has been cancelled
TestDiscordAdapter_SendMessage_ErrorDoesNotLeakToken previously relied
on a real network dial to an unroutable address and t.Skip'd when the
request unexpectedly succeeded.  This made the test flaky and caused
silent loss of security-coverage in environments where the dial did not
fail.

- Add optional httpClient field to DiscordAdapter so tests can inject a
  fake http.RoundTripper.
- Replace the skip-based test with a deterministic fake-transport that
  returns an error containing the webhook URL, then assert the adapter
  still redacts the token.
- Zero behavioral change when httpClient is nil (production path).

Fixes RCA #1763 Finding 2 — webhook token redaction skip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 04:00:29 +00:00
20 changed files with 50 additions and 50 deletions
+4 -4
View File
@@ -547,12 +547,12 @@ def file_or_update(
if dry_run:
print(f"::notice::[dry-run] would file/update drift issue for {branch}")
print(f"::group::[dry-run] title")
print("::group::[dry-run] title")
print(title)
print(f"::endgroup::")
print(f"::group::[dry-run] body")
print("::endgroup::")
print("::group::[dry-run] body")
print(body)
print(f"::endgroup::")
print("::endgroup::")
return
existing = find_open_issue(title)
@@ -13,7 +13,6 @@ from __future__ import annotations
import argparse
import glob
import re
import sys
from pathlib import Path
from typing import NamedTuple
+1 -1
View File
@@ -283,7 +283,7 @@ def _ensure_labels(repo: str, names: list[str]) -> list[int]:
if status != "ok" or not isinstance(labels, list):
return []
out: list[int] = []
by_name = {l["name"]: l["id"] for l in labels if isinstance(l, dict)}
by_name = {lbl["name"]: lbl["id"] for lbl in labels if isinstance(lbl, dict)}
for n in names:
if n in by_name:
out.append(by_name[n])
@@ -82,7 +82,7 @@ import sys
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timedelta, timezone
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
+2 -3
View File
@@ -338,7 +338,6 @@ def compute_ack_state(
# Filter out self-acks and unknown slugs.
ackers_per_slug: dict[str, list[str]] = {s: [] for s in items_by_slug}
rejected_self: dict[str, list[str]] = {s: [] for s in items_by_slug}
rejected_unknown: dict[str, list[str]] = {s: [] for s in items_by_slug}
pending_team_check: dict[str, list[str]] = {s: [] for s in items_by_slug}
for (user, slug), kind in latest_directive.items():
@@ -842,7 +841,7 @@ def render_status(
def get_tier_mode(pr: dict[str, Any], cfg: dict[str, Any]) -> str:
"""Read tier label, return 'hard' or 'soft' per cfg.tier_failure_mode."""
labels = pr.get("labels") or []
tier_labels = [l.get("name", "") for l in labels if (l.get("name", "") or "").startswith("tier:")]
tier_labels = [lbl.get("name", "") for lbl in labels if (lbl.get("name", "") or "").startswith("tier:")]
mode_map = cfg.get("tier_failure_mode") or {}
default_mode = cfg.get("default_mode", "hard")
for tl in tier_labels:
@@ -865,7 +864,7 @@ def is_high_risk(pr: dict[str, Any], cfg: dict[str, Any]) -> bool:
Governance fix for internal#442 — closes the inconsistency between
sop-tier-check (tier-aware) and sop-checklist (was tier-blind).
"""
label_set = {(l.get("name") or "") for l in (pr.get("labels") or [])}
label_set = {(lbl.get("name") or "") for lbl in (pr.get("labels") or [])}
if "tier:high" in label_set:
return True
high_risk_labels = set(cfg.get("high_risk_labels") or [])
@@ -80,7 +80,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
# GET /repos/{owner}/{name}/pulls/{pr_number}
m = re.match(r"^/api/v1/repos/([^/]+)/([^/]+)/pulls/(\d+)$", path)
if m:
owner, name, pr_num = m.group(1), m.group(2), m.group(3)
_owner, _name, pr_num = m.group(1), m.group(2), m.group(3)
if sc == "T2_pr_closed":
return self._json(200, {
"number": int(pr_num),
@@ -146,7 +146,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
# GET /teams/{team_id}/members/{username}
m = re.match(r"^/api/v1/teams/(\d+)/members/([^/]+)$", path)
if m:
team_id, login = m.group(1), m.group(2)
_team_id, _login = m.group(1), m.group(2)
if sc == "T8_team_not_member":
return self._empty(404)
if sc == "T9_team_403":
@@ -15,7 +15,6 @@ Mirrors the pattern in scripts/ops/test_check_migration_collisions.py
from __future__ import annotations
import importlib.util
import os
import sys
import unittest
from pathlib import Path
@@ -22,7 +22,6 @@ from __future__ import annotations
import os
import sys
import tempfile
import unittest
# Resolve sibling script regardless of where pytest is invoked from.
+2 -2
View File
@@ -281,8 +281,8 @@ def main() -> int:
for prefix, peers in sorted(open_pr_collisions.items()):
peer_str = ", ".join(f"#{p['number']} ({p['headRefName']})" for p in peers)
print(f"::error::migration prefix {prefix:03d} also claimed by open PR(s): {peer_str}")
print(f"::error::rebase coordination needed — only one PR can land a given prefix; "
f"renumber yours or theirs")
print("::error::rebase coordination needed — only one PR can land a given prefix; "
"renumber yours or theirs")
return 1
-2
View File
@@ -18,9 +18,7 @@ No network. No live Gitea calls.
from __future__ import annotations
import importlib.util
import json
import os
import sys
import textwrap
from pathlib import Path
from unittest import mock
+1 -3
View File
@@ -55,9 +55,7 @@ from __future__ import annotations
import importlib.util
import os
import sys
from pathlib import Path
from unittest import mock
import pytest
@@ -164,7 +162,7 @@ def test_bp_orphan_context_fails(envset, monkeypatch, capsys):
" all-required:\n runs-on: x\n steps:\n - run: echo hi\n",
)
m = _import_lint()
posted = _stub_api(
_posted = _stub_api(
monkeypatch,
m,
("ok", {"status_check_contexts": [
@@ -60,10 +60,8 @@ from __future__ import annotations
import importlib.util
import os
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest import mock
import pytest
-3
View File
@@ -53,10 +53,7 @@ from __future__ import annotations
import importlib.util
import os
import subprocess
import sys
import textwrap
from pathlib import Path
from unittest import mock
import pytest
@@ -61,9 +61,7 @@ from __future__ import annotations
import importlib.util
import os
import subprocess
import sys
from pathlib import Path
from unittest import mock
import pytest
-2
View File
@@ -38,9 +38,7 @@ from __future__ import annotations
import importlib.util
import os
import sys
from pathlib import Path
from unittest import mock
import pytest
+1 -3
View File
@@ -37,7 +37,6 @@ from __future__ import annotations
import importlib.util
import json
import os
import sys
import urllib.error
from pathlib import Path
from unittest import mock
@@ -542,7 +541,6 @@ def test_auto_close_skips_when_main_pending(wd_module, monkeypatch):
"""main pending (CI still running) at NEW_SHA → leave old issue alone.
Pending could resolve to red, so closing prematurely would lose the
breadcrumb of the prior red."""
old_title = f"[main-red] owner/repo: {SHA_RED[:10]}"
stub = _make_stub_api({
("GET", "/repos/owner/repo/branches/main"): (200, _branches_response(SHA_GREEN)),
("GET", f"/repos/owner/repo/commits/{SHA_GREEN}/status"): (
@@ -790,7 +788,7 @@ def test_emit_loki_event_prints_json_line(wd_module, capsys, monkeypatch):
captured = capsys.readouterr()
assert "main-red-watchdog event:" in captured.out
# Find the JSON payload after the prefix and verify it parses
line = [l for l in captured.out.splitlines() if "main-red-watchdog event:" in l][0]
line = [ln for ln in captured.out.splitlines() if "main-red-watchdog event:" in ln][0]
payload = json.loads(line.split("main-red-watchdog event:", 1)[1].strip())
assert payload["event_type"] == "main_red_detected"
assert payload["repo"] == "owner/repo"
-2
View File
@@ -40,7 +40,6 @@ Dependencies: stdlib + pytest + PyYAML. No network.
from __future__ import annotations
import importlib.util
import json
import os
import sys
from pathlib import Path
@@ -853,7 +852,6 @@ def test_reap_skips_combined_success_shas(sr_module, monkeypatch):
Mock 2 SHAs with combined=success + 1 with combined=failure → only
the failure-SHA's statuses get the per-context loop applied.
"""
per_context_iterated_for: list[str] = []
posts: list[tuple[str, dict]] = []
failure_statuses = [
+3 -5
View File
@@ -23,11 +23,9 @@ import json
import os
import re
import sys
import time
import urllib.request
import urllib.error
from datetime import datetime, timezone
from typing import Any, Optional
# ── Gitea API client ────────────────────────────────────────────────────────
@@ -160,9 +158,9 @@ def signal_1_comment_scan(pr_number: int, repo: str) -> dict:
# Build reverse map: login -> (group, agent_key)
login_to_group = {}
for group, login in relevant_roles.items():
for role, l in AGENT_LOGIN_MAP.items():
if l == login:
login_to_group[l] = (group, f"core-{role}")
for role, agt_login in AGENT_LOGIN_MAP.items():
if agt_login == login:
login_to_group[agt_login] = (group, f"core-{role}")
# Collect all agent-tag matches from comments
comments = []
+10 -2
View File
@@ -33,7 +33,12 @@ const (
//
// StartPolling returns nil immediately — Discord does not support long-polling;
// use the Interactions webhook route instead.
type DiscordAdapter struct{}
type DiscordAdapter struct {
// httpClient overrides the default HTTP client used by SendMessage.
// Used in tests to inject a fake transport so token-redaction checks
// are deterministic and do not depend on real network behavior.
httpClient *http.Client
}
func (d *DiscordAdapter) Type() string { return "discord" }
func (d *DiscordAdapter) DisplayName() string { return "Discord" }
@@ -95,7 +100,10 @@ func (d *DiscordAdapter) SendMessage(ctx context.Context, config map[string]inte
// Split long messages into chunks at word boundaries where possible.
chunks := splitMessage(text, maxLen)
client := &http.Client{Timeout: discordHTTPTimeout}
client := d.httpClient
if client == nil {
client = &http.Client{Timeout: discordHTTPTimeout}
}
for _, chunk := range chunks {
payload, err := json.Marshal(map[string]string{"content": chunk})
if err != nil {
@@ -3,6 +3,7 @@ package channels
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
@@ -288,17 +289,25 @@ func TestSplitMessage_LongMessage(t *testing.T) {
}
// TestDiscordAdapter_SendMessage_ErrorDoesNotLeakToken verifies that when the
// HTTP call to the Discord webhook fails (e.g. DNS error), the returned error
// message does NOT contain the webhook URL — which embeds the Discord token.
// HTTP call to the Discord webhook fails, the returned error message does NOT
// contain the webhook URL — which embeds the Discord token. Uses a fake
// http.RoundTripper so the test is deterministic and does not depend on real
// network behavior (fixes RCA #1763 Finding 2).
// Regression test for the MEDIUM security finding in PR #659.
func TestDiscordAdapter_SendMessage_ErrorDoesNotLeakToken(t *testing.T) {
a := &DiscordAdapter{}
// Use a valid-looking webhook URL with a fake token so we can check it
// doesn't appear in the error string.
fakeToken := "SUPER_SECRET_DISCORD_TOKEN_12345"
webhookURL := discordWebhookPrefix + "123456789/" + fakeToken
// Point at an unroutable address to force a dial error.
// Fake transport that returns an error containing the full webhook URL,
// mimicking the real behavior of net/http when a dial fails.
fakeTransport := roundTripperFunc(func(req *http.Request) (*http.Response, error) {
return nil, fmt.Errorf("dial tcp: connect to %s: connection refused", req.URL.String())
})
a := &DiscordAdapter{
httpClient: &http.Client{Transport: fakeTransport},
}
err := a.SendMessage(
context.Background(),
map[string]interface{}{"webhook_url": webhookURL},
@@ -307,14 +316,20 @@ func TestDiscordAdapter_SendMessage_ErrorDoesNotLeakToken(t *testing.T) {
)
if err == nil {
// In some environments the request might actually succeed; that's fine.
t.Skip("request unexpectedly succeeded — skipping token-leak check")
t.Fatal("expected error from fake transport, got nil")
}
if strings.Contains(err.Error(), fakeToken) {
t.Errorf("error message leaks Discord webhook token: %q", err.Error())
}
}
// roundTripperFunc adapts a function to the http.RoundTripper interface.
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req)
}
func TestSplitMessage_SplitsAtNewline(t *testing.T) {
// Build a message where a newline falls within the split window.
line1 := strings.Repeat("a", 1500) + "\n"