20 KiB
Browser transfer and Image CDN recovery
이 runbook은 presigned capability, multipart/resumable upload, streaming download와 Image CDN reference runtime을 제품에 조합한 경우에만 적용한다. skeleton의 기본 bootstrap은 이 runtime을 실행하지 않는다.
현재 Range resumable download와 top-level transfer/Image descriptor composition은
각각 DESIGNED_NOT_IMPLEMENTED다. app-managed background download와 upload는
각각 NOT_SELECTED, 그 cross-browser guarantee는 PLATFORM_LIMITED다. 아래에서
이 기능을 다루는 절차는 VD-14/VD-16 구현과 제품 조합이 완료된 뒤에만
활성화한다. 기존 whole-object streaming, foreground upload resume나
browser-managed handoff를 그 기능의 운영 증거로 사용하지 않는다.
1. 관측 신호
허용된 aggregate 신호:
- capability issue/claim의 success, expired, policy-rejected bucket
- upload session create/resume/reconcile/complete/abort outcome
- part size/count/concurrency와 retry bucket
- download expected/actual byte bucket과 truncated/overrun/integrity outcome
- Image CDN preset/format/width/candidate bucket과 policy rejection
- session age, orphan count, quarantine backlog와 promotion latency
금지:
- presigned URL, query, signed request/response header
- resource/session/asset/account ID와 object key
- file name, local path, raw ETag, checksum, receipt/token의 실제 값
strict upload checkpoint의 protocol-defined SHA-256 fingerprint/part checksum과 bounded opaque non-authorizing part receipt는 server reconcile을 위한 durable 예외다. account partition/retention 안에서만 보존하고 관측 신호나 ticket에는 절대 투영하지 않는다.
- raw backend/body/DOMException message와 stack
2. 초기 분류
| 증상 | 우선 확인 |
|---|---|
| capability expiry/403 급증 | BFF/object-store clock, signer credential rotation, expiry ceiling |
| CORS/preflight 실패 | exact origin, method, signed headers, exposed receipt/checksum headers |
| redirect/network failure | proxy/CDN redirect 변경, redirect:error, URL origin/path policy |
| 특정 part 반복 실패 | exact offset/length/checksum/idempotency, capability expiry |
| resume conflict | server status와 checkpoint revision/part binding |
| status 404/410 반복 | terminal checkpoint 제거와 새 session 생성 여부 |
| complete 뒤 file 불일치 | ordered parts, checksum type, ETag 오해, scan/promotion |
| download truncation/overrun | capability length, content encoding, proxy buffering/transform |
| destination partial save | picker writable abort/close, integrity-before-close |
| image policy rejection 급증 | asset revision, preset registry, dimensions, format/CDN origin |
| stale/broken private image | lazy load 시점과 signed URL minimum lifetime |
| image timeout/decode 실패 급증 | encoded cap, pre-decode metadata, decoded-byte ceiling, probe deadline |
| CDN cache poison 의심 | immutable revision, cache key, Vary, source asset registry |
3. Presigned capability
- 신규 capability 발급을 중지할 kill switch가 동작하는지 확인한다.
- BFF와 object storage의 clock skew와 signer credential lifetime을 확인한다.
- whole-object control plane이 versioned target을 구현한 제품이면 request,
response, vault와 executor의
PRESIGNED_TRANSFER_V1exact match 및 unknown/missing version rejection을 확인한다. 현재 reference wire에 이 target이 아직 없으면 구현되지 않은 protocol을 통과했다고 기록하지 않는다. - capability가 method, object/part, content constraints와 expiry를 server-side로 강제하는지 확인한다.
- upload part 발급 BFF가
sessionId로 server session을 조회하고PRESIGNED_MULTIPART_V1canonical request/upload binding과 part plan을 재계산하는지,UPLOAD_PARTcapability binding에도 exact protocol을 넣는지 확인한다. client digest 일치만으로 authorization하지 않는다. - redirect가 추가되었으면 allowlist를 넓히지 말고 direct endpoint를 복구한다.
- URL/query를 ticket 또는 log에 복사하지 않는다. 노출 가능성이 있으면 signer credential/policy와 해당 capability scope를 폐기한다.
- 만료 capability를 retry하지 않고 control plane에서 새 capability를 발급한다.
- client claim은 server replay authority가 아니므로 중복 사용 여부는 server access/audit의 안전한 aggregate로 판단한다.
4. Multipart/resumable upload
4.1 안전한 일시 중지
현재 runtime에는 공식 pause()와 checkpoint inventory/retention sweep이 없다.
caller abort 뒤 checkpoint가 남아 재개될 수 있다는 사실을 PAUSED 완료로
기록하지 않는다. 다음 절차의 PAUSED state와 탭 간 pause는 VD-16 목표
runtime이 구현된 제품에만 적용한다.
- 신규 session create를 중지한다.
- active part는 사용자가 취소하지 않았다면 현재 bounded attempt만 마무리한다.
- complete를 중지하고 server status reconcile만 허용한다.
- presigned URL은 checkpoint에 쓰지 않고 즉시 메모리에서 폐기한다.
- 현재 runtime은 checkpoint를
ACTIVE또는ABORT_PENDING으로 유지한다. 목표 pause runtime은 exact scope/generation을 확인해PAUSE_REQUESTED -> PAUSED로 전환한다. - 목표 pause runtime은 cross-context
RESUMABLE_UPLOAD_PAUSE_V1, checkpointschemaVersion: 2와ACTIVE | PAUSED | ABORT_PENDINGclosed state를 사용한다. v1 old writer drain, historical migration과 N-1 fail-closed를 확인한다. - 목표 runtime의 inventory/sweep는 count/cursor/deadline 안에서 abandoned와 expired 후보만 반환하고, server reconcile/CAS 없이 checkpoint를 삭제하지 않는다.
4.2 Resume
- session/checkpoint/control DTO의 protocol이 모두
PRESIGNED_MULTIPART_V1인지 확인하고, session expiry와 source binding/total bytes/part layout을 검사한다. - server-authoritative status/list-parts를 읽는다.
- 완료 part의 local range SHA-256을 다시 계산해 exact checksum/receipt와 대조한다.
- checkpoint-only part와 server-only part를 자동 complete하지 않는다.
- 불일치는 session을 격리하고 새 session/명시적 abort를 선택한다.
- missing part만 동일 bytes/checksum/idempotency binding으로 전송한다.
- complete 직전에 1부터 연속적인 ordered receipt set을 다시 확인한다.
- status HTTP 404/410 또는
NOT_FOUND/EXPIRED이면 stale checkpoint를 CAS 제거하고 새 session으로 restart한다. 같은 terminal session을 반복 조회하지 않는다. - network/429/모든 5xx retry가 attempt count,
Retry-After, backoff와 operation timeout ceiling 안인지 확인한다.
4.3 PUT acknowledgement
- capability가 expected success status, receipt response header와
expectedResponseByteLength를 exact하게 묶는지 확인한다. - 실제
Content-Length가 expected bytes와 같고 hard response cap 이하인지 확인한다. 204는 expected bytes가 0이어야 하며 header 부재를 0으로 정규화한다. - response body EOF까지 동일 deadline 안에서 bounded drain된 뒤 receipt가 accepted됐는지 확인한다.
- receipt/ETag는 part acknowledgement일 뿐 whole-file digest나 authorization proof로 해석하지 않는다.
4.4 Abort/orphan
- 사용자 cancel은 browser work cancel이며 server abort가 아니다.
- 다른 tab에서 명시적 abort하면
RESUMABLE_UPLOAD_CANCEL_V1의 opaque upload-key-only BroadcastChannel 신호가 active read/fetch/backoff를 먼저 중단하고 Web Lock이 bounded 시간 안에 반환되는지 확인한다. - BroadcastChannel 미지원/정책 차단 환경에서는 abort caller deadline 안에
lock을 얻지 못하면 성공으로 가장하지 않고
ABORT_PENDING또는 safe non-terminal failure로 닫은 뒤 server reconcile을 재시도한다. - abort 결과가 불명확하면 checkpoint를 제거하지 않고
ABORT_PENDING으로 남긴다. - 다음 invocation에서 server status가
ABORTED/EXPIRED/NOT_FOUND임을 확인한 뒤 checkpoint를 제거한다. - abort endpoint의 HTTP 404/410은 terminal orphan으로 mapping하고 checkpoint를 제거한다.
- backend TTL cleanup은 비용과 보안 경계다. orphan upload count/bytes/age SLO를 운영한다.
- multipart complete는
QUARANTINED; scanner/CDR와 domain metadata commit이 끝나기 전 public delivery capability를 발급하지 않는다. - application-facing success/log에는 session ID, request binding 또는 file fingerprint를 노출하지 않는다.
5. Streaming download
- capability media/length/digest와 실제 response header/body count를 비교한다.
Content-Encoding이 capability byte semantics와 다른지 확인한다.- overrun/truncation/integrity failure면 reader와 destination writable을 abort한다.
- partial local destination을 성공으로 보고하지 않는다.
- same resource를 새 capability와 새 destination으로 restart한다.
- Range resume가 별도 승인되지 않았으면 기존 partial destination에 append하지 않는다.
- anchor/navigation 결과는 browser handoff일 뿐 saved/verified 증거가 아니다.
5.1 Range resumable download
이 절차는 RANGE_RESUMABLE_DOWNLOAD_V1 port/runtime, seekable destination 또는
owned OPFS staging과 provider contract가 구현·조합된 경우에만 적용한다.
- checkpoint의 protocol, opaque account partition, representation binding, total bytes, next offset, destination binding, revision과 retention을 검증한다. raw URL, signed header, validator, file path는 checkpoint나 incident ticket에 없어야 한다.
- destination의 실제 durable length가 checkpoint offset과 다르면 append하지
않는다. 더 긴 uncommitted tail은 정책과 destination capability가 허용할 때만
truncate()하고, 더 짧으면 마지막 confirmed segment로 rollback하거나 새 staging으로 재시작한다. - 새 capability가 같은 immutable generation/strong validator, total length,
media type와 whole-object digest에 묶였는지 확인한다. capability의
preconditionMode,allowWholeObjectFallback과 exactallowedStatuses도 확인하며 허용되지 않은 status에서는 body나 destination을 소비하지 않는다. - 허용된
206은 exactContent-Range: bytes S-E/T,S=requestedStart,E+1=requestedEndExclusive,Content-Length=E-S+1, identity encoding과 capability precondition binding이 모두 맞을 때만 segment를 쓴다. - 허용된 full
200은 response validator/generation evidence가 capability representation binding과 같을 때만 평가한다. mismatch면 body를 쓰지 않고 staging/checkpoint를 quarantine한 뒤 새 representation으로 restart한다. binding이 같아도 start가 0이고 whole-object fallback이 허용된 경우만 fresh destination에서 consume하며, 그 밖에는 body를 쓰지 않고 Range reissue, byte-0 restart, handoff 또는 unsupported로 닫는다. 일반If-Rangemismatch의 표준 응답은 full200이지412가 아니다. - 허용된
412는 별도If-Match또는 provider generation precondition을 쓴 계약에서만 representation replacement로 처리한다. - 허용된
416은 checkpoint 완료 증거가 아니다. body를 data로 소비하지 않고, final URL/header와 mode별 validator/generation binding을 먼저 검증한다. provider response만으로 binding을 증명할 수 없으면 BFF reconcile이 exact immutable generation을 증명해야 한다. 그 뒤 server total이 expected total과 같은지,nextOffset <= total인지, destination length와 offset이 일치하는지를 순서대로 reconcile한다. length mismatch는 먼저 truncate/verified rollback 또는 quarantine하고 같은 invocation에서 fall through하지 않는다.length == nextOffset == total과 처음부터 다시 계산한 whole-object SHA-256이 모두 일치할 때만 final commit 후보가 된다.length == nextOffset < total이면 exact missing range를 재발급하고 반복416은 contract mismatch로 닫는다. - capability expiry는 같은 representation binding으로 재발급한다. binding이 달라지면 기존 partial에 이어 쓰지 않는다.
- segment flush와 destination length 재확인 뒤에만 checkpoint CAS를 전진한다.
- final whole-object SHA-256과 destination close/commit 뒤에만
SAVED_VERIFIED로 기록한다.
malformed Content-Range, encoded representation drift, validator mismatch,
seek/truncate 실패 또는 crash 뒤 ambiguous tail에서는 신규 Range traffic을
중지하고 whole-object restart, browser-managed handoff 또는 명시적 unsupported로
degrade한다.
5.2 Background download와 browser-managed handoff
BROWSER_HANDOFF는 브라우저 download manager가 이후 작업을 소유한다는 outcome이며 application progress, retry, destination integrity를 증명하지 않는다.- page abort/reload 뒤 checkpoint가 남는 것은 app-managed background download 실행 증거가 아니다.
- app-managed background download를 선택한 제품만 별도 worker control plane, owned OPFS staging, runtime/version/logout fence와 foreground export 절차를 운영한다.
- worker가 종료되거나 capability가 만료되면 checkpoint로 foreground recovery를 시도하며 장시간 keepalive를 가정하지 않는다.
- 미지원 Safari/Firefox/WebView에서는 browser-managed handoff 또는 명시적 unsupported UX를 사용한다. unbounded Blob fallback은 금지한다.
6. Image CDN
- 신규 private descriptor 발급 또는 영향 preset을 kill switch로 중지한다.
- 안전한 placeholder/original-approved rendition으로 fallback한다.
- asset revision과 CDN cache key, format, width/height metadata를 확인한다. PNG/JPEG/WebP/AVIF header를 native decode 전에 파싱해 선언 dimensions, pixel/decoded-byte budget과 static-only 조건을 통과했는지 확인한다. versioned preset binding ID를 server registry에서 조회해 요청의 width/height/DPR/fit/format/quality 전체를 재계산하고 query mismatch를 거절하는지도 확인한다.
- 같은 immutable URL의 content가 변경됐다면 purge만으로 봉합하지 않고 새 asset revision을 발급한다.
- signed private URL이 노출됐으면 expiry를 기다리지 말고 backend asset/capability scope를 revoke한다.
- private descriptor가
PRIMARY_REQUIREDprobe를 사용하고 실제 response에서 exact URL, credential omission과Cache-Control: no-store를 확인했는지 검사한다. CDN origin이 application origin과 분리되어 실제<img crossorigin="anonymous">요청에도 application cookie가 실리지 않는지 배포 설정을 함께 확인한다. - fetch header/body/decode 전체 timeout과 abort cleanup을 확인한다. 늦게
resolve한 response body/
ImageBitmap이 즉시 cancel/close되는지 fault injection으로 재현한다. - source-fetch SSRF가 의심되면 arbitrary source URL transform을 차단하고 quarantine을 통과한 asset registry ID만 허용한다.
- dimensions/pixel/decoded-byte budget 위반은 CDN transform과 descriptor 양쪽을 중지한다.
- 정상 signing key 회전은 verifier와
acceptedKeyIds에 old/new key를 먼저 함께 배포하고 client 채택을 확인한 뒤 backend signer를 전환한다. 기존 capability lifetime, clock skew와 client rollout 기간이 모두 지난 후에만 old key를 제거한다. - key 유출, logout, tenant/account partition 변경 또는 feature teardown이면
backend capability를 revoke하고 기존 Image CDN runtime을
close()해 진행 중 verification/probe와 기존 reference를 폐기한 뒤 새 runtime을 조합한다. - descriptor HTTP provider를 설치했다면 response가
IMAGE_CDN_DESCRIPTOR_V1, fixed endpoint, strict body/content-type/deadline과 현재 runtime generation을 만족하는지 확인한다. - refresh는 asset/preset/scope/generation별 single-flight인지, 기존 descriptor expiry 뒤 stale-while-error를 허용하지 않는지 확인한다.
- logout/account switch 뒤 늦게 끝난 descriptor fetch/signature verification/decode가 새 runtime에 채택되지 않는지 확인한다.
- safe picture primitive가 verified presentation descriptor를 그대로 projection할 뿐 URL/query/transform을 재조립하지 않는지 확인한다.
7. Kill switch
- direct object-storage transfer off → same-origin BFF proxy 또는 기능 중지
- concurrent multipart → sequential part
- resumable off → 승인된 small-file simple upload 또는 upload 중지
- complete off → active session 유지/reconcile only
- upload pause/resume off → 신규 part 중지, explicit reconcile only
- streaming save off → authorized browser handoff
- Range resume off → whole-object restart 또는 authorized browser handoff
- app-managed background download off → browser-managed handoff 또는 foreground only
- app-managed background upload off → foreground checkpoint resume 또는 upload 중지
- Image descriptor refresh off → fresh descriptor expiry까지만 사용 후 placeholder
- private Image CDN off → authenticated same-origin placeholder
- advanced formats off → approved JPEG/PNG preset
- responsive candidates off → 한 개의 bounded fallback rendition
kill switch는 hard byte/pixel/security ceiling을 늘리지 않는다.
8. 복구 완료 조건
- capability expiry/replay/CORS 실패율이 baseline으로 복귀
- 신규·resume upload의 part checksum과 ordered complete 증적 통과
- orphan TTL cleanup과 quarantine backlog가 SLO 안으로 복귀
- download truncation/overrun/integrity fault injection 통과
- Range를 선택한 경우
200/206/412/416, capability reissue, destination seek/truncate/crash와 final whole-object integrity matrix 통과 - pause/inventory를 구현한 경우 abandoned checkpoint retention/reconcile drill 통과
- image preset/revision/cache/CSP matrix 통과
- Image descriptor provider를 설치한 경우 expiry/refresh/logout generation fence와 safe projection 통과
- image pre-decode static metadata, private no-store와 timeout cleanup fault 통과
- Chromium/Firefox/WebKit 동일 case set, zero failure/skipped
- raw URL/query/signed header/bearer token/capability가 log, telemetry, checkpoint에 없음을 확인
- checkpoint allowlist의 SHA-256 fingerprint/part checksum/opaque receipt가 account partition과 retention 안에만 있고 log/telemetry에는 없음을 확인
9. 제거
- 신규 capability/session/descriptor 발급 중지
- active upload complete 또는 explicit abort와 orphan cleanup
- non-secret checkpoint를 scope에 맞게 purge
- private capability revoke와 CDN grace window drain
- feature transfer/image facade와 composition 제거
- browser-transfer source와 전용 tests/catalog wiring 제거
- runtime removal gate, production module inventory, 전체 test/build 재검증