Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/noema-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ jobs:
print(f"::error::Could not parse GitHub workflow context JSON: {exc}", file=sys.stderr)
raise SystemExit(1)

trusted_repository = str(
job_context.get("workflow_repository") or "ContextualWisdomLab/.github"
).strip()
trusted_ref = str(
job_context.get("workflow_sha") or github_context.get("workflow_sha") or ""
).strip()
Expand All @@ -101,10 +104,14 @@ jobs:
if workflow_ref.startswith(prefix):
trusted_ref = workflow_ref.split("@", 1)[1]

if trusted_repository != "ContextualWisdomLab/.github":
print("::error::Trusted Noema workflow repository resolved outside ContextualWisdomLab/.github.", file=sys.stderr)
raise SystemExit(1)
if not re.fullmatch(r"[0-9a-fA-F]{40}|refs/[^\s]+|[A-Za-z0-9._/-]+", trusted_ref):
print("::error::Trusted Noema workflow ref resolved to an invalid value.", file=sys.stderr)
raise SystemExit(1)

print(f"repository={trusted_repository}")
print(f"ref={trusted_ref}")
PY

Expand Down
1 change: 0 additions & 1 deletion .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1663,7 +1663,6 @@ jobs:
done <"$context_env_file"
printf 'Resolved bounded OpenCode review context for %s#%s at %s.\n' \
"$GH_REPOSITORY" "$PR_NUMBER" "$PR_HEAD_SHA"
printf 'OPENCODE_CHANGED_FILES_FILE=%s\n' "$OPENCODE_CHANGED_FILES_FILE" >>"$GITHUB_ENV"

