# 원본 one-time importer 안전 감사 기준일은 2026-07-12이며, 설치된 패키지는 `Wickedness.MBNStockWebView_1.0.3.0_x64__qbv3jkvsn3aj0`이다. 이 감사에서는 원본 `Data`를 읽기 전용으로 사용했고 Oracle/MariaDB에는 SELECT만 실행했다. 운영 `%LOCALAPPDATA%\MBN_STOCK_WEBVIEW\OperatorData`와 Tornado2에는 쓰기나 명령을 보내지 않았다. 비교쌍에 한해서는 설치된 signed package의 명시적 버튼을 한 번 눌러 WebView2 `localStorage`에 8쌍과 one-time marker를 같은 envelope로 저장했다. ## 감사 시점 운영 profile 분류 FSell/VI 저장소에는 marker와 intent 없이 원본과 byte 단위로 같은 네 파일이 이미 있다. | 파일 | 길이 | SHA-256 | 고정 원본과 일치 | |---|---:|---|---| | `개인.dat` | 56 | `F953366A5D9F940CBE54F05C256F8BFB0AF4674C8F4D46CCBF839A63113342FA` | 예 | | `외국인.dat` | 58 | `45F62038E3216E80FCF22F4E923056858E9C3537BF0DC46BC359148E82DD7CD8` | 예 | | `기관.dat` | 27 | `BC4D62D734E701744E10DD830A2D6B9AEBEBEB795BB4DABC77CDE6577F016EB7` | 예 | | `VI발동.dat` | 276 | `46AFBA07D2C4AF50717450582DEB10EC10367701D09161BECF5E32342AD4AA45` | 예 | - `.legacy-manual-operator-import.v1.json`: 없음 - `.legacy-manual-operator-import.v1.in-progress.json`: 없음 - importer staging 파일: 없음 - startup 상태: marker가 없는 기존 원본 파일을 그대로 쓰는 `ReadyLegacyData` - 현재 버튼 실행 판정: `DestinationNotEmpty`, `OutcomeUnknown=false` 따라서 현재 profile에서 `원본 데이터 가져오기`를 누르면 네 파일을 덮어쓰지 않고 source read 전에 전체 import가 중단된다. marker를 임의로 만들어 `AlreadyImported`로 바꾸거나 파일을 지워 import를 재현하면 안 된다. 다음 명령은 운영 profile을 쓰지 않고 이 판정을 다시 출력한다. ```powershell dotnet run --project .\tools\MBN_STOCK_WEBVIEW.DbSmoke\MBN_STOCK_WEBVIEW.DbSmoke.csproj ` -c Release -p:Platform=x64 -- --manual-import-audit ``` 현재 결과: ```text MANUAL_IMPORT_AUDIT: PASS sourceFiles=4/4 operatingFiles=4/4 sourceByteIdentical=true marker=false intent=false staged=false runtime=ReadyLegacyData decision=DestinationNotEmpty outcomeUnknown=false writesAttempted=false ``` ## 격리 FSell/VI 실제 원본 import `--manual-import-fixture`는 사용자가 destination을 지정할 수 없게 닫혀 있다. 고정 원본을 읽고 `%TEMP%\MBN_STOCK_WEBVIEW-manual-import-fixture-`에만 쓴다. 전체 실행은 1분 timeout으로 제한되며 timeout/cancel 뒤 자동 재시도하지 않는다. 다음 항목을 모두 확인한 뒤 자신이 만든 prefix의 non-reparse directory만 삭제한다. 1. 4파일 stage 및 no-overwrite commit 2. production parser read-back 3. marker-last commit과 intent 제거 4. 재시작 guard의 `ReadyImportedData` 5. 같은 fixture의 두 번째 실행 `AlreadyImported`, `OutcomeUnknown=false` 6. 원본 4파일과 운영 import 대상 6항목의 실행 전후 SHA-256 불변 7. 원본 aggregate SHA-256 `fd39a363…a2114`, FSell 15행과 VI 9건 고정 ```powershell dotnet run --project .\tools\MBN_STOCK_WEBVIEW.DbSmoke\MBN_STOCK_WEBVIEW.DbSmoke.csproj ` -c Release -p:Platform=x64 -- --manual-import-fixture ``` 실제 결과: ```text MANUAL_IMPORT_FIXTURE: PASS rows=15 vi=9 sourceSha256=fd39a3639e574a19f174225e8e65234d6bdd4c58700bb5d10230a779a03a2114 state=ReadyImportedData secondAttempt=AlreadyImported outcomeUnknown=false sourceUnchanged=true operatingImportSetUnchanged=true ``` ## `종목비교.dat` 분류 - 고정 원본: 358바이트, 8행 - SHA-256: `1EE76BC464FB1C44C8B6546E99CDC21C726C0E2C24AD344F5C19AE41BFC0D2F2` - 실제 Oracle/MariaDB read-only 재검증: 8/8 identity 해소 - 실행 경로: `비교` 화면의 `원본 비교쌍 가져오기` - 성공 저장 위치: package WebView2 `localStorage`의 단일 comparison envelope 감사 초기에 package WebView2가 닫힌 상태에서 LevelDB에는 87바이트 `VERSION=1` log 외 comparison key byte가 없었고 비교 화면은 `0 PAIRS`였다. signed 1.0.3 패키지에서 `원본 비교쌍 가져오기`를 명시적으로 한 번 실행한 결과, native가 고정 원본의 SHA-256과 8행을 다시 읽고 현재 DB에서 모든 identity를 해소한 뒤 화면이 `8 PAIRS`와 `원본 8쌍 가져옴`으로 바뀌었다. 부분 저장이나 `복구 필요` 상태는 발생하지 않았다. 이 성공은 같은 package profile의 단일 comparison envelope에 pair list와 marker를 함께 기록한 결과다. 앱을 정상 종료한 뒤 설치된 signed 1.0.3을 다시 실행해 다음 상태를 확인했다. - 비교 화면: `8 PAIRS` - 저장 비교쌍: 원본 순서의 8개 항목 유지 - one-time gate: 완료형 `원본 8쌍 가져옴`, 다시 실행할 수 없는 상태 - 고정 원본 SHA-256: `1EE76BC464FB1C44C8B6546E99CDC21C726C0E2C24AD344F5C19AE41BFC0D2F2`로 불변 따라서 package UI 저장과 재시작 marker 영속성은 PASS다. 실행 중 LevelDB는 process lock 상태이므로 raw 파일 검색으로 live logical state를 단정하거나 파일을 복사·수정하지 않았다. 정규 DB smoke에는 실제 importer를 추가했다. 아래 명령은 원본을 읽고 8개 identity를 fresh SELECT로 해소하지만 package `localStorage`에는 쓰지 않는다. ```powershell dotnet run --project .\tools\MBN_STOCK_WEBVIEW.DbSmoke\MBN_STOCK_WEBVIEW.DbSmoke.csproj ` -c Release -p:Platform=x64 --no-build ``` 확인 결과는 다음과 같다. ```text LegacyComparisonImport: rows=8, sha256=1EE76BC464FB1C44C8B6546E99CDC21C726C0E2C24AD344F5C19AE41BFC0D2F2, identities=fresh-read-only REAL_DB_SMOKE: PASS ``` 현재 package profile에서는 실제 0→8 import와 정상 종료 후 재시작 marker 검증을 완료했으므로 이를 재현하려고 package family 데이터를 초기화하지 않는다. 빈 profile의 실패 fixture가 추가로 필요하면 별도 test package identity를 사용한다. 운영 identity에서 `Remove-AppxPackage`, app data reset, LevelDB 파일 복사·삭제로 빈 profile을 만들지 않는다. ## 차단 판정표 | 상태 | FSell/VI 판정 | 비교쌍 판정 | 자동 재시도 | |---|---|---|---| | 대상 파일 없음, source 정상 | import 가능 | marker 없고 envelope 정상일 때 가능 | 명시적 버튼만 | | 기존 4파일, marker 없음 | `DestinationNotEmpty` | 해당 없음 | 금지 | | 유효 marker 있음 | `AlreadyImported` | 버튼이 `원본 N쌍 가져옴`으로 비활성 | 금지 | | intent 있음 | `InterruptedImport`, `OutcomeUnknown=true` | 해당 없음 | 금지, 수동 대조 | | marker/4파일 불일치 | `InvalidImportedState` | 해당 없음 | 금지, 수동 대조 | | split/손상 envelope | 해당 없음 | `복구 필요`, 전체 차단 | 금지, local state 대조 | | DB timeout/identity 오류 | 해당 없음 | 저장 전 전체 취소 | 금지 | FSell/VI 전용 화면은 원본 운영 항목의 개인·외국인·기관 또는 VI 수동 항목을 열어 진입하며, header의 `원본 데이터 가져오기`가 importer 버튼이다. 비교쌍은 상단 `비교` 업무 탭 안의 `원본 비교쌍 가져오기` 버튼으로 진입한다.