Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5257,7 +5257,7 @@ jobs:
--no-trigger-reviews
--enable-auto-merge
--merge-mode direct_or_auto
--no-update-branches
--update-branches
)
if [ -n "${PR_NUMBER:-}" ]; then
args+=(--pr-number "$PR_NUMBER")
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/pr-review-merge-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ on:
review_dispatch_limit:
description: Maximum OpenCode/Strix review dispatch actions per scheduler run
required: false
default: "-1"
default: "1"
type: string
enable_auto_merge:
description: Enable auto-merge for current-head approved PRs
Expand Down Expand Up @@ -95,7 +95,7 @@ on:
review_dispatch_limit:
description: Maximum OpenCode/Strix review dispatch actions per scheduler run
required: false
default: "-1"
default: "1"
enable_auto_merge:
description: Enable auto-merge for current-head approved PRs
required: false
Expand Down Expand Up @@ -233,7 +233,7 @@ jobs:
printf 'ref=%s\n' "$trusted_ref" >>"$GITHUB_OUTPUT"

- name: Checkout trusted scheduler
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: ContextualWisdomLab/.github
ref: ${{ steps.trusted_source.outputs.ref }}
Expand All @@ -260,7 +260,15 @@ jobs:
fi
review_dispatch_limit="$REVIEW_DISPATCH_LIMIT_INPUT"
if [ -z "$review_dispatch_limit" ]; then
review_dispatch_limit="-1"
if [ -n "$PULL_REQUEST_NUMBER" ]; then
review_dispatch_limit="1"
else
case "$GITHUB_EVENT_NAME" in
schedule|workflow_dispatch|workflow_run) review_dispatch_limit="1" ;;
push) review_dispatch_limit="0" ;;
*) review_dispatch_limit="0" ;;
esac
fi
fi
args=(
--repo "$GITHUB_REPOSITORY"
Expand Down
86 changes: 21 additions & 65 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ permissions:
contents: read
id-token: write
models: read
statuses: write

jobs:
strix:
Expand All @@ -65,7 +64,7 @@ jobs:
disable-file-monitoring: true

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.13"

Expand Down Expand Up @@ -292,25 +291,6 @@ jobs:
printf 'Running bounded Strix required-workflow smoke test.\n'
bash "$TRUSTED_STRIX_REQUIRED_SMOKE"

- name: Materialize central Strix dependency lock from PR head
if: >-
github.event_name == 'pull_request_target'
&& github.repository == 'ContextualWisdomLab/.github'
&& github.event.pull_request.base.repo.full_name == 'ContextualWisdomLab/.github'
&& github.event.pull_request.head.repo.full_name == 'ContextualWisdomLab/.github'
env:
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
if ! [[ "$PR_HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::PR head SHA must be a 40-character git SHA."
exit 1
fi
if git -C "$TRUSTED_WORKSPACE" cat-file -e "$PR_HEAD_SHA:requirements-strix-ci-hashes.txt" 2>/dev/null; then
git -C "$TRUSTED_WORKSPACE" show "$PR_HEAD_SHA:requirements-strix-ci-hashes.txt" > "$TRUSTED_STRIX_SOURCE/requirements-strix-ci-hashes.txt"
printf 'Materialized central Strix dependency lock from same-repository PR head.\n'
fi

- name: Gate Strix secrets
id: gate
env:
Expand Down Expand Up @@ -368,7 +348,7 @@ jobs:

- name: Set up Python
if: steps.gate.outputs.enabled == 'true'
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.13"

Expand Down Expand Up @@ -539,7 +519,7 @@ jobs:
VERTEX_LOCATION: ${{ secrets.VERTEX_LOCATION || 'us-central1' }}
STRIX_TARGET_PATH: ${{ (github.event_name == 'pull_request_target' || github.event.inputs.pr_number != '') && '__PR_SCOPE__' || './' }}
STRIX_SOURCE_DIRS: ". backend frontend"
STRIX_REASONING_EFFORT: high
STRIX_REASONING_EFFORT: low
STRIX_LLM_MAX_RETRIES: 1
STRIX_TRANSIENT_RETRY_PER_MODEL: 2
STRIX_TRANSIENT_RETRY_BACKOFF_SECONDS: 60
Expand Down Expand Up @@ -604,8 +584,7 @@ jobs:
- name: Publish same-head manual Strix status
if: ${{ always() && !cancelled() && github.event_name == 'workflow_dispatch' && github.event.inputs.pr_head_sha != '' }}
env:
PRIMARY_STATUS_TOKEN: ${{ steps.target_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || '' }}
FALLBACK_STATUS_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ steps.target_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || github.token }}
TARGET_REPOSITORY: ${{ github.event.inputs.target_repository || github.repository }}
PR_HEAD_SHA: ${{ github.event.inputs.pr_head_sha }}
STRIX_RESULT: ${{ job.status }}
Expand All @@ -631,25 +610,14 @@ jobs:
;;
esac

post_strix_status() {
token="$1"
if [ -z "$token" ]; then
return 1
fi
GH_TOKEN="$token" gh api -X POST "repos/${TARGET_REPOSITORY}/statuses/${PR_HEAD_SHA}" \
-f state="$state" \
-f context="strix" \
-f description="$description" \
-f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
}

if post_strix_status "$PRIMARY_STATUS_TOKEN"; then
exit 0
fi
if [ "$TARGET_REPOSITORY" = "$GITHUB_REPOSITORY" ] && post_strix_status "$FALLBACK_STATUS_TOKEN"; then
exit 0
fi
echo "::warning::Could not publish manual Strix status from scan job; keeping scan evidence result authoritative in the workflow run."
gh api -X POST "repos/${TARGET_REPOSITORY}/statuses/${PR_HEAD_SHA}" \
-f state="$state" \
-f context="strix" \
-f description="$description" \
-f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" || {
echo "::warning::Could not publish manual Strix status from scan job; keeping scan evidence result authoritative in the workflow run."
exit 0
}

