Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
23ddbe4
feat(security): aggregate Strix findings in appguardrail
seonghobae Jul 14, 2026
16d687a
fix(ci): stop review workflow recursion
seonghobae Jul 14, 2026
d2ce7d8
fix(ci): parse repository dispatch review context
seonghobae Jul 14, 2026
ad0241e
fix(ci): precompute trusted review line receipts
seonghobae Jul 14, 2026
674a4bc
test(ci): cover receipt generator failure paths
seonghobae Jul 14, 2026
b422460
fix(review): repair verified adversarial line bindings
seonghobae Jul 14, 2026
c8a0d66
feat(security): mirror code scanning alerts to AppGuardrail
seonghobae Jul 14, 2026
e8ff150
fix(opencode): bind probe locations to sealed receipts
seonghobae Jul 14, 2026
6900880
fix(opencode): rebind sealed cited probe locations
seonghobae Jul 14, 2026
de8391f
merge(main): AppGuardrail 보안 집계 최신화
seonghobae Jul 20, 2026
e543d49
fix(strix): fail closed during issue reconciliation
seonghobae Jul 22, 2026
a72315a
chore(ci): bootstrap PR 560 scope repair
seonghobae Aug 3, 2026
06bd7e1
fix(ci): make PR 560 bootstrap expression-safe
seonghobae Aug 3, 2026
29e3d78
chore(ci): trigger reviewed PR 560 scope repair
seonghobae Aug 3, 2026
936a495
chore(ci): remove inactive PR 560 bootstrap workflow
seonghobae Aug 3, 2026
ad6344f
fix(ci): retry PR 560 null-safe scan-scope repair
seonghobae Aug 3, 2026
366cb7a
fix(ci): trigger PR 560 null-safe scan-scope repair
seonghobae Aug 3, 2026
67bc090
chore(ci): apply bounded PR 560 scan-scope repair
seonghobae Aug 3, 2026
e524a3b
fix(ci): make PR 560 repair expression-safe
seonghobae Aug 3, 2026
8c0944e
chore(ci): trigger PR 560 scan-scope repair
seonghobae Aug 3, 2026
1f36cc6
chore(ci): retrigger PR 560 scan-scope repair
seonghobae Aug 3, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/noema-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ jobs:
|| (
github.event_name == 'workflow_run'
&& github.event.workflow_run.conclusion != 'cancelled'
&& (
github.event.workflow_run.path == '.github/workflows/opencode-review.yml'
|| github.event.workflow_run.path == '.github/workflows/strix.yml'
)
)
|| (
github.event_name == 'pull_request_target'
Expand Down
15 changes: 14 additions & 1 deletion .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2662,7 +2662,7 @@ jobs:
fi
printf -- "- Merge base SHA: \`%s\`\n\n" "$PR_MERGE_BASE"
printf '## Current-head authority order\n\n'
printf 'Treat current-head sections in this file as authoritative for this run: Other unresolved review thread evidence, Failed GitHub Check evidence, Coverage execution evidence, Changed files, and Focused changed hunks.\n'
printf 'Treat current-head sections in this file as authoritative for this run: Other unresolved review thread evidence, Failed GitHub Check evidence, Coverage execution evidence, Changed files, Focused changed hunks, and Trusted changed-line source receipts.\n'
printf 'All PR reviews and comments evidence is historical context only and may contain stale bot conclusions. Do not infer active failed checks, unresolved threads, or missing changed files from those comments unless current-head evidence corroborates the same claim for Head SHA `%s`.\n\n' "$PR_HEAD_SHA"
if ! git -C "$OPENCODE_SOURCE_WORKDIR" diff --name-only --find-renames "$PR_MERGE_BASE" "$PR_HEAD_SHA" |
awk 'NF > 0 && $0 !~ /^\// && $0 !~ /(^|\/)\.\.($|\/)/ { print }' >"$OPENCODE_CHANGED_FILES_FILE"; then
Expand Down Expand Up @@ -2743,6 +2743,18 @@ jobs:
fi
printf '\n```\n'

printf '\n## Trusted changed-line source receipts\n\n'
if python3 "$GITHUB_WORKSPACE/scripts/ci/opencode_source_line_receipts.py" \
--repo-root "$OPENCODE_SOURCE_WORKDIR" \
--diff-base "$PR_MERGE_BASE" \
--head-sha "$PR_HEAD_SHA" \
--changed-files "$OPENCODE_CHANGED_FILES_FILE"; then
printf '\n'
else
printf 'Trusted changed-line source receipt generation failed; approval evidence is incomplete.\n' >&2
exit 1
fi

printf '\n## Review inspection contract\n\n'
printf 'Use the local checkout for exact source and diff inspection.\n'
printf 'Do not run a broad full-diff read into the model context; inspect changed files and focused hunks only.\n'
Expand Down Expand Up @@ -2853,6 +2865,7 @@ jobs:
append_evidence_section "Coverage execution evidence" 7000
append_evidence_section "Changed files" 7000
append_evidence_section "Focused changed hunks" 14000
append_evidence_section "Trusted changed-line source receipts" 12000
printf '\n\n[Full evidence is available in ./bounded-review-evidence.md inside the isolated review workspace.]\n'
} >"$OPENCODE_REVIEW_WORKDIR/bounded-review-evidence-excerpt.md"
fi
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pr-review-merge-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ jobs:
github.event_name != 'workflow_run' ||
(
github.event.workflow_run.conclusion != 'cancelled' &&
(
github.event.workflow_run.path == '.github/workflows/opencode-review.yml' ||
github.event.workflow_run.path == '.github/workflows/strix.yml'
) &&
github.event.workflow_run.pull_requests[0].number
)
) &&
Expand Down
97 changes: 97 additions & 0 deletions .github/workflows/pr560-scan-scope-repair-v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: PR 560 Scan Scope Repair v2

