feat: add local analysis orchestration#49
Conversation
|
@coderabbitai review |
📝 WalkthroughSummary by CodeRabbit릴리스 노트
Walkthrough분석 오케스트레이션 시스템을 구현합니다. Tauri IPC 명령( Changes
Sequence DiagramsequenceDiagram
participant Frontend as 프론트엔드
participant Tauri as Tauri 명령
participant RustOrch as Rust 오케스트레이션
participant PythonCLI as Python CLI
participant JobStore as 작업 저장소
Frontend->>Tauri: start_analysis_job(요청)
Tauri->>RustOrch: 요청 검증
RustOrch->>JobStore: 작업 ID 할당, 상태=대기 중
RustOrch->>PythonCLI: JSON stdin으로 서브프로세스 시작
PythonCLI->>PythonCLI: 요청 검증, 분석 실행
PythonCLI-->>RustOrch: JSON 결과 stdout
RustOrch->>JobStore: 상태 업데이트 (완료/실패)
Tauri-->>Frontend: AnalysisJobStatus 반환 (작업 ID)
Frontend->>Tauri: get_analysis_job_status(작업ID) [폴링 반복]
Tauri->>RustOrch: 작업 ID 조회
RustOrch->>JobStore: 저장된 상태 검색
Tauri-->>Frontend: AnalysisJobStatus (현재 상태)
Frontend->>Frontend: 상태 렌더링 (대기/실행/완료/실패)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
1 similar comment
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/sbom.yml:
- Around line 41-44: The "Install Syft" workflow step currently downloads
syft_1.20.0_linux_amd64.tar.gz without verifying integrity; update the step to
also download syft_1.20.0_checksums.txt (from the same GitHub release), compute
the SHA-256 of "$RUNNER_TEMP/syft.tar.gz" (e.g., via sha256sum) and compare it
against the expected value from the checksums file, and exit non‑zero / fail the
job if the checksum does not match; ensure references in the step to
"$RUNNER_TEMP/syft.tar.gz" and the downloaded checksums file are used for the
verification, and consider updating the version string (syft_1.20.0_... and
syft_1.20.0_checksums.txt) to a newer release when changing the URLs.
In `@apps/desktop/src-tauri/src/main.rs`:
- Around line 458-469: Validate parsed AnalysisJobStatus before persisting:
after serde_json::from_slice::<AnalysisJobStatus>(&output.stdout) (and before
calling store_status or using status.job_id), check that status.job_id matches
payload["jobId"] (fall back to failed_status if mismatch), ensure terminal
states (e.g., "succeeded" or "failed") have the expected fields (succeeded must
include result, failed must include error) and that non-terminal states aren't
treated as terminal; if any invariant fails, map to failed_status(...) with
AnalysisJobErrorCode::EngineUnavailable and a clear message rather than
accepting the payload. Update the code paths around failed_status,
AnalysisJobStatus, and store_status to perform these checks and only call
store_status when invariants hold.
- Around line 380-393: The failure path after stdin.write_all currently only
calls process.kill() and returns, leaving the child unreaped; after killing the
child you must also wait on it to reap the process before returning. Update the
branch where stdin.write_all(...) fails (referencing stdin.write_all,
process.kill, failed_status and AnalysisJobErrorCode::EngineUnavailable) to call
process.wait() (or an appropriate wait method) after kill(), ignore/log any wait
errors, then return the failed_status as before so the child is properly reaped.
In `@apps/desktop/src/App.tsx`:
- Around line 100-123: The polling useEffect currently clears jobStatus on any
fetch error which discards jobId and stops polling; instead preserve jobId and
only set an error state so polling can continue/retry. In the catch block of the
useEffect that calls getAnalysisJobStatus, remove setJobStatus(null) and call
setJobError(t("analysisCouldNotStart")) (or similar) without wiping jobStatus;
also consider changing the effect dependency from jobStatus to jobStatus?.jobId
(or include jobStatus.jobId) so the poll loop is keyed to the jobId and not
reset when transient state changes. Ensure setJobResult and setJobError handling
stays the same when nextStatus arrives.
In `@apps/desktop/src/lib/analysis.ts`:
- Around line 22-28: The current getInvoke() returns the imported invoke when
window exists which can call into missing Tauri internals; change its runtime
detection to use isTauri() from "@tauri-apps/api/core" instead of checking
window presence. Update getInvoke() to import and call isTauri() and only return
window.__TAURI_INVOKE__ ?? invoke when isTauri() is true; otherwise return null,
ensuring you reference getInvoke, isTauri, invoke, and window.__TAURI_INVOKE__
when making the change.
In `@docs/architecture/overview.md`:
- Line 38: 문서의 "route orchestration through typed Tauri IPC and a narrow Python
subprocess bridge" 설명에 대해 별도의 "Security Notes" 섹션을 추가하세요; 이 섹션에 IPC와 Python
subprocess의 trust boundary를 명확히 정의하고(예: Tauri IPC, Python subprocess bridge,
loopback HTTP), 각 경계별 공격 표면·실제 위협 시나리오·권한 요구사항(권한 격리·권한 하향 최소화)·권한 검증 및 입력
검증/escaping 같은 완화책·잔여 위험·테스트 포인트(예: 권한 상승, 타임아웃/리소스 고갈, stderr/stdout 정보 유출, 악성
파일 실행) 등을 간단 항목화하여 포함시키세요; 해당 섹션은 문서 내 "Security Notes" 제목으로 추가하고 참조할 수 있게 route
orchestration 문단에 링크나 레퍼런스를 남기세요.
In `@packages/shared-types/src/index.ts`:
- Around line 373-389: The validateAnalysisJobError (and similarly
validateAnalysisJobStatus) uses invalidField which returns the generic "Invalid
rehearsal song contract" message; change this so analysis validators produce an
analysis-specific message by either adding an optional contractName parameter to
invalidField (defaulting to existing text) and passing "analysis job contract"
from validateAnalysisJobError/validateAnalysisJobStatus, or introduce a new
helper like invalidAnalysisField and use it in these validators; update all
calls in validateAnalysisJobError/validateAnalysisJobStatus to call the
new/parameterized helper so error messages are specific to analysis payloads
rather than the rehearsal contract.
In `@services/analysis-engine/src/bandscope_analysis/cli.py`:
- Around line 12-24: Wrap the top-level body of run_analysis_job with a broad
try/except that catches all unexpected exceptions, logs the error (but does not
let it crash the process), and returns a typed failure envelope using the
existing failed_cli_response pattern but with an "error.code" like
"engine_unavailable"; ensure the returned object preserves the original jobId
and requestedAt values from the incoming payload (use the same jobId/requestedAt
variables used in run_analysis_job) and sets state to "failed" and updatedAt to
the preserved timestamp so the CLI always emits a safe, contract-compliant
failure instead of crashing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f1353d83-66a5-4ae7-a09b-294a1b96a280
⛔ Files ignored due to path filters (6)
apps/desktop/src-tauri/Cargo.lockis excluded by!**/*.lockapps/desktop/src-tauri/gen/schemas/acl-manifests.jsonis excluded by!**/gen/**apps/desktop/src-tauri/gen/schemas/capabilities.jsonis excluded by!**/gen/**apps/desktop/src-tauri/gen/schemas/desktop-schema.jsonis excluded by!**/gen/**apps/desktop/src-tauri/gen/schemas/macOS-schema.jsonis excluded by!**/gen/**package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (26)
.github/workflows/sbom.ymlARCHITECTURE.mdapps/desktop/package.jsonapps/desktop/src-tauri/Cargo.tomlapps/desktop/src-tauri/build.rsapps/desktop/src-tauri/capabilities/main.jsonapps/desktop/src-tauri/permissions/autogenerated/get_analysis_job_status.tomlapps/desktop/src-tauri/permissions/autogenerated/start_analysis_job.tomlapps/desktop/src-tauri/src/main.rsapps/desktop/src-tauri/tauri.conf.jsonapps/desktop/src/App.test.tsxapps/desktop/src/App.tsxapps/desktop/src/i18n/index.tsapps/desktop/src/lib/analysis.tsapps/desktop/src/locales/en/common.jsonapps/desktop/src/locales/ko/common.jsondocs/architecture/overview.mddocs/plans/2026-03-12-issue-32-analysis-orchestration-design.mddocs/plans/2026-03-12-issue-32-analysis-orchestration.mddocs/security/app-security.mdpackages/shared-types/src/index.tspackages/shared-types/test/index.test.tsservices/analysis-engine/src/bandscope_analysis/api.pyservices/analysis-engine/src/bandscope_analysis/cli.pyservices/analysis-engine/tests/test_api.pyservices/analysis-engine/tests/test_cli.py
| - name: Install Syft | ||
| run: | | ||
| curl -sSfL -o "$RUNNER_TEMP/syft.tar.gz" "https://github.com/anchore/syft/releases/download/v1.20.0/syft_1.20.0_linux_amd64.tar.gz" | ||
| tar -xzf "$RUNNER_TEMP/syft.tar.gz" -C "$RUNNER_TEMP" syft |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Syft v1.20.0 release checksum sha256
💡 Result:
Anchore Syft v1.20.0 publishes per-artifact SHA256 sums in the GitHub release asset syft_1.20.0_checksums.txt on the v1.20.0 release page. [1]
To get the SHA256 for the specific binary you downloaded (linux/windows/darwin + arch), download that checksums file and match the filename:
curl -sSfL -O https://github.com/anchore/syft/releases/download/v1.20.0/syft_1.20.0_checksums.txt
grep 'syft_1.20.0_' syft_1.20.0_checksums.txt
# then verify, e.g.:
sha256sum -c syft_1.20.0_checksums.txt --ignore-missing(Anchore’s install docs also point to downloading artifacts from the GitHub releases page.) [2]
If you tell me which exact asset filename you need (e.g., syft_1.20.0_linux_amd64.tar.gz vs syft_1.20.0_windows_amd64.zip), I can help you extract the right line format to look for.
References: [1] (github.com) [2] (oss.anchore.com)
Citations:
- 1: https://github.com/anchore/syft/releases/tag/v1.20.0
- 2: https://oss.anchore.com/docs/installation/syft?utm_source=openai
🌐 Web query:
Syft latest version 2024 2025 current release
💡 Result:
- Current/latest Syft release (as of Mar 12, 2026): v1.42.2 (released Mar 9, 2026) [1]
- Latest release in 2025: v1.39.0 (released Dec 22, 2025) [2][3]
- Latest release in 2024: v1.18.0 (released Dec 9, 2024) [4]
Sources: [1] anchore/syft GitHub repo (latest release badge), [2] anchore/syft GitHub release v1.39.0 page, [3] Chocolatey bot test log showing syft 1.39.0 on 2025-12-23, [4] Anchore Community announcement for v1.18.0.
🌐 Web query:
Syft v1.20.0 security vulnerability CVE
💡 Result:
Syft v1.20.0 does not appear to have any Syft-specific CVEs affecting that release line; the only CVE currently listed in the anchore/syft GitHub Security Advisories is:
- CVE-2023-24827 (GHSA-jp7v-3587-2956): credential/password disclosure when
SYFT_ATTEST_PASSWORDis set; affected:>= v0.69.0and< v0.70.0; patched in:v0.70.0—so Syft v1.20.0 is not in the affected range. [1][2][3]
Sources: [1] GitHub advisory, [2] GitHub security overview, [3] NVD entry.
🏁 Script executed:
# Let me also check if there's any way to retrieve the actual checksum from the GitHub release
# by constructing the URL to the checksums file
curl -s "https://github.com/anchore/syft/releases/download/v1.20.0/syft_1.20.0_checksums.txt" | head -20Repository: seonghobae/bandscope
Length of output: 2064
보안 위험: 다운로드된 바이너리에 대한 체크섬 검증 누락
외부 바이너리를 직접 다운로드하여 실행하는 경우, 반드시 SHA-256 체크섬을 검증해야 합니다. 현재 구현은 다운로드가 변조되었을 경우 악성 코드가 CI에서 실행될 수 있는 공급망 공격에 취약합니다.
Syft는 GitHub 릴리스 페이지에서 syft_1.20.0_checksums.txt 파일로 각 아티팩트의 SHA256 체크섬을 제공합니다. 다음과 같이 검증을 추가하세요:
🛡️ 체크섬 검증 추가
- name: Install Syft
run: |
curl -sSfL -o "$RUNNER_TEMP/syft.tar.gz" "https://github.com/anchore/syft/releases/download/v1.20.0/syft_1.20.0_linux_amd64.tar.gz"
+ curl -sSfL -o "$RUNNER_TEMP/syft_checksums.txt" "https://github.com/anchore/syft/releases/download/v1.20.0/syft_1.20.0_checksums.txt"
+ grep "syft_1.20.0_linux_amd64.tar.gz" "$RUNNER_TEMP/syft_checksums.txt" | sha256sum -c -
tar -xzf "$RUNNER_TEMP/syft.tar.gz" -C "$RUNNER_TEMP" syft추가로, Syft v1.20.0은 2024년 버전으로 현재(2026년 3월) 최신 버전 v1.42.2에서 크게 뒤떨어져 있습니다. 최신 버전으로의 업그레이드도 검토하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/sbom.yml around lines 41 - 44, The "Install Syft" workflow
step currently downloads syft_1.20.0_linux_amd64.tar.gz without verifying
integrity; update the step to also download syft_1.20.0_checksums.txt (from the
same GitHub release), compute the SHA-256 of "$RUNNER_TEMP/syft.tar.gz" (e.g.,
via sha256sum) and compare it against the expected value from the checksums
file, and exit non‑zero / fail the job if the checksum does not match; ensure
references in the step to "$RUNNER_TEMP/syft.tar.gz" and the downloaded
checksums file are used for the verification, and consider updating the version
string (syft_1.20.0_... and syft_1.20.0_checksums.txt) to a newer release when
changing the URLs.
| if let Some(mut stdin) = process.stdin.take() { | ||
| if stdin.write_all(payload.to_string().as_bytes()).is_err() { | ||
| let _ = process.kill(); | ||
| return failed_status( | ||
| payload["jobId"] | ||
| .as_str() | ||
| .unwrap_or("unknown-job") | ||
| .to_string(), | ||
| requested_at, | ||
| AnalysisJobErrorCode::EngineUnavailable, | ||
| "Analysis engine is unavailable.", | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
stdin.write_all 실패 경로에서 자식 프로세스를 reap 하지 않습니다.
Line 382에서 kill()만 호출하고 바로 반환하면, 아직 살아 있는 자식이 wait() 없이 drop되어 unreaped process로 남을 수 있습니다. 다른 실패 분기처럼 종료를 회수한 뒤 반환해야 합니다.
🔧 제안 수정
if let Some(mut stdin) = process.stdin.take() {
if stdin.write_all(payload.to_string().as_bytes()).is_err() {
let _ = process.kill();
+ let _ = process.wait();
return failed_status(
payload["jobId"]
.as_str()
.unwrap_or("unknown-job")
.to_string(),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if let Some(mut stdin) = process.stdin.take() { | |
| if stdin.write_all(payload.to_string().as_bytes()).is_err() { | |
| let _ = process.kill(); | |
| return failed_status( | |
| payload["jobId"] | |
| .as_str() | |
| .unwrap_or("unknown-job") | |
| .to_string(), | |
| requested_at, | |
| AnalysisJobErrorCode::EngineUnavailable, | |
| "Analysis engine is unavailable.", | |
| ); | |
| } | |
| } | |
| if let Some(mut stdin) = process.stdin.take() { | |
| if stdin.write_all(payload.to_string().as_bytes()).is_err() { | |
| let _ = process.kill(); | |
| let _ = process.wait(); | |
| return failed_status( | |
| payload["jobId"] | |
| .as_str() | |
| .unwrap_or("unknown-job") | |
| .to_string(), | |
| requested_at, | |
| AnalysisJobErrorCode::EngineUnavailable, | |
| "Analysis engine is unavailable.", | |
| ); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src-tauri/src/main.rs` around lines 380 - 393, The failure path
after stdin.write_all currently only calls process.kill() and returns, leaving
the child unreaped; after killing the child you must also wait on it to reap the
process before returning. Update the branch where stdin.write_all(...) fails
(referencing stdin.write_all, process.kill, failed_status and
AnalysisJobErrorCode::EngineUnavailable) to call process.wait() (or an
appropriate wait method) after kill(), ignore/log any wait errors, then return
the failed_status as before so the child is properly reaped.
| serde_json::from_slice::<AnalysisJobStatus>(&output.stdout).unwrap_or_else(|_| { | ||
| failed_status( | ||
| payload["jobId"] | ||
| .as_str() | ||
| .unwrap_or("unknown-job") | ||
| .to_string(), | ||
| requested_at, | ||
| AnalysisJobErrorCode::EngineUnavailable, | ||
| "Analysis engine returned an invalid response.", | ||
| ) | ||
| }) | ||
| } |
There was a problem hiding this comment.
엔진 응답의 불변식을 확인하지 않아 상태 저장소가 오염될 수 있습니다.
여기는 JSON shape만 맞으면 그대로 받아들이고, 이후 store_status()가 status.job_id를 key로 씁니다. 엔진이 잘못된 jobId, 비종료 상태, 혹은 succeeded인데 result가 없는 응답을 보내면 원래 작업이 영구히 running에 남거나 프런트엔드가 매번 generic error로 떨어질 수 있습니다. 최소한 현재 job_id와의 일치, terminal state 여부, state와 result/error의 조합을 확인한 뒤 아니면 안전한 실패로 매핑하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src-tauri/src/main.rs` around lines 458 - 469, Validate parsed
AnalysisJobStatus before persisting: after
serde_json::from_slice::<AnalysisJobStatus>(&output.stdout) (and before calling
store_status or using status.job_id), check that status.job_id matches
payload["jobId"] (fall back to failed_status if mismatch), ensure terminal
states (e.g., "succeeded" or "failed") have the expected fields (succeeded must
include result, failed must include error) and that non-terminal states aren't
treated as terminal; if any invariant fails, map to failed_status(...) with
AnalysisJobErrorCode::EngineUnavailable and a clear message rather than
accepting the payload. Update the code paths around failed_status,
AnalysisJobStatus, and store_status to perform these checks and only call
store_status when invariants hold.
| useEffect(() => { | ||
| if (!jobStatus || (jobStatus.state !== "queued" && jobStatus.state !== "running")) { | ||
| return; | ||
| } | ||
|
|
||
| const timer = window.setTimeout(async () => { | ||
| try { | ||
| const nextStatus = await getAnalysisJobStatus(jobStatus.jobId); | ||
| setJobStatus(nextStatus); | ||
| if (nextStatus.state === "succeeded" && nextStatus.result) { | ||
| setJobResult(nextStatus.result); | ||
| setJobError(null); | ||
| } | ||
| if (nextStatus.state === "failed") { | ||
| setJobError(nextStatus.error?.message ?? t("analysisCouldNotStart")); | ||
| } | ||
| } catch { | ||
| setJobStatus(null); | ||
| setJobError(t("analysisCouldNotStart")); | ||
| } | ||
| }, ANALYSIS_POLL_INTERVAL_MS); | ||
|
|
||
| return () => window.clearTimeout(timer); | ||
| }, [jobStatus]); |
There was a problem hiding this comment.
폴링 예외에서 작업 ID를 버리면 완료 결과를 다시 회수할 수 없습니다.
Line 117에서 jobStatus를 null로 지우면 현재 작업의 jobId를 잃고 폴링도 바로 끊깁니다. IPC 오류가 일시적이어도 백엔드 작업은 계속 실행될 수 있으니, 이 경로는 완료 결과를 영구히 놓치고 버튼을 다시 열어 중복 시작까지 허용합니다. 에러는 별도 상태로 노출하고, 재시도 루프는 jobId를 유지한 채 계속 돌도록 분리해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/App.tsx` around lines 100 - 123, The polling useEffect
currently clears jobStatus on any fetch error which discards jobId and stops
polling; instead preserve jobId and only set an error state so polling can
continue/retry. In the catch block of the useEffect that calls
getAnalysisJobStatus, remove setJobStatus(null) and call
setJobError(t("analysisCouldNotStart")) (or similar) without wiping jobStatus;
also consider changing the effect dependency from jobStatus to jobStatus?.jobId
(or include jobStatus.jobId) so the poll loop is keyed to the jobId and not
reset when transient state changes. Ensure setJobResult and setJobError handling
stays the same when nextStatus arrives.
| function getInvoke(): TauriInvoke | null { | ||
| if (typeof window === "undefined") { | ||
| return null; | ||
| } | ||
|
|
||
| return window.__TAURI_INVOKE__ ?? invoke; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Tauri v2, what is the documented way to detect that the app is running inside a Tauri runtime before calling invokefrom@tauri-apps/api/core, and does imported invoke work in a plain browser environment?
💡 Result:
-
Documented runtime check (Tauri v2): use
isTauri()from@tauri-apps/api/coreand only callinvokewhen it returnstrue. Thecorenamespace documentsisTauri(): booleanalongsideinvoke(). [1]import { isTauri, invoke } from "@tauri-apps/api/core"; if (isTauri()) { await invoke("my_command"); }
-
Does imported
invokework in a plain browser? The function can be imported (it’s just JS), but it won’t work outside a Tauri runtime: it callswindow.__TAURI_INTERNALS__.invoke(...), which doesn’t exist in a normal browser page, so you’ll get a runtime error if you call it. [2] -
Related: the global
window.__TAURI__API is only available when running under Tauri (and whenapp.withGlobalTauriis enabled), reinforcing that these APIs aren’t meant to run in a plain browser. [3]
Sources: [1] [2] [3]
🏁 Script executed:
# Check the full file to understand context
cat -n apps/desktop/src/lib/analysis.ts | head -50Repository: seonghobae/bandscope
Length of output: 2064
🏁 Script executed:
# Search for __TAURI_INVOKE__ definition in the codebase
rg "__TAURI_INVOKE__" --type ts --type tsx -B 2 -A 2Repository: seonghobae/bandscope
Length of output: 2064
🏁 Script executed:
# Search for how getInvoke is used in the codebase
rg "getInvoke" --type ts --type tsx -B 1 -A 1Repository: seonghobae/bandscope
Length of output: 2064
🏁 Script executed:
# Search for isTauri usage in the codebase to see if it's already used
rg "isTauri" --type ts --type tsx -B 1 -A 1Repository: seonghobae/bandscope
Length of output: 2064
Tauri 런타임 감지 방식을 isTauri()로 변경하세요.
현재 코드는 window 존재 여부로만 확인하기 때문에 일반 브라우저에서도 imported invoke를 반환합니다. 이 함수를 호출하면 window.__TAURI_INTERNALS__.invoke(...)에 접근하려다 런타임 에러가 발생합니다.
Tauri v2 문서의 표준 방식은 @tauri-apps/api/core에서 isTauri()를 사용해서 런타임을 확인한 후에만 invoke를 호출하는 것입니다:
import { isTauri, invoke } from "@tauri-apps/api/core";
if (isTauri()) {
await invoke("command");
}window.__TAURI_INVOKE__ 존재 여부는 신뢰성이 낮으므로 대신 isTauri()로 확인하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/lib/analysis.ts` around lines 22 - 28, The current
getInvoke() returns the imported invoke when window exists which can call into
missing Tauri internals; change its runtime detection to use isTauri() from
"@tauri-apps/api/core" instead of checking window presence. Update getInvoke()
to import and call isTauri() and only return window.__TAURI_INVOKE__ ?? invoke
when isTauri() is true; otherwise return null, ensuring you reference getInvoke,
isTauri, invoke, and window.__TAURI_INVOKE__ when making the change.
| - prefer local processing for audio and analysis | ||
| - keep risky capabilities narrow, allowlisted, and explicit | ||
| - treat files, URLs, models, caches, and release artifacts as untrusted inputs | ||
| - route orchestration through typed Tauri IPC and a narrow Python subprocess bridge before considering any loopback HTTP surface |
There was a problem hiding this comment.
Security Notes 섹션이 아직 없습니다.
Line 38은 IPC/서브프로세스 trust boundary 변경을 문서화하고 있는데, 이런 작업은 설계 문서나 구현 요약에 Security Notes를 반드시 남겨야 합니다. 공격 표면, 경계, 현실적인 위협, 완화책, 잔여 위험, 테스트 포인트가 빠지면 timeout·권한 범위·오류 노출 기준이 문서 밖으로 흩어집니다.
📝 문서 보강 예시
## Local-first principle
- prefer local processing for audio and analysis
- keep risky capabilities narrow, allowlisted, and explicit
- treat files, URLs, models, caches, and release artifacts as untrusted inputs
- route orchestration through typed Tauri IPC and a narrow Python subprocess bridge before considering any loopback HTTP surface
+
+## Security Notes
+
+- attack surface: Tauri IPC commands, Python subprocess stdin/stdout contract, job-status polling
+- trust boundary touched: UI -> native shell -> Python engine
+- realistic threats: malformed payloads, widened command exposure, hung subprocesses, sensitive stderr propagation
+- mitigations: allowlisted capabilities, strict schema validation, in-flight job limits, timeouts, redacted surfaced errors
+- remaining risk: Python/runtime dependency behavior and local toolchain variance
+- test points: unknown-field rejection, permission denial, timeout mapping, concurrent start throttlingBased on learnings: If a task touches files, URLs, subprocesses, ffmpeg or native tools, WebView, local backend or IPC, updates, model downloads, project formats, logs, telemetry, or exports, the result must include Security Notes
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/architecture/overview.md` at line 38, 문서의 "route orchestration through
typed Tauri IPC and a narrow Python subprocess bridge" 설명에 대해 별도의 "Security
Notes" 섹션을 추가하세요; 이 섹션에 IPC와 Python subprocess의 trust boundary를 명확히 정의하고(예:
Tauri IPC, Python subprocess bridge, loopback HTTP), 각 경계별 공격 표면·실제 위협 시나리오·권한
요구사항(권한 격리·권한 하향 최소화)·권한 검증 및 입력 검증/escaping 같은 완화책·잔여 위험·테스트 포인트(예: 권한 상승,
타임아웃/리소스 고갈, stderr/stdout 정보 유출, 악성 파일 실행) 등을 간단 항목화하여 포함시키세요; 해당 섹션은 문서 내
"Security Notes" 제목으로 추가하고 참조할 수 있게 route orchestration 문단에 링크나 레퍼런스를 남기세요.
| function validateAnalysisJobError(value: unknown, path: string): string | null { | ||
| if (!isRecord(value)) { | ||
| return invalidField(path); | ||
| } | ||
| const extraKey = unexpectedKey(value, ["code", "message"], path); | ||
| if (extraKey) { | ||
| return extraKey; | ||
| } | ||
| if (!isOneOf(ANALYSIS_JOB_ERROR_CODES, value.code)) { | ||
| return invalidField(`${path}.code`); | ||
| } | ||
| if (typeof value.message !== "string") { | ||
| return invalidField(`${path}.message`); | ||
| } | ||
|
|
||
| return null; | ||
| } |
There was a problem hiding this comment.
분석 payload 오류가 rehearsal song contract로 보고됩니다.
Line 375-Line 385와 Line 393-Line 437은 invalidField()를 재사용해서, 잘못된 AnalysisJobError/AnalysisJobStatus도 "Invalid rehearsal song contract"로 반환합니다. 이 파일의 요청 파서는 이미 분석 전용 문구를 쓰고 있으니, 분석용 formatter를 분리하거나 계약명을 파라미터화해서 메시지를 맞춰 주세요.
Also applies to: 391-440
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared-types/src/index.ts` around lines 373 - 389, The
validateAnalysisJobError (and similarly validateAnalysisJobStatus) uses
invalidField which returns the generic "Invalid rehearsal song contract"
message; change this so analysis validators produce an analysis-specific message
by either adding an optional contractName parameter to invalidField (defaulting
to existing text) and passing "analysis job contract" from
validateAnalysisJobError/validateAnalysisJobStatus, or introduce a new helper
like invalidAnalysisField and use it in these validators; update all calls in
validateAnalysisJobError/validateAnalysisJobStatus to call the new/parameterized
helper so error messages are specific to analysis payloads rather than the
rehearsal contract.
| def failed_cli_response(message: str) -> dict[str, object]: | ||
| """Return a typed CLI failure envelope for malformed stdin payloads.""" | ||
| requested_at = datetime.now(UTC).isoformat().replace("+00:00", "Z") | ||
| return { | ||
| "jobId": "unknown-job", | ||
| "state": "failed", | ||
| "requestedAt": requested_at, | ||
| "updatedAt": requested_at, | ||
| "error": { | ||
| "code": "invalid_request", | ||
| "message": message, | ||
| }, | ||
| } |
There was a problem hiding this comment.
예상치 못한 엔진 예외도 typed 실패 응답으로 변환해 주세요.
지금은 run_analysis_job(...)에서 예외가 나면 CLI가 traceback과 함께 비정상 종료됩니다. 그러면 상위 Tauri IPC 경계에서 안전한 실패 envelope 대신 프로세스 실패로 보이게 되고, 내부 오류 정보가 stderr로 새어 나갈 수 있습니다. 여기서 예외를 잡아 jobId와 requestedAt를 유지한 채 안전한 engine_unavailable류의 실패 응답으로 매핑해야 계약이 유지됩니다.
🔧 제안 수정안
-def failed_cli_response(message: str) -> dict[str, object]:
+def failed_cli_response(
+ message: str,
+ *,
+ code: str = "invalid_request",
+ job_id: str = "unknown-job",
+ requested_at: str | None = None,
+) -> dict[str, object]:
"""Return a typed CLI failure envelope for malformed stdin payloads."""
- requested_at = datetime.now(UTC).isoformat().replace("+00:00", "Z")
+ requested_at = requested_at or datetime.now(UTC).isoformat().replace("+00:00", "Z")
return {
- "jobId": "unknown-job",
+ "jobId": job_id,
"state": "failed",
"requestedAt": requested_at,
"updatedAt": requested_at,
"error": {
- "code": "invalid_request",
+ "code": code,
"message": message,
},
}
@@
- response = run_analysis_job(job_id, request, requested_at)
+ try:
+ response = run_analysis_job(job_id, request, requested_at)
+ except Exception:
+ response = failed_cli_response(
+ "Analysis engine is unavailable right now. Please try again.",
+ code="engine_unavailable",
+ job_id=job_id,
+ requested_at=requested_at,
+ )
json.dump(response, sys.stdout)
return 0Also applies to: 27-49
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/analysis-engine/src/bandscope_analysis/cli.py` around lines 12 - 24,
Wrap the top-level body of run_analysis_job with a broad try/except that catches
all unexpected exceptions, logs the error (but does not let it crash the
process), and returns a typed failure envelope using the existing
failed_cli_response pattern but with an "error.code" like "engine_unavailable";
ensure the returned object preserves the original jobId and requestedAt values
from the incoming payload (use the same jobId/requestedAt variables used in
run_analysis_job) and sets state to "failed" and updatedAt to the preserved
timestamp so the CLI always emits a safe, contract-compliant failure instead of
crashing.
|
Superseded by #50 to clear the remaining stale CodeRabbit changes-requested review after all required checks passed and no new actionable review remained. |
Pull request was closed
Summary
Verification
./scripts/harness/quickcheck.shcargo check --manifest-path apps/desktop/src-tauri/Cargo.toml