publish-manual-pr-evidence-status:
name: publish-manual-pr-evidence-status
Expand Down Expand Up @@ -728,8 +696,7 @@ jobs:

- name: Publish same-head manual Strix status
env:
PRIMARY_STATUS_TOKEN: ${{ steps.target_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || '' }}
FALLBACK_STATUS_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ steps.target_app_token.outputs.token || secrets.OPENCODE_APPROVE_TOKEN || github.token }}
TARGET_REPOSITORY: ${{ github.event.inputs.target_repository || github.repository }}
PR_HEAD_SHA: ${{ github.event.inputs.pr_head_sha }}
STRIX_RESULT: ${{ needs.strix.result }}
Expand All @@ -755,22 +722,11 @@ jobs:
;;
esac

post_strix_status() {
token="$1"
if [ -z "$token" ]; then
return 1
fi
GH_TOKEN="$token" gh api -X POST "repos/${TARGET_REPOSITORY}/statuses/${PR_HEAD_SHA}" \
-f state="$state" \
-f context="strix" \
-f description="$description" \
-f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
}

if post_strix_status "$PRIMARY_STATUS_TOKEN"; then
exit 0
fi
if [ "$TARGET_REPOSITORY" = "$GITHUB_REPOSITORY" ] && post_strix_status "$FALLBACK_STATUS_TOKEN"; then
exit 0
fi
echo "::warning::Could not publish manual Strix status from follow-up job; scan job publishes the authoritative status when target credentials are available."
gh api -X POST "repos/${TARGET_REPOSITORY}/statuses/${PR_HEAD_SHA}" \
-f state="$state" \
-f context="strix" \
-f description="$description" \
-f target_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" || {
echo "::warning::Could not publish manual Strix status from follow-up job; scan job publishes the authoritative status when target credentials are available."
exit 0
}
16 changes: 2 additions & 14 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,7 @@
**Vulnerability:** Workflow CI Security Bypass / Markdown Injection
**Learning:** The GitHub Actions workflow `opencode-review.yml` attempted to optimize performance by doing a fast-path bash string extraction. If this succeeded, it skipped the Python JSON normalizer (`opencode_review_normalize_output.py`). This is a security flaw because the bash script does not escape `<, >, &` characters, allowing attackers to inject `-->` directly in JSON strings to break out of HTML comment sections.
**Prevention:** Removed the fast-path check entirely. We must always enforce JSON normalization via `opencode_review_normalize_output.py` because it correctly parses the JSON payload and safely escapes all characters as `\u003c`, `\u003e` and `\u0026`.
## 2026-06-28 - Align Sensitive Log Redaction Across Languages
**Vulnerability:** Information Disclosure / Secret Leakage
**Learning:** The Bash CI script (`collect_failed_check_evidence.sh`) aggressively redacted a broad range of secrets like AWS keys, Slack tokens, and generic API keys. However, the Python PR review scheduler script (`pr_review_merge_scheduler.py`) only redacted a very narrow set of standard GitHub tokens (`ghp_` and `github_pat_`). This disparity left the Python-driven command logs vulnerable to exposing other high-value secrets on command failure if they were passed via environment or arguments and inadvertently caught in error tracebacks.
**Prevention:** We must maintain parity between cross-language redaction strategies that operate on CI environments. Replicated the extensive regular expressions for secrets (e.g., Slack, AWS, password combinations, all GitHub token prefixes) to the Python error handler.
## 2026-06-25 - Prevent CI Logs Security Exposure and Explicit Shell Usage
**Vulnerability:** Information Disclosure / Command Injection
**Learning:** `subprocess.run` defaults to `shell=False`, but linters like Bandit require explicit `shell=False` to pass security checks. Furthermore, failing GitHub CLI commands or curl requests can include full command arguments and stderr in raised errors. These strings can contain GitHub PATs, Bearer/token authorizations, API keys, or specialized GitHub token prefixes such as `gho_`, `ghu_`, `ghs_`, and `ghr_`.
**Prevention:** Always explicitly define `shell=False` when using `subprocess.run()`. Scrub sensitive tokens from both command arguments and `stderr` before including them in exceptions or logs from CI scripts, including the `gh[pousr]_` prefix family and `github_pat_`.
## 2026-06-30 - Prevent Security Theater in Subprocess Fixes
**Vulnerability:** Command Injection / Incomplete Fix
**Learning:** Fixing a `shell=True` vulnerability by replacing it with `shell=False` and wrapping the command string in `["/bin/bash", "-c", command]` is security theater. If `command` contains untrusted input, passing it to `bash -c` as a single string means it is still completely vulnerable to shell injection, while misleading linters into reporting the code as secure.
**Prevention:** When refactoring away from `shell=True`, avoid invoking shells entirely. Use `shlex.split(command)` to safely parse the string into a list of arguments and pass that list directly to `subprocess.Popen` or `subprocess.run`, ensuring untrusted input is never evaluated by a shell.
## 2026-06-30 - Prevent SSRF and Local File Inclusion via Unvalidated URL Schemes
**Vulnerability:** Server-Side Request Forgery (SSRF) / Local File Inclusion
**Learning:** Functions that fetch URLs provided via user inputs (e.g., `wait_for_url` fetching `--backend-ready-url` in CI scripts) can inadvertently read local files if they do not validate the scheme. Python's `urllib.request.urlopen` supports `file://` schemes, allowing attackers to access arbitrary file contents from the host machine or sandbox if they can control the URL parameter.
**Prevention:** Always validate URL inputs to restrict allowed schemes. Check that URLs explicitly start with `http://` or `https://` before fetching them with standard libraries like `urllib`.
**Learning:** `subprocess.run` defaults to `shell=False`, but linters like Bandit require explicit `shell=False` to pass security checks. Furthermore, failing GitHub CLI commands or curl requests can include full command arguments and stderr in raised errors. These strings can contain GitHub PATs, Bearer/token authorizations, or API keys and leak credentials into CI logs.
**Prevention:** Always explicitly define `shell=False` when using `subprocess.run()`. Scrub sensitive tokens from both command arguments and `stderr` before including them in exceptions or logs from CI scripts.
Loading
Loading