on:
push:
branches:
- agent/appguardrail-security-aggregation
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
paths:
- .github/workflows/pr560-scan-scope-repair-v2.yml
- .github/workflows/strix.yml
- tests/test_required_workflow_queue_contract.py

permissions:
contents: write

concurrency:
group: pr560-scan-scope-repair-v2
cancel-in-progress: true

jobs:
repair:
if: >-
github.actor != 'github-actions[bot]'
&& (
github.event_name == 'push'
|| github.event.pull_request.head.ref == 'agent/appguardrail-security-aggregation'
)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Checkout exact repair branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: agent/appguardrail-security-aggregation
fetch-depth: 0
persist-credentials: true

- name: Apply null-safe scope repair
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path

workflow = Path(".github/workflows/strix.yml")
source = workflow.read_text(encoding="utf-8")
expression_open = "$" + "{{"
old = (
"STRIX_SCAN_SCOPE: "
+ expression_open
+ " (github.event_name == 'pull_request_target' || "
"github.event.client_payload.pr_number != '') && 'pr' || 'full' }}"
)
new = (
"STRIX_SCAN_SCOPE: "
+ expression_open
+ " (github.event_name == 'pull_request_target' || "
"(github.event.client_payload.pr_number || '') != '') && 'pr' || 'full' }}"
)
if new not in source:
if source.count(old) != 1:
raise SystemExit("expected null-unsafe Strix scope expression was not found")
workflow.write_text(source.replace(old, new, 1), encoding="utf-8")

