빌드

블랙박스 — 영상은 한 프레임도 건드리지 않고

작성: makemind · 2026년 4월 9일

블랙박스는 차 안에서 가장 민감한 것을 들고 있다. 그런데 영상 말고 나머지는 전부 평범한 운영 데이터다. 오늘 몇 번 운행했는지, 카드가 얼마나 남았는지, 충격 감지가 너무 예민해서 방지턱마다 걸리는지. 차량 관리자가 매일 봐야 하는 것들이다.

이걸 앱으로 만드는 흔한 방법은 하나다. 전부 주는 API를 만들고, 영상 엔드포인트 앞에만 권한 검사를 세운다.

그 검사는 누가 지우면 사라진다. 급한 배포에서, 리팩토링 중에, 테스트를 통과시키려고. 지워도 나머지는 다 잘 돌기 때문에 아무도 모른다.

도구 목록이 표면이다

이 샘플의 블랙박스가 제공하는 것 전부다.

the dashcam offers: trips.list, camera.settings, camera.set_sensitivity
  trips.list — Trips recorded today, with duration, distance and impact counts
  camera.settings — Read the impact sensitivity and the card space
  camera.set_sensitivity — Set impact sensitivity, 1 (least) to 5 (most)

셋이다. 영상을 반환하는 도구가 잠겨 있는 게 아니라 없다.

권한 검사와 이건 성질이 다르다. 검사는 코드고, 코드는 지울 수 있다. 없는 도구는 호출할 대상 자체가 없다. 프런트가 아무리 잘못 짜여도, 토큰이 아무리 새어도, 목록에 없는 것은 호출되지 않는다.

클립은 손잡이만 오간다

그럼 영상은 어떻게 되나. 운행 줄마다 핸들이 하나씩 붙는다.

clips referenced: clip:8841, clip:8842, clip:8843, clip:8844
TODAY · 4 TRIPS · 124.4 km — 운행 4건, 2건에 충격 표시, 각 줄에 clip 핸들
TODAY · 4 TRIPS · 124.4 km — 운행 4건, 2건에 충격 표시, 각 줄에 clip 핸들

clip:8842 는 파일 이름이 아니라 불투명한 손잡이다. 이 서버는 그 손잡이로 아무것도 못 연다. 차 앞에 서서 볼 권한이 있는 사람이 그 클립을 찾을 때 쓰는 것이고, 동시에 이 서버가 영상을 읽은 적 없다는 증거다.

핸들이 오갔다는 것과 내용이 오갔다는 것은 전혀 다른 사건이다. 로그를 보면 그게 구분된다 — 그러라고 핸들을 남긴다.

영상은 문장으로도 샌다

여기서 한 번 더 나간다. 영상은 파일로만 새는 게 아니다.

"카메라가 본 것의 요약"은 문장으로 세탁된 영상이다. 번호판을 읽어서 텍스트로 주면 그건 영상을 안 준 게 아니다. 보행자가 몇 명 지나갔다고 세어 주면 그것도 마찬가지다.

그래서 서버가 자기 출력을 스스로 막는다.

/// Words this server must never emit. Footage leaks by being described as
/// much as by being served: a "summary of what the camera saw" is the video,
/// laundered through a sentence.
static const forbidden = [
  'plate', 'licence', 'license', 'face', 'pedestrian', 'frame',
  'thumbnail', 'preview', 'snapshot', 'image', 'footage of',
];

이 목록에 걸리면 응답을 만들다 던진다. 조용히 지우지 않는다 — 지우면 다음에 또 넣는다.

검증이 서버가 뱉은 모든 문자열을 훑는다.

no footage-describing vocabulary in anything the server returned

카메라가 자기를 자른다

감도는 1에서 5다. 화면 버튼 하나는 일부러 9를 요청한다.

{ "type": "button", "label": "More sensitive",
  "onTap": { "type": "tool", "tool": "camera.set_sensitivity",
             "params": { "level": 9 } } }
asked for level 9 -> 5 of 5 ("sensitivity now 5")
impact sensitivity 5 of 5 · sensitivity now 5
impact sensitivity 5 of 5 · sensitivity now 5

카메라가 잘랐다.

// The camera clamps to its own range. A fleet console asking for 9
// gets 5, and is told so.
if (v < 1) v = 1;
if (v > 5) v = 5;

화면 쪽에서 min/max 를 걸 수도 있었다. 그건 예의지 안전이 아니다. 화면은 JSON이고, JSON은 배포마다 바뀌고, 바뀐 화면이 잘못된 값을 보내는 날이 온다. 마지막에 붙잡는 것은 언제나 얼어 있는 쪽이어야 한다.

그리고 잘랐다고 말해 준다. 조용히 5로 만들면 관리자는 9로 설정된 줄 알고 산다.

이 콘텐츠는 개발자 이상이 필요합니다

로그인 후 플랜을 업그레이드하면 계속 읽을 수 있습니다.

플랜 보기
Twitter