current_peer_checks_still_running() {
local owner="${GH_REPOSITORY%%/*}"
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/pr-review-merge-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ jobs:
print(f"::error::Could not parse GitHub workflow context JSON: {exc}", file=sys.stderr)
raise SystemExit(1)

trusted_repository = str(
job_context.get("workflow_repository") or "ContextualWisdomLab/.github"
).strip()
trusted_ref = str(
job_context.get("workflow_sha") or github_context.get("workflow_sha") or ""
).strip()
Expand All @@ -272,10 +275,14 @@ jobs:
if workflow_ref.startswith(prefix):
trusted_ref = workflow_ref.split("@", 1)[1]

if trusted_repository != "ContextualWisdomLab/.github":
print("::error::Trusted scheduler workflow repository resolved outside ContextualWisdomLab/.github.", file=sys.stderr)
raise SystemExit(1)
if not re.fullmatch(r"[0-9a-fA-F]{40}|refs/[^\s]+|[A-Za-z0-9._/-]+", trusted_ref):
print("::error::Trusted scheduler workflow ref resolved to an invalid value.", file=sys.stderr)
raise SystemExit(1)

print(f"repository={trusted_repository}")
print(f"ref={trusted_ref}")
PY

Expand All @@ -285,6 +292,7 @@ jobs:
repository: ContextualWisdomLab/.github
ref: ${{ steps.trusted_source.outputs.ref }}
fetch-depth: 1
persist-credentials: false

- name: Self-test scheduler
run: python3 scripts/ci/pr_review_merge_scheduler.py --self-test
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,14 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set +e
config_args=()
if [ -f .gitleaks.toml ]; then
config_args=(--config .gitleaks.toml)
fi
if [ "${IS_PR}" = "true" ]; then
# Diff-scoped: only the commits this PR introduces.
./gitleaks git . \
"${config_args[@]}" \
--log-opts="${BASE_SHA}..${HEAD_SHA}" \
--redact \
--report-format sarif \
Expand All @@ -89,13 +94,30 @@ jobs:
else
# Full git history on schedule / push to a protected branch.
./gitleaks git . \
"${config_args[@]}" \
--redact \
--report-format sarif \
--report-path gitleaks-results.sarif \
--exit-code 2
fi
echo "rc=$?" >> "$GITHUB_OUTPUT"
set -e
- name: Summarize redacted gitleaks findings
if: always() && hashFiles('gitleaks-results.sarif') != ''
run: |
set -euo pipefail
count="$(jq '[.runs[].results[]?] | length' gitleaks-results.sarif)"
if [ "$count" = "0" ]; then
echo "::notice::gitleaks completed with no findings."
exit 0
fi
echo "::error::gitleaks reported ${count} redacted finding(s). Rule, path, and line summary follows; secret values are not printed."
jq -r '
.runs[].results[]?
| "- rule: `" + (.ruleId // "unknown") + "`"
+ ", path: `" + (.locations[0].physicalLocation.artifactLocation.uri // "unknown") + "`"
+ ", line: `" + ((.locations[0].physicalLocation.region.startLine // "unknown") | tostring) + "`"
' gitleaks-results.sarif | sort | uniq -c
- name: Filter test-classified Gitleaks SARIF results
if: always() && hashFiles('gitleaks-results.sarif') != ''
run: |
Expand Down
17 changes: 17 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
title = "ContextualWisdomLab central gitleaks configuration"

[extend]
useDefault = true

[allowlist]
description = "False-positive token-like strings used by scheduler scrubber regression tests only."
regexTarget = "match"
paths = [
'''^\.gitleaks\.toml$''',
'''(^|/)__pycache__/''',
'''\.pyc$''',
]
regexes = [
'''gh[pousr]_(?:1234567890abcdef(?:1234)?|abcdef1234567890abcdef1234567890abcdef|installation_token_value|user_token_value|server_token_value|runner_token_value|placeholder_token_with_underscores_123)''',
'''github_pat_11AAAAA_(?:abcdefg|abcdefg1234567890)''',
]
24 changes: 24 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Historical false-positive GitHub-token fixtures from scheduler secret-scrubbing tests.
# The live tests now construct these token-like strings at runtime; new findings remain blocking.
123bddb83426e21367f61affa004f9259b101fe8:tests/test_pr_review_merge_scheduler.py:github-pat:224
123bddb83426e21367f61affa004f9259b101fe8:tests/test_pr_review_merge_scheduler.py:github-pat:227
14655a9304c2dbe1e2ad6c963af4a2a0b7bd4b89:tests/test_pr_review_merge_scheduler.py:github-pat:2770
14655a9304c2dbe1e2ad6c963af4a2a0b7bd4b89:tests/test_pr_review_merge_scheduler.py:github-pat:2778
14655a9304c2dbe1e2ad6c963af4a2a0b7bd4b89:tests/test_pr_review_merge_scheduler.py:github-pat:2793
14655a9304c2dbe1e2ad6c963af4a2a0b7bd4b89:tests/test_pr_review_merge_scheduler.py:github-pat:2798
2ce72097a608290af5828c205f009c430bde7f9e:tests/test_pr_review_merge_scheduler.py:github-pat:2488
2ce72097a608290af5828c205f009c430bde7f9e:tests/test_pr_review_merge_scheduler.py:github-pat:2496
2ce72097a608290af5828c205f009c430bde7f9e:tests/test_pr_review_merge_scheduler.py:github-pat:2511
2ce72097a608290af5828c205f009c430bde7f9e:tests/test_pr_review_merge_scheduler.py:github-pat:2516
3909b97ae8e42b5e7a0ea6ba78b6f4f4e4a294d4:tests/test_pr_review_merge_scheduler.py:github-pat:2770
3909b97ae8e42b5e7a0ea6ba78b6f4f4e4a294d4:tests/test_pr_review_merge_scheduler.py:github-pat:2778
3909b97ae8e42b5e7a0ea6ba78b6f4f4e4a294d4:tests/test_pr_review_merge_scheduler.py:github-pat:2793
3909b97ae8e42b5e7a0ea6ba78b6f4f4e4a294d4:tests/test_pr_review_merge_scheduler.py:github-pat:2798
697d44d6a06d148e086340cd25fd026d5d603899:tests/test_pr_review_merge_scheduler.py:github-pat:2488
697d44d6a06d148e086340cd25fd026d5d603899:tests/test_pr_review_merge_scheduler.py:github-pat:2496
697d44d6a06d148e086340cd25fd026d5d603899:tests/test_pr_review_merge_scheduler.py:github-pat:2511
697d44d6a06d148e086340cd25fd026d5d603899:tests/test_pr_review_merge_scheduler.py:github-pat:2516
79885ae2741c2c8f44b73ba7f7c7b20e7b675966:tests/test_pr_review_merge_scheduler.py:github-pat:697
79885ae2741c2c8f44b73ba7f7c7b20e7b675966:tests/test_pr_review_merge_scheduler.py:github-pat:701
79885ae2741c2c8f44b73ba7f7c7b20e7b675966:tests/test_pr_review_merge_scheduler.py:github-pat:718
79885ae2741c2c8f44b73ba7f7c7b20e7b675966:tests/test_pr_review_merge_scheduler.py:github-pat:722
17 changes: 17 additions & 0 deletions scripts/ci/strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2550,6 +2550,18 @@ is_transient_same_model_retry_error() {
return 1
}

github_models_rate_limit_should_skip_same_model_retry() {
local model="$1"

if ! is_rate_limit_error; then
return 1
fi
if ! is_github_models_api_compatible_model "$model"; then
return 1
fi
github_models_api_base_is_active
}

run_strix_with_transient_retry() {
local model="$1"
local max_attempts=$((STRIX_TRANSIENT_RETRY_PER_MODEL + 1))
Expand Down Expand Up @@ -2578,6 +2590,11 @@ run_strix_with_transient_retry() {
return 1
fi

if github_models_rate_limit_should_skip_same_model_retry "$model"; then
echo "GitHub Models rate limit detected for model '$model'; skipping same-model retry and moving directly to fallback models or current-head neutral classification." >&2
return 1
fi

if ! is_transient_same_model_retry_error "$model"; then
return 1
fi
Expand Down
50 changes: 47 additions & 3 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,9 @@ assert_pr_review_merge_scheduler_uses_github_actions_bot_token() {
assert_file_not_contains "$workflow_file" "INPUT_CANONICAL_REF" "scheduler trusted source checkout must not be controlled by workflow input"
assert_file_not_contains "$workflow_file" "inputs.canonical_ref" "scheduler no longer accepts checkout-ref override input"
assert_file_contains "$workflow_file" 'repository: ContextualWisdomLab/.github' "scheduler checks out the canonical implementation instead of relying on repo-local copies"
assert_file_not_contains "$workflow_file" 'repository: ${{ steps.trusted_source.outputs.repository }}' "scheduler does not pass a dynamic repository expression to privileged checkout"
assert_file_contains "$workflow_file" 'ref: ${{ steps.trusted_source.outputs.ref }}' "scheduler checks out the resolved central ref"
assert_file_contains "$workflow_file" "persist-credentials: false" "scheduler trusted checkout does not persist workflow credentials into the checkout"
assert_file_contains "$workflow_file" "contents: write" "scheduler has write permission for GitHub Actions bot branch updates"
assert_file_contains "$workflow_file" "pull-requests: write" "scheduler has pull-request write permission for update-branch and auto-merge"
assert_file_not_contains "$workflow_file" "format('pr-{0}-{1}', github.event.pull_request.number, github.event.pull_request.head.sha)" "scheduler does not keep stale head-specific concurrency groups"
Expand Down Expand Up @@ -5130,6 +5132,17 @@ PY
"scenario=$scenario does not rewrite logs through symlinked report directories"
fi

if [ "$scenario" = "github-models-primary-ratelimit-fallback-success" ]; then
assert_file_contains \
"$output_log" \
"GitHub Models rate limit detected for model 'openai/gpt-5'; skipping same-model retry and moving directly to fallback models or current-head neutral classification." \
"scenario=$scenario logs why same-model retry was skipped"
assert_file_not_contains \
"$output_log" \
"Retrying model 'openai/gpt-5' due to rate limit" \
"scenario=$scenario does not sleep in same-model retry after GitHub Models rate limiting"
fi

if [ "$scenario" = "pr-changed-scope-full-set" ]; then
assert_internal_pr_scope_targets "$target_log" "$repo_root_dir" "$expected_calls"
fi
Expand Down Expand Up @@ -5278,6 +5291,37 @@ run_filtered_gate_case_if_requested() {
"pull_request" \
"frontend/src/components/CalendarLayout.tsx"
;;
github-models-primary-ratelimit-fallback-success)
run_gate_case "github-models-primary-ratelimit-fallback-success" \
"openai/gpt-5" \
"" \
"0" \
"REGEX:Strix quick scan succeeded with fallback model 'deepseek/deepseek-r1-0528' in [0-9]+s\\." \
"2" \
"openai/gpt-5|openai/deepseek/deepseek-r1-0528" \
"https://models.github.ai/inference|https://models.github.ai/inference" \
"openai" \
"https://models.github.ai/inference" \
"" \
"2" \
"CRITICAL" \
"0" \
"" \
"" \
"1200" \
"0" \
"" \
"" \
"" \
"" \
"0" \
"" \
"" \
"" \
"__SAME_AS_FALLBACK_MODELS__" \
"deepseek/deepseek-r1-0528 deepseek/deepseek-v3-0324" \
"1"
;;
github-models-fallback-baseline-vulnerability-before-next-success-continues)
run_gate_case "github-models-fallback-baseline-vulnerability-before-next-success-continues" \
"openai/gpt-5" \
Expand Down Expand Up @@ -8248,9 +8292,9 @@ run_gate_case "github-models-primary-ratelimit-fallback-success" \
"" \
"0" \
"REGEX:Strix quick scan succeeded with fallback model 'deepseek/deepseek-r1-0528' in [0-9]+s\\." \
"4" \
"openai/gpt-5|openai/gpt-5|openai/gpt-5|openai/deepseek/deepseek-r1-0528" \
"https://models.github.ai/inference|https://models.github.ai/inference|https://models.github.ai/inference|https://models.github.ai/inference" \
"2" \
"openai/gpt-5|openai/deepseek/deepseek-r1-0528" \
"https://models.github.ai/inference|https://models.github.ai/inference" \
"openai" \
"https://models.github.ai/inference" \
"" \
Expand Down
1 change: 1 addition & 0 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def test_opencode_bounded_evidence_context_is_resolved_from_event_payload():
assert "GITHUB_EVENT_PATH" in step
assert "Invalid OpenCode review context value for" in step
assert "Resolved bounded OpenCode review context for %s#%s at %s." in step
assert "GITHUB_ENV" not in step


def test_opencode_target_coverage_materializes_merge_tree_without_checkout_action():
Expand Down
54 changes: 36 additions & 18 deletions tests/test_pr_review_merge_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
from scripts.ci import pr_review_merge_scheduler as sched


def fake_github_token(prefix, body):
return f"{prefix}_{body}"


def fake_github_pat(body):
return f"github_pat_{body}"


def make_pr(**overrides):
value = {
"number": 1,
Expand Down Expand Up @@ -1175,7 +1183,7 @@ def mock_run(args, **kwargs):

assert sched.run(["success"]) == "success"

token_placeholder = "ghp_placeholder_token_with_underscores_123"
token_placeholder = fake_github_token("ghp", "placeholder_token_with_underscores_123")
with pytest.raises(RuntimeError) as exc_info:
sched.run(["gh", "api", "fail", "-H", f"Authorization: token {token_placeholder}"])

Expand Down Expand Up @@ -3083,18 +3091,18 @@ def fake_inspect(repo, pr, **kwargs):
def test_scrub_sensitive_data_and_run_error():
assert sched.scrub_sensitive_data("Authorization: Bearer mytoken123") == "Authorization: Bearer ***"
assert sched.scrub_sensitive_data("token mytoken123") == "token ***"
assert sched.scrub_sensitive_data("ghp_1234567890abcdef") == "***"
assert sched.scrub_sensitive_data("ghs_1234567890abcdef") == "***"
assert sched.scrub_sensitive_data("gho_1234567890abcdef") == "***"
assert sched.scrub_sensitive_data("ghp_1234567890abcdef1234") == "***"
assert sched.scrub_sensitive_data("gho_1234567890abcdef1234567890extra") == "***"
assert sched.scrub_sensitive_data("github_pat_11AAAAA_abcdefg1234567890") == "***"
assert sched.scrub_sensitive_data("ghp_placeholder_token_with_underscores_123") == "***"
assert sched.scrub_sensitive_data("gho_installation_token_value") == "***"
assert sched.scrub_sensitive_data("ghu_user_token_value") == "***"
assert sched.scrub_sensitive_data("ghs_server_token_value") == "***"
assert sched.scrub_sensitive_data("ghr_runner_token_value") == "***"
assert sched.scrub_sensitive_data("github_pat_11AAAAA_abcdefg") == "***"
assert sched.scrub_sensitive_data(fake_github_token("ghp", "1234567890abcdef")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("ghs", "1234567890abcdef")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("gho", "1234567890abcdef")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("ghp", "1234567890abcdef1234")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("gho", "1234567890abcdef1234567890extra")) == "***"
assert sched.scrub_sensitive_data(fake_github_pat("11AAAAA_abcdefg1234567890")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("ghp", "placeholder_token_with_underscores_123")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("gho", "installation_token_value")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("ghu", "user_token_value")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("ghs", "server_token_value")) == "***"
assert sched.scrub_sensitive_data(fake_github_token("ghr", "runner_token_value")) == "***"
assert sched.scrub_sensitive_data(fake_github_pat("11AAAAA_abcdefg")) == "***"
assert sched.scrub_sensitive_data("sk-1234567890abcdef") == "***"
assert sched.scrub_sensitive_data("xoxb-1234567890-1234") == "***"
assert sched.scrub_sensitive_data("AKIA1234567890ABCDEF") == "***"
Expand All @@ -3105,7 +3113,15 @@ def test_scrub_sensitive_data_and_run_error():
assert sched.scrub_sensitive_data(None) is None

with pytest.raises(RuntimeError, match=r"Command failed \([12]\): .* \*\*\*"):
sched.run([sys.executable, "-c", "import sys; sys.exit(1)", "ghp_1234567890abcdef1234"], stdin=None)
sched.run(
[
sys.executable,
"-c",
"import sys; sys.exit(1)",
fake_github_token("ghp", "1234567890abcdef1234"),
],
stdin=None,
)


def test_main_keeps_scanning_after_update_branch_403_and_422(monkeypatch, capsys):
Expand Down Expand Up @@ -3214,22 +3230,23 @@ def test_parse_conflict_reason_missing_branches():


def test_run_masks_secrets():
token = fake_github_token("ghp", "abcdef1234567890abcdef1234567890abcdef")
with pytest.raises(RuntimeError) as exc_info:
sched.run(
[
sys.executable,
"-c",
(
"import sys; "
"sys.stderr.write('ghp_abcdef1234567890abcdef1234567890abcdef\\n"
f"sys.stderr.write({token!r} + '\\n"
"Bearer super_secret\\ntoken my_secret\\n'); "
"sys.exit(1)"
),
]
)

err_msg = str(exc_info.value)
assert "ghp_abcdef1234567890abcdef1234567890abcdef" not in err_msg
assert token not in err_msg
assert "***" in err_msg
assert "Bearer super_secret" not in err_msg
assert "Bearer ***" in err_msg
Expand All @@ -3238,16 +3255,17 @@ def test_run_masks_secrets():


def test_run_masks_secrets_in_args():
token = fake_github_token("ghp", "abcdef1234567890abcdef1234567890abcdef")
with pytest.raises(RuntimeError) as exc_info:
sched.run(
[
sys.executable,
"-c",
"import sys; sys.exit(1)",
"ghp_abcdef1234567890abcdef1234567890abcdef",
token,
]
)

err_msg = str(exc_info.value)
assert "ghp_abcdef1234567890abcdef1234567890abcdef" not in err_msg
assert token not in err_msg
assert "***" in err_msg
Loading
Loading