tests = Path("tests/test_required_workflow_queue_contract.py")
test_source = tests.read_text(encoding="utf-8")
marker = "def test_strix_scan_scope_requires_a_nonempty_pr_number()"
if marker not in test_source:
test_source += '''\n\ndef test_strix_scan_scope_requires_a_nonempty_pr_number():\n from pathlib import Path\n\n expression_open = "$" + "{{"\n workflow_text = Path(".github/workflows/strix.yml").read_text(encoding="utf-8")\n expected = (\n "STRIX_SCAN_SCOPE: "\n + expression_open\n + " (github.event_name == 'pull_request_target' || "\n "(github.event.client_payload.pr_number || '') != '') && 'pr' || 'full' }}"\n )\n assert expected in workflow_text\n assert \"github.event.client_payload.pr_number != ''\" not in workflow_text\n'''
tests.write_text(test_source, encoding="utf-8")
PY
rm -f .github/workflows/pr560-scan-scope-repair-v2.yml

- name: Validate focused Strix contracts
run: |
set -euo pipefail
python3 -m pytest -q tests/test_required_workflow_queue_contract.py
bash -n scripts/ci/test_strix_quick_gate.sh
git diff --check
test ! -e .github/workflows/pr560-scan-scope-repair-v2.yml

- name: Commit verified repair
run: |
set -euo pipefail
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add -A
git diff --cached --check
git commit -m "fix(strix): require a nonempty PR number for PR scope"
git push origin HEAD:agent/appguardrail-security-aggregation
96 changes: 96 additions & 0 deletions .github/workflows/pr560-scan-scope-repair.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: PR 560 Scan Scope Repair

on:
push:
branches:
- agent/appguardrail-security-aggregation
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
paths:
- .github/workflows/pr560-scan-scope-repair.yml
- .github/workflows/strix.yml
- tests/test_required_workflow_queue_contract.py

permissions:
contents: write

concurrency:
group: pr560-scan-scope-repair
cancel-in-progress: true

jobs:
repair:
if: >-
github.event_name == 'push'
|| github.event.pull_request.head.ref == 'agent/appguardrail-security-aggregation'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Harden runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit

- name: Checkout exact branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: agent/appguardrail-security-aggregation
fetch-depth: 0
persist-credentials: true

- name: Apply bounded scan-scope repair
run: |
set -euo pipefail
python3 - <<'PY'
from pathlib import Path

expression_prefix = "$" + "{{ "
expression_suffix = " }}"
old = (
"STRIX_SCAN_SCOPE: "
+ expression_prefix
+ "(github.event_name == 'pull_request_target' || "
+ "github.event.client_payload.pr_number != '') && 'pr' || 'full'"
+ expression_suffix
)
new = (
"STRIX_SCAN_SCOPE: "
+ expression_prefix
+ "(github.event_name == 'pull_request_target' || "
+ "(github.event.client_payload.pr_number || '') != '') && 'pr' || 'full'"
+ expression_suffix
)
workflow = Path('.github/workflows/strix.yml')
source = workflow.read_text(encoding='utf-8')
if new not in source:
if source.count(old) != 1:
raise SystemExit('expected STRIX_SCAN_SCOPE expression was not found exactly once')
workflow.write_text(source.replace(old, new, 1), encoding='utf-8')

tests = Path('tests/test_required_workflow_queue_contract.py')
test_source = tests.read_text(encoding='utf-8')
marker = 'def test_strix_scan_scope_null_coalesces_repository_dispatch_pr_number()'
if marker not in test_source:
test_source += '''\n\ndef test_strix_scan_scope_null_coalesces_repository_dispatch_pr_number() -> None:\n from pathlib import Path\n\n workflow_text = Path('.github/workflows/strix.yml').read_text(encoding='utf-8')\n expected = \"(github.event.client_payload.pr_number || '') != ''\"\n assert expected in workflow_text\n assert \"github.event.client_payload.pr_number != ''\" not in workflow_text\n'''
tests.write_text(test_source, encoding='utf-8')
PY
rm -f .github/workflows/pr560-scan-scope-repair.yml

- name: Validate focused contracts
run: |
set -euo pipefail
python3 -m pytest -q tests/test_required_workflow_queue_contract.py
git diff --check
test ! -e .github/workflows/pr560-scan-scope-repair.yml

- name: Commit verified repair
run: |
set -euo pipefail
git config user.name github-actions[bot]
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
git add -A
git diff --cached --check
git commit -m "fix(security): classify absent Strix PR payload as full scan"
git push origin HEAD:agent/appguardrail-security-aggregation
92 changes: 92 additions & 0 deletions .github/workflows/strix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,13 @@ jobs:
test -f "$trusted_strix_source/scripts/ci/strix_quick_gate.sh"
test -f "$trusted_strix_source/scripts/ci/test_strix_quick_gate.sh"
test -f "$trusted_strix_source/scripts/ci/strix_required_workflow_smoke.sh"
test -f "$trusted_strix_source/scripts/ci/strix_emit_appguardrail_issues.py"
{
echo "TRUSTED_STRIX_SOURCE=$trusted_strix_source"
echo "TRUSTED_STRIX_GATE=$trusted_strix_source/scripts/ci/strix_quick_gate.sh"
echo "TRUSTED_STRIX_GATE_TEST=$trusted_strix_source/scripts/ci/test_strix_quick_gate.sh"
echo "TRUSTED_STRIX_REQUIRED_SMOKE=$trusted_strix_source/scripts/ci/strix_required_workflow_smoke.sh"
echo "TRUSTED_STRIX_ISSUE_EMITTER=$trusted_strix_source/scripts/ci/strix_emit_appguardrail_issues.py"
} >> "$GITHUB_ENV"

- name: Exchange OpenCode app token for target repository reads
Expand Down Expand Up @@ -694,6 +696,7 @@ jobs:
echo "STRIX_LLM_FILE=$strix_llm_file" >> "$GITHUB_ENV"

- name: Run Strix (quick)
id: run_strix
if: steps.gate.outputs.enabled == 'true'
timeout-minutes: 100
# Security invariant for pull_request_target: execute only from the
Expand Down Expand Up @@ -767,13 +770,15 @@ jobs:
set -e

if [ "$strix_rc" -eq 0 ]; then
echo "scan_complete=true" >> "$GITHUB_OUTPUT"
exit 0
fi

# Preserve configuration failures (exit 2) and any unexpected exit
# code as hard failures — only the scan-failure code (1) can be an
# infrastructure/backend-unavailability outcome.
if [ "$strix_rc" -ne 1 ]; then
echo "scan_complete=false" >> "$GITHUB_OUTPUT"
exit "$strix_rc"
fi

Expand All @@ -792,9 +797,11 @@ jobs:
if grep -Eiq "$backend_unavailable_signal" "$strix_run_log" \
&& ! grep -Eiq "$reported_vulnerability_signal" "$strix_run_log"; then
echo "::warning title=Strix backend unavailable::Strix could not complete because its LLM backend was unavailable (rate limit / token cap / connection or warm-up failure) before producing a vulnerability report. Treating as a neutral skip so an infrastructure outage does not block merges; genuine findings still fail the check. See the strix-reports artifact and the run log."
echo "scan_complete=false" >> "$GITHUB_OUTPUT"
exit 0
fi

echo "scan_complete=false" >> "$GITHUB_OUTPUT"
echo "Strix reported security findings or failed for a non-backend reason; failing the required check (gate exit ${strix_rc})." >&2
exit "$strix_rc"

Expand Down Expand Up @@ -837,6 +844,91 @@ jobs:
if-no-files-found: error
retention-days: 5

- name: Validate AppGuardrail issue emitter credential
if: ${{ always() && !cancelled() && steps.gate.outputs.enabled == 'true' }}
id: appguardrail_issue_credential
env:
NOEMA_GITHUB_APP_CLIENT_ID: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID || '' }}
NOEMA_GITHUB_APP_PRIVATE_KEY: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY || '' }}
run: |
set -euo pipefail
if [ -z "${NOEMA_GITHUB_APP_CLIENT_ID:-}" ] || [ -z "${NOEMA_GITHUB_APP_PRIVATE_KEY:-}" ]; then
echo "::error::AppGuardrail issue collection is unconfigured: NOEMA_GITHUB_APP_CLIENT_ID and NOEMA_GITHUB_APP_PRIVATE_KEY are both required. Findings cannot be silently dropped."
exit 1
fi
echo "::notice::AppGuardrail issue collection will use a repository-scoped cwl-noema-review installation token."

- name: Mint AppGuardrail-scoped Noema GitHub App token
if: ${{ always() && !cancelled() && steps.gate.outputs.enabled == 'true' && steps.appguardrail_issue_credential.outcome == 'success' }}
id: noema_issue_token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY }}
owner: ContextualWisdomLab
repositories: appguardrail
permission-issues: write
permission-metadata: read

- name: Resolve source repository for Code Scanning
if: ${{ always() && !cancelled() && steps.gate.outputs.enabled == 'true' && steps.appguardrail_issue_credential.outcome == 'success' }}
id: code_scanning_source
env:
SOURCE_REPO: ${{ github.event.pull_request.base.repo.full_name || github.event.client_payload.target_repository || github.repository }}
run: |
set -euo pipefail
python3 - <<'PY'
import os
import re

source_repo = os.environ.get("SOURCE_REPO", "")
match = re.fullmatch(r"ContextualWisdomLab/([A-Za-z0-9_.-]+)", source_repo)
if match is None:
raise SystemExit(f"Invalid organization source repository: {source_repo!r}")
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output:
print(f"repository={match.group(1)}", file=output)
PY

- name: Mint source-scoped Noema GitHub App token for Code Scanning
if: ${{ always() && !cancelled() && steps.gate.outputs.enabled == 'true' && steps.code_scanning_source.outcome == 'success' }}
id: noema_code_scanning_token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY }}
owner: ContextualWisdomLab
repositories: ${{ steps.code_scanning_source.outputs.repository }}
permission-security-events: read
permission-metadata: read

- name: Emit Strix and GitHub Code Scanning findings to AppGuardrail
if: ${{ always() && !cancelled() && steps.gate.outputs.enabled == 'true' }}
env:
STRIX_ISSUE_APP_TOKEN: ${{ steps.noema_issue_token.outputs.token || '' }}
CODE_SCANNING_SOURCE_TOKEN: ${{ steps.noema_code_scanning_token.outputs.token || '' }}
STRIX_SOURCE_REPO: ${{ github.event.pull_request.base.repo.full_name || github.event.client_payload.target_repository || github.repository }}
STRIX_PR_NUMBER: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.event.client_payload.pr_number || '' }}
STRIX_HEAD_SHA: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.event.client_payload.pr_head_sha || github.sha }}
STRIX_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
STRIX_SCAN_SCOPE: ${{ (github.event_name == 'pull_request_target' || github.event.client_payload.pr_number != '') && 'pr' || 'full' }}
STRIX_SCAN_COMPLETE: ${{ steps.run_strix.outputs.scan_complete || 'false' }}
run: |
set -euo pipefail
emitter_args=(
--run-dir "$GITHUB_WORKSPACE/strix_runs"
--source-repo "$STRIX_SOURCE_REPO"
--issues-repo ContextualWisdomLab/appguardrail
--pr-number "$STRIX_PR_NUMBER"
--head-sha "$STRIX_HEAD_SHA"
--run-url "$STRIX_RUN_URL"
--scope "$STRIX_SCAN_SCOPE"
--include-code-scanning
)
Comment thread
seonghobae marked this conversation as resolved.
if [ "$STRIX_SCAN_COMPLETE" = "true" ]; then
emitter_args+=(--scan-complete)
fi
python3 "$TRUSTED_STRIX_ISSUE_EMITTER" "${emitter_args[@]}"

- name: Publish same-head manual Strix status
if: ${{ always() && !cancelled() && github.event_name == 'repository_dispatch' && github.event.client_payload.pr_head_sha != '' }}
env:
Expand Down
Loading
Loading