본문 바로가기
C.W.K.
Stream
Lesson 05 of 05 · published

다른 문 하나, 사용자 의도가 곧 권한이야

~12 min · identity-handoff, authority, design, appkit

Level 0릴 입문자
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Finder, Open With, drag/drop, NSOpenPanel 로 파일을 여는 건 별도의 standalone 파일-열기 경로야. macOS/사용자 의도가 권한자라서 파일 URL 을 실을 수 있어."

문 둘, 권한 둘

'경로는 절대' 를 네 레슨 내내 말한 뒤에, 그 규칙이 도그마가 안 되게 잡아주는 반전이 여기 있어. Ashen Reel 은 사실 경로를 늘 열어. 파일을 창에 드래그하거나, Open With 를 쓰거나, open panel 에서 고르면, 진짜 파일 URL 이 흘러들어와. 그리고 그건 아무 문제 없어. identity-only 규칙은 경로가 나빠서가 아니었거든. 누가 타겟을 고르느냐에 관한 거였지.

Recall 딥링크랑 사용자 파일-열기는, 권한 출처가 다른 두 개의 다른 문이야. 딥링크는 웹 페이지가 흉내낼 수 있는 안 신뢰된 호출자한테서 오니까, identity-only 랑 양쪽 검증을 받아. 파일-열기는 사용자 본인한테서, macOS 가 중개해서 와. 그러니 경로가 실릴 딱 맞는 자리야. 키보드 앞에 앉은 사람이 권한자고, 걔가 일부러 그 파일을 골랐으니까.

권한이 다르면 문도 달라. 그리고 하나를 다른 하나로 절대 흘려보내지 마. handoff 의 보안은 각 진입점을, 그걸 실제로 승인하는 것에 맞추는 데서 나왔어. 둘을 뭉치면 정상 사용을 불구로 만들거나 구멍을 다시 열어.

왜 코드에서 떨어뜨려 둬야 하나

실수 두 개를 상상해봐. 모든 Finder 열기를 identity-only 해석기로 흘려보내면, 정상 사용이 깨져. Desktop 에 있는 사용자 본인 MKV 는 video_id 가 없으니까, 완벽히 정상인 파일이 안 열리거든. 반대로 Recall 딥링크를 사용자-열기 경로로 흘려보내면, 트랙 통째로 없앤 그 경로 수용을 공격자한테 딱 넘긴 거고. 문은 물리적으로 떨어져 있어야 해. 각자 자기 신뢰 가정을 자기 함수에 실은 채로.

권한은 데이터 모양이 아니라 출처에 관한 거야. 사용자 open panel 의 파일 경로랑 적대적 URL 의 파일 경로는 문자열로는 똑같아. 다른 건 누가 골랐느냐고. 좋은 보안 설계는 값의 타입만이 아니라 출처를 추적해. 같은 경로가 한 문에선 신뢰되고 다른 문에선 금지되는 거지.

두 진입점을 나란히 놓고, 각각 신뢰가 어디서 오는지 정직하게 봐.

규칙, 일반화하면

이건 비디오 플레이어를 한참 넘어서 써먹을 패턴이야. 들어오는 길이 하나 넘는 앱은 다 권한이 하나 넘어. CLI 플래그랑 config 파일, API 요청이랑 어드민 콘솔, webhook 이랑 사람이 채운 폼. 이런 것들이 같은 신뢰를 받을 자격은 드물어. 규율은 각 진입점의 권한을 소리 내서 이름 붙이고, 그것에 맞춰 검증하는 거야. 사람한텐 너무 빡빡하고 낯선 이한텐 너무 헐거운 하나의 미지근한 정책으로 평균 내는 게 절대 아니라.

Code

두 진입점, 각각 자기 권한에 정직하게·swift
// DOOR 1: the Recall deep link. Caller is UNTRUSTED (a web page could build it).
// Identity only -> strict parse -> resolve id in Recall -> re-validate -> open.
func handleRecallURL(_ url: URL) {
    guard case .ok(let req) = parseRecallOpen(url) else { return }   // strict
    resolveAndValidate(req) { validatedFile in open(validatedFile) }  // both sides
}

// DOOR 2: a file the USER explicitly chose. Authority = user intent + macOS.
// A real file URL is fine here, because a person -- not a page -- picked it.
func openUserSelectedFile(_ fileURL: URL) {
    guard fileURL.isFileURL,
          (try? fileURL.resourceValues(forKeys: [.isReadableKey]))?.isReadable == true
    else { return }
    open(fileURL)   // no video_id needed; the user IS the authority
}

// The two NEVER merge. A Recall URL never reaches Door 2's raw-path open;
// a Finder file never reaches Door 1's identity resolver. One app, two doors.

External links

Exercise

네가 만든 시스템으로 입력이 들어오는 모든 길을 나열해. 뻔한 것만 말고 전부. 각각에 권한을 적어. 누가, 뭐가 이 입력을 믿을 만하게 만들어? 지금 똑같이 검증하는 두 진입점을 찾아서, 실제로 같은 신뢰를 받을 자격이 있는지 봐. 하나는 사람 선택이고 다른 하나는 같은 데이터 모양을 뒤집어쓴 네트워크 낯선 이 아냐?
Hint
신호는, 권한은 다른데 검증 정책은 하나로 공유하는 두 진입점이야. 사용자 본인 open panel 의 경로랑 원격 요청의 경로는 같은 타입이지 같은 신뢰가 아니야. 정책을 쪼개. 낯선 이한텐 엄격하게, 사용자한텐 관대하게.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.