점검 항목
Hostveil은 셀프호스터가 실제로 침해당하는 영향이 가장 큰 경로들을 점검한 뒤, 모든 것을 하나의 0–100 점수로 합칩니다. 각 발견 항목은 domain.rule 형태의 고정된 ID를 가지며, explain, fix, rollback과 함께 사용합니다.
영역 한눈에 보기
| 영역 | 접두사 | 필요 조건 |
|---|---|---|
| Docker / Compose | compose. | Docker |
| SSH | ssh. | — |
| 방화벽 | firewall. | — |
| 자동 업데이트 | updates. | — |
| 노출된 서비스 | ports. | ss |
| 계정 | accounts. | root (/etc/shadow용) |
| 파일 권한 | fileperms. | — |
| AI 에이전트 런타임 | agent. | OpenClaw 또는 Hermes 설치 |
| 커널 하드닝 | sysctl. | — |
| Docker 데몬 | dockerd. | Docker |
| 서비스 하드닝 | systemd. | systemd |
| 리버스 프록시 | proxy. | nginx 또는 Docker |
| 이미지 CVE (선택) | cve. | Trivy |
Docker나 Trivy가 없나요? 해당 영역은 깔끔하게 건너뛰어지고 점수가 재정규화되므로, 실행되지도 않은 스캔에 대해 오해를 살 만큼 완벽한 결과를 받는 일은 없습니다.
Hostveil은 이 영역들에서 발견 항목 175개를 보고할 수 있고, 그중 128개에 수정이 붙어 있습니다. 75개는 무인으로 적용하고, 53개는 차이를 읽은 뒤에만 적용합니다. 나머지는 일부러 Manual로 둔 것이고, 하나하나 internal/fix/register.go의 목록에 Hostveil이 손대지 않는 이유와 함께 적혀 있습니다. 계정 삭제는 체크포인트로 되돌릴 수 없고, Docker 소켓 마운트를 떼면 그것을 필요로 하는 도구가 망가집니다. 아무도 들여다보지 않아서 Manual인 것이 아니라 판단해서 Manual인 것이며, 그 차이에서 테스트가 빌드를 깨뜨립니다. 이 문단의 개수도 여기 타이핑한 것이 아니라 레지스트리에서 계산한 값입니다.
심각도와 점수
Hostveil은 발견 항목을 얼마나 급한지로 등급을 매깁니다. 얼마나 나쁜지가 아닙니다. 설정 파일을 읽는 스캐너는 "root로 도는 컨테이너"가 얼마나 나쁜지 말해 줄 수 없습니다. 그건 그 컨테이너가 무엇을 하느냐에 전적으로 달려 있으니까요. 스캐너가 말할 수 있는 것은 지금 이 순간 공격자와 그 문제 사이에 무엇이 얼마나 서 있느냐이고, 이 세 단계가 재는 것이 바로 그것입니다.
| 단계 | 뜻 | 가져가는 몫 |
|---|---|---|
| HIGH | 아무것도 가지지 않은 사람이 지금 호스트 밖에서 닿거나 쓸 수 있습니다. 인증 없는 열린 서비스, 비밀번호 없는 계정, 이미 호스트 root를 넘겨받은 컨테이너. 먼저 뚫어야 할 것이 없습니다. | 남은 점수의 1/2 |
| MEDIUM | 발판이나 추측한 자격 증명, 로컬 계정이 있으면 무너지는 경계입니다. 공격자에게 아직 없는 무언가가 필요하고, 그것을 손에 넣을 값어치를 만들어 주는 것이 이 항목입니다. | 1/8 |
| LOW | 심층 방어입니다. 오늘 알려진 경로는 없고, 앞으로의 침해가 닿을 범위를 좁혀 줍니다. | 1/16 |
네 단계가 아니라 세 단계이고, 위의 정의가 분류 체계의 전부입니다. 이름이 평범한 것은 여기서 이름이 할 일이 순서를 담는 것 하나뿐이기 때문입니다. 그 하나를 칩 하나, --json 한 줄, SARIF 레벨 하나에서 해내야 하니, 다른 것이 들어갈 자리는 없습니다. 각 단계가 무엇을 뜻하는지는 옆 칸에 있고, 그것은 발견 항목에 대한 의견이 아니라 호스트에 대한 진술입니다.
Hostveil에는 원래 심각/높음/보통/낮음, 네 단계가 있었습니다. CVE의 공개 등급을 그대로 통과시키려고 Trivy에서 물려받은 것이었습니다. 지금은 취약점을 이미지 단위로 묶기 때문에 발견 항목에 닿는 것은 CVE별 등급이 아니라 이미지 하나당 한 단계이고, 네 번째 단계는 설정 파일이 답할 수 없는 질문을 계속 묻고 있었습니다.
HIGH는 기존 심각과 높음을 합한 집합과 정확히 같으므로, scan의 종료 코드와 SARIF 내보내기는 예전과 똑같은 뜻입니다. Trivy 출력과 나란히 비교한다면 한 가지만 유의하세요. Trivy가 CRITICAL이라 부르는 취약점은 여기서 HIGH로 표시됩니다. Hostveil은 세 단계이고 Trivy는 네 단계라, 한쪽의 최상위와 다른 쪽의 최상위는 이름으로 맞출 수 없습니다.
고정된 점수가 아니라 남은 점수의 일부를 가져가는 방식이라, 항목 두 개만으로 축이 바닥나지 않습니다. 열 번째 항목도 여전히 대가를 치르고, 문제가 30개인 호스트는 3개인 호스트보다 낮은 점수를 받습니다. 전체 모형은 점수 산정에 있습니다 — 계산식, 실제 스캔으로 만든 예시, 그리고 이 숫자가 알려주지 않는 것까지.
Hostveil이 Unavailable로 표시한 항목 — 업스트림 패치가 없는 취약점 — 도 조치 가능한 항목과 똑같이 심각도가 정한 만큼 그대로 반영됩니다. 아직 고칠 방법이 없다고 위험이 줄어드는 건 아니라서 따로 깎지 않으며, 미패치 이미지를 계속 쓰는 호스트라면 그 사실이 취약점 축에 그대로 드러납니다.
도메인별 비중
축은 셀프호스트 서버에서 얼마나 중요한지에 따라 가중치를 갖고, 합은 100입니다. 실행되지 않은 도메인은 가중치가 총합에서 빠지고 나머지가 재정규화됩니다.
| 축 | 가중치 |
|---|---|
| 컨테이너 노출 | 13 |
| SSH 하드닝 | 13 |
| 취약점 | 9 |
| 호스트 방화벽 | 8 |
| 노출된 서비스 | 7 |
| AI 에이전트 런타임 | 7 |
| 리버스 프록시 | 6 |
| 계정 위생 | 7 |
| 자동 업데이트 | 7 |
| Docker 데몬 | 7 |
| 서비스 하드닝 | 6 |
| 파일 권한 | 5 |
| 커널 하드닝 | 5 |
영역을 온전히 점검하지 못했을 때
"들여다보지 못했다"와 "거기에 아무것도 없었다"는 똑같이 빈 목록을 만들어 내지만, 뜻은 정반대입니다. Hostveil은 둘을 절대 뒤섞지 않으며, 모든 영역이 실제로 얼마나 확인했는지를 함께 보고합니다.
| 상태 | 의미 | 점수에 미치는 영향 |
|---|---|---|
| 완료 | 영역을 끝까지 점검했습니다. | 정상적으로 점수에 반영됩니다. |
| 건너뜀 | 의존 도구가 없습니다 — Docker 없음, Trivy 없음, ufw 없음. | N/A로 표시되고 제외되며, 나머지 축이 재정규화됩니다. |
| 부분 | 일부만 확인했습니다 — 예를 들어 Trivy가 이미지 다섯 개 중 셋만 스캔한 경우. | 확인한 범위로 채점하되 표시가 남습니다. TUI는 42~, 대시보드는 호박색 42~로 씁니다. |
| 실패 | 점검기가 해당 영역을 전혀 확인하지 못했습니다. | 건너뜀과 마찬가지로 제외되며, 이유가 표시됩니다. |
두 UI 모두 발견 목록 위에 그 이유를 출력합니다 — "Container partial: cannot inspect containers started outside Compose", "CVEs skipped: Trivy not installed". 제외된 축은 결코 100점으로 처리되지 않습니다. 이미지를 볼 수 없었던 스캔은 이미지가 깨끗하다고 확인한 스캔이 아니기 때문입니다. root 없이 실행하면 점수가 높아지는 게 아니라 낮아지는 이유가 이것입니다.
hostveil scan은 수정되지 않은 발견 항목 중 HIGH가 하나라도 있으면 0이 아닌 값으로 종료합니다 — CI나 cron 게이트로 유용합니다.
SSH ssh.
sshd_config에서 직접 파싱하며, Include 지시어를 따라 sshd_config.d/까지 읽습니다. Debian과 Ubuntu는 Include를 파일 맨 위에 두고, sshd는 각 키워드에 대해 처음 얻은 값을 사용하므로 drop-in 파일이 아래 줄들을 이깁니다. 읽으려면 root 권한이 필요하며, hostveil은 sudo로 자동 상승하여 이를 얻습니다. 수정은 실제로 적용되고 있는 지시어가 있는 파일에 가해집니다.
읽기는 첫 Match에서 멈춥니다. sshd가 그 뒤의 지시어를 조건에 맞는 접속에만 적용하므로, 그 안의 지시어는 전역 지시어처럼 호스트를 설명하지 않기 때문입니다 — 다만 읽지 않았다는 것은 “문제 없음”이 아니라 못 본 영역이므로, 도메인은 해당 파일을 밝히며 Degraded로 보고됩니다. Match Address 0.0.0.0/0 다음에 PasswordAuthentication yes가 오면 존재하는 모든 접속에 비밀번호가 다시 열리는데, 그런 호스트를 “전부 점검했다”고 하는 스캔은 들여다본 적도 없는 것을 주장하는 셈입니다.
| ID | 무엇을 표시하는지 | 심각도 | 수정 |
|---|---|---|---|
ssh.emptypasswords | 빈 비밀번호가 허용됨 | HIGH | Auto-fix |
ssh.rootlogin | 비밀번호를 사용한 root 로그인이 허용됨 | HIGH | Review |
ssh.passwordauth | 비밀번호 인증이 허용됨 | MEDIUM | Review |
ssh.gatewayports | 원격 포워딩 포트가 네트워크에 노출됨 | MEDIUM | Review |
ssh.hostbasedauth | 호스트 기반 인증이 활성화됨 | MEDIUM | Review |
ssh.kbdinteractive | PasswordAuthentication no 이후에도 대화형 비밀번호 입력이 살아 있음 | MEDIUM | Review |
ssh.permituserenvironment | SSH 세션에 사용자 환경 변수를 주입할 수 있음 | MEDIUM | Review |
ssh.permittunnel | SSH 네트워크 터널을 만들 수 있음 | MEDIUM | Review |
ssh.maxauthtries | 연결당 인증 시도 횟수가 너무 많음 | LOW | Auto-fix |
ssh.logingracetime | 미인증 연결을 너무 오래 열어 둠 | LOW | Auto-fix |
ssh.x11forwarding | X11 포워딩이 활성화됨 | LOW | Auto-fix |
ssh.allowtcpforwarding | TCP 포워딩이 활성화됨 | LOW | Review |
ssh.clientalivecountmax | 응답 없는 SSH 세션을 너무 오래 유지함 | LOW | Auto-fix |
ssh.clientaliveinterval | 사용하지 않는 SSH 세션에 서버 측 제한 시간이 없음 | LOW | Auto-fix |
ssh.fingerprinthash | 호스트 키 지문에 오래된 해시를 사용함 | LOW | Auto-fix |
ssh.ignorerhosts | 오래된 rhosts 인증을 명시적으로 무시하지 않음 | LOW | Auto-fix |
ssh.loglevel | SSH 인증 로그가 충분히 상세하지 않음 | LOW | Auto-fix |
ssh.maxsessions | SSH 연결 하나가 너무 많은 세션을 열 수 있음 | LOW | Review |
ssh.printlastlog | 사용자에게 이전 로그인 기록을 보여 주지 않음 | LOW | Auto-fix |
ssh.strictmodes | SSH가 소유권과 파일 권한을 검사하지 않음 | LOW | Auto-fix |
ssh.tcpkeepalive | SSH가 위조 가능한 TCP keepalive를 받아들임 | LOW | Auto-fix |
ssh.usedns | SSH가 불필요한 역방향 DNS 조회를 수행함 | LOW | Auto-fix |
ssh.allowagentforwarding | SSH 에이전트 포워딩이 활성화됨 | LOW | Review |
Docker / Compose compose.
Compose 파일에 대한 네이티브 감사입니다 — 외부 스캐너가 필요 없습니다. docker run으로 직접 띄운 컨테이너도 파일이 아니라 데몬에서 읽어 함께 점검합니다. 이 발견 항목들은 항상 Manual입니다 — 수정할 파일이 없으므로, 동작하지 않을 버튼을 보여주는 대신 컨테이너를 다시 만들 때 무엇을 바꿔야 하는지 알려줍니다.
무엇을 노출로 보는가. 공개된 포트의 호스트 주소가 0.0.0.0이거나 ::이거나 아예 적혀 있지 않으면 모든 인터페이스에서 닿을 수 있는 것으로 봅니다. 마지막 경우가 많이들 걸리는 자리입니다. "6379:6379"는 주소를 적지 않았고 Docker는 그것을 모든 인터페이스에 바인딩하므로, 이 규칙에서도 데몬에서도 노출입니다. "127.0.0.1:6379:6379"처럼 루프백을 명시한 경우만 아니며, 호스트 쪽이 아예 없는 포트는 공개된 것이 아니라 애초에 해당하지 않습니다.
이 규칙은 어느 한 파일이 아니라 docker compose config가 출력하는 병합된 프로젝트에 적용합니다. Compose는 포트 매핑을 파일 간에 교체하지 않고 덧붙이므로, 베이스 파일이 루프백에 묶어 두고 오버라이드가 다시 공개하면 그 서비스는 노출된 상태입니다. 베이스만 읽으면 그 호스트를 깨끗하다고 부르게 됩니다.
| ID | 무엇을 표시하는지 | 심각도 | 수정 |
|---|---|---|---|
compose.ds016 | Docker 소켓이 컨테이너에 마운트됨 | HIGH | Manual |
compose.ds018 | 데이터 저장소가 모든 인터페이스에 노출됨 | HIGH | Auto-fix |
compose.ds001 | 컨테이너가 특권 모드로 실행됨 | HIGH | Manual |
compose.ds019 | 관리자 패널이 모든 인터페이스에 노출됨 | HIGH | Auto-fix |
compose.dr001 | 컨테이너가 호스트 네트워킹 모드를 사용함 | HIGH | Manual |
compose.ds017 | 민감한 호스트 경로가 읽기-쓰기로 마운트됨 | HIGH | Manual |
compose.ds005 | 위험한 리눅스 capability를 추가함 | HIGH | Manual |
compose.ds020 | 호스트 PID 네임스페이스를 공유함 | HIGH | Manual |
compose.dr005 | 환경 변수에 하드코딩된 비밀값 | HIGH | Manual |
compose.ds006 | no-new-privileges 강화 설정 누락 | MEDIUM | Auto-fix |
compose.ds021 | 호스트 IPC 네임스페이스를 공유함 | MEDIUM | Manual |
compose.ds009 | 컨테이너가 root로 실행됨 | MEDIUM | Manual |
compose.dr002 | 포트가 모든 인터페이스에 게시됨 | MEDIUM | Auto-fix |
compose.ds008 | 재시작 정책이 설정되지 않음 | LOW | Auto-fix |
compose.ds022 | 컨테이너 파일시스템이 쓰기 가능함 | LOW | Manual |
compose.ds023 | seccomp 필터가 꺼져 있음 | HIGH | Manual |
compose.ds024 | AppArmor 격리가 꺼져 있음 | MEDIUM | Manual |
compose.ds026 | 호스트 사용자 네임스페이스를 사용함 | MEDIUM | Manual |
compose.ds010 | 메모리 제한이 설정되지 않음 | LOW | Review |
compose.ds012 | 헬스체크가 정의되지 않음 | LOW | Manual |
compose.dr004 | env_file에서 비밀값을 불러옴 | LOW | Manual |
방화벽 firewall.
| ID | 무엇을 표시하는지 | 심각도 | 수정 |
|---|---|---|---|
firewall.inactive | 활성화된 호스트 방화벽이 없음 (ufw, firewalld, nftables, 또는 iptables) | HIGH | Review |
firewall.default-allow | 방화벽이 켜져 있지만 기본 인바운드 정책이 전부 허용 | HIGH | Review |
firewall.docker-bypass | ufw가 켜져 있는데도 publish된 컨테이너 포트가 외부에서 접근 가능 | HIGH | Manual |
자동 업데이트 updates.
| ID | 무엇을 표시하는지 | 심각도 | 수정 |
|---|---|---|---|
updates.disabled | 자동 보안 업데이트가 활성화되지 않음 (apt unattended-upgrades 또는 dnf-automatic) | MEDIUM | Review |
updates.pending-security | 보안 업데이트가 나와 있지만 설치되지 않음 | HIGH / MEDIUM | Review |
updates.reboot-required | 설치된 업데이트가 적용되려면 재부팅이 필요함 | HIGH | Manual |
updates.process-accounting | 프로세스 사용 기록 도구가 설치되지 않음 | LOW | Review |
updates.sysstat | 시스템 활동 기록 도구가 설치되지 않음 | LOW | Review |
updates.auditd | Linux 감사 데몬이 설치되지 않음 | MEDIUM | Review |
updates.debsums | 설치된 패키지 파일을 검증할 수 없음 | LOW | Review |
updates.rkhunter | rootkit 점검기가 설치되지 않음 | LOW | Review |
updates.pam-pwquality | PAM에 비밀번호 품질 구성 요소가 없음 | MEDIUM | Review |
updates.apt-show-versions | 패치 목록 도구가 설치되지 않음 | LOW | Review |
updates.sysstat-disabled | 시스템 활동 수집이 비활성화됨 | LOW | Auto-fix |
updates.apt-listchanges | APT 변경 기록 검토 도구가 설치되지 않음 | LOW | Review |
updates.pam-tmpdir | PAM 세션이 전역 임시 디렉터리를 공유함 | LOW | Review |
updates.fail2ban | 반복되는 인증 실패를 자동 차단하지 않음 | MEDIUM | Review |
노출된 서비스 ports.
ss로 호스트의 수신 대기 중인 TCP 소켓을 읽어, 루프백이 아닌 주소에 바인딩된 서비스를 표시합니다 — 컨테이너가 아니기 때문에 Compose 파일 점검으로는 볼 수 없는, 호스트에 직접 설치된 데이터베이스·관리 패널·앱입니다. 루프백 전용 바인딩은 무시하며, SSH는 정상으로 간주해 여기서 표시하지 않습니다.
| ID | 표시 대상 | 심각도 | 수정 |
|---|---|---|---|
ports.exposed-datastore | 데이터스토어(Postgres, MySQL, Redis, MongoDB 등)가 네트워크에서 접근 가능 | HIGH | Manual |
ports.exposed-admin | 관리 UI(예: Portainer)가 네트워크에서 접근 가능 | HIGH | Manual |
ports.exposed | 다른 서비스가 루프백이 아닌 주소에서 수신 대기 중이고 활성 호스트 방화벽이 없음 | LOW | Manual |
ports.redis-bind | Redis가 루프백 밖에서 수신하도록 설정됨 | MEDIUM | Review |
ports.redis-protected-mode | Redis 보호 모드가 비활성화됨 | MEDIUM | Auto-fix |
ports.redis-disable-config | Redis CONFIG 명령이 노출됨 | MEDIUM | Review |
계정 accounts.
하나의 질문을 세 가지 방식으로 던집니다. 누가 root가 될 수 있고, 그 사이에 무엇이 서 있는가. 앞의 두 답은 /etc/passwd와 /etc/shadow에서 직접 파싱합니다. /etc/shadow 읽기에는 root 권한이 필요하며, hostveil은 sudo로 자동 승격해 이를 얻습니다. 권한이 없으면 빈 비밀번호 점검은 건너뛰고 UID 0 점검은 계속 실행됩니다.
세 번째 답은 파일에서 나오지 않습니다. sudo 규칙은 별칭과, /etc/group이 아니라 LDAP로 해석될 수도 있는 그룹 지정과, include 순서를 가진 계층형 설정입니다. 그래서 Hostveil은 /etc/sudoers를 다시 해석하는 대신 sudo 자신에게 각 계정이 실제로 무엇을 실행할 수 있는지 물어봅니다. 여기에도 root 권한이 필요하며, 권한이 없으면 아무도 발견되지 않았다고 보고하는 대신 확인하지 못했다고 보고합니다.
| ID | 표시 대상 | 심각도 | 수정 |
|---|---|---|---|
accounts.uid0 | root가 아닌 계정이 root의 UID(0)를 가짐 | HIGH | Manual |
accounts.emptypassword | 로그인 계정에 빈 비밀번호가 설정됨 | HIGH | Review |
accounts.sudo-nopasswd | sudo 규칙이 비밀번호를 묻지 않고 무엇이든 root로 실행하도록 허용함 | MEDIUM | Manual |
accounts.duplicate-uid | 여러 계정이 같은 UID를 사용함 | MEDIUM | Manual |
accounts.weak-password-hash | 로그인 계정이 약한 비밀번호 해시를 사용함 | MEDIUM | Manual |
accounts.password-rounds | 비밀번호 해시 반복 횟수가 강화되지 않음 | LOW | Auto-fix |
accounts.default-umask | 새 파일의 기본 권한이 너무 넓음 | LOW | Auto-fix |
accounts.local-banner | 로컬 로그인 경고가 무단 접근을 경고하지 않음 | LOW | Review |
accounts.remote-banner | 원격 로그인 경고가 무단 접근을 경고하지 않음 | LOW | Review |
accounts.password-aging | 비밀번호 사용 기간 기본값이 너무 느슨함 | LOW | Auto-fix |
accounts.core-dumps | core dump가 명시적으로 비활성화되지 않음 | LOW | Auto-fix |
리버스 프록시 proxy.
이 도구의 사용자가 가장 갖고 있을 법하고 가장 설정해 두지 않았을 법한 구성 요소입니다. 여기 나머지 전부가 443번에서 응답하는 그것 뒤에 있습니다. 표면이 둘인 이유는 사람들이 프록시를 굴리는 방식이 둘이기 때문입니다. 패키지로 설치한 nginx는 /etc/nginx에서 읽고 include를 따라 conf.d와 sites-enabled까지 들어갑니다. 컨테이너로 띄운 Traefik은 컨테이너 영역이 이미 찾아 둔 Compose 파일에서 읽습니다.
세 가지는 일부러 점검하지 않습니다. 평문 HTTP로 서빙하는 프록시는, HTTPS로 리다이렉트만 하는 vhost와 이미 TLS를 끝낸 무언가 뒤에 있는 프록시가 둘 다 올바른데 설정만 봐서는 구분되지 않기 때문입니다. 빠진 보안 헤더는, 있으면 좋지만 그것 때문에 침해당하지는 않기 때문입니다. 그리고 인증서에 관한 것은, 만료와 발급자가 파일이 아니라 네트워크에 대한 사실이기 때문입니다.
| ID | 표시 대상 | 심각도 | 수정 |
|---|---|---|---|
proxy.traefik-api-insecure | Traefik의 API와 대시보드가 인증 없이 떠 있어, 프록시 뒤의 모든 라우터와 백엔드 주소를 나열함 | HIGH | Manual |
proxy.tls-deprecated-protocols | ssl_protocols가 아직 TLS 1.0이나 1.1을 제공함. RFC 8996이 폐기했고 요즘 브라우저는 전부 거절함 | MEDIUM | Manual |
proxy.directory-listing | autoindex on이 인덱스 파일 없는 모든 디렉터리를 훑어볼 수 있게 만듦 | MEDIUM | Manual |
proxy.no-scan-jail | fail2ban은 설치돼 있지만, 존재하지 않는 경로나 취약점 스캔 시도를 잡아내는 nginx-botsearch 규칙이 꺼져 있음 | MEDIUM | Review |
파일 권한 fileperms.
보안상 중요한 파일 목록의 권한을 확인해, 필요 이상으로 느슨하게 설정된 권한을 표시합니다.
| ID | 표시 대상 | 심각도 | 수정 |
|---|---|---|---|
fileperms.shadow | /etc/shadow가 0640보다 느슨함 | HIGH | Auto-fix |
fileperms.passwd | /etc/passwd가 root가 아닌 사용자에게 쓰기 가능 | HIGH | Auto-fix |
fileperms.group | /etc/group이 root가 아닌 사용자에게 쓰기 가능 | HIGH | Auto-fix |
fileperms.hostkey | SSH 호스트 개인 키를 root 외 사용자가 읽을 수 있음 | HIGH | Auto-fix |
fileperms.gshadow | /etc/gshadow 권한이 너무 넓음 | HIGH | Auto-fix |
fileperms.sudoers | /etc/sudoers 권한이 너무 넓음 | HIGH | Auto-fix |
fileperms.sudoers-dropins | sudoers 추가 파일 권한이 너무 넓음 | HIGH | Auto-fix |
fileperms.cron | 시스템 cron 정의를 root 외 사용자가 쓸 수 있음 | HIGH | Auto-fix |
fileperms.systemd-units | 시스템 서비스 유닛을 root 외 사용자가 쓸 수 있음 | HIGH | Auto-fix |
fileperms.docker-config | root의 Docker 자격 증명 파일 권한이 너무 넓음 | HIGH | Auto-fix |
fileperms.grub-config | GRUB 설정 권한이 너무 넓음 | HIGH | Auto-fix |
fileperms.grub2-config | GRUB2 설정 권한이 너무 넓음 | HIGH | Auto-fix |
fileperms.at-allow | at 허용 목록 권한이 너무 넓음 | MEDIUM | Auto-fix |
fileperms.at-deny | at 차단 목록 권한이 너무 넓음 | MEDIUM | Auto-fix |
fileperms.cron-allow | cron 허용 목록 권한이 너무 넓음 | MEDIUM | Auto-fix |
fileperms.cron-deny | cron 차단 목록 권한이 너무 넓음 | MEDIUM | Auto-fix |
fileperms.crontab | 시스템 crontab 권한이 너무 넓음 | HIGH | Auto-fix |
fileperms.passwd-backup | passwd 백업을 root 외 사용자가 쓸 수 있음 | HIGH | Auto-fix |
fileperms.owner | 위 파일 중 하나가 root 소유가 아님 — 누가 읽을 수 있는지는 권한 비트만으로 정해지지 않음 | HIGH | Manual |
fileperms.sshd-config | sshd_config가 root가 아닌 사용자에게 쓰기 가능 | MEDIUM | Auto-fix |
fileperms.compiler | 모든 로컬 사용자가 시스템 컴파일러를 실행할 수 있음 | LOW | Review |
커널 하드닝 sysctl.
/proc/sys에서 직접 읽습니다 — sysctl 바이너리가 필요 없습니다. 이 항목들은 사실상 모든 서버에서 안전한 값이 같은 조용한 손잡이들입니다. 공격자가 들어오는 구멍은 아니지만, 각각은 발판이 root가 되는 것과 위조된 패킷이 경로가 되는 것을 막아 줍니다. 이 커널에 없는 파라미터(예: Yama가 빌드되지 않은 경우)는 그냥 건너뜁니다.
값이 모호하지 않은 항목은 Auto-fix입니다. Hostveil은 /etc/sysctl.d 아래에 되돌릴 수 있는 드롭인 파일 하나를 쓰고 실행 중인 커널은 건드리지 않습니다. 다음 부팅 때 적용되며, 직접 sysctl --system을 실행하면 즉시 적용됩니다. 롤백은 파일을 삭제하거나 복원하고, 그 뒤 파일이 직접 수정됐다면 안전하게 거절합니다.
라우터·릴레이·다중 회선 구성에 따라 답이 달라지는 항목은 Review로 남습니다. 엄격한 역방향 경로 필터링은 비대칭 라우팅을 깨뜨릴 수 있고 Proxy ARP 비활성화는 의도적으로 릴레이 역할을 하는 호스트를 깨뜨릴 수 있습니다. 이런 항목은 영구 적용과 즉시 적용 대안을 유지해 운영자가 서버 구성에 맞게 선택합니다.
net.ipv4.ip_forward는 의도적으로 점검하지 않습니다. Docker, WireGuard, Tailscale 종료 노드, 가상화 호스트가 모두 정당하게 켜는 값이고, Hostveil은 그것을 실수와 구별할 수 없습니다.
| ID | 무엇을 표시하는지 | 심각도 | 수정 |
|---|---|---|---|
sysctl.ptrace-scope | 아무 프로세스나 형제 프로세스를 디버깅해 메모리를 읽을 수 있음 | MEDIUM | Auto-fix |
sysctl.syncookies | TCP SYN 플러드 보호가 꺼져 있음 | MEDIUM | Auto-fix |
sysctl.accept-redirects | ICMP 리다이렉트가 이 호스트의 라우팅을 바꿀 수 있음 | MEDIUM | Auto-fix |
sysctl.protected-links | 심볼릭/하드 링크 경쟁 보호가 비활성화됨 | MEDIUM | Auto-fix |
sysctl.kptr-restrict | 커널 포인터 주소가 모든 사용자에게 보임 | LOW | Auto-fix |
sysctl.dmesg-restrict | 아무 사용자나 커널 로그를 읽을 수 있음 | LOW | Auto-fix |
sysctl.sysrq | Magic SysRq가 전부 활성화됨 | LOW | Review |
sysctl.rp-filter | 출발지 주소 위조 필터가 꺼져 있음 (strict와 loose 모두 통과) | LOW | Review |
sysctl.accept-source-route | 소스 라우팅 패킷을 허용함 | MEDIUM | Auto-fix |
sysctl.send-redirects | ICMP 리다이렉트를 전송함 | MEDIUM | Review |
sysctl.suid-dumpable | 권한 프로세스가 core dump를 쓸 수 있음 | MEDIUM | Auto-fix |
sysctl.protected-fifos | FIFO 보호가 약함 | MEDIUM | Auto-fix |
sysctl.protected-regular | 일반 파일 보호가 약함 | MEDIUM | Auto-fix |
sysctl.unprivileged-bpf | 비권한 사용자가 BPF를 로드할 수 있음 | MEDIUM | Auto-fix |
sysctl.perf-events | 성능 이벤트가 사용자에게 노출됨 | MEDIUM | Auto-fix |
sysctl.icmp-broadcasts | 브로드캐스트 ping을 허용함 | LOW | Auto-fix |
sysctl.bogus-icmp-errors | 잘못된 ICMP 오류를 무시하지 않음 | LOW | Auto-fix |
sysctl.log-martians | 의심스러운 출발지 주소를 로그하지 않음 | LOW | Auto-fix |
sysctl.aslr | ASLR이 완전히 활성화되지 않음 | MEDIUM | Auto-fix |
sysctl.core-uses-pid | core dump 이름에 PID가 없음 | LOW | Auto-fix |
sysctl.ctrl-alt-del | Ctrl-Alt-Delete가 즉시 재부팅함 | LOW | Auto-fix |
sysctl.bpf-jit-harden | BPF JIT 보호가 충분하지 않음 | MEDIUM | Auto-fix |
sysctl.tty-ldisc-autoload | TTY가 line discipline을 자동 로드함 | MEDIUM | Auto-fix |
sysctl.ipv6-accept-redirects-all | IPv6 redirect를 전역으로 허용함 | MEDIUM | Auto-fix |
sysctl.ipv6-accept-redirects-default | 새 IPv6 interface가 redirect를 허용함 | MEDIUM | Auto-fix |
sysctl.ipv6-accept-source-route-all | IPv6 source routing을 허용함 | MEDIUM | Auto-fix |
sysctl.ipv6-accept-source-route-default | 새 IPv6 interface가 source routing을 허용함 | MEDIUM | Auto-fix |
sysctl.ipv6-send-redirects | IPv6 redirect를 전송함 | LOW | Auto-fix |
sysctl.ipv4-default-accept-redirects | 새 IPv4 interface가 redirect를 허용함 | MEDIUM | Auto-fix |
sysctl.ipv4-default-rp-filter | 새 IPv4 interface에 출발지 검증이 없음 | LOW | Review |
sysctl.proxy-arp-all | Proxy ARP가 전역으로 활성화됨 | MEDIUM | Review |
sysctl.proxy-arp-default | 새 interface가 Proxy ARP를 활성화함 | MEDIUM | Review |
sysctl.multicast-forwarding | IPv4 multicast forwarding이 활성화됨 | LOW | Review |
sysctl.bootp-relay | BOOTP relay가 활성화됨 | LOW | Review |
sysctl.tcp-rfc1337 | TCP TIME-WAIT 보호가 꺼져 있음 | LOW | Auto-fix |
sysctl.module-dccp | 선택적 DCCP 프로토콜을 사용할 수 있음 | LOW | Review |
sysctl.module-sctp | 선택적 SCTP 프로토콜을 사용할 수 있음 | LOW | Review |
sysctl.module-rds | 선택적 RDS 프로토콜을 사용할 수 있음 | LOW | Review |
sysctl.module-tipc | 선택적 TIPC 프로토콜을 사용할 수 있음 | LOW | Review |
sysctl.module-usbstorage | USB 저장 장치 드라이버를 사용할 수 있음 | LOW | Review |
Docker 데몬 dockerd.
Compose 점검은 서비스가 무엇을 선언했는지를 보고, CVE 점검은 그 서비스가 돌리는 이미지를 봅니다. 이 영역은 그 둘 밑에 있는 데몬 자체 — 실제로 root를 쥐고 있는 부분 — 을 봅니다.
여기서 가장 심각한 항목은 Hostveil 전체에서 가장 심각한 항목이므로 분명히 말해 둘 필요가 있습니다. TLS 클라이언트 검증 없이 TCP 포트로 듣고 있는 데몬은 그 포트에 닿을 수 있는 누구에게나 호스트의 root입니다. 비밀번호도, 취약점도 필요 없고, 파일시스템을 통째로 마운트한 컨테이너를 시작하는 HTTP 요청 하나면 됩니다. 소켓 그룹의 멤버십은 로컬 계정에서 얻는 같은 권한이고, sudo와 달리 아무것도 묻지 않고 아무것도 기록하지 않습니다.
출처가 셋이고, 서로 어긋납니다. Docker 설정은 /etc/docker/daemon.json, 서비스 유닛의 플래그, 그리고 실행 중인 데몬 안에 각각 있습니다 — 재시작 없이 파일만 고쳤다면 이 셋은 서로 다른 답입니다. 그래서 데몬 기본값은 docker info에서 읽습니다. 누가 적어 둔 내용이 아니라 데몬이 실제로 적재한 내용이기 때문입니다. 소켓과 TLS 설정은 파일과 유닛을 함께 읽습니다. docker info가 아예 보고하지 않기 때문입니다. ss는 직접 확인해 볼 수 있는 엔드포인트를 증거로 덧붙이되, 발견 항목의 존재 여부를 결정하지는 않습니다.
여기의 모든 항목은 의도적으로 Manual입니다. 점검기는 실행 중인 데몬을 읽지만, 수정은 데몬이 재시작하기 전까지 다시 읽지 않는 파일을 고치는 셈입니다. 문제는 Docker를 재시작하면 호스트의 모든 컨테이너가 멈춘다는 것입니다. 공격자에게 보이는 것은 아무것도 바꾸지 않은 채 항목을 해결됨으로 표시하고 점수만 올리는 수정은 수정이 없는 편만 못합니다. 그래서 조치 안내에 정확한 변경 내용만 담아 두고, 적용 시점은 운영자가 직접 고르게 합니다.
| ID | 의미 | 심각도 | 수정 |
|---|---|---|---|
dockerd.api-unauthenticated | Docker API가 TLS 클라이언트 검증 없이 TCP로 제공됩니다 — 포트에 닿는 누구에게나 인증 없는 root | HIGH | Manual |
dockerd.socket-world-writable | 모든 로컬 계정이 Docker 소켓에 접속할 수 있고, 따라서 root가 될 수 있습니다 | HIGH | Manual |
dockerd.api-tls-unverified | API가 암호화되어 있지만 아무 클라이언트나 받습니다 — tlsverify 없는 TLS는 서버를 인증할 뿐 호출자를 인증하지 않습니다 | HIGH | Manual |
dockerd.group-members | root 외의 계정이 소켓 그룹을 갖고 있습니다. 비밀번호 확인도 감사 기록도 없는 root 동등 권한입니다 | MEDIUM / HIGH | Manual |
dockerd.no-new-privileges | 컨테이너가 여전히 setuid 바이너리로 권한을 얻을 수 있습니다 | MEDIUM | Manual |
dockerd.userns-remap | 사용자 네임스페이스가 재매핑되지 않아 컨테이너 root가 곧 호스트 root입니다 | LOW | Manual |
dockerd.live-restore | 데몬을 재시작하면 모든 컨테이너가 멈춥니다. 그래서 데몬 업데이트가 미뤄집니다 | LOW | Manual |
dockerd.group-members는 그룹을 가진 계정이 사람일 때 MEDIUM, 그중 하나라도 서비스 계정 — 시스템 UID이거나 대화형 로그인이 없는 계정 — 이면 HIGH입니다. CI 러너나 모니터링 에이전트에게 root가 될 능력을 일부러 주는 사람은 없고, 한 번도 로그인하지 않는 자격 증명은 아무도 지켜보지 않는 자격 증명입니다.
루프백 전용 TCP 소켓은 표시하지 않습니다. 유닉스 소켓이 닿지 않던 곳에 닿지 않고, 데몬 앞에 프록시를 두는 문서화된 방법이기 때문입니다. rootless로 실행 중인 데몬은 userns-remap과 group-members가 억제됩니다. API 항목은 그대로 HIGH입니다. rootless는 피해 범위를 호스트가 아니라 그 사용자의 컨테이너로 한정하며 그 차이는 설명문에 적히지만, 포트에 닿기 어려워지는 것은 아니기 때문입니다. live-restore는 Docker가 지원하지 않는 swarm 노드에서 억제됩니다.
서비스 하드닝 systemd.
자체 호스팅 서버의 절반가량은 컨테이너 밖에 있습니다. Compose 도메인은 컨테이너가 선언한 내용 — privileged, root 실행, no-new-privileges 해제 — 을 읽는데, 유닛 파일로 뜬 서비스에도 똑같은 판단이 필요합니다. 이 도메인이 생기기 전까지는 아무도 그쪽을 보지 않았습니다.
답은 systemd가 이미 알고 있습니다. systemctl show는 유닛 파일과 모든 드롭인, 그리고 기본값까지 병합한 뒤의 실효 값을 알려줍니다. 그래서 Hostveil이 읽는 값은 어떤 파일에 적힌 값이 아니라 서비스가 실제로 그 값으로 돌아간다는 뜻입니다.
직접 만든 유닛만 검사합니다. 유닛 파일이 /etc/systemd/system 또는 /usr/local/lib/systemd/system 아래에 있으면 대상이 됩니다 — 사용자가 직접 쓰거나 손으로 설치한 것들입니다. 배포판 유닛은 배포판이 자기 일정에 맞춰 하드닝하며, 그걸 뒤집는 것은 남의 서비스 유지보수를 떠안는 일입니다. 게다가 그것들까지 보고하면 사용자가 고르지도 않은 소프트웨어에 대한 수십 건의 항목에 정작 본인 서비스가 묻힙니다. 그렇게 되면 아무도 그 도메인을 읽지 않습니다.
| ID | 의미 | 심각도 | 수정 |
|---|---|---|---|
systemd.no-new-privileges | setuid 바이너리를 통해 서비스가 권한을 얻을 수 있습니다. 서비스 안의 발판이 서비스 밖의 발판으로 바뀌는 통상적인 경로입니다 | LOW / MEDIUM | Review |
systemd.protect-system | 서비스가 /usr, /boot, /etc에 쓸 수 있습니다. /etc를 고칠 수 있는 서비스는 이 호스트의 모든 로그인을 손에 넣습니다 | MEDIUM / LOW | Manual |
systemd.protect-home | 서비스가 /home, /root, /run/user를 읽을 수 있습니다. 모든 사용자의 SSH 키, 클라우드 자격 증명, 비밀번호 데이터베이스가 그 안에 있습니다 | MEDIUM / LOW | Manual |
systemd.private-tmp | 서비스가 다른 모든 프로세스와 /tmp를 공유합니다. 심볼릭 링크 경쟁이 벌어지는 바로 그 자리입니다 | LOW | Manual |
systemd.private-devices | 서비스가 호스트 디바이스에 접근할 수 있음 | MEDIUM | Manual |
systemd.protect-kernel-tunables | 서비스가 커널 설정을 바꿀 수 있음 | MEDIUM | Manual |
systemd.protect-kernel-modules | 서비스가 kernel module을 바꿀 수 있음 | MEDIUM | Review |
systemd.protect-control-groups | 서비스가 제어 그룹을 바꿀 수 있음 | MEDIUM | Manual |
systemd.protect-kernel-logs | 서비스가 커널 로그에 접근할 수 있음 | LOW | Review |
systemd.protect-clock | 서비스가 시스템 시계를 바꿀 수 있음 | LOW | Review |
systemd.restrict-suid-sgid | 서비스가 setuid 파일을 만들 수 있음 | MEDIUM | Review |
systemd.restrict-namespaces | 서비스가 네임스페이스를 만들 수 있음 | MEDIUM | Manual |
systemd.lock-personality | 서비스가 실행 퍼스널리티를 바꿀 수 있음 | LOW | Review |
systemd.memory-deny-write-execute | 서비스가 쓰기와 실행이 모두 가능한 메모리를 만들 수 있음 | MEDIUM | Manual |
심각도가 두 가지이며, 어느 쪽인지는 실행 계정이 정합니다. no-new-privileges는 root가 아닌 계정으로 도는 서비스에서 MEDIUM, root로 도는 서비스에서 LOW입니다. root 서비스는 이미 무엇이든 할 수 있으므로 이 설정이 막는 setuid 경로의 값어치가 거기서는 작습니다. 파일시스템 보호 두 가지는 같은 이유로 반대 방향입니다 — no-new-privileges의 값어치가 가장 작은 자리에서 가장 큽니다.
여기의 모든 항목은 의도적으로 Manual입니다. 드롭인은 두 줄이고 Hostveil이 쓸 수도 있습니다. 하지만 쓰는 것과 안전하다고 아는 것은 다릅니다. ProtectSystem=full은 /usr 아래에 쓰는 서비스를 망가뜨리고, PrivateTmp=yes는 /tmp로 파일을 주고받는 두 서비스를 망가뜨립니다. 어느 쪽도 다음 재시작 전까지는 드러나지 않는데, 자체 호스팅 환경에서 그 시점은 다음 재부팅입니다 — 그리고 돌아오지 못한 서비스가 하필 데이터를 쥐고 있던 쪽입니다. 그래서 수정 안내에 정확한 경로와 정확한 두 줄을 담고, 재시작해서 지켜볼 시점은 사용자가 고릅니다.
이 systemd가 구현하지 않은 속성은 아무 값도 보고되지 않으며, 값이 없다는 것은 보호가 꺼져 있다는 근거가 아닙니다. 그런 경우는 항목으로 만들지 않고 그대로 둡니다.
AI 에이전트 런타임 agent.
셀프호스트 AI 에이전트인 OpenClaw와 Hermes Agent는 네트워크 게이트웨이를 띄우고 API 키를 홈 디렉터리에 저장합니다. 설정이 잘못되면 최악의 경우 외부에서 그 게이트웨이에 접근해, 내 파일을 읽고 내 권한으로 명령을 실행하는 에이전트를 그대로 조종할 수 있습니다.
이 도메인은 해당 런타임이 실제로 설치되어 있지 않으면 아예 건너뜁니다. 에이전트를 쓴 적 없는 호스트는 이 항목으로 점수를 매기지 않습니다. Hostveil은 /etc/passwd에서 홈 디렉터리를 찾은 뒤 아래 표에 적힌 경로만 stat합니다. 홈 디렉터리 전체를 훑지 않습니다.
두 프로젝트 모두 자체 설정 점검 도구(openclaw security audit)를 제공하며, 설정 항목 자체는 그쪽이 훨씬 촘촘합니다. Hostveil이 더하는 것은 그 도구들이 볼 수 없는 부분입니다. 게이트웨이가 실제로 외부에서 닿는 주소에서 대기 중인지, 그 앞에 방화벽이 있는지, 자격 증명 파일을 같은 호스트의 다른 계정이 읽을 수 있는지입니다.
| ID | 무엇을 잡아내는지 | 심각도 | 수정 |
|---|---|---|---|
agent.auth-disabled | 외부에 노출된 게이트웨이가 인증 없이 요청을 받음 | HIGH | Manual |
agent.gateway-exposed | 게이트웨이가 네트워크에서 닿는 주소에 바인딩됨 | HIGH | Manual |
agent.secret-exposed | 자격 증명 파일 또는 디렉터리를 소유자 외에도 읽을 수 있음 | HIGH | Auto-fix |
agent.exec-unrestricted | 에이전트가 승인 절차 없이 셸 명령을 실행할 수 있음 | HIGH | Review |
agent.elevated-enabled | 호스트 수준의 권한 상승 실행이 활성화됨 | HIGH | Auto-fix |
agent.sandbox-off | 에이전트 도구가 샌드박스 없이 호스트에서 직접 실행됨 | HIGH | Manual |
agent.control-ui-insecure | 제어 UI의 인증 또는 기기 확인이 꺼져 있음 | HIGH | Auto-fix |
agent.config-perms | 설정·상태 경로가 업스트림 기본값보다 느슨함 | MEDIUM | Auto-fix |
agent.ssrf-private-network | 에이전트 브라우저가 사설망 주소에 접근할 수 있음 | MEDIUM | Auto-fix |
설정 관련 항목은 이제 대부분 고칠 수 있습니다. OpenClaw 설정은 JSON5라 사용자가 직접 쓴 주석이 들어 있는데, Hostveil은 값 하나에 해당하는 바이트만 정확히 바꾸고 나머지 파일은 건드리지 않습니다. 그리고 자기가 쓴 결과를 다시 읽어, 지정한 키 외에 달라진 것이 있으면 저장을 거부합니다. 키 하나를 바꾼 변경은 한 줄 diff로 남고, 주석은 쓴 자리에 그대로 있습니다.
세 항목은 여전히 Manual이며, 이유는 각각 다릅니다. agent.sandbox-off는 off가 잘못됐다는 것은 알아도 샌드박스를 켜는 값이 무엇인지 모르며, 찍어서 넣은 값은 조치가 아닙니다. agent.auth-disabled는 키가 아예 없는 상태가 안전한 설정인데(OpenClaw는 이 값이 없으면 닫힌 쪽으로 동작합니다) Hostveil은 값을 바꿀 뿐 지우지는 않습니다. 그리고 노출된 게이트웨이를 다시 바인딩하면 원격으로 관리하던 에이전트와의 연결이 끊길 수 있습니다. Hermes 설정은 전부 손대지 않습니다. 설정 파일, .env, systemd 유닛, docker -e 플래그 중 어디서 온 값인지 판별할 수 없기 때문입니다.
Auto-fix되는 것은 권한 관련 두 항목뿐이며, 둘 다 접근 권한을 줄이기만 하는 chmod입니다. 체크포인트가 남으므로 hostveil rollback으로 이전 권한을 그대로 되돌릴 수 있습니다.
이미지 CVE cve. (선택)
Trivy가 설치되어 있으면, Hostveil은 Compose 서비스가 실행하는 이미지를 스캔하여 알려진 심각·높음·보통 등급의 취약점을 찾습니다. 발견 항목은 취약점 단위가 아니라 이미지 단위로 보고됩니다 — 이미지 하나가 CVE 수백 개를 싣고 있는 건 흔한 일이고, 그 전부가 같은 조치 하나를 공유하기 때문입니다.
스캔된 이미지마다 최대 2개의 발견 항목이 생기며, 실제로 할 수 있는 조치를 기준으로 나뉩니다:
| ID | 탐지 내용 | 심각도 | 수정 |
|---|---|---|---|
cve.outdated-image | 업스트림에 이미 패치가 나온 취약점들 | 해당 묶음 중 최악 | Review |
cve.unpatched-image | 아직 아무도 패치를 내놓지 않은 취약점들 | 해당 묶음 중 최악 | Unavailable |
각 항목은 심각도별 개수와 가장 심각한 CVE 몇 개를 함께 보여줍니다. 전체 취약점 ID 목록은 항목의 evidence에 들어 있으며 hostveil scan --json으로 확인할 수 있습니다.
Trivy는 패키지가 3.0.11-1~deb12u2 버전에 도달해야 한다고 알려줍니다. 이건 이미지 안에 있는 OS 패키지 버전이지 이미지 태그가 아니고, 둘 사이를 잇는 매핑은 존재하지 않습니다 — 개별 CVE 수정을 만들려면 그 매핑을 지어내야 합니다. 애초에 CVE 하나에 대해 할 수 있는 일이 전체에 대해 할 일과 다르지 않으므로, 정직한 단위는 이미지입니다. 이미 사용자가 고른 태그를 다시 받는 데는 버전 매핑이 필요 없고, 약속하는 것도 "태그를 다시 해석한다"까지일 뿐 특정 CVE가 사라진다고 말하지 않습니다. 다이제스트로 고정된 이미지에는 수정이 붙지 않습니다 — 다이제스트를 pull해봐야 아무것도 바뀌지 않기 때문입니다.
패치가 없는 취약점은 수정 가능한 쪽에 섞거나 버리지 않고 별도 항목으로 남깁니다. 이걸 뭉개버리면 취약점이 전부 미패치인 이미지는 리포트에서 통째로 사라지고, Hostveil은 그저 손쓸 방법이 없을 뿐인 호스트를 깨끗하다고 말하게 됩니다. 그래서 고칠 수 있는 척하는 대신 Unavailable이라는 정식 상태로 모델링합니다.
각 fix가 실제로 하는 일
위 표에서 Fix 열에 Auto-fix 또는 Review가 적힌 항목은 모두 internal/fix에 등록된 fix를 갖고 있고, 아래는 그 fix가 실제로 하는 일입니다. 현재 빌드에서 바로 생성하므로 코드가 하지 않는 일을 적어 둘 수 없습니다. Review 항목 중 대안이 둘 이상인 것은 제공하는 모든 대안을 나열하며, 첫 번째가 fix --all --review를 포함해 어디서나 먼저 선택되는 대안입니다. 항목별 설명 문구는 아직 한국어로 새로 쓰지 않아 원문 그대로 영어로 보여 줍니다.
컨테이너
compose.dr002Auto-fix-
Bind published port to 127.0.0.1
✓ Once recreated, the service stops being reachable from the network at all — only processes on this host can reach it, closing off whatever guessing or scanning the open port currently invites.
⚠ Once app is recreated, it is reachable only from this host. If you access it from another machine, use an SSH tunnel, VPN, or reverse proxy. This edits the file, not the running container:
docker compose up -d appis what puts the change into force. compose.ds006Auto-fix-
Add security_opt no-new-privileges:true
✓ A process that breaks into this container can no longer gain more privilege than it started with via a setuid binary — one of the more common container-escape stepping stones, closed.
⚠ This edits the file, not the running container:
docker compose up -d appis what puts the change into force. compose.ds008Auto-fix-
Set restart: unless-stopped
✓ The service comes back on its own after a crash, an OOM kill, or a host reboot instead of silently staying down until someone notices.
⚠ This edits the file, not the running container:
docker compose up -d appis what puts the change into force. compose.ds010Review-
-
Limit app to 1g — typical application container (recommended)
✓ Gives the container a memory ceiling, so a leak or a runaway process inside it gets OOM-killed and restarted instead of exhausting the host's memory and taking every other service down with it.
⚠ Too low a limit gets the container OOM-killed under load. Start generous, watch
docker stats, and tighten later. This is a file edit, so it is fully reversible. This edits the file, not the running container:docker compose up -d appis what puts the change into force. -
Limit app to 512m — small service (proxy, exporter, static site)
✓ Gives the container a memory ceiling, so a leak or a runaway process inside it gets OOM-killed and restarted instead of exhausting the host's memory and taking every other service down with it.
⚠ Too low a limit gets the container OOM-killed under load. Start generous, watch
docker stats, and tighten later. This is a file edit, so it is fully reversible. This edits the file, not the running container:docker compose up -d appis what puts the change into force. -
Limit app to 2g — database or JVM service
✓ Gives the container a memory ceiling, so a leak or a runaway process inside it gets OOM-killed and restarted instead of exhausting the host's memory and taking every other service down with it.
⚠ Too low a limit gets the container OOM-killed under load. Start generous, watch
docker stats, and tighten later. This is a file edit, so it is fully reversible. This edits the file, not the running container:docker compose up -d appis what puts the change into force.
-
compose.ds018Auto-fix-
Bind published port to 127.0.0.1
✓ Once recreated, the service stops being reachable from the network at all — only processes on this host can reach it, closing off whatever guessing or scanning the open port currently invites.
⚠ Once app is recreated, it is reachable only from this host. If you access it from another machine, use an SSH tunnel, VPN, or reverse proxy. This edits the file, not the running container:
docker compose up -d appis what puts the change into force. compose.ds019Auto-fix-
Bind published port to 127.0.0.1
✓ Once recreated, the service stops being reachable from the network at all — only processes on this host can reach it, closing off whatever guessing or scanning the open port currently invites.
⚠ Once app is recreated, it is reachable only from this host. If you access it from another machine, use an SSH tunnel, VPN, or reverse proxy. This edits the file, not the running container:
docker compose up -d appis what puts the change into force.
SSH
ssh.allowagentforwardingAuto-fix-
Disable SSH agent forwarding
✓ Stops a compromised host from riding a forwarded SSH agent to authenticate elsewhere as the user, using credentials that were never actually stored here.
⚠ Existing workflows that hop through this host using a forwarded agent will stop working.
ssh.allowtcpforwardingAuto-fix-
Disable SSH TCP forwarding
✓ Closes SSH's local, remote, and dynamic port forwarding — a common pivot and tunnel-out vector for anyone who gets a session on this host.
⚠ Existing local, remote, and dynamic SSH tunnels will stop working.
ssh.clientalivecountmaxAuto-fix-
Limit unanswered SSH keepalives
✓ Bounds how long a dead or hung session is held open, freeing the session slot instead of leaving it reserved indefinitely.
ssh.clientaliveintervalAuto-fix-
Probe idle SSH clients every five minutes
✓ Detects and disconnects idle or dead clients on a schedule, instead of leaving a session open for as long as the network happens to keep the TCP connection alive.
⚠ Long-running idle sessions may be disconnected when their clients stop responding.
ssh.emptypasswordsAuto-fix-
Disable empty passwords
✓ Closes SSH login with a blank password — the one credential check an attacker never even has to guess.
ssh.fingerprinthashAuto-fix-
Use SHA-256 host-key fingerprints
✓ Uses a stronger hash for the host-key fingerprint shown to users and tooling verifying this server's identity.
ssh.gatewayportsAuto-fix-
Bind remote-forwarded ports to loopback only
✓ Stops an
ssh -Rremote-forwarded port becoming reachable from other machines, closing a route around whatever this host's own firewall is doing.⚠ If you rely on
ssh -Rtunnels being reachable from other machines, this closes them to loopback. ssh.hostbasedauthAuto-fix-
Disable host-based authentication
✓ Removes a trust-based login path that bypasses per-user keys entirely.
⚠ If any user logs in via host-based trust rather than their own key, this removes that path.
ssh.ignorerhostsAuto-fix-
Ignore legacy rhosts trust files
✓ Ignores legacy .rhosts trust files, closing a decades-old authentication bypass that has no place on a hardened host.
ssh.kbdinteractiveAuto-fix-
Disable keyboard-interactive authentication
✓ Closes the same password-equivalent prompt path PasswordAuthentication closes, for the keyboard-interactive method some clients use instead.
⚠ PAM-based one-time codes (2FA prompts) also use this mechanism — keep it enabled if your logins go through one.
ssh.logingracetimeAuto-fix-
Lower LoginGraceTime to 60 seconds
✓ Shortens the window an unauthenticated connection can hold a login slot open, reducing exposure to connection-slot exhaustion.
ssh.loglevelAuto-fix-
Log SSH key fingerprints
✓ Logs the key fingerprint used on every login, giving the operator forensic evidence of exactly which key authenticated — not just that a login succeeded.
ssh.maxauthtriesAuto-fix-
Lower MaxAuthTries to 3
✓ Throttles how many credential guesses a single SSH connection gets before it is dropped, slowing a brute-force attempt against this host specifically.
ssh.maxsessionsAuto-fix-
Limit multiplexed SSH sessions
✓ Caps multiplexed sessions per connection, limiting how much one compromised or stolen connection can do at once.
⚠ Clients opening more than two sessions over one SSH connection will be refused.
ssh.passwordauthAuto-fix-
Disable password authentication
✓ Removes password-guessing as an SSH attack surface entirely — only a held key gets in from here on.
⚠ Make sure key-based login works BEFORE applying this, or you may lock yourself out of SSH.
ssh.permittunnelAuto-fix-
Disable SSH tun/tap tunnels
✓ Disables SSH's own VPN-like tun/tap tunneling, removing a route around whatever network controls this host is behind.
⚠ Existing SSH VPN or tun/tap workflows will stop working.
ssh.permituserenvironmentAuto-fix-
Disable user-supplied SSH environments
✓ Stops a user's ~/.ssh/environment overriding the server process's own environment — a known way to hijack LD_PRELOAD or similar into a login session.
⚠ Login automation that relies on ~/.ssh/environment will stop receiving those variables.
ssh.printlastlogAuto-fix-
Show the previous login
✓ Shows the previous login on every connect, so a user has a chance to notice a login they did not make.
ssh.rootloginReview-
-
Allow root only with an SSH key (prohibit-password) (recommended)
✓ Keeps key-based root access working while removing the one thing root logins are actually dangerous for: a guessable or stolen password.
⚠ Keep a working key for root, or use a sudo user instead.
-
Disable root login entirely (no)
✓ Removes direct root login over SSH entirely, so compromising this host's authentication means compromising a named, sudo-capable user instead of the one account every attacker already knows the name of.
⚠ Make sure another user can log in and use sudo before applying this.
-
ssh.strictmodesAuto-fix-
Enforce SSH login-file ownership
✓ Refuses to honor login files (keys, config) with loose ownership or permissions, closing a local tampering vector.
ssh.tcpkeepaliveAuto-fix-
Disable TCP keepalives for SSH
✓ Stops relying on spoofable TCP keepalives for liveness, leaving ClientAliveInterval's encrypted check as the one source of truth for whether a session is still alive.
⚠ Use ClientAliveInterval for encrypted liveness checks; dead connections may otherwise take longer to disappear from intermediate network devices.
ssh.usednsAuto-fix-
Disable SSH reverse-DNS lookups
✓ Removes a DNS lookup from the login path — both a minor timing/DoS surface and a spoofing vector, for a lookup the login never actually needed.
ssh.x11forwardingAuto-fix-
Disable X11 forwarding
✓ Removes an X11 forwarding channel that malware or an attacker with a foothold could otherwise ride out of the session.
방화벽
firewall.default-allowReview-
Allow SSH on 22/tcp, then set ufw's default inbound policy to deny
✓ Flips a firewall that is already running from allow-by-default to deny-by-default, closing every port nothing has explicitly opened on a host where a firewall was already assumed to be doing that job.
⚠ Every inbound port except 22/tcp stops being reachable the moment this runs, including anything a container publishes. There is no rollback checkpoint — exec fixes are not file-backed — so undoing it means
ufw default allow incomingby hand. firewall.inactiveReview-
Allow SSH on 22/tcp, then enable ufw with a default-deny inbound policy
✓ Turns on default-deny, so only the ports hostveil confirmed sshd is actually listening on stay reachable — everything else stops accepting connections from off this host.
⚠ Every inbound port except 22/tcp stops being reachable the moment this runs, including anything a container publishes. There is no rollback checkpoint — exec fixes are not file-backed — so undoing it means
ufw disableby hand.
업데이트
updates.apt-listchangesReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.apt-show-versionsReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.auditdReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.debsumsReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.disabledReview-
Install and enable dnf-automatic
✓ Once enabled, security patches for every installed package arrive on their own instead of depending on someone remembering to run apt/dnf.
⚠ Installs dnf-automatic and enables its timer. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.fail2banReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.pam-pwqualityReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.pam-tmpdirReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.pending-securityReview-
Refresh apt metadata and fully upgrade installed packages
✓ Applies whatever security patches are already sitting unapplied right now — closes today's already-known gaps rather than waiting for the next automatic run.
⚠ Upgrading packages can restart the services they belong to, including sshd and the Docker daemon — and a container whose compose file has no restart policy in force stays down until
docker compose up -dbrings it back. A kernel or libc update may also need a reboot to actually take effect. There is no rollback checkpoint; undoing this means downgrading the affected packages by hand. updates.process-accountingReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.rkhunterReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.sysstatReview-
Install and enable auditd
✓ Installs a kernel-level audit trail of security-relevant actions — file access, privilege changes — that survives a process exiting or an application log being cleared.
⚠ Installs auditd and enables its service. There is no rollback checkpoint; undoing this means removing the package by hand.
updates.sysstat-disabledAuto-fix-
Set ENABLED to true
✓ Turns on the data collection the sysstat package already has installed, so historical resource data actually starts accumulating instead of the package sitting present but silent.
CVE
cve.outdated-imageReview-
-
Pull the new image and recreate app now (recommended)
✓ Re-resolves the tag to whatever it currently points at, which is how the vendor ships fixes for a floating-tag image — it may include a patch for the CVE that triggered this finding, though hostveil cannot guarantee it does, since the same tag can also move for unrelated reasons. On a host where staying current matters more than staying unchanged, this is the fastest way to find out.
⚠ This recreates the container: the service goes down briefly and comes back on a different image. There is no rollback checkpoint: exec fixes are not file-backed, so Hostveil cannot undo this. Note the current image ID (
docker compose -f /opt/example/docker-compose.yml images) before applying, so you can pin it back if the new one misbehaves. The new image may also carry unrelated upstream changes since you last pulled — on a host you are not trying to disturb, that is a real cost to weigh against a fix that is not guaranteed to land anyway. -
Download the new image only; recreate app on your own schedule
✓ Downloads the new image without touching the running container, so there is a chance to inspect it — its digest, its changed packages, its release notes — before deciding whether pulling it in is worth the risk to a service that is stable today.
⚠ This changes nothing that is running: the image is downloaded but the container keeps using the old one until you recreate it, and the finding will still be reported until then. There is no rollback checkpoint: exec fixes are not file-backed, so Hostveil cannot undo this.
-
포트
ports.redis-bindAuto-fix-
Bind Redis to loopback
✓ Redis ships with no authentication by default; binding it to loopback means the only way to reach it is already being on this host — closes it to the network entirely.
⚠ Restarting Redis can interrupt clients; validate this setting first.
ports.redis-disable-configAuto-fix-
Disable the Redis CONFIG command
✓ Removes remote CONFIG SET/GET, closing the well-known Redis-to-remote-code-execution chain that writes a webshell or SSH key to disk via CONFIG SET dir/dbfilename.
⚠ Restarting Redis can interrupt clients; validate this setting first.
ports.redis-protected-modeAuto-fix-
Enable Redis protected mode
✓ Turns on Redis's own built-in refusal to serve remote clients when no password is set — a second, independent barrier behind the bind address, so a firewall or compose slip elsewhere doesn't leave Redis exposed anyway.
⚠ Restarting Redis can interrupt clients; validate this setting first.
계정
accounts.core-dumpsAuto-fix-
Set a hard core-size limit of zero
✓ Stops a crashed privileged process writing a core file to disk — the easiest place a live secret in memory (a password, a key) ends up sitting in plaintext after a crash.
accounts.default-umaskAuto-fix-
Set the default umask to 027
✓ New files a user creates default to unreadable by other accounts on the box, closing the easiest way a stray world-readable file leaks something it shouldn't.
accounts.emptypasswordReview-
Lock alice with
passwd -l✓ Closes the one account whose password prompt currently succeeds for anyone who tries it, including a stranger who has never touched this host before.
⚠ If alice is your only way to reach this machine locally (console, su) and it has no other credential, locking it removes that access. Confirm you have another route in — an SSH key, a different sudo-capable account — before applying. The remote SSH path is already closed by ssh.emptypasswords; this only closes the local one.
-
Append the standard access warning
✓ Puts a legal notice in front of anyone who logs into this host, local or remote, which several jurisdictions require before monitoring or logging a session can be used as evidence against an intruder.
⚠ Have counsel or the system owner approve login-banner wording for this organization.
accounts.password-agingAuto-fix-
Harden password aging defaults
✓ Forces credentials to actually expire and blocks changing a password back to itself immediately, so a leaked password has a shelf life instead of being valid forever.
accounts.password-roundsAuto-fix-
Harden password hashing rounds
✓ Raises the SHA-512 rounds used to hash new or changed passwords, slowing an offline dictionary or brute-force attempt against a stolen /etc/shadow by roughly the same factor.
-
Append the standard access warning
✓ Puts a legal notice in front of anyone who logs into this host, local or remote, which several jurisdictions require before monitoring or logging a session can be used as evidence against an intruder.
⚠ Have counsel or the system owner approve login-banner wording for this organization.
파일 권한
fileperms.at-allowAuto-fix-
Tighten /etc/shadow to 0640
✓ Keeps control of who is allowed to schedule future one-off commands to root alone.
fileperms.at-denyAuto-fix-
Tighten /etc/shadow to 0640
✓ Keeps control of who is blocked from scheduling future one-off commands to root alone.
fileperms.compilerAuto-fix-
Tighten /etc/shadow to 0640
✓ Removes the system compiler from every local account's reach by default, taking away a convenient way to build exploit code from a local foothold.
fileperms.cronAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account scheduling a command to run as root through a system cron definition.
fileperms.cron-allowAuto-fix-
Tighten /etc/shadow to 0640
✓ Keeps control of who is allowed to schedule recurring cron jobs to root alone.
fileperms.cron-denyAuto-fix-
Tighten /etc/shadow to 0640
✓ Keeps control of who is blocked from scheduling recurring cron jobs to root alone.
fileperms.crontabAuto-fix-
Tighten /etc/shadow to 0640
✓ Closes an avoidable persistence path: the system crontab can run commands as root, and this keeps it restricted to what the scheduler itself needs.
fileperms.docker-configAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops other accounts on the host reading root's Docker registry credentials, which could otherwise pull private images or publish under this host's identity.
fileperms.groupAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account adding itself to a privileged group — sudo, docker — by editing the group database directly.
fileperms.grub-configAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account reading or rewriting the bootloader configuration, which controls what runs before any of the host's normal access controls are active.
fileperms.grub2-configAuto-fix-
Tighten /etc/shadow to 0640
✓ Same protection as the GRUB config, for the GRUB2 variant.
fileperms.gshadowAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account reading group passwords and membership data that /etc/gshadow is supposed to keep to root and the shadow group alone.
fileperms.hostkeyAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account reading this server's SSH private host key — the one thing that would let them impersonate this host to anyone connecting.
fileperms.passwdAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account editing the account database directly, closing a path to adding an account or handing itself UID 0.
fileperms.passwd-backupAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account planting a change in the passwd backup that becomes a live credential the next time it is restored.
fileperms.shadowAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops any account other than root (and the shadow group) reading password hashes off disk, closing the offline-cracking path entirely.
fileperms.sshd-configAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account weakening the SSH server's own config (re-enabling root login or password auth) to open a way back in.
fileperms.sudoersAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account granting itself root by editing sudo's own policy file.
fileperms.sudoers-dropinsAuto-fix-
Tighten /etc/shadow to 0640
✓ Same protection as the main sudoers file, for a drop-in carrying the exact same authority.
fileperms.systemd-unitsAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops a non-root account replacing the command a systemd unit runs the next time it starts as root.
AI 에이전트
agent.config-permsAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops other local accounts on this host reading the agent runtime's own configuration, which can carry API keys and tool permissions.
agent.control-ui-insecureAuto-fix-
Set kernel.kptr_restrict to 1
✓ Closes the agent's control UI to unauthenticated access, so reaching it requires being someone the operator actually let in.
agent.elevated-enabledAuto-fix-
Set kernel.kptr_restrict to 1
✓ Stops the agent running with elevated or administrative privileges by default, so a prompt-injected or misused tool call can't act with more authority than the task ever needed.
agent.exec-unrestrictedAuto-fix-
Set kernel.kptr_restrict to 1
✓ Stops the agent executing shell commands at all without an explicit human decision each time — closes the most direct path from "the agent was tricked by its input" to "a shell command ran on this host."
agent.secret-exposedAuto-fix-
Tighten /etc/shadow to 0640
✓ Stops other local accounts reading a secret file the agent runtime keeps in its state directory.
agent.ssrf-private-networkAuto-fix-
Set kernel.kptr_restrict to 1
✓ Stops the agent's own tool calls reaching internal or private network addresses on the operator's behalf — closes the classic SSRF pivot from "the agent fetched a URL" to "the agent probed the internal network."
커널
sysctl.accept-redirectsAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-accept-redirects.conf
✓ Stops the kernel trusting ICMP redirects, closing an easy way for something on the local network to quietly re-route this host's outbound traffic through itself.
sysctl.accept-source-routeAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-accept-source-route.conf
✓ Stops the kernel honoring source-routed packets, closing a way to make traffic take a path chosen by whoever sent it rather than by this network's own routing.
sysctl.aslrAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-aslr.conf
✓ Turns on full address space layout randomization, making a memory-corruption bug substantially harder to turn into a reliable exploit.
sysctl.bogus-icmp-errorsAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-bogus-icmp-errors.conf
✓ Stops the kernel logging bogus ICMP error responses as if real, reducing log noise and a minor spoofing-detection blind spot.
sysctl.bootp-relayReview-
-
Persist it: write /etc/sysctl.d/60-hostveil-bootp-relay.conf (recommended)
✓ Stops this host forwarding BOOTP/DHCP broadcasts between networks it has no business relaying for.
-
Apply it now: kernel.kptr_restrict
✓ Stops this host forwarding BOOTP/DHCP broadcasts between networks it has no business relaying for.
⚠ Changes the running kernel immediately and has no rollback checkpoint.
-
sysctl.bpf-jit-hardenAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-bpf-jit-harden.conf
✓ Hardens the BPF JIT compiler's output against the spraying techniques used to turn a BPF bug into kernel code execution.
sysctl.core-uses-pidAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-core-uses-pid.conf
✓ Includes the process ID in a core dump's filename, so a crash does not silently overwrite an earlier one a security investigation might still need.
sysctl.ctrl-alt-delAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-ctrl-alt-del.conf
✓ Stops Ctrl-Alt-Del triggering an instant reboot, closing a trivial denial-of-service available to anyone with physical or console access.
sysctl.dmesg-restrictAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-dmesg-restrict.conf
✓ Stops unprivileged users reading the kernel log, which can leak addresses and details useful for a kernel-level exploit.
sysctl.icmp-broadcastsAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-icmp-broadcasts.conf
✓ Stops this host answering ICMP echo requests sent to a broadcast address, closing its use as an amplifier in a Smurf-style denial-of-service attack.
sysctl.ipv4-default-accept-redirectsAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-ipv4-default-accept-redirects.conf
✓ Same protection as accept-redirects, as the IPv4 default template new interfaces inherit.
sysctl.ipv4-default-rp-filterReview-
-
Persist it: write /etc/sysctl.d/60-hostveil-ipv4-default-rp-filter.conf (recommended)
✓ Same protection as rp-filter, as the default template.
-
Apply it now: kernel.kptr_restrict
✓ Same protection as rp-filter, as the default template.
⚠ Changes the running kernel immediately and has no rollback checkpoint.
-
sysctl.ipv6-accept-redirects-allAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-ipv6-accept-redirects-all.conf
✓ Same protection as accept-redirects, for IPv6, applied to every interface.
sysctl.ipv6-accept-redirects-defaultAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-ipv6-accept-redirects-default.conf
✓ Same protection as accept-redirects, for IPv6, as the default template new interfaces inherit.
sysctl.ipv6-accept-source-route-allAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-ipv6-accept-source-route-all.conf
✓ Same protection as accept-source-route, for IPv6, every interface.
sysctl.ipv6-accept-source-route-defaultAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-ipv6-accept-source-route-default.conf
✓ Same protection as accept-source-route, for IPv6, as the default template.
sysctl.ipv6-send-redirectsAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-ipv6-send-redirects.conf
✓ Same protection as send-redirects, for IPv6.
sysctl.kptr-restrictAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-kptr-restrict.conf
✓ Hides kernel pointer addresses from unprivileged reads of /proc, taking away information an attacker would otherwise use to build a kernel exploit.
sysctl.log-martiansAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-log-martians.conf
✓ Logs packets carrying an impossible source or destination address, giving visibility into spoofing or misconfiguration attempts that would otherwise pass silently.
sysctl.module-dccpAuto-fix-
Persist a modprobe install block
✓ Removes the kernel's ability to load the DCCP module at all — a rarely-used protocol on an ordinary self-hosted server, and one that has shown up repeatedly as a local attack vector when left loadable.
⚠ Confirm this host does not use dccp before applying.
sysctl.module-rdsAuto-fix-
Persist a modprobe install block
✓ Removes the kernel's ability to load the RDS module at all — the same reasoning as DCCP: rarely used here, and a repeat source of local kernel bugs when loadable.
⚠ Confirm this host does not use dccp before applying.
sysctl.module-sctpAuto-fix-
Persist a modprobe install block
✓ Removes the kernel's ability to load the SCTP module at all — the same reasoning as DCCP: rarely used here, and a repeat source of local kernel bugs when loadable.
⚠ Confirm this host does not use dccp before applying.
sysctl.module-tipcAuto-fix-
Persist a modprobe install block
✓ Removes the kernel's ability to load the TIPC module at all — the same reasoning as DCCP: rarely used here, and a repeat source of local kernel bugs when loadable.
⚠ Confirm this host does not use dccp before applying.
sysctl.module-usbstorageAuto-fix-
Persist a modprobe install block
✓ Removes the kernel's ability to load the USB mass-storage driver, closing a common way to pull data off or introduce malware onto a physically-accessible server via a USB drive.
⚠ Confirm this host does not use dccp before applying.
sysctl.multicast-forwardingReview-
-
Persist it: write /etc/sysctl.d/60-hostveil-multicast-forwarding.conf (recommended)
✓ Stops this host relaying multicast traffic between interfaces it has no business routing for.
-
Apply it now: kernel.kptr_restrict
✓ Stops this host relaying multicast traffic between interfaces it has no business routing for.
⚠ Changes the running kernel immediately and has no rollback checkpoint.
-
sysctl.perf-eventsAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-perf-events.conf
✓ Restricts performance-counter access so an unprivileged user can't use it to leak kernel addresses or run a side-channel attack against other processes.
sysctl.protected-fifosAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-protected-fifos.conf
✓ Stops a process writing into a FIFO it does not own in a world-writable sticky directory — the same class protected-links closes for symlinks.
sysctl.protected-linksAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-protected-links.conf
✓ Stops a process following a symlink it does not own in a world-writable sticky directory, closing a classic local privilege-escalation and file-clobbering trick.
sysctl.protected-regularAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-protected-regular.conf
✓ Stops a process writing into a regular file it does not own in a world-writable sticky directory, for the same reason.
sysctl.proxy-arp-allReview-
-
Persist it: write /etc/sysctl.d/60-hostveil-proxy-arp-all.conf (recommended)
✓ Stops the kernel answering ARP on behalf of other hosts across interfaces, closing an easy way to bridge or spoof between network segments this host sits on.
-
Apply it now: kernel.kptr_restrict
✓ Stops the kernel answering ARP on behalf of other hosts across interfaces, closing an easy way to bridge or spoof between network segments this host sits on.
⚠ Changes the running kernel immediately and has no rollback checkpoint.
-
sysctl.proxy-arp-defaultReview-
-
Persist it: write /etc/sysctl.d/60-hostveil-proxy-arp-default.conf (recommended)
✓ Same protection as proxy-arp-all, as the default template.
-
Apply it now: kernel.kptr_restrict
✓ Same protection as proxy-arp-all, as the default template.
⚠ Changes the running kernel immediately and has no rollback checkpoint.
-
sysctl.ptrace-scopeAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-ptrace-scope.conf
✓ Restricts ptrace to a process's own children, closing a well-known technique for one compromised process to read another's memory or inject code into it.
sysctl.rp-filterReview-
-
Persist it: write /etc/sysctl.d/60-hostveil-rp-filter.conf (recommended)
✓ Drops packets whose source address could not have arrived on the interface they came in on, closing a common IP-spoofing technique.
-
Apply it now: kernel.kptr_restrict
✓ Drops packets whose source address could not have arrived on the interface they came in on, closing a common IP-spoofing technique.
⚠ Changes the running kernel immediately and has no rollback checkpoint.
-
sysctl.send-redirectsReview-
-
Persist it: write /etc/sysctl.d/60-hostveil-send-redirects.conf (recommended)
✓ Stops this host sending ICMP redirects itself, which it should not be doing unless it is actually a router — removes a route-injection tool a compromised host could use against its neighbors.
-
Apply it now: kernel.kptr_restrict
✓ Stops this host sending ICMP redirects itself, which it should not be doing unless it is actually a router — removes a route-injection tool a compromised host could use against its neighbors.
⚠ Changes the running kernel immediately and has no rollback checkpoint.
-
sysctl.suid-dumpableAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-suid-dumpable.conf
✓ Stops a crashed setuid process writing a core dump that can contain whatever secrets were in its memory at the moment it crashed.
-
Persist it: write /etc/sysctl.d/60-hostveil-syncookies.conf
✓ Enables SYN cookies, so a SYN-flood cannot exhaust the connection queue and deny the host's legitimate services.
sysctl.sysrqReview-
-
Persist it: write /etc/sysctl.d/60-hostveil-sysrq.conf (recommended)
✓ Restricts the SysRq magic-key functions to the safe subset, closing the ones that can dump memory or force a reboot from an unprivileged local session.
-
Apply it now: kernel.kptr_restrict
✓ Restricts the SysRq magic-key functions to the safe subset, closing the ones that can dump memory or force a reboot from an unprivileged local session.
⚠ Changes the running kernel immediately and has no rollback checkpoint.
-
sysctl.tcp-rfc1337Auto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-tcp-rfc1337.conf
✓ Protects against TIME-WAIT assassination, so an off-path attacker cannot reset a connection sitting in TIME_WAIT by spoofing packets at it.
sysctl.tty-ldisc-autoloadAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-tty-ldisc-autoload.conf
✓ Stops the kernel auto-loading obscure TTY line disciplines on request, closing a local privilege-escalation path specific line-discipline modules have shipped in the past.
sysctl.unprivileged-bpfAuto-fix-
Persist it: write /etc/sysctl.d/60-hostveil-unprivileged-bpf.conf
✓ Removes unprivileged users' ability to load BPF programs into the kernel, closing a well-documented local privilege-escalation and kernel-exploit vector.
서비스
systemd.lock-personalityAuto-fix-
Set LockPersonality=yes for app
✓ Stops the unit switching to an alternate execution personality, closing an old technique for bypassing ASLR.
⚠ Needing an alternate execution personality is rare outside emulation and compatibility layers. An ordinary service is unaffected; one that needs one fails at the next restart.
systemd.no-new-privilegesAuto-fix-
Set NoNewPrivileges=yes for app
✓ Closes the setuid escalation path out of this unit — a compromised process cannot gain more privilege than the service already had, the same protection compose.ds006 gives a container.
⚠ Closes the setuid path out of this service. A service that deliberately escalates — anything calling a setuid helper — stops working, and it stops at the next restart rather than now.
systemd.protect-clockAuto-fix-
Set ProtectClock=yes for app
✓ Stops the unit changing the system or hardware clock, closing off a way a compromised service could hide its tracks by tampering with timestamps or break time-based authentication.
⚠ Only time-sync daemons (chronyd, ntpd, systemd-timesyncd) legitimately need to change the system or hardware clock. A service that is not one of those stops being able to, at the next restart.
systemd.protect-kernel-logsAuto-fix-
Set ProtectKernelLogs=yes for app
✓ Stops the unit reading /dev/kmsg directly, closing an information-leak path that can hand an attacker kernel addresses useful for a further exploit.
⚠ Only a service that reads kernel logs directly — a diagnostics tool, an agent reading /dev/kmsg — needs this off. Most services never touch it.
systemd.protect-kernel-modulesAuto-fix-
Set ProtectKernelModules=yes for app
✓ Stops the unit loading or removing kernel modules itself, closing a direct path to running arbitrary code in kernel space from what should be an ordinary service.
⚠ Only a service that loads or removes kernel modules itself at runtime — rather than modules already loaded at boot — needs this off.
systemd.restrict-suid-sgidAuto-fix-
Set RestrictSUIDSGID=yes for app
✓ Stops the unit creating new setuid or setgid files, closing a persistence and privilege-escalation path a compromised service could otherwise leave behind for later.
⚠ Only a service that itself creates setuid or setgid files — a package manager, an installer — needs this off. An ordinary network daemon does not create such files and is unaffected.
프록시
proxy.no-scan-jailReview-
-
Enable it (fail2ban's own default: 10 minute ban) (recommended)
✓ Once fail2ban reloads, an IP that repeatedly requests nonexistent or known-vulnerable paths gets banned automatically — no more finding it and banning it yourself.
⚠ Takes effect on the next
fail2ban-client reload, not immediately. A shared or NAT'd IP address could ban more than the one visitor responsible for it. This only ever affects HTTP(S) traffic — never SSH, and never the operator's own access to the host. -
Enable it with a longer ban (1 week)
✓ Once fail2ban reloads, an IP that repeatedly requests nonexistent or known-vulnerable paths gets banned automatically — no more finding it and banning it yourself.
⚠ Takes effect on the next
fail2ban-client reload, not immediately. A shared or NAT'd IP address could ban more than the one visitor responsible for it. This only ever affects HTTP(S) traffic — never SSH, and never the operator's own access to the host.
-