터미널에서 명령을 입력했다.

글자가 화면에 보인다. Enter도 눌렀다. 심지어 커서까지 다음 줄로 내려갔다.

그러면 우리는 거의 자동으로 결론을 낸다.

실행됐다.

그런데 접근 제어와 명령 차단이 얽힌 터미널에서는 이 결론이 꽤 위험하다. 화면은 사용자가 입력한 것을 보여줄 뿐, 서버가 실행한 것을 증명하지는 않기 때문이다.

이 작은 차이를 깨닫고 나면, 터미널 관련 버그를 보는 눈이 바뀐다.

화면은 영수증이 아니라 안내판이다

웹 터미널은 생각보다 긴 길이다.

키보드
→ 브라우저
→ 웹소켓
→ 터미널 중계기
→ 접근 정책
→ SSH
→ 실제 서버

이 길 어디에서든 일이 달라질 수 있다.

  • 한글 입력이 여러 바이트로 쪼개질 수 있다.
  • Backspace가 환경마다 다른 값으로 들어올 수 있다.
  • 화면에는 명령이 남았지만 정책 엔진이 실행 직전에 막을 수 있다.
  • 반대로 정책이 예상과 다르게 해석되어 실제 서버까지 전달될 수도 있다.

그래서 화면에 rm ...이나 touch ...가 보였다는 사실은 “사용자가 이렇게 입력하려 했다”는 증거에 가깝다. 실행 영수증은 아니다.

공항 전광판에 내 이름이 떴다고 비행기에 탄 것은 아닌 것과 비슷하다. 탑승권을 찍고, 게이트를 통과하고, 좌석에 앉아야 진짜 탑승이다.

터미널 명령도 마지막 서버까지 갔는지 확인해야 한다.

좋은 재현은 영수증을 세 장 모은다

명령 차단 문제를 재현할 때 나는 세 장의 영수증을 모으는 방식이 가장 믿을 만하다고 생각한다.

1. 입력 영수증: 무엇을 보냈는가

먼저 입력을 사람이 읽는 문자열 하나로 뭉개지 않는다.

특히 한글과 편집 키가 섞이면 더 그렇다. 예를 들어 한글 한 글자를 입력한 뒤 Delete를 누르는 과정은 개념적으로 이렇게 기록할 수 있다.

UTF-8 한글 바이트
→ Delete: 0x7f
→ 검사할 명령
→ Enter

일부 환경에서는 Backspace가 0x08으로 들어온다. 둘은 사람에게는 같은 “지우기”지만, 중간 프로그램에는 다른 신호다.

이 차이를 기록하지 않으면 나중에 “한글을 지웠다”는 모호한 문장만 남는다. 재현은 모호함을 싫어한다.

2. 발자국 영수증: 실제 서버에 무슨 일이 있었는가

다음은 실제 서버의 작은 발자국이다.

위험한 명령으로 확인할 필요는 없다. 실험 전용 경로에 아주 무해한 marker 파일을 남기면 된다.

printf permitted > /tmp/lab-marker

이 명령이 허용되는 control이라고 해보자. 실험 뒤 실제 서버의 /tmp/lab-marker가 존재하면, 입력이 중계기를 지나 서버까지 도착했다는 뜻이다.

반대로 차단해야 하는 명령에는 다른 marker를 쓴다.

touch /tmp/blocked-marker

화면에 명령이 보여도 marker가 없다면, 적어도 서버에서 그 side effect는 일어나지 않았다.

이렇게 하면 “보였다”와 “실행됐다”를 깔끔하게 분리할 수 있다.

3. 정책 영수증: 왜 막혔는가

마지막 영수증은 감사 기록이다.

차단 정책이 정상 동작했다면 단순히 marker가 없는 것만으로는 조금 아쉽다. 서버가 잠시 느렸거나 세션이 끊겼어도 marker는 없을 수 있기 때문이다.

그래서 다음 두 문장을 같이 얻어야 한다.

서버 side effect 없음
+ 정책 엔진이 restricted command로 판정함

이 둘이 만나면 이야기가 선명해진다.

명령은 서버에 못 갔고, 단순한 통신 실패가 아니라 정책이 의도대로 차단했다.

이것이 좋은 증거의 힘이다. 하나의 로그를 크게 믿는 대신, 서로 다른 층의 작은 증거를 교차시킨다.

허용 명령을 꼭 넣어야 하는 이유

여기서 자주 빠지는 함정이 하나 있다.

차단 명령의 marker가 없다고 해서 바로 “정책이 잘 막았다”고 말하는 것이다.

그날 실험실 전체가 고장 났다면 어떨까? SSH 접속이 실패했거나, 권한이 잘못 연결됐거나, 입력이 서버까지 가지 못했을 수도 있다. 이 경우에도 차단 marker는 없다.

그래서 같은 실험 안에 허용 control을 반드시 둔다.

허용 marker: 존재해야 한다
차단 marker: 없어야 한다
감사 기록: 차단으로 남아야 한다

세 조건이 함께 맞아야 비로소 “차단이 정상 동작했다”고 말할 수 있다.

과학 실험에서 대조군을 두는 이유와 같다. 실패만 보면 실패의 원인을 알 수 없다. 성공해야 할 것이 성공하는지 먼저 확인해야 한다.

재현 도구가 지켜야 할 예절

터미널 문제를 재현하는 도구는 특히 조심해야 한다. 사람의 권한과 서버에 손을 대기 때문이다.

그래서 좋은 실험 도구에는 몇 가지 예절이 필요하다.

  • 외부 서버 대신 일회용 로컬 컨테이너를 쓴다.
  • 실험이 만든 사용자, 역할, 정책, 서버 등록만 지운다.
  • 기존 권한이나 운영 설정은 건드리지 않는다.
  • 실패해도 무엇을 만들었는지 기록해 cleanup할 수 있게 한다.
  • 비밀번호, token, 사용자 식별자는 결과물에 남기지 않는다.

나는 이 마지막 단계를 꽤 중요하게 본다.

재현 도구는 문제를 잡으러 갔다가 새로운 문제를 남기면 안 된다. 좋은 실험은 끝난 뒤 실험실이 원래 모습으로 돌아온다.

"재현되지 않았다"도 꽤 좋은 결론이다

모든 버그가 로컬에서 다시 나타나는 것은 아니다.

브라우저 버전, 입력기, 운영체제, 네트워크 보안 도구처럼 실험실 밖의 조건이 영향을 줄 수 있다. 그럴 때 “재현 실패”를 부끄러운 결과로 취급하면, 팀은 자꾸 추측으로 넘어간다.

더 좋은 문장은 이것이다.

이 환경과 이 입력 경로에서는 우회가 재현되지 않았다. 다만 다른 환경 조건은 아직 별도로 확인해야 한다.

이 문장에는 두 가지가 함께 들어 있다.

  • 확인한 사실은 분명히 말한다.
  • 아직 확인하지 않은 범위도 숨기지 않는다.

엔지니어링에서 신뢰는 자신감의 크기보다 경계선의 정확도에서 나온다.

터미널에 보인 한 줄을 보고 “실행됐겠지”라고 말하는 대신, 입력·발자국·정책이라는 세 장의 영수증을 모아보자.

그러면 버그 재현은 조금 느려질지 몰라도, 결론은 훨씬 단단해진다.