From 113eb0f0f3a8ad3a38659e35e9ae33fc7493a725 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 12:59:52 +0900 Subject: [PATCH 1/2] fix(strix): include backend app dependency context --- CHANGELOG.md | 5 +++ .../strix-pr-head-context-boundary.md | 43 +++++++++++++++++++ scripts/ci/strix_quick_gate.sh | 26 +++++++++++ scripts/ci/test_strix_quick_gate.sh | 22 +++++++++- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 docs/doctoring/strix-pr-head-context-boundary.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f4903c2f3..458fb8239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,11 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Include the exact `backend/app/*.py` package context in PR-scoped Strix + scans when a backend application module changes. This prevents a real local + import from being reported as missing merely because the dependency was + unchanged and therefore outside the changed-file attribution set. + - Parsed `opencode.jsonc` as JSONC (stripping `//` and `/* */` comments outside string literals) in the reasoning-effort guard and its contract tests, instead of raw `json.loads`, which rejected the file the moment it carried its first explanatory comment (added for the `contextual-orchestrator` provider block) with `Expecting property name enclosed in double quotes`. Comment markers inside string values, such as the `$schema` URL, are left untouched. - Download the pinned `uv` 0.12.1 exporter from the official GitHub Releases URL instead of `releases.astral.sh`, which now returns HTTP 403 and blocks org-wide OpenCode `coverage-evidence`. The SHA-256 pin is unchanged. The opener may follow one hop onto `release-assets.githubusercontent.com` or `objects.githubusercontent.com` and still rejects every other host, userinfo, non-HTTPS scheme, and nondefault port (ContextualWisdomLab/.github#1109). - Compared the trusted `uv` executable's post-install `--version` output against the real GitHub Releases build's full string, `uv 0.12.1 (x86_64-unknown-linux-gnu)`, instead of the bare `uv 0.12.1` the prior check required; the genuine release binary always prints the target triple, so every installation was failing the pin check immediately after the archive download itself was fixed (ContextualWisdomLab/.github#1109). diff --git a/docs/doctoring/strix-pr-head-context-boundary.md b/docs/doctoring/strix-pr-head-context-boundary.md new file mode 100644 index 000000000..d6c1beae2 --- /dev/null +++ b/docs/doctoring/strix-pr-head-context-boundary.md @@ -0,0 +1,43 @@ +# Strix PR-head dependency context boundary + +Status: accepted 2026-08-21 + +## Incident + +The Strix run for LineageWeave PR #192 materialized changed Python files but +not the unchanged local `backend/app` dependency package. The scanner then +reported `backend.app.post_eligibility` as missing even though that module was +present in the PR head and base repository. Earlier attempts also encountered +NVIDIA NIM rate limits; those provider failures must remain visible and must +not be confused with a source finding. + +## Decision + +When a PR changes a Python module under `backend/app`, the trusted Strix scope +resolver enumerates every Python file under `backend/app` from the exact PR +head tree. The scope builder copies changed files from that head and unchanged +context from the trusted base checkout. The changed-file list remains the +finding-attribution boundary; this does not turn a context file into a changed +finding. The scan still executes only trusted scanner code and treats PR-head +blobs as non-executable data. + +This is a product-neutral extension of the existing backend context contract; +it does not replace the repository-specific context list for other backend +layouts and does not downgrade provider or vulnerability failures. + +## Evidence and rollback + +The regression fixture creates a changed `backend/app/knowledge_graph.py` that +imports an unchanged `backend/app/post_eligibility.py`, then asserts that the +production scope contains the dependency and the trusted content. Roll back +this change only with an equivalent exact-head dependency-context contract; +removing the context or weakening the Strix gate is not an acceptable rollback. + +## References + +National Institute of Standards and Technology. (2008). *Technical guide to +information security testing and assessment* (Special Publication 800-115). +https://doi.org/10.6028/NIST.SP.800-115 + +OWASP Foundation. (n.d.). *Web security testing guide*. Retrieved August 21, +2026, from https://owasp.org/www-project-web-security-testing-guide/ diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 0f37f3460..301a4d8f4 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -1184,6 +1184,7 @@ is_scannable_changed_file() { pull_request_scope_context_files() { local needs_backend_python=0 + local needs_backend_app_python=0 local needs_frontend_email_api_context=0 local needs_deployment_context=0 local changed_file normalized_changed_file @@ -1194,6 +1195,9 @@ pull_request_scope_context_files() { if [[ "$normalized_changed_file" =~ ^backend/.+\.py$ ]]; then needs_backend_python=1 fi + if [[ "$normalized_changed_file" =~ ^backend/app/.+\.py$ ]]; then + needs_backend_app_python=1 + fi ;; # The app shell, email components, threading URL builder, and API client can # shape frontend email retrieval flows; include backend auth context with them. @@ -1257,6 +1261,28 @@ backend/services/threading_service.py EOF fi + if [ "$needs_backend_app_python" -eq 1 ]; then + # Some products use backend/app rather than the central backend/api layout. + # Include every Python module in that exact PR-head package so a changed + # module's local imports are available to Strix without scanning the whole + # repository. Unchanged files are copied from the trusted base checkout; + # changed files are copied from PR_HEAD_SHA by the scope builder. + if [ -n "${PR_HEAD_SHA:-}" ] && is_valid_git_commit_sha "$PR_HEAD_SHA"; then + local backend_app_files + if ! backend_app_files="$(git -c core.quotepath=false ls-tree -r --name-only "$PR_HEAD_SHA" -- backend/app)"; then + echo "ERROR: backend/app PR-head context could not be enumerated; failing closed." >&2 + return 2 + fi + while IFS= read -r context_file; do + case "$context_file" in + backend/app/*.py) + printf '%s\n' "$context_file" + ;; + esac + done <<<"$backend_app_files" + fi + fi + if [ "$needs_frontend_email_api_context" -eq 1 ]; then cat <<'EOF' backend/api/auth.py diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index ac9ce1d8b..a3d17795f 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -6951,6 +6951,20 @@ if [ -f "$target_path/backend/services/email_parser.py" ]; then matched_backend_context=1 fi +if [ -f "$target_path/backend/app/knowledge_graph.py" ]; then + if [ ! -f "$target_path/backend/app/post_eligibility.py" ]; then + echo "Error: backend/app local import context missing from PR scope ($target_path)" >&2 + exit 78 + fi + if ! grep -Fq -- 'BASE_POST_ELIGIBILITY_SHOULD_BE_SCANNED' "$target_path/backend/app/post_eligibility.py"; then + echo "Error: backend/app dependency context did not use trusted base content" >&2 + cat -- "$target_path/backend/app/post_eligibility.py" >&2 + exit 79 + fi + echo "scan ok with backend/app local import context" + matched_backend_context=1 +fi + if [ "$matched_backend_context" -eq 1 ]; then exit 0 fi @@ -6967,11 +6981,12 @@ EOF git config user.name 'Strix Test' git config user.email 'strix-test@example.invalid' echo 'seed' >README.md - mkdir -p backend/api backend/services + mkdir -p backend/api backend/app backend/services printf '%s\n' 'BASE_AUTH_CONTENT_SHOULD_NOT_BE_SCANNED' >backend/api/auth.py printf '%s\n' 'BASE_EMAILS_CONTENT_SHOULD_NOT_BE_SCANNED' >backend/api/emails.py printf '%s\n' 'BASE_CALENDAR_SERVICE_SHOULD_BE_SCANNED' >backend/services/calendar_service.py printf '%s\n' 'BASE_LLM_PROVIDER_URLS_SHOULD_NOT_BE_SCANNED' >backend/services/llm_provider_urls.py + printf '%s\n' 'BASE_POST_ELIGIBILITY_SHOULD_BE_SCANNED' >backend/app/post_eligibility.py git add . git commit -qm 'base commit' ) @@ -7020,6 +7035,10 @@ EOF cat >backend/api/runner_config.py <<'EOF' def require_workspace_admin(): return 'HEAD_RUNNER_CONFIG_SHOULD_BE_SCANNED' +EOF + cat >backend/app/knowledge_graph.py <<'EOF' +from .post_eligibility import SOURCE_POST_ELIGIBILITY_SQL +HEAD_KNOWLEDGE_GRAPH_SHOULD_BE_SCANNED EOF git add . git commit -qm 'head commit' @@ -7054,6 +7073,7 @@ EOF assert_file_contains "$output_log" "scan ok with PR-head backend dependency context" "case=pull-request-target-changed-backend-context-uses-head-blob output" assert_file_contains "$output_log" "scan ok with PR-head LLM provider URL validation context" "case=pull-request-target-changed-backend-context-includes-llm-provider-url-validation output" assert_file_contains "$output_log" "scan ok with PR-head email parser text safety context" "case=pull-request-target-changed-backend-context-includes-email-parser-text-safety output" + assert_file_contains "$output_log" "scan ok with backend/app local import context" "case=pull-request-target-changed-backend-context-includes-backend-app-local-import output" assert_equals "1" "$(wc -l <"$call_log" | tr -d ' ')" "case=pull-request-target-changed-backend-context-uses-head-blob strix call count" rm -rf "$tmp_dir" From d9479cf486f731e8efe582e7b029234e05b36cae Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 13:08:09 +0900 Subject: [PATCH 2/2] fix(strix): normalize backend context head sha --- scripts/ci/strix_quick_gate.sh | 6 ++++-- scripts/ci/test_strix_quick_gate.sh | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 301a4d8f4..f7c0a356c 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -1267,9 +1267,11 @@ EOF # module's local imports are available to Strix without scanning the whole # repository. Unchanged files are copied from the trusted base checkout; # changed files are copied from PR_HEAD_SHA by the scope builder. - if [ -n "${PR_HEAD_SHA:-}" ] && is_valid_git_commit_sha "$PR_HEAD_SHA"; then + local backend_app_head_sha + backend_app_head_sha="$(trim_whitespace "${PR_HEAD_SHA:-}")" + if [ -n "$backend_app_head_sha" ] && is_valid_git_commit_sha "$backend_app_head_sha"; then local backend_app_files - if ! backend_app_files="$(git -c core.quotepath=false ls-tree -r --name-only "$PR_HEAD_SHA" -- backend/app)"; then + if ! backend_app_files="$(git -c core.quotepath=false ls-tree -r --name-only "$backend_app_head_sha" -- backend/app)"; then echo "ERROR: backend/app PR-head context could not be enumerated; failing closed." >&2 return 2 fi diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index a3d17795f..141bdbf69 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -7056,7 +7056,7 @@ EOF STRIX_INPUT_FILE_ROOT="$tmp_dir" \ GITHUB_EVENT_NAME="pull_request_target" \ PR_BASE_SHA="$base_sha" \ - PR_HEAD_SHA="$head_sha" \ + PR_HEAD_SHA=" $head_sha " \ STRIX_DISABLE_PR_SCOPING="0" \ FAKE_STRIX_CALL_LOG="$call_log" \ STRIX_LLM_FILE="$strix_llm_file" \