사이버 공격에 악용된 AI "ARTEX" 조사 후, 파이썬 독학 반년 차가 직접 만든 Flask 앱의 안전성을 검증해 봤습니다
요약
AI 기반 침투 테스트 도구 'ARTEX'가 사이버 공격에 악용된 뉴스를 계기로, 필자는 직접 개발한 Flask 웹 앱의 안전성을 재검토했습니다. 단순 기능 테스트를 넘어 보안 취약점을 집중적으로 조사하며 개선 작업을 진행했음을 공유합니다.
핵심 포인트
- AI 도구 ARTEX가 사이버 공격에 악용될 수 있음을 인지함.
- 개발한 웹 앱의 안전성 검증을 위해 보안 테스트를 수행함.
- 기능적 테스트 외 예상치 못한 취약점 발견 및 개선 과정을 기록함.
- AI 에이전트가 대량/단시간 공격 시도를 자동화할 수 있다는 점에 경고.
최근 기업을 대상으로 한 사이버 공격이나 정보 유출 뉴스를 접할 기회가 늘었습니다.
시스템 중단, 정보 유출, 복구에 걸리는 시간까지.
뉴스를 볼 때마다 여러 가지 의문이 떠오릅니다.
- 애초에 정보 유출이란 어디부터가 정보 유출인 것일까요?
- 취약성이란 구체적으로 무엇을 의미할까요?
- 오랫동안 운영된 시스템인데도 왜 문제가 발생하는 걸까요?
- 최근에는 AI까지 사이버 공격에 사용되는 건가요?
그러던 중, ARTEX라는 AI를 이용한 도구의 존재를 알게 되었습니다.
보안을 조사하는 데에도 사용할 수 있는 기술이 실제 사이버 공격에 악용되었다는 뉴스였습니다.
저는 평소 물류 운전사로 일하고 있습니다.
2026년 5월 12일부터 파이썬(Python)을 중심으로 프로그래밍을 독학하며, AI와 대화하면서 웹 앱을 개발해 왔습니다.
그중 하나가 빵집의 매장 운영을 지원하는 Bakery Hub입니다.
처음에는 매출을 기록하는 작은 앱이었습니다.
그 후 상품 관리, 매출 분석, 재료 준비(仕込み) 관리, 계산대 회계 등 기능을 조금씩 늘려왔습니다.
테스트도 작성하고, 문제가 발견되면 수정하면서 개발했습니다.
하지만 ARTEX에 대해 조사하던 중 문득 생각했습니다.
AI의 힘을 빌려 여기까지 만들었지만, 제 웹 앱의 안전성은 어디까지 확인된 걸까요?
지금까지는 테스트를 많이 작성해 왔습니다.
다만 그것은 제가 생각해낸 조건에서의 테스트입니다.
애초에 제가 떠올리지 못한 문제가 남아있을지도 모릅니다.
그래서 새로운 기능 개발을 잠시 멈추고, AI와 함께 Bakery Hub의 안전성을 조사해 보기로 했습니다.
그러자 실제로 재검토해야 할 문제들이 발견되었습니다.
이번에는 발견한 문제뿐만 아니라, 그때 무엇을 생각했고, 왜 그 방법을 선택했으며, 어디까지 확인했는지를 기록해 보려고 합니다.
📌 이 글을 3줄로 요약하자면
- AI "ARTEX"가 사이버 공격에 악용된 뉴스를 계기로, 직접 만든 Flask 앱의 안전성을 조사해 봤습니다.
- 관리자의 로그아웃이나 오프라인 회계 저장 등에 대해 실제로 문제를 발견하고 개선을 진행했습니다.
- 테스트가 성공했더라도 확인하지 못한 것이 남아있다면, 본 공개를 서두르지 않겠다는 판단을 내렸습니다.
이 글은 2026년 10월 30일까지의 개발 및 검증 기록을 바탕으로 합니다.
Bakery Hub가 ARTEX에 의해 공격받은 것은 아닙니다. 또한, ARTEX 자체를 사용해서 검증한 것도 아닙니다.
이번 계기가 된 뉴스부터 소개합니다.
2026년 10월 21일, Microsoft는 AI를 이용한 침투 테스트(Penetration Test) 프레임워크인 "ARTEX"가 사이버 공격에 악용되었다고 보고했습니다.
Microsoft가 "Storm-0940"이라고 부르는 공격자가 ARTEX를 사용해 전 세계 수백 개의 조직을 표적으로 한 공격을 자동화하고 있었다고 합니다.
보고에 따르면, 일부 공격에서는 목표물을 겨냥하기 시작한 지 3시간 이내에 클라우드 환경의 높은 권한을 획득한 사례도 있었다고 합니다.
조사해 보니 ARTEX는 오픈 소스 침투 테스트 프레임워크였습니다.
여기서 또 모르는 단어가 나왔습니다.
침투 테스트(Penetration Test).
어떻게 보면, 허가된 시스템에 실제로 침입할 수 있는 약점이 없는지 확인하는 보안 검증을 말한다고 합니다.
ARTEX는 여러 AI 에이전트(AI Agent)를 사용해 다양한 작업을 자동화할 수 있는 구조입니다.
AI가 상황을 판단하면서 다음 작업을 진행할 수 있습니다.
편리한 기술이라고 생각합니다.
하지만 그 편리함을 공격자도 이용하고 있었습니다.
제가 특히 놀랐던 것은, AI에 의해 공격 시도를 대량으로, 그리고 단시간에 진행할 수 있다는 점이었습니다.
사람이 하나하나 조작하던 것을 자동으로 진행할 수 있습니다.
지키는 입장에서도 생각하게 만드는 이야기입니다.
JPCERT/CC도 2026년 10월 23일에 국내 조직 대상의 부정 접속에 관한 주의 환기를 업데이트했습니다.
그중에는 AI를 이용한 침투 테스트 기반이 악용된 공격 시도에 대한 기재가 있었습니다.
또한, 웹 애플리케이션의 알려진 취약점을 찾거나 설정 파일 및 백업 파일을 노리는 수법도 소개되어 있습니다.
다만, 모든 사건에서 ARTEX가 사용되었다는 것은 아닙니다.
뉴스를 읽고 있어도, 확인된 사실과 아직 알지 못하는 것을 구분해야 한다는 것을 깨달았습니다.
저는 평소에도 AI의 도움을 받으면서 개발하고 있습니다.
설계를 상담하고, 코드를 작성하며, 테스트를 만들고, 문제가 있으면 수정하는 과정이었습니다.
AI가 없었다면 지금과 같은 속도로 개발하기 어려웠을 것이라고 생각합니다.
그렇기에 이번 뉴스에는 많은 생각을 하게 만드는 부분이 있었습니다.
같은 기술이라도, 지키기 위해 사용할 수도 있지만 공격에 악용될 수도 있다는 것입니다.
그렇다면 저 자신도 AI의 힘을 빌려 자신이 만든 앱의 안전성을 점검해 봐야겠다 싶었습니다.
그런 생각에서 이번 검증을 시작하게 되었습니다.
잠시 프로그래밍 이야기에서 벗어나겠습니다.
저는 트럭 운전사로 8년 동안 일했습니다.
오랫동안 운전을 하면서 느끼는 것은, 경험이 쌓였다고 해서 반드시 안전해지는 것은 아니라는 것입니다.
예를 들어,
- 사이드 미러를 확인했다고 생각했지만 실제로는 그렇지 않은 경우
- 정지 신호를 했다고 착각하는 경우
- 충분히 감속했다고 생각하는 경우
- 늘 다니던 길이라 괜찮겠지라고 안일하게 생각하는 경우
이런 착각은 누구에게나 일어날 수 있습니다.
물론 저 자신에게도 간과한 부분이 있습니다.
그렇기 때문에 평소부터 방어 운전이나 안전 확인을 의식하고 있습니다.
프로그래밍을 배우면서 이 생각이 개발에도 통용될 것이라고 느꼈습니다.
| 트럭 운전 | 웹 앱 개발 |
|---|---|
| 미러에서 보이지 않는 사각지대 | 아직 검증하지 않은 처리 부분 |
| ... | |
| 물론, 교통사고와 사이버 공격은 같은 것은 아닙니다. |
하지만,
자신에게도 눈에 띄지 않는 문제가 있을 수 있다라는 생각은 공통하는 부분이 있는 것 같습니다.
이번에 뉴스를 찾아보는 과정에서 '취약점(Vulnerability)'이라는 단어도 다시 한번 배웠습니다.
제가 이해하기로는, 보안상의 약점에 의해 원래 허용되지 않은 조작 등이 가능해지는 문제라고 합니다.
다만, 취약점이 발견되었다고 해서 실제로 공격당했다는 의미는 아닙니다.
이 차이점도 이번에 제대로 인지하고 싶었습니다.
처음에는 보안이라고 하면 정보 유출을 막는 것이라고 생각했습니다.
하지만 조사해 보니 그것만이 아니었습니다.
| 개념 | 의미 | Bakery Hub에서 고려한 점 |
|---|---|---|
| 기밀성 (Confidentiality) | 허가된 사람만 정보를 볼 수 있음 | 다른 매장의 매출을 함부로 열람할 수 없음 |
| ... | ||
| 정보 보안의 기본이 되는 'CIA 3요소'라는 개념입니다. |
영어의 첫 글자를 따온 것이라고 하는데, 저는 이 표가 더 이해하기 쉬웠습니다 (웃음).
Bakery Hub에서 지키고 싶은 것은 개인 정보만이 아닙니다.
점주님이 매일 기록하는 상품이나 매출도 소중한 데이터입니다.
이것이 나중에 오프라인 레지(POS) 검증으로 이어졌습니다.
Bakery Hub는 Python과 Flask를 중심으로 개발하고 있는 점포 운영 지원 웹 애플리케이션입니다.
주요 기술 구성은 다음과 같습니다:
| 분류 | 사용 기술 |
|---|---|
| 백엔드 | Python / Flask |
| ... | |
| 처음에는 매출을 기록하는 기능부터 시작했습니다. | |
| 여기에 상품 관리나 재료 준비(仕込み) 관리를 추가했고, 현재는 레지 계산까지 처리하게 되었습니다. |
기능이 늘어나는 것은 즐겁습니다.
다만, 늘어난 만큼 확인해야 할 것도 많아졌습니다.
이번에 AI와 상담하며 우선적으로 조사한 부분은 다음과 같습니다:
- 관리자 로그인 및 로그아웃 기능
- 인증의 유효 기간 설정
- 점포별 데이터 분리
- 오프라인 상태에서의 회계 저장
- 통신 복구 후 동기화 과정
- 오류 발생 시 데이터 보호 방안
그중에서도 신경 쓰였던 부분은, 누가 조작하고 있는지 정확히 확인할 수 있는 것과 매출 기록을 잃지 않는 것이었습니다.
관리자가 아닌 사람이 함부로 조작할 수 있는 것은 곤란합니다.
반면, 인증을 너무 엄격하게 하면 회계 데이터가 사라져도 곤란합니다.
이 두 가지를 의식하며 조사하기로 했습니다.
검증에는 가상의 상품이나 회계 데이터를 사용했습니다.
실제 점포 데이터를 사용하지 않고 격리된 환경에서 시도합니다.
운영 중인 데이터를 지키기 위해 검증하는 것인데, 그 검증 과정에서 매출 데이터가 망가지면 의미가 없습니다.
이 판단은 기술적인 지식이라기보다는, 평소 업무에서 중요하게 생각하는 안전 확인과 비슷한 감각이었을지도 모릅니다.
⚠️ 이번 검증에 대하여
본 기사는 자신이 관리하는 Bakery Hub를 격리된 환경에서 검증한 기록입니다.
ARTEX를 사용해 공격을 재현한 것은 아닙니다.
또한, Bakery Hub가 실제로 사이버 공격을 받은 기록도 아닙니다.
실제 인증 정보나 비밀값, 악용으로 이어질 구체적인 재현 절차는 게재하지 않았습니다.
이번에 가장 먼저 큰 발견이 있었던 부분은 관리자의 세션 관리였습니다.
저는 로그아웃 기능을 상당히 단순하게 생각하고 있었습니다.
로그아웃 버튼을 누르고, 로그인 화면으로 돌아갑니다.
그러면 이전의 로그인 정보는 사용할 수 없게 됩니다.
하지만 실제로 조사해 보니, 반드시 그런 것은 아니었습니다.
웹 애플리케이션에서는 로그인한 사용자를 식별하기 위해 '세션(Session)'이라는 메커니즘이 사용됩니다. Flask의 표준 세션은 서명된 쿠키(signed Cookie)를 이용합니다.
서명을 통해 쿠키 내용이 임의로 수정되지 않았는지 확인할 수 있습니다.
다만, 그것만으로는 현재 로그인 상태가 계속 허용되어야 하는지까지는 알 수 없습니다.
브라우저에서 로그아웃하더라도, 이전 인증 정보가 다른 곳에 남아 있을 가능성이 있습니다. 서버 측에서 해당 정보를 무효화(invalidate)하는 메커니즘이 없다면, 오래된 인증을 받아들일 수 있는 경우가 생기는 것입니다.
이번 Bakery Hub에서도 관리자 세션의 무효화 관리에 문제가 발견되었습니다.
화면상으로는 로그아웃된 것처럼 보여도, 그것만으로는 불충분했던 것입니다.
AI와 코드를 조사하면서, 로그인 상태를 서버 측에서도 관리하는 방법을 확인했습니다. 이렇게 하면 이전 로그인 정보가 남아 있더라도, 서버 측에서 이미 무효화되었다고 판단할 수 있습니다.
이번 수정에서는 관리자가 로그인한 기록을 DB에 보관하고, 인증 시 그 상태를 확인하는 방식을 채택했습니다. 실제 수정 코드의 일부를 소개합니다.
def active_admin_session(token):
digest = token_hash(token)
if digest is None:
...
※가독성을 위해 줄 바꿈을 조정한, 실제 코드 발췌본입니다.
처음 봤을 때는 바로 이해할 수 없었습니다. 하지만 특히 중요한 부분은 조금씩 파악할 수 있었습니다.
먼저,
digest = token_hash(token)
여기서는 인증에 사용되는 값을 해시화하여, 대조하는 데 사용할 값을 만들고 있습니다. 인증용 값 자체를 DB에 저장하는 것이 아니라, 해시화된 값을 저장하는 방식입니다.
그리고 이번에 특히 중요했던 조건이 바로 이것입니다.
AdminAuthSession.revoked_at.is_(None)
revoked_at은 세션이 무효화된 날짜와 시간을 기록하는 항목입니다. None은 여기서는 무효화 일시가 기록되지 않은 상태를 의미합니다.
즉, 무효화된 세션을 인정하지 않기 위한 조건인 것입니다.
게다가,
AdminAuthSession.expires_at > now
이 조건을 통해 유효 기간이 지나지 않았는지도 확인하고 있습니다.
이렇게 하여, 화면상의 로그인 상태뿐만 아니라 서버 측의 기록까지 확인하도록 했습니다.
여기서 또 하나 고려할 점이 있었습니다. 서버 측에서 인증을 관리한다는 것은 DB에 문제가 생겼을 때의 동작도 생각해야 한다는 것입니다. 예를 들어, 인증을 확인하려고 할 때 DB 연결에 실패하는 경우입니다.
그때, 확인할 수 없다는 이유로 작업을 허용해 버리면, 무효화된 인증까지 받아들일 가능성이 있습니다.
그래서 이번에는 인증 상태를 확인할 수 없는 경우에는 관리자 작업을 허용하지 않는다는 방침을 세웠습니다.
다만, 이는 사용자에게 불편할 가능성도 있습니다. DB에 문제가 생기면, 정상적으로 로그인했던 사람조차 일시적으로 작업을 할 수 없게 될 수도 있습니다.
그럼에도 불구하고, 관리자로서 작업해도 되는지 확인할 수 없는 상태에서 처리를 진행하는 것보다 안전하다고 판단했습니다.
이 판단이 옳은지는 서비스의 용도에 따라 달라질 것이라고 생각합니다. 적어도 이번 Bakery Hub에서는 그렇게 생각했습니다.
수정 후에는 정상적으로 로그인할 수 있는지뿐만 아니라, 다음과 같은 조건들도 확인했습니다.
| 확인한 것 | 기대하는 동작 |
|---|---|
| 정상적인 로그인 | 관리자로서 작업 가능 |
| ... |
수정 내용과 테스트는 GitHub의 PR #150에 기록되어 있습니다. 이 PR은 main 브랜치에 병합되었습니다.
여기서 배운 것은, 로그아웃 버튼이 정상적으로 작동하는 것과 오래된 인증 정보가 확실하게 거부되는 것은 별개의 확인 사항이라는 점입니다.
트럭 운전으로 비유하자면, 사이드 미러를 본 것과 안전을 확인할 수 있는 것이 반드시 같지는 않은데 비슷하다고 느꼈습니다.
이로 인해 더 많은 것을 생각하게 되었습니다. Bakery Hub에는 오프라인 레지(POS) 기능이 있습니다. 통신할 수 없을 때도 사전에 준비한 조건 범위 내에서 회계 처리를 단말기에 기록하고, 통신 복구 후에 서버와 동기화하는 메커니즘입니다.
아직 제약이 있는 기능이지만, 레지(POS) 개발에서는 중요하게 다루고 싶은 부분입니다.
왜냐하면 인터넷에 연결할 수 없는 상황에서도 고객과의 정산(회계)이 발생할 가능성이 있기 때문입니다.
처음에는 로그인 기한이 만료되면 다시 로그인만 하면 된다고 생각했습니다.
하지만 오프라인 레지 시스템을 고려했을 때, 그것만으로는 해결되지 않습니다.
단말기에는 아직 서버로 전송되지 않은 정산 기록이 남아있을 수 있습니다.
그런 상태에서 로그인 기한이 만료된다면요.
보안을 생각하면, 만료된 인증 정보로는 서버에 데이터를 전송하는 것을 허용하고 싶지 않습니다.
하지만 그렇다고 해서 단말기에 남아있는 정산 기록까지 지워버리면 곤란합니다.
인증을 보호하기 위해 가게 사장님의 매출(판매 데이터)을 잃어버린다면 의미가 없습니다.
여기서, 인증과 데이터 저장(보존)을 분리해서 생각할 필요가 있다는 것을 깨달았습니다.
① 서버로 전송하는 것을 허용할 것인가?
② 단말기에 남아있는 정산 기록을 어떻게 보호할 것인가?
이 두 가지는 비슷해 보이지만 다른 문제입니다. 인증이 만료되었으니, 서버로의 전송은 거부합니다.
하지만 원래의 정산 기록은 유지합니다.
재로그인이나 필요한 확인 과정을 거쳐 안전하게 동기화(Sync)할 수 있는 상태가 되었을 때 전송하는 것입니다.
이번 설계에서는 그 사고방식을 중요하게 다루었습니다.
정산 기록 저장
|
v
...
※실제 처리를 이해하기 쉽도록 간략화한 그림입니다.
이렇게 정리해 보니, 단순히 에러 처리만 늘리는 문제가 아니었습니다.
무엇을 허용하고, 무엇을 남길 것인가.
그것을 결정할 필요가 있었습니다.
예를 들어, JavaScript로 다음과 같은 판단을 한다고 가정해 보겠습니다.
// 사고방식을 보여주기 위한 간략 예시
if (!authReady) {
keepLocalReceipt();
...
authReady가 false라면, 동기화를 진행하지 않고 단말기의 정산 기록을 보존한다는 생각입니다.
하지만 이것만으로는 충분하지 않습니다. 브라우저 측의 처리는 서버로 전송되는 요청의 안전성까지 보장해주지 못하기 때문입니다.
서버 측에서도 인증이나 단말기 권한을 확인해야 합니다.
이 부분은 이번에 특히 이해가 깊어진 부분이었습니다.
또 하나, 깨달은 것이 있습니다. 단말기 내 기록을 삭제하지 않는 설계라고 해도, 그것만으로는 정산 데이터가 절대 사라지지 않는다고 장담할 수 없습니다.
단말기 자체가 고장 날 수도 있습니다.
브라우저의 저장 데이터가 삭제되는 상황도 생각할 수 있습니다.
즉, 임의로 삭제하지 않도록 시스템을 만드는 것과, 어떤 상황에서도 데이터를 잃지 않는 것은 별개인 것입니다.
이번에는 우선 인증 에러 등을 이유로 원래의 정산 기록을 삭제하지 않는 것을 최우선으로 했습니다. 그럼에도 불구하고, 데이터 보호에 대해 생각해야 할 부분은 남아있습니다.
📌 이번 설계 판단 사항
- 인증이 무효화되면 서버 전송을 허용하지 않는다.
- 전송할 수 없더라도 단말기 내의 원래 정산 기록을 임의로 삭제하지 않는다.
- 재로그인 후에도 필요한 확인 과정을 거쳐서 동기화한다.
- 단말기 내부 저장만으로는 모든 데이터 손실을 막을 수 있다고 생각할 수 없다.
'전송을 허용하지 않음'과 '정산 기록을 삭제함'은 별개의 판단이다.
이것이 이번에 제가 특히 중요하게 다룬 사고방식입니다.
다음으로 생각해 본 것은, 통신 에러가 발생했을 때의 정산 데이터입니다.
예를 들어, 300엔짜리 상품을 판매했다고 가정해 보겠습니다. 이 정산을 서버로 전송했습니다.
그런데 도중에 통신이 끊겨버렸습니다.
앱에는 성공 응답이 도착하지 않습니다.
여기서 저는, 통신 에러에도 여러 가지 상황이 있다는 것을 알게 되었습니다.
예를 들어,
| 상황 | 서버 측에서 발생했을 가능성 |
|---|---|
| 전송 전에 통신이 끊김 | 정산 기록이 도착하지 않음 |
| ... | |
| 화면에서는 비슷한 에러로 보일지라도, 실제 저장 상태는 다를 수 있습니다. |
이 차이를 생각하지 않고, 새로운 정산으로 다시 전송한다면 어떻게 될까요?
300엔의 매출이 600엔이 되어버릴 가능성이 있습니다.
Bakery Hub에서는 정산마다 식별용 키(Key)를 부여하고 있습니다. 사고방식을 간단한 Python 딕셔너리로 표현하면 다음과 같습니다.
# 설명용 간략 예시
receipt = {
"submission_key": "원래의 정산을 식별하는 키",
...
submission_key는 해당 정산을 식별하기 위한 값입니다.
통신 에러가 발생해도, 원래의 키를 임의로 변경하지 않습니다. 같은 정산이라면, 같은 식별 정보를 사용합니다.
그리고 서버 측에서도 중복을 막고, 원래의 정산과 내용이 일치하는지 확인해야 합니다.
여기서 등장한 것이 바로 **멱등성(冪等性, Idempotency)**이라는 개념이었습니다.
처음에는 발음 때문에 이해하기 어려웠습니다(웃음).
제가 이해하기로는, 동일한 처리를 반복하더라도 의도하지 않은 추가적인 결과를 만들지 않도록 하는 사고방식이라고 생각합니다.
이번 계산대(레지)의 경우로 치면, 같은 회계 내역을 재전송한다고 해서 매출이 이중으로 등록되지 않게 하는 것입니다.
하지만 단순히 키가 같다고 해서 무엇이든 받아들여도 되는 것은 아닙니다.
같은 키를 가지고 있더라도 금액이나 상품 상세 내역이 다르다면 문제가 됩니다.
그럴 경우에는 억지로 성공 처리하기보다는, 원래 기록을 보존하고 확인이 필요한 상태로 만들어야 합니다.
이번 설계에서는 이러한 사고방식을 적용했습니다.
여기서도 트럭 운전에서 중요하게 생각하는 것과 비슷한 감각이 있었습니다.
상황을 모른 채 진행하기보다, 먼저 확인하는 것입니다.
처리를 멈추는 것이 데이터를 지키는 방법일 때가 있습니다.
계산대를 개발하면서 그런 생각을 하게 되었습니다.
여기부터는 실제로 조작하며 검증했던 이야기입니다.
제 앞에 놓인 Lenovo G580에 격리된 환경을 준비하고, 가상의 상품을 사용해 계산 내역을 만들었습니다.
| 상품 | 수량 | 금액 |
|---|---|---|
| 검증 크루아상 | 1개 | 120엔 |
| 검증 식빵 | 1개 | 300엔 |
| 총합 | ||
| 2개 | ||
| 420엔 |
이 두 건을 오프라인 상태로 단말기에 기록했습니다.
그 후, 관리자 로그인 시간이 만료된 상태로 두고 동작을 확인했습니다.
제가 알고 싶었던 것은, 인증이 끊어진 경우에도 원래의 회계 기록이 손실되지 않는지 여부였습니다.
재로그인 후 화면에는 다음과 같이 표시되었습니다.
| 항목 | 표시된 건수 |
|---|---|
| 서버 저장됨 | 2건 |
| 미동기화 | 0건 |
두 건 모두 동기화된 것으로 표시되었습니다.
일단 기대했던 방향으로 움직이는 것 같았습니다.
그런데 '저장 상태 확인' 버튼을 눌렀을 때였습니다.
다음 안내가 표시되었습니다.
서버에 반영된 것을 확인할 수 없었습니다. 단말기의 기록을 보존하고 있습니다.
어라?
방금 전에는 저장됨이 2건이었는데 말입니다.
저장되고 있는 것인지, 아니면 되지 않고 있는 건지.
이 표시에는 위화감이 있었습니다.
AI와 함께 코드를 조사해 보니, 저장 확인 대상을 선택하는 처리에 문제가 있었습니다.
이전 코드에서는 다음 조건이 사용되었습니다.
record.status !== "synced"
!==는 JavaScript에서 '같지 않다(not equal to)'라는 의미입니다.
즉, 여기서는 "synced"가 아닌 회계 내역만 확인 대상으로 삼고 있습니다.
이미 동기화된 2건은 새로운 확인 대상에서 제외되는 것입니다.
그런데 그 결과로 확인 대상이 0건이 되었을 때도, 확인하지 못했다는 안내가 표시되었습니다.
단말기에 회계가 2건 있음
|
v
...
'이번에 새로 확인할 회계 내역이 없다'는 것을 '확인에 실패했다'처럼 보여주고 있었던 것입니다.
서버의 데이터가 사라졌다고 확인된 것은 아닙니다.
하지만 사용자 입장에서는 이해하기 어려운 표시입니다.
예를 들어, 하루 영업을 마치고 가게 주인이 매출을 확인할 경우를 가정해 봅시다.
그때 갑자기,
'서버에 반영된 것을 확인할 수 없었습니다'
라고 표시된다면 불안할 수 있습니다.
저장되지 않았다고 생각하여 같은 회계를 다시 등록해 버릴 가능성도 있습니다.
이렇게 생각하니, 이것은 단순히 문장의 문제만은 아니라고 느꼈습니다.
화면의 안내에 따라 사용자가 잘못된 판단을 할 수도 있다.
그 정도까지 고려할 필요가 있다고 느꼈습니다.
이번 수정에서는 저장 상태의 안내를 분리하기로 했습니다.
| 상태 | 사용자에게 전하고 싶은 것 |
|---|---|
| 회계 기록 없음 | 아직 회계 기록이 없습니다 |
| ... | |
| 특히 중요하게 생각한 것은, 이전 확인 결과와 이번에 실제로 확인한 결과를 혼동하지 않는 것입니다. |
이전에 저장됨으로 판정되었더라도, 이번에 새로운 대조를 하지 않았다면 그 점을 전달해야 합니다.
반대로, 확인할 대상이 없을 뿐인데 실패한 것처럼 표시해서는 안 됩니다.
또한, 긴 회계 키 등은 일반 화면에서 제외하고, 필요한 경우에만 상세 화면에서 확인하도록 했습니다.
가게 주인에게 중요한 것은 긴 영문 숫자가 아닙니다.
지금 매출이 어떤 상태인지. 다음에 무엇을 해야 하는지.
그것을 알 수 있는 화면으로 만들고 싶었습니다.
📌 이번 표시 개선을 통해 생각한 점
- '저장됨(保存済み)'이라는 표시만으로도, 지금 이 순간에 DB를 대조했다고 단정할 수는 없습니다.
- 새로운 확인을 하지 않은 것과, 확인에 실패한 것은 별개입니다.
- 사용자가 오해하지 않도록 상태의 차이를 전달해야 합니다.
- 실제 저장 결과를 확인할 수 없는 경우, 임의로 성공으로 처리해서는 안 됩니다.
처리 자체의 정확성뿐만 아니라, 그 상태를 올바르게 전달하는 것도 중요하다고 느꼈습니다.
이번 저장 상황 UI 수정에서는 먼저 문제를 재현할 테스트 케이스를 추가했습니다.
수정 전에는 실패하는 테스트를 준비하고, 그 후에 코드를 고쳤습니다.
이전부터 배우고 있는, 테스트를 활용한 개발의 사고방식입니다.
설명용으로 간략화하자면, 다음과 같은 JavaScript 테스트가 됩니다.
// 설명을 위한 간략 예시
// 실제 Bakery Hub의 테스트 코드는 아닙니다
const assert = require("node:assert/strict");
...
every()는 배열의 모든 요소가 조건에 맞는지 확인하는 것입니다.
이번 경우에는 두 건 모두 동기화된 상태인지 확인하고 있습니다.
다만, 이 테스트로 확인하는 것은 표시(Display)상의 판단일 뿐입니다. 서버 DB에 2건이 저장되어 있는지를 조사하는 테스트는 아닙니다.
이 차이를 의식하게 된 것도 이번의 배움이었습니다.
이번 저장 상황 UI 수정에서는 다음과 같은 결과를 얻었습니다.
| 항목 | 결과 |
|---|---|
| 새로 추가한 테스트 | 10건 |
| ... | |
| 대상 JavaScript 테스트는 36건 모두 성공했습니다. |
이는 기쁜 결과입니다.
하지만, 거기서 검증이 전부 끝난 것은 아닙니다. Chromium을 사용한 브라우저 E2E(End-to-End) 테스트는 검증 환경에서 브라우저 도입에 실패했기 때문에 실행할 수 없었습니다.
또한, G580으로 확인했던 회계 2건에 대해서도, 서버 DB에 원래의 식별 키로 2건이 저장되어 있는지 독립적으로 대조하는 작업이 남아 있습니다.
화면에는 동기화됨(同期済み)이라고 표시되어 있었습니다.
하지만, 화면상의 표시와, DB 내부를 독립적으로 확인한 결과는 별개입니다.
이번에는 아직 확인하지 못한 것을 그대로 미확인 상태로 남겨두었습니다.
Bakery Hub에서는 지금까지 많은 pytest를 추가해 왔습니다. 이번 관리자 세션 검증에서도 PostgreSQL을 사용한 테스트를 진행했습니다.
관련 Draft PR #152에서는 PostgreSQL 16계 및 18계를 사용한 검증 기록이 있습니다.
또한, 다른 커밋을 대상으로 한 CI(Continuous Integration)에서는 Python 테스트 1,493건의 성공도 확인되었습니다.
다만, 이 결과들은 이번 저장 상황 UI 수정보다 이전 커밋에 대한 결과입니다. 따라서 최신 UI 수정까지 검증되었다는 의미는 아닙니다.
처음에는 성공한 테스트 건수가 늘어나는 것이 매우 기뻤습니다. 물론 지금도 기쁩니다.
다만, 최근에는 단순히 건수만 보는 것이 아니라,
'이 테스트로 무엇을 확인할 수 있었는지'
를 생각하게 되었습니다.
이번 36건 역시 이번에 대상으로 한 JavaScript 처리 부분에 대해 확인했다는 의미입니다. 실기나 DB까지 포함하여 모든 검증이 끝났다는 의미는 아닙니다.
이러한 구분을 앞으로도 소중히 하고 싶습니다.
이번에는 또 하나의 큰 판단을 내렸습니다.
안전 확인이 끝날 때까지, 수정 버전의 본 서비스 공개를 서두르지 않기로 한 것입니다.
Bakery Hub는 Render에서 공개하고 있는 웹 애플리케이션입니다. 개발한 것이 브라우저에서 사용 가능하다는 것은 역시 기쁩니다. 새로운 수정이 생기면 빨리 사용할 수 있는 상태로 만들고 싶은 마음도 있습니다.
하지만, 이번 검증에서는 인증이나 회계 데이터 보호에 관한 확인이 아직 남아 있었습니다. 수정된 부분은 있고 관련 테스트도 성공했습니다. 그럼에도 불구하고, 아직 확실하게 검증되지 않은 부분이 존재합니다. 그래서 공개를 서두르기보다 일단 멈춰서 확인하기로 했습니다.
예를 들어, 관리자 세션의 관리 방법을 변경하면 새로운 DB 테이블이 필요해집니다. 개발 환경에서는 정상적으로 작동하더라도 본 서비스 환경에서는 어떻게 될까요? 기존 데이터는 그대로 남아있을까요? 오래된 처리가 돌아가는 동안 새로운 처리를 넣어도 문제가 없을까요? 실패했을 경우 원래 상태로 되돌릴 수 있을까요? 회계 데이터를 잃지 않고 복구할 수 있을까요?
이러한 것들을 생각하니, 코드 수정 작업과는 또 다른 어려움이 있다는 것을 알게 되었습니다. 이곳은 저에게 아직 공부가 더 필요한 부분입니다.
⚠️ [IMG:N] 형식 토큰은 이미지 placeholder 입니다. 번역하지 말고 원래 위치에 그대로 유지하세요.
| 상태 | 이번 판단 |
|---|---|
| 수정 코드가 완성됨 | 아직 공개를 결정하지 않음 |
| ... | |
| 이번 검증에서는 임시 유지보수 버전(temporary maintenance version)으로 전환하는 것을 승인하고, 안전 확인을 우선순위로 하는 방침을 취했습니다. |
유지보수 버전은 유지보수 표시를 하고 HTTP 503을 반환하도록 설계되어 있습니다.
503은 서버가 일시적으로 요청을 처리할 수 없음을 나타내는 HTTP 상태 코드입니다.
다만, 이 검증 기록만으로는 실제 운영 커밋(operational commit), HTTP 응답, 구 버전 처리의 종료까지 최종 확인이 완료되었다고 말하기는 어렵습니다.
따라서 유지보수 버전 전환이나 본 서비스 복구가 완료되었다고 단정하는 것은 피하고 있습니다.
이번 판단은 수정된 코드가 모두 잘못되었기 때문만은 아닙니다.
수정된 부분도 있고,
테스트를 통해 확인된 부분도 있습니다.
그럼에도 불구하고, 본 서비스에 반영해도 좋다고 판단하기 위한 확인 과정이 아직 부족했던 것입니다.
이번에는 공식 공개를 연기하기로 했습니다.
⚠️ Phase 5 검증 시점의 공개 판단: NO-GO
| 대상 | 검증 기록상의 상태 |
|---|---|
| 관리자 세션 수정 | PR #150으로 main에 병합됨 |
| ... | |
| 이는 이번 검증 단계에서의 판단입니다. 기사 공개 시점의 실제 운영 상황 그 자체를 나타내는 것은 아닙니다. |
서비스의 규모나 사용자, 운영 체제에 따라 공개 판단 기준은 달라진다고 생각합니다.
다만, 이번 Bakery Hub의 경우, 빠르게 공개하는 것보다 회계 데이터를 보호하는 것을 우선했습니다.
이번 보안 검증에서도 AI 덕분에 정말 큰 도움을 받았습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기