변경 이력
모든 릴리스와 각각에서 바뀐 것. 이 페이지는 저장소의 CHANGELOG.ko.md에서 생성되므로 페이지와 파일이 어긋날 수 없습니다. 각 버전 제목은 GitHub의 전체 diff로 이어집니다.
그 이전 릴리스는 영어로만 작성됐고 소급해서 번역하지 않았습니다. 아무도 쓴 적 없는 릴리스의 번역을 지어내는 것보다, 기록이 어디서 시작하는지 말하는 편이 낫기 때문입니다. 이전 내용은 영어 변경 이력에서 볼 수 있습니다. 3.20.1 이후로는 한쪽 언어에만 있는 릴리스가 생기면 빌드가 실패합니다.
각 버전의 릴리스 아카이브, 체크섬, SBOM, 서명된 빌드 프로버넌스는 릴리스 페이지에 있습니다. 업그레이드는 hostveil update로 합니다.
3.26.2 2026-08-31
버그 수정
-
model: 업스트림 패치가 없는 취약점(
RemediationUnavailable)에 주던 할인을 없앴습니다. 예전에는 고정된 나눗값 때문에 조치 가능한 항목의 4분의 1만 반영됐는데, 이 숫자 4는 점수 모형의 다른 상수들 — HIGH 하나가 남은 점수의 정확히 절반을 가져가는 기준, 반복 항목의 조화급수 감쇠 — 과 달리 그럴 만한 근거가 없었습니다. 이제는 다른 발견 항목과 똑같이 심각도가 정한 무게를 그대로 냅니다. 그 결과로, 고칠 수 없는 CVE가 하나라도 있는 호스트는 취약점 축에서 영영 100점에 못 미칠 수 있습니다 — 패치가 있든 없든 위험은 똑같이 실재하기 때문입니다. 이 할인을 설명하던 문서 페이지도 두 언어 모두 함께 고쳤습니다.
3.26.1 2026-08-31
버그 수정
-
fix:
proxy.no-scan-jail의 Fix가 실제로 nginx 로그를 감시하도록 고쳤습니다. 3.26.0에서는[nginx-botsearch]\nenabled = true만 쓰고 그 밖에는 아무것도 적지 않았습니다. 실제 fail2ban 프로세스로 확인해 보니, 문서만 보고 짐작했던 것과 달리 이 설정은 아무것도 감시하지 않았습니다.auto백엔드는 필터에journalmatch가 정의돼 있으면 jail 자체logpath보다 systemd 저널을 우선하는데,nginx-botsearch가 바로 그런 필터입니다. nginx는 요청 단위 기록을 저널에 남기지 않으므로 실제로는 아무것도 잡히지 않았습니다. 이제 Fix는backend = auto를 항상 명시적으로 적습니다. jail을 로그 파일 감시로 전환시키는 것이 바로 이 줄입니다. access 로그와 error 로그도 항상 둘 다 적어 둡니다. 조건부로 생략하는 대신 fail2ban 자체 경로 매크로를 기본값으로 씁니다. 존재하지 않는 경로에 nginx 자체 404로 응답하는 평범한 vhost는 정적 파일 조회 단계까지 가지 않아, access 로그에만 기록이 남기 때문입니다.
3.26.0 2026-08-31
지금까지 Fix의 득실은 일반론으로만 적혀 있었습니다. 이제 hostveil은 그 판단을 특정 호스트에 맞춰 내릴 수 있습니다. hostveil ai-context는 이 호스트가 어떤 용도인지 한 줄로 저장해 둡니다. hostveil advise와 explain --ai, TUI의 v 키, 새로 생긴 대시보드 버튼은 이 설명을 읽어, 고칠 수 있는 발견 항목마다 득실을 판단합니다. 원하면 AI 의견도 덧붙습니다. 별도로, 존재하지 않거나 취약한 URL로 반복 접근하는 스캔에 대응하는 기능도 추가됐습니다. fail2ban이 설치돼 있어도 nginx를 감시하도록 켜져 있지 않은 경우를 찾아내, nginx-botsearch 감시 규칙을 켜자고 제안합니다. proxy 영역에 등록된 첫 Fix입니다.
새 기능
-
ai:
hostveil ai-context와hostveil advise를 추가했습니다. 이제 Fix의 일반적인 득실을 특정 호스트 사정에 맞춰 판단할 수 있습니다.ai-context는 호스트를 한 줄로 설명한 문구를 저장합니다(테마·레이아웃 설정과 같은 상태 디렉터리).advise [--ai]는 고칠 수 있는 발견 항목마다 득실을 나열하고, 원하면 저장된 설명에 비추어 발견 항목별 AI 판단도 덧붙입니다.explain --ai도 이제 같은 설명을 자동으로 읽습니다. TUI(v키로 여는 판단 화면,c키로 여는 설명 편집)와 대시보드(Advise 버튼과 편집 가능한 설명 입력창)에서 모두 쓸 수 있으며, 기본이 로컬 Ollama이고 아무것도 직접 적용하지 않는 기존 AI 계층 위에 얹었습니다. -
check: URL/경로 스캔에 자동으로 대응하지 못하는 리버스 프록시를 찾아내고 고치는 기능을 추가했습니다.
proxy.no-scan-jail은 fail2ban은 설치돼 있지만, 존재하지 않거나 취약한 경로로의 반복 요청을 잡아내는 업스트림nginx-botsearch감시 규칙이 꺼져 있을 때 발생합니다.proxy영역에 등록된 첫 Fix로, fail2ban 자체 기본 차단 시간과 더 긴 차단 시간 중 하나를 고르는 Review 두 개를 제공하며, 미리보기·적용·롤백을 거치는 평소 경로 그대로 작은 jail.d 드롭인 파일 하나를 씁니다. 새로운 데몬은 없습니다. 감시와 차단은 fail2ban이 그대로 맡습니다.
3.25.0 2026-08-30
이제 hostveil은 스캔 결과를 터미널을 보지 않는 사람에게도 전달할 수 있습니다. hostveil export 명령(TUI와 웹 대시보드에서도 사용 가능)은 결과를 동료에게 보여줄 Markdown, Word, PDF로, 다른 프로그램이 읽을 JSON이나 SARIF로 각각 만들어 줍니다. 등록된 모든 Fix는 이제 이미 안내하던 위험 옆에 실제로 얻는 효과도 함께 적습니다. bugreport의 네트워크 전송 경로는 사라지고 로컬 전용 hostveil diagnostics가 그 자리를 대신하며, 설치 스크립트에는 색상 출력과 시작 배너가 추가됐습니다.
새 기능
-
core:
hostveil export명령을 추가했습니다. 스캔 결과를 JSON, SARIF, Markdown, DOCX, PDF로 만들어 줍니다. JSON과 SARIF는scan --json/--sarif와 완전히 같은 결과물로, 다른 프로그램이 읽기 위한 형식입니다. Markdown, DOCX, PDF는 각 발견 항목과 그 수정 방법을 쉬운 말로 설명합니다. 결과를 직접 읽거나 동료에게 전달할 때 씁니다. DOCX와 PDF는 새 의존성 없이 직접 작성했습니다. SARIF를 만들 때 이미 같은 선택을 했던 것과 같은 이유입니다. CLI, TUI의e키(새로 만든 자유 입력 경로 창 포함), 웹 대시보드의 Export 버튼에서 모두 쓸 수 있습니다. -
fix: 등록된 모든 Fix 동작에
Benefit을 추가했습니다. 기존Warning옆에 미리보기 화면마다 함께 표시됩니다. 약 봉투가 효능과 부작용을 나란히 적듯, 실제로 얻는 효과를 위험 옆에 적은 것입니다.model.Finding은 미리보기를 열기 전부터 추천 Fix의 득실을 담고 있고, 위의 export 보고서에도 함께 나타납니다.cve.outdated-image의 경고 문구는 이제 태그를 다시 받아오는 것이 CVE 수정을 보장하지도, 공짜이지도 않다는 점을 분명히 말합니다. 지금 안정적으로 돌아가는 호스트라면 따져볼 만한 실제 비용입니다. -
cmd:
bugreport를 없애고 로컬 전용hostveil diagnostics로 대체했습니다.--send네트워크 경로(GitHub API 토큰이나ghCLI가 보고서 전송 시점을 정하던 방식) 자체를 없앴습니다.diagnostics는 버전, OS, 최근 크래시, 마지막 스캔 요약을 파일 하나로 모으고, 운영자가 직접 이슈에 붙여 넣습니다. -
install: 설치 스크립트의 오류·성공·경고 줄에 색을 입혔습니다 (터미널 여부와
NO_COLOR를 확인하며, CLI가 쓰는 팔레트와 같습니다). 설치를 시작하기 전에 무엇을 설치하는지 알려주는 짧은 배너도 추가했습니다.
버그 수정
-
sitegen: 한국어 사이트의 checks 페이지에서 Fix 열 링크를 연결했습니다. 링크를 다시 쓰는 정규식이 영어 "Auto-fix"/"Review" 라벨에 고정되어 있어서, 영어 페이지에서는 클릭하면 열리던 Fix 상세가 한국어 페이지에서는 아무 반응이 없었습니다.
3.24.0 2026-08-20
체커나 fix 밖에서 일어나는 panic은 지금까지 갈 곳이 없었습니다. 지켜보던 터미널에 Go 스택이 그대로 찍히고, 나중에 그걸 모아서 보고할 방법도 없었습니다. 이제는 모든 recover 지점이 크래시 기록을 로컬에 남깁니다. 체커 자신의 recover가 닿지 못하는 두 곳, CVE 스캐너의 병렬 이미지 스캔과 웹 대시보드의 백그라운드 스캔에도 각자 recover가 붙었습니다. 새로 생긴 hostveil bugreport 명령은 그 기록과 가려진 스캔 요약을 리포트로 묶습니다. 리포트는 로컬에서 미리 보여 주고, --send와 명시적인 확인을 거쳤을 때만 전송합니다.
새 기능
-
cmd: CLI 전체에 크래시 복구를 추가하고
hostveil bugreport를 새로 만들었습니다. 최근 크래시 트레이스와 마지막으로 저장된 스캔의 점수·영역 상태·발견 항목 ID/심각도를 리포트로 묶습니다. 발견 항목의 설명이나 근거는 담지 않습니다. 전송은 선택 사항이며--send와 확인을 거쳐야 하고,HOSTVEIL_GITHUB_TOKEN, 이미 인증된ghCLI, 또는 로컬 파일과 직접 이슈를 여는 안내 중 하나로 이루어집니다. Hostveil이 자체 수집 서버를 두는 일은 없습니다 (#787).
3.23.0 2026-08-20
검사 페이지에 이제 각 fix가 실제로 무엇을 하는지 설명이 붙었고, 그 항목을 가리키는 표에서 바로 연결됩니다. 마케팅 사이트는 유일하게 그라데이션이 있던 띠를 걷어내고, 문서에 이미 고정돼 있는 수치로 그리는 차트 컴포넌트로 바꿨습니다. 나머지는 버그 수정 둘입니다. Review 일괄 적용 화면에서 firewall.inactive 옆에는 자세한 경고가 뜨는데 sshd를 재시작할 수 있고 롤백 체크포인트도 없는 패키지 업그레이드 옆에는 아무 경고도 없었고, compose 편집기가 다시 읽어들일 수 없는 yaml.v3 재인코딩 결과를 그대로 믿고 있었습니다.
새 기능
-
sitegen: 검사 페이지에 "각 fix가 하는 일" 섹션을 생성합니다. fix 레지스트리에서 생성 시점에 바로 렌더링하므로,
Label/Warning문구를 고치고go run ./cmd/sitegen을 깜빡하면 오래된 설명을 그대로 배포하는 대신 CI가 실패합니다 (#785). 검사 표의 Fix 열도 해당 항목으로 연결했습니다. 링크는 매 생성마다 레지스트리와 다시 대조하며, 행에 적힌 라벨을 그대로 믿지 않습니다 (#786). -
site: 유일하게 그라데이션이 들어가 있던
.split-section/.install-section크림색 띠를 어두운 팔레트로 바꿨습니다. 실제 수치로 그리는 "레저 바" 차트 컴포넌트도 새로 추가했습니다. 홈페이지의 적용 전/후 수치, 검사 페이지의 Auto/Review/Manual 비율, 측정 페이지의 5단계 점수 추이 모두 테스트로 값이 고정돼 있습니다 (#778).
버그 수정
-
fix: exec 기반 Review fix 전체에 경고를 답니다. 그동안은 firewall 쪽 fix만 자세한 경고를 달고 있었고, 패키지 업그레이드나 강화 패키지 설치 열 개, 자동 업데이트 설치 프로그램 둘 다 경고 없이 미리보기됐습니다.
firewall.inactive/firewall.default-allow에도 나머지 레지스트리가 이미 갖고 있던 실제 검사기 왕복 테스트를 추가했습니다 (#783). -
compose: 재인코딩 결과를 다시 읽을 수 있는지 확인한 뒤에야 신뢰하도록 고쳤습니다.
Doc.Bytes()는 yaml.v3의 전체 재인코딩 결과가 다시 로드 가능한지 확인하지 않고 그대로 믿고 있었는데, 퍼징으로 파싱은 되지만 그대로 다시 쓸 수는 없는 입력을 찾아냈습니다 (#782).
3.22.0 2026-08-19
이번 주기에는 등록된 검사 항목이 거의 두 배로 늘었습니다. Lynis 기준에 맞춘 커널·계정·패키지·Redis 검사가 새로 들어왔고, 리버스 프록시 영역이 생겼고, 기존 항목 중 상당수가 Review에서 Auto로 승격됐습니다. explain --ai는 더 이상 로컬 Ollama만 써야 하는 게 아닙니다. 나머지는 버그 수정입니다. 컨테이너가 자기 소유도 아닌 방화벽을 가진 것처럼 채점되고 있었고, 권한이 필요한 여러 파일 경로가 실제로 있는 것 대신 경로 이름 자체를 믿고 있었고, 느린 스캔이나 느린 fix 적용 중에는 두 UI 모두 화면에 아무것도 알려 주지 않은 채 조용해졌습니다.
새 기능
-
fix: 검사기 자신의 판단이 다른 형제 항목보다 느슨했던 Review 항목 넷을 Auto로 승격합니다 (
accounts.default-umask,ssh.tcpkeepalive,ssh.clientaliveinterval,ports.redis-protected-mode). 그리고 기존 fix 메커니즘을 재사용해 Manual이던 항목 일곱을 Review로 등록합니다. 비밀번호가 빈 계정은passwd -l로 잠그고,firewall.default-allow는firewall.inactive가 쓰던 방식을 그대로 재사용하고, systemd 강화 플래그 다섯은 이미 있는 범용 드롭인 fix 빌더를 재사용합니다 (#774). -
ai:
explain --ai가 이제 Anthropic이나 OpenAI 호환 API 어디에든 물어볼 수 있습니다.HOSTVEIL_AI_PROVIDER=anthropic또는HOSTVEIL_AI_PROVIDER=openai로 고르면 됩니다. 로컬 모델을 제대로 돌리기 어려운 호스트를 위한 선택지입니다. 기본값은 여전히 Ollama이고, 제공자를 명시적으로 지정하지 않으면 아무것도 호스트 밖으로 나가지 않습니다. 새 제공자 둘 다 Anthropic SDK 대신 순수 HTTP로 통신합니다. 이 도구의 나머지 부분이 지켜 온 무의존성을 그대로 유지하기 위해서입니다. -
check: Lynis 기준에 맞춰 SSH·계정·패키지·커널 모듈·컴파일러·Redis 검사와 fix를 추가해 강화 커버리지를 끌어올립니다. 밀린 보안 업데이트를 20분까지 기다리는 패키지 작업 타임아웃과 함께 실행 가능한 fix로 승격시켰습니다. 등록된 검사 항목은 이제 174개입니다 (Auto 71, Review 49, Manual/Unavailable 54). 시딩된 Ubuntu 측정 VM에서 Auto와 Review fix를 적용하고 활성화한 뒤 Lynis 자체 점수가 57에서 81로 올랐습니다 (#771).
-
check: Lynis 기준 커널 검사를 한 차례 더 추가합니다. 되돌릴 수 있는 드롭인 fix가 딸린 sysctl 검사 17개와 FILE-7524 권한 검사·fix 8개입니다. 토폴로지에 따라 값이 달라지는 sysctl은 Review로 남기고, 모호할 게 없는 영구 편집만 Auto로 승격했습니다. 카탈로그는 136개 항목에 이르렀습니다 (Auto 60, Review 21, Manual 54, Unavailable 1). 업스트림 Lynis로 측정한 깨끗한 Ubuntu 24.04 VM에서 Auto fix를 활성화한 뒤 점수가 66에서 71로 올랐습니다.
-
check: 강화 커버리지를 정적 검사 항목 78개에서 111개로 넓힙니다. 자동 적용 권한 fix 8개와 Review 단계를 거치는 SSH/sysctl fix 12개를 추가했고, 자동화가 안전하지 않은 곳마다 이유를 명시한 계정·Compose·systemd 탐지를 새로 넣었습니다 (#769).
-
check: 리버스 프록시 영역을 새로 추가합니다. 자체 호스팅하는 사람이 가장 흔히 갖고 있으면서도 가장 점검받지 않는 구성 요소이기 때문입니다. hostveil이 살펴보는 다른 모든 것은 443에서 응답하는 무언가의 뒤에 있습니다. 패키지로 설치한 nginx는
sshd_config에서 배운 방식 그대로include를 따라가며 읽고, 컨테이너로 띄운 Traefik은 컨테이너 영역이 이미 찾아낸 Compose 파일에서 읽습니다. 명확하고 모호하지 않은 항목 셋을 새로 냈습니다. 인증 없는 Traefik 대시보드, 폐기된 TLS 프로토콜, 디렉터리 목록 노출입니다. 나머지 셋(평문 HTTP, 보안 헤더 누락, 인증서)은 패키지 자체 문서에 이유를 적고 의도적으로 제외했습니다. 채점 축 여섯 개가 한 점씩 내주어 새 축의 자리를 만들었고, 점수 페이지가 이름까지 붙여 가며 주장하는 동점 관계는 하나도 깨지지 않았습니다 (#764).
버그 수정
-
ui: 대시보드는 리스너를 열기도 전에 첫 스캔을 동기적으로 실행했습니다. 그래서 화면에 찍힌 주소로 들어가도 첫 스캔이 끝날 때까지, 이미지가 많은 호스트라면 몇 분 동안, 아무것도 응답하지 않았습니다. 이제는 리스너를 바로 열고 그 스캔을 재스캔과 똑같이 추적되는 백그라운드 작업으로 돌립니다. 페이지도 로드되는 순간부터 재스캔 때와 같은 도메인별 진행 상황을 실시간으로 보여줍니다. TUI에는 거울처럼 반대편 문제가 있었습니다. fix를 확정하면(Review 미리보기에서 y, 또는 일괄 적용의 a) 엔진이 최종 답을 줄 때까지 화면이 직전 프레임에 멈춰 있었는데, 적용 후 해당 영역 전체를 다시 검사하는 Review fix라면(cve.* 항목이 Trivy를 다시 돌리는 경우처럼) 몇 분이 걸릴 수도 있었습니다. 이제는 키를 누른 순간 바로 경과 시간이 실시간으로 올라가는 화면을 보여줍니다 (#776).
-
ui: TUI의 선택적 after-fixes 라벨을 좁은 터미널에서 통째로 보여주거나 아예 생략하도록 고쳤습니다. 대시보드의 rail 레이아웃이 모바일에서 접힐 때 도메인 축이 사라지던 것도 복원했습니다. 데모 포트 8787이 충돌하면 오래된 libvirt SSH 포워딩과 뒤섞여 엉뚱하게 동작하는 대신 바로 거부하도록 했습니다 (#773).
-
core: 권한이 필요한 파일 읽기·쓰기·chmod·체크포인트·롤백을 심볼릭 링크 우회와 동시 파일 교체로부터 강화했습니다. fix는 이제 자신이 다룰 입력이 그 밑에서 바뀌었으면 낡은 상태로 밀어붙이는 대신 멈추고 재스캔을 요구합니다. 자체 업데이트 메타데이터와 아카이브는 쓰기 전에 검증하고, 설치 스크립트는 패키지 매니저가 설치한 버전을 덮어쓰지 않으면서 검증된 정확한 바이너리만 배치합니다. Ollama 응답과 출처도 범위를 제한하고 검증합니다. 안전하지 않거나 형식이 잘못된 입력을 거부하게 된 것 말고는 기존 동작이 달라지지 않았습니다 (#772).
-
check: 컨테이너가 자기 것도 아닌 방화벽을 가진 것처럼 채점되고 있었습니다. 포트가 응답할지 결정하는 방화벽은 호스트 소유이고 컨테이너 안에서는 애초에 보이지 않는데, 스캔을 돌린 적 있는 컨테이너라면 어디든 안에서 손댈 수도 없는 방화벽에 대해 hostveil이 가장 심각한 등급의 항목을 띄우고 있었습니다. 더 나쁜 쪽은 커널 강화 Review fix였습니다.
/etc/sysctl.d를 컨테이너 안에 써 놓고 성공했다고 보고했지만, 그 값을 실제로 갖고 있는 호스트 커널에는 끝내 닿지 않았습니다.platform.ContainerRuntime이 한 번 물어보고 그 답을 필요한 모든 검사기가 공유하도록 했습니다. 방화벽 영역은 이제 건너뛰고(N/A) sysctl fix는 Manual로 내려갑니다. 컨테이너가 실제로 감사 대상 호스트 그 자체인 경우를 위해HOSTVEIL_ASSUME_HOST=1로 둘 다 되돌릴 수 있습니다 (#761). -
site: 한국어 페이지는 지금까지 쭉 모노스페이스 대체 글꼴로 그려지고 있었습니다. Georgia에도 모노스페이스 스택에도 한글 글리프가 없는데, 둘 다 어떤 배포판도 등록하지 않는 폰트 이름을 요청하고 있었기 때문입니다. 새로 만든 한국어 전용 스타일시트는 이제 본문에 Noto Serif KR을, 라틴 디자인이 모노스페이스로 그리던 자리에 Noto Sans KR을 씁니다. 각각
unicode-range로 한글 블록에만 걸어 두어 영어 페이지는 이 중 아무것도 내려받지 않고, 두 문자 체계 사이에서 그대로 옮겨지지 않는 줄 간격·자간· 줄바꿈 규칙도 함께 손봤습니다 (#767). -
site: 리버스 프록시 문서가 새로 들여온 한글 음절 다섯 개(
듦,줬,즘,폈,폐)가 배포된 글꼴 서브셋 범위 밖에 있어TestEveryKoreanSyllableOnTheSiteHasAGlyph가 실패하고 있었습니다. 서브셋을 다시 빌드해 다섯 글자를 채웠습니다 (#768).
3.21.0 2026-08-16
실제 서버에서 보낸 오후 하나로 발견 항목 둘이 바뀌었습니다. 하나는 hostveil이 한 번도 찾아본 적 없던 것이고, 하나는 그렇지 않은 호스트에 대고 보고하고 있던 것입니다.
새 기능
-
check: sudo가 비밀번호 없이 root를 내줄 수 있는데, 아무도 보지 않았습니다 (#753). 계정 영역은 누가 root가 될 수 있는지를 두 번 물었습니다. 두 번째 UID 0과 빈 비밀번호입니다. 그런데 사람들이 실제로 부딪히는 경로를 놓치고 있었습니다. 클라우드와 VM 이미지는 첫 로그인이 되도록
NOPASSWD: ALL을 넣어 두고, 그건 대개 몇 년 뒤에도 그대로 있습니다. 그 계정의 SSH 키를 쥔 사람은 두 번째 자격 증명 없이 즉시 root입니다.sudoers를 읽는 대신 sudo에게 물어봅니다. 그 설정에는 별칭이 있고,
/etc/group이 아니라 LDAP로 해석될 수도 있는 그룹 지정이 있고, 넷그룹과 include 순서가 있습니다. 그 전부를 거쳐 실효 값을 다시 유도하는 것은internal/compose.Discover가 보여 주려고 존재하는 바로 그 실수입니다. 그리고 제한 없는 경우만 표시합니다. 이름 붙은 명령 하나에 걸린NOPASSWD규칙은 평범한 운영입니다.뻔한 구현이 틀리는 지점이 셋이고, 각각 그 지점에서 실패하는 테스트가 있습니다. 한 권한 줄 안에서
NOPASSWD는 뒤에PASSWD가 나와 프롬프트를 되살릴 때까지 유지됩니다.Defaults !authenticate는 호스트의 모든 규칙에서 비밀번호를 끄는데 권한 줄에는 그렇다는 태그가 없습니다. 그리고sudo -l -U 누군가는 sudo 권한이 아예 없는 계정에 대해서도, "root가 아니면-U를 못 쓴다"에 대해서도 0이 아닌 값으로 끝나서 상태만으로는 구분되지 않습니다. 그래서 답을 아는 root를 먼저 물어봅니다. 그 대조 실행이 없으면 root 아닌 스캔이, 물어볼 기회조차 얻지 못한 채로 "비밀번호 없이 root가 될 수 있는 사람은 없다"고 보고합니다.판단해서 수동입니다. 이미지가 그 규칙을 넣어 둔 것은 그 계정에 비밀번호가 없기 때문이고, 그래서 규칙을 떼면 그 계정이 sudo를 아예 못 쓰게 될 수 있습니다.
버그 수정
-
platform: Debian에서 돌고 있는 ufw가 방화벽 없음으로 읽혔습니다 (#755). Debian은 root가 아닌 사용자의
PATH에서 sbin을 뺐고,ufw와nft와iptables는 전부/usr/sbin에 있습니다. 그래서 설치돼 돌고 있는 방화벽에 대해exec.LookPath가 실패했고, 방화벽 영역은 찾지 못한 프런트엔드를 설치되지 않은 것으로 읽었고, 프런트엔드가 전부 없는 것을 "활성 방화벽 없음"으로 읽었습니다. 이미 돌고 있는 방화벽을 설치하라는, 이 도구에서 가장 심각한 등급의 발견 항목을 호스트에 건넨 셈입니다. 포트 영역도 같은 실수 위에서 자기 발견 항목을 하나 더 냈습니다.같은 기계에서 몇 분 사이에, 권한 없이는 High 하나와 함께 50/100, root로는 100/100 깨끗함이었습니다. hostveil은 자동으로 권한을 올리기 때문에 지금까지 보이지 않았습니다.
HOSTVEIL_NO_SUDO=1은 스크립트와 CI를 위해 문서화돼 있고, 권한 상승은 실패할 수 있고, 컨테이너는 nobody로 돌 수 있습니다.LookPath가 관리자 디렉터리로 물러나 찾습니다. 앞이 아니라 뒤에 붙이고, 구분자가 들어간 이름에는 적용하지 않습니다. 바이너리를 찾는 것이 수정의 전부입니다. 탐침은 나머지 절반에 대해서는 이미 조심스러웠기 때문에, 읽을 수 없는 ufw는 이제 비난이 아니라 확인하지 못한 영역이 됩니다.
문서
-
measurements: 공개된 실측이 열한 릴리스나 낡아 있었고, 3.20.2에서 다시 측정했습니다 (#754). 아울러 실행한 아키텍처와 무엇을 심었는지에 대한 명세를 기록한 첫 실행입니다. 두 필드 모두 만들어져 있었지만 결과물을 낸 적이 없었고, 그래서 이전 릴리스는 페이지의 아키텍처 주장을 고치는 대신 지워야 했습니다.
같은 씨앗인데 점수가 40에서 29로 내려갔고, 페이지가 그 이유를 말합니다. hostveil이 더 많이 찾아냈고, 3.20.1이 기록만 되고 발효되지 않은 수정을 자기 공으로 세는 것을 그만뒀기 때문입니다. 수치 검사가 볼 수 없는 산문 주장 셋이 사실이 아니게 돼 있었고 바로잡았습니다. 그중 "모든 숫자가 시작한 자리로 돌아왔다"는 흥미로운 방식으로 틀려 있었습니다. 방화벽을 켜는 것은 체크포인트가 없어서 ufw가 롤백을 살아남고, 컨테이너가 공개한 포트 다섯 개는 그것을 통과해서 응답합니다.
firewall.docker-bypass가, 그것을 한 번도 들어본 적 없는 계측기 안에서 스스로를 증명한 셈입니다. -
이 저장소가 스스로에 대해 증명할 수 있는 것을 공개합니다 (#757). 수정 커버리지는 릴리스 노트 안에만 있었고, 테스트 체계는 어느 README에도 어느 페이지에도 없었고, 취약하게 시드된 VM은 기여자용 안내로만 적혀 있었습니다. 추가한 숫자는 전부 타이핑한 것이 아니라 계산한 것입니다. 페이지가
data-counted="findings.fixable"을 달고 테스트가 그것을 수정 레지스트리에 맞춰 풀기 때문에, 수정이 없던 발견 항목에 수정을 등록하면 페이지가 따라 움직일 때까지 빌드가 깨집니다.
3.20.2 2026-08-16
공개돼 있었는데 틀린 것들에 대한 릴리스입니다. 웹사이트의 스크린샷이 hostveil이 낼 수 없는 발견 항목을 보여주고 있었고, 네 페이지가 바이너리와 어긋나 있었고, 내장 도움말에서 플래그 여섯 개가 빠져 있었고, 동아시아 모호 폭 문자를 넓게 그리는 터미널에서는 미터가 배정된 폭의 두 배로 그려지고 있었습니다.
버그 수정
-
tui: 미터와 추세선을 룬이 아니라 칸 단위로 그립니다 (#749).
internal/ui/tui/alignment_test.go는 3.16부터 "미터는 룬 수로 만들어져서RUNEWIDTH_EASTASIAN에서는 모든 스트립이 넘친다, 그건 별도 수정"이라며 스스로를 건너뛰고 있었습니다. 미터를 이루는 글리프가 전부 그 부류라, 축 레일과 점수 게이지와 도메인 미터가 모두 터미널 밖으로 밀려 나가 잘렸습니다. 한국어나 일본어 환경의 운영자가 가장 그럴 법한 모드에서요. 추세선도 같은 문제였는데 그 스킵이 언급하지 않았습니다. 100칸 터미널이 162칸짜리 스파크라인을 그리고 있었습니다.█은 모호 폭인데░는 아니라서, 셀 크기 하나로 나누는 방식도 안 됩니다. 이제 칸 단위로 배치해 두 모드 모두에서 배정된 폭을 정확히 차지합니다. 테스트 세 개가 속성이 아니라 좁은 폭 조건을 주장하고 있었고, 그중 하나는 룬을 칸으로 재고 있었습니다. 바로 그 테스트가 잡으려던 결함이었습니다. 이제 CI가 레이아웃 패키지를 그 변수와 함께 한 번 더 돌립니다. 어떤 잡도 그러지 않았다는 것이, 미터가 있던 모든 릴리스를 이 결함이 살아남은 이유입니다. -
check: systemd 드롭인은 이겨야 하고, 이건 확인되지 않고 있었습니다 (#750).
systemd.unit(5)는 드롭인을 어느 디렉터리에 있든 파일명 사전순으로 적용합니다./etc가/usr를 이긴다는 규칙은 이름이 같은 파일에만 해당합니다. 그래서 벤더의90-vendor.conf가, 3.20.0부터systemd.no-new-privileges가 써 온50-hostveil.conf를 이겼습니다. 지는 드롭인은persistSysctl이 막으려고 존재하는 바로 그 결과입니다. 파일은 생기고, 수정은 성공을 보고하고, 체크포인트도 남는데, 값은 다음daemon-reload에 되돌아옵니다.이제 점검기가
DropInPaths를 읽어, systemd가 실제로 읽어들인 모든 드롭인 뒤에 오도록 파일명을 정합니다. 그 파일들의 내용이 무엇이든 이깁니다. 이길 수 있는 이름이 없으면 제안할 것도 없습니다. 수정은 거절하고, 해당 항목의 안내가 실제로 결정권을 가진 드롭인을 알려 줍니다. 모순 하나도 정리됩니다 —dockerd.socket-world-writable은 우선순위를 해석하지 못한다는 이유로 거절돼 있었는데, 정작 이 도메인은 묻지도 않고 쓰고 있었습니다. -
cmd: 내장 도움말이 바이너리와 어긋나 있었습니다 (#742).
fix --review,history --scans,update --yes,uninstall --yes,HOSTVEIL_NO_UPDATE_CHECK, 종료 코드 10이 전부 빠져 있었고,--all줄은 검토 수정을 건드리지 않는다고 했는데--review가 그걸 사실이 아니게 만든 뒤였습니다.update와uninstall에는 플래그 항목 자체가 없었습니다. 사이트의 플래그 표는 오래전부터 실제 플래그 집합에 고정돼 있었는데, 도움말은 손으로 쓴 네 개짜리 목록에 고정돼 있었습니다. 이제 둘 다 같은 출처를 따릅니다. -
docs: 공개된 스크린샷이 hostveil이 낼 수 없는 발견 항목을 보여주고 있었습니다 (#740). 웹사이트와 두 README 맨 위에 있는
site/assets/tui.png가ssh.maxauth(진짜 id는ssh.maxauthtries)와, 발견 항목이 아예 아닌fileperms.envfile을 보여주고 있었습니다. 전체 모드 픽스처에 네 개가 더 있었습니다.TestSiteFrameIsCurrent는 그림이 그것을 그린 코드와 일치하는지를 고정할 뿐, 그림이 말하는 내용이 참인지는 보지 않습니다. 어떤 테스트도 id를 읽지 않았습니다. 이제 읽습니다. 독자에게 닿는 모든 픽스처에 대해서요. -
docs: 바이너리와 어긋나던 페이지들 (#741). 공개된 두 페이지가 서로 반대되는 업그레이드 안내를 하고 있었고, README 쪽 경로는
.deb나.rpm설치를 어긋난 상태로 만들 수 있었습니다.hostveil update는 3.18에 나왔는데 어느 README에도 없었습니다.cli.html은 실행하면 종료 코드 2가 나는-o플래그를 문서화하고 있었습니다. 네 페이지가fix --all --review를 부정했고, 그중 README는 두 섹션 간격으로 자기모순이었습니다. 환경 변수 표의 행 다섯 개가 4열 표에 셀 2개만 갖고 있어서 설명이 엉뚱한 열에 렌더됐습니다. 양쪽 언어 모두에서요. AGENTS.md는 퍼즈 타깃 다섯 개 중 셋만 적고 있었습니다.
문서
-
site: 변경 이력이 웹사이트에 올라갔습니다 (#739). 양쪽 언어로, 그리고 손으로 쓰는 대신
CHANGELOG.md에서 생성하므로 페이지가 파일과 다른 말을 할 수 없습니다. Markdown 의존성은 추가하지 않았습니다. 변환기는 파일이 쓰는 문법만 구현하고, 렌더할 수 없는 것을 만나면 파일과 줄 번호를 대며 빌드를 실패시킵니다. -
site: 사람들이 설치하는 자리의 공급망 무결성과, Lynis 질문 (#745). 모든 릴리스가 아카이브마다 SBOM과 서명된 프로버넌스 증명을 담는데, 웹사이트는 CLI 레퍼런스 바깥에서 둘 다 언급한 적이 없었습니다. 이제 설치 페이지가 체크섬이 증명하는 것과 증명하지 못하는 것, 그리고 증명이 더해 주는 것을 말합니다. FAQ는 Lynis나 CIS 벤치마크와 무엇이 다른지 답합니다 — 그것들에게 측정당함으로써요.
-
README가 영어로 시작하고 근거를 앞세웁니다 (#746). Lynis와 CIS Docker 벤치마크와 호스트 밖 포트 스캔에 대한 실측이 점수 섹션 아래 H3로 65% 지점에 있었습니다. 이제 접힌 부분 바로 아래 H2입니다. 영어 독자가 처음 만나는 내용이 번역되지 않은 한국어 수상 문구였습니다. 이제
CITATION.cff가 이미 쓰고 있던 표현 그대로 영어로 적혀 있습니다. -
3.20.1이 바꾼 것을, 사람들이 찾아볼 자리에 (#743).
Match블록 관련 변경이 문서 없이 나갔고, 평범한Match User가 있는 호스트는 업그레이드하면 Degraded가 됩니다.Pending은 한 문단뿐이었습니다. 둘 다 독자가 실제로 닿는 페이지들에 적었고, 매일 나가는 업데이트 확인도 README에 밝혔습니다. 아무것도 호스트를 떠나지 않는다고 하면서 그건 한 번도 언급하지 않았거든요. -
site: sitemap과 robots.txt와 링크 카드 (#744). 렌더 루프가 이미 계산하는 URL에서 생성합니다.
<lastmod>는 없습니다. 비결정적이거나 거짓이거든요. -
소셜 프리뷰 카드와, 주인이 있는 메타데이터 (#747, #748). 저장소에 토픽이 하나도 없어서 어떤 토픽 페이지에도 나타나지 않았습니다. 이제
CITATION.cff의 키워드가 그 목록이고, 카드는 사이트 자신의 팔레트와 제품 마크의 기하로 그렸습니다.
3.20.1 2026-08-15
틀린 것 세 가지, 그중 둘은 공개돼 있던 것입니다. 호스트가 아직 보지도 못한 수정을 인정하던 점수, 자기가 측정했다는 실측치와 어긋나던 공개 수치 네 개, 그리고 Match 블록에서 말없이 끝나던 SSH 감사입니다.
버그 수정
-
core: 아직 적용되지 않은 수정은 점수에 반영되지 않습니다 (#735).
applyFix가 호스트를 확인하기도 전에 항목을 수정됨으로 표시하고 점수를 다시 매겼고,ApplyFix는 그 뒤에야 재점검했습니다. 그래서 "해당 항목이 아직 그대로 보고됩니다"라는 메시지가, 이미 그 수정을 인정해 올라간 점수 밑에 찍혔습니다. Debian 컨테이너에서 실제 바이너리로 측정한 결과,sysctl.ptrace-scope드롭인을 적용하자/proc/sys는 전혀 바뀌지 않았는데도 점수가 78 → 80으로 움직였습니다. 커널이 읽지도 않은 파일을 쓴 대가로 2점입니다.model.Finding.Pending이 "적용됨"과 "실제로 적용 중"을 분리합니다. 변경이 호스트에 닿을 때까지 해당 항목은 계속 감점되고,Fixed는 "hostveil이 뭔가를 적용했다"는 뜻을 그대로 유지합니다 — 일괄 적용이 같은 수정을 두 번 하지 않는 것이 이 구분 덕분입니다. 위험이 아직 서 있는지는Finding.Active에, 일괄 적용이 손댈 대상인지는IsAutoFixable에 물어보면 됩니다.Pending은
fix.Action.TakesEffectOn, 즉 수정 스스로의 선언에서 나옵니다. 그래서 재점검을 하지 않는 일괄 경로도 단건 경로와 같은 답에 도달합니다.VerifyStillPresent는 이를 승격시키지만VerifyUnavailable은 그러지 않습니다. "확인할 수 없었다"는 증거가 아니고, 그것을 증거로 치면 재점검이 재점검하지 않는 쪽보다 비관적이 되어 — 하나씩 적용한 사람이fix --all을 돌린 사람보다 낮은 점수를 받게 됩니다.persistSysctl에 진작 있었어야 할TakesEffectOn을 붙였습니다. sysctl 점검기는/proc/sys를 읽는데 수정은 드롭인 파일을 씁니다. 이 필드가 존재하는 이유가 바로 그 "산출물과 판정 근거의 불일치"인데, 그 사실이 아무것도 손댈 수 없는Warning문구 안에 갇혀 있었습니다.컨테이너가 많은 호스트에서
fix --all은 이제 적용한 내역 옆에 움직이지 않은 점수를 함께 보고합니다. 그래서BatchOutcome이 어떤 수정이 무엇을 기다리고 있는지 밝힙니다 — 이 문장이 없으면 부정직한 숫자를 설명 불가능한 숫자로 바꾸는 셈이 됩니다.dockerd관련 기록은 지우지 않고 다시 썼습니다. "점수 문제는 해결되지 않았다"가 이 도메인의 공통 사유였고 이제 해결됐지만, 그렇다고 일곱 항목 중 어느 하나도 풀리지 않습니다. 다섯은daemon.json에 키를 추가하거나 삭제해야 하는데internal/json5는 이미 있는 값만 바꾸고, 나머지 둘은 애초에 점수에 기대고 있지 않았습니다.socket-world-writable은 사유가 실제로 무너진 유일한 항목이고, 그 자리에는 드롭인 우선순위 문제와Manual선언이 대신 서 있습니다.이 변경은 적용 직후부터 다음 스캔까지의 점수만 바로잡습니다. 새 스캔은 점검기 결과로 항목을 다시 만들기 때문에 공개된 실측치는 그대로입니다 — 측정 하네스는 매 단계마다 새로 스캔합니다.
-
docs: 어떤 검사도 읽지 않던 수치들 (#736).
measurements.html은 모든 수치가 커밋된 실측 파일에 고정돼 있고, 그 검사는 두 번이나 확장됐지만 매번 파일 하나만 읽었습니다. 랜딩 페이지와 README 두 개는 같은 숫자를 아무 검사 없이 공개하고 있었고, 결국 어긋났습니다. 페이지는 자기가 4로 고정해 둔 값 두 문단 아래에서 "Lynis 경고 셋 중 둘"이라고 말했고, README들은 최신 실측 표 바로 옆에 v3.13.0 수치(체크포인트 33개에 걸쳐 5개 중 5개, 검토 수정 17건 중 8건)를 달고 있었습니다 — 이번 실측은 37개에 걸쳐 6개 중 6개, 16건 중 7건입니다. 그리고 #731이 "어떤 실측도host.arch를 기록하지 않는다"는 이유로 페이지에서 지운 ARM64 주장이 다른 네 파일에 그대로 남아 있었습니다.아키텍처 검사는 이제 실측을 인용하는 모든 파일을 읽되 코드 블록은 건너뜁니다. 측정 대상 호스트에 대한 주장과 릴리스 페이지에 있는 파일 이름은 다른 진술이기 때문입니다. 랜딩 페이지의 대표 수치 네 개에는
data-measured가 붙어 셀 단위로 검사되고, 속성을 붙일 수 없는 서술형 주장은 실측에서 문장을 만들어 그대로 있는지 확인합니다 — 숫자를 말로 풀어써서 자릿수 검색으로도 걸리지 않던 사이트 섹션 제목까지 포함해서요. -
check:
Match블록은 깨끗하다는 뜻이 아니라 못 본 영역입니다 (#737).sshd_config파서는 첫Match에서 멈추면서 아무것도 기록하지 않았습니다. 멈추는 것 자체는 옳습니다 — sshd는 그 지시어들을 조건에 맞는 접속에만 적용하니까요. 문제는 말없이 멈추는 것입니다.Match Address 0.0.0.0/0다음에PasswordAuthentication yes가 오면 존재하는 모든 접속에 비밀번호가 허용되는데, 도메인은 감사가 완료됐다고 보고했고 출력은 그 블록이 없는 같은 파일과 바이트 단위로 동일했습니다. 이제 Degraded로 보고하면서 어떤 파일에서 무엇을 보지 못했는지 밝힙니다. 이 정보는check.Coverage를 거치므로, 읽지 못한 include와Match블록이 동시에 있는 설정은 먼저 발견된 하나가 아니라 둘 다 보고합니다.