First Commit
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
detections / detections (push) Canceled after 0s

This commit is contained in:
dela
2026-10-09 08:38:16 +08:00
commit 0335d572de
756 changed files with 201663 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
name: ci
# 푸시와 PR 마다 빌드·정적 분석·테스트를 돌려, 한국어화 과정에서 생긴 회귀를
# 머지 전에 잡는다. 릴리스 워크플로(release.yml)는 태그(v*)에서만 돌고 빌드·배포만
# 하므로, 일상적인 변경을 검증하는 역할은 이 워크플로가 맡는다.
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
jobs:
# 빌드·정적 분석·테스트를 데이터베이스 없이 돌린다. 기여자가 저장소를 clone 한 뒤
# PostgreSQL 없이 `go test ./...` 를 돌리는 환경을 그대로 재현한다. 데이터베이스가
# 있어야 하는 통합 테스트는 이 환경에서 t.Skipf 로 건너뛰므로(스킵은 통과로 집계된다),
# 빌드·go vet·DB 를 쓰지 않는 단위 테스트가 전부 green 이어야 한다. 이 조합만으로도
# 패키지 경계를 넘나드는 회귀(예: 한 패키지의 문구를 바꾸면서 다른 패키지의 테스트
# 단언을 깨뜨리는 경우)를 머지 전에 잡을 수 있다.
go:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
cache: true
- name: go build
run: go build ./...
- name: go vet
run: go vet ./...
- name: go test (DB 없음 — 통합 테스트는 스킵)
run: go test ./... -count=1
# 실제 PostgreSQL 이 있어야 도는 DB 통합 테스트(알림·증거·사용량 계량 등)를 맡는다.
# 위 go job 이 스킵하는 경로(알림 채널 검증, 증거 저장, llm_usage 계량 등)를 실제
# 데이터베이스로 강제해, DB 통합 경로에서만 드러나는 한국어화 회귀까지 머지 전에 잡는다.
#
# 상류의 테스트 하네스는 모든 패키지가 하나의 데이터베이스를 공유하고, 테이블을 한 번만
# 만들어 재사용하며, 패키지 실행 순서와 누적 데이터에 암묵적으로 의존하도록 설계되어
# 있어, 하나의 데이터베이스에 전체를 몰아 돌리면 서로 다른 방식으로 깨진다(누적 데이터
# 오염, 커넥션 teardown 경쟁, 리셋 시 교차 패키지 스키마 의존). 그래서 여기서는 패키지마다
# 자체 PostgreSQL 서비스를 띄워 "빈 데이터베이스 + 단일 패키지" 로 격리 실행한다. 각
# 패키지가 독립 job(matrix) 으로 돌아 서로의 데이터·커넥션·스키마에 닿지 않으므로 그 세
# 상충이 구조적으로 사라진다. db.Open 이 스키마를 자동 마이그레이션하며, llm_usage 처럼
# db.Open 이 만들지 않는 테이블은 각 테스트가 EnsureLLMUsageTable 로 스스로 보장한다.
go-db:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pkg: [agent, config, db, evidence, llmrec, server]
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: artex
POSTGRES_PASSWORD: artex
POSTGRES_DB: artex
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U artex -d artex"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
cache: true
- name: go test (DB 통합 · ${{ matrix.pkg }} 패키지 격리)
env:
ARTEX_PG_DSN: postgres://artex:artex@localhost:5432/artex?sslmode=disable
run: go test ./${{ matrix.pkg }}/ -count=1
+82
View File
@@ -0,0 +1,82 @@
name: detections
# 탐지 규칙(Sigma·Suricata·ATT&CK 레이어)이 바뀌는 푸시·PR 마다, 그 규칙이 실제로
# 발화하고, 여러 SIEM 백엔드로 변환되며, 커버리지 레이어가 규칙과 일치하고, 지표가 상류
# 소스와 여전히 맞으며, SigmaHQ 관례 전체 검증을 통과하는지 재현 테스트로 검증한다. 규칙만
# 바꾸고 테스트·레이어를 갱신하지 않은 변경은 여기서 빨갛게 드러난다. CONTRIBUTING.md 와
# detections/tests/README.md 가 약속하는 "여덟 테스트가 기여 계약을 기계적으로 강제한다"를
# 머지 게이트로 실제로 뒷받침하는 워크플로다. Go 빌드·정적 분석·테스트는 ci.yml 이 맡는다.
# 여덟 테스트에 더해, 규칙이 아닌 두 게이트도 같은 머지 게이트에서 돈다: 하네스 레지스트리
# 동기 검사와, 호스트 분류 도구(detections/triage/artex_host_triage.py)의 자가 테스트다.
# 둘 다 여덟 규칙 테스트와 별개이고 run-all.sh 가 똑같이 돌린다.
#
# 여덟 테스트는 Docker 만 있으면 돈다(ubuntu-latest 러너에 Docker 가 들어 있다). 러너가
# sigma-cli·scapy·suricata·python 이미지를 내려받아 컨테이너에서 격리 실행하므로 러너
# 자체에는 아무것도 설치하지 않고, 규칙 트리와 지표가 가리키는 소스 파일을 읽기 전용으로만
# 마운트해 저장소에 쓰지 않는다. detections/ 아래가 바뀔 때 외에, 지표 테스트가 고정해 둔
# 소스 파일이 바뀔 때도 돌린다. enrich·selfupdate·guard·db 의 User-Agent·마커·파괴명령
# 토큰, cmd/artex/main.go 의 기본 리슨·프록시 포트, traffic/traffic.go 의 MITM CA 인증서
# 파일명, db/schema.sql 의 탐색 그래프 스키마 지문이 여기에 해당한다. 소스 변경이 지표를
# 바꿔 규칙·지표 목록이 조용히 낡는 경우를 머지 게이트에서 잡는다. 공개 Docker 이미지를 인증
# 없이 내려받으므로 드물게 Docker Hub 내려받기 속도 제한에 걸릴 수 있고, 그때는 작업을
# 다시 돌리면 해소된다.
on:
push:
paths:
- "detections/**"
- "enrich/enrich.go"
- "selfupdate/github.go"
- "selfupdate/stage.go"
- "guard/guard.go"
- "db/db.go"
- "db/schema.sql"
- "cmd/artex/main.go"
- "traffic/traffic.go"
- ".github/workflows/detections.yml"
pull_request:
paths:
- "detections/**"
- "enrich/enrich.go"
- "selfupdate/github.go"
- "selfupdate/stage.go"
- "guard/guard.go"
- "db/db.go"
- "db/schema.sql"
- "cmd/artex/main.go"
- "traffic/traffic.go"
- ".github/workflows/detections.yml"
permissions:
contents: read
# 같은 브랜치에 새 푸시가 오면 앞선 실행을 취소해, 자주 푸시하는 동안 불필요한 실행이
# 쌓이지 않게 한다.
concurrency:
group: detections-${{ github.ref }}
cancel-in-progress: true
jobs:
detections:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- name: 테스트 하네스 레지스트리 동기 검사 (run-all.sh ↔ CI ↔ 디렉터리)
run: detections/tests/check-harness-sync.sh
- name: 호스트 트리아지 도구 자가 테스트 (artex_host_triage.py --self-test · 규칙 아닌 게이트)
run: detections/tests/triage-selftest.sh
- name: Sigma 규칙 재현 테스트 (sigma check + 백엔드 변환 + 지표 보존)
run: detections/tests/sigma/run.sh
- name: Sigma 실제 이벤트 매칭 테스트 (원자·상관 규칙이 악성 샘플/타임라인에 발화·정상에 침묵)
run: detections/tests/sigma_match/run.sh
- name: Sigma SigmaHQ 관례 린트 (전체 검증기 세트 + 문서화된 기준)
run: detections/tests/sigma_lint/run.sh
- name: Sigma 백엔드 이식성 테스트 (상관 규칙이 여러 백엔드에서 변환되는지 확인)
run: detections/tests/sigma_backends/run.sh
- name: Suricata 규칙 재현 테스트 (pcap 합성 → suricata -r → 경보 수 단언)
run: detections/tests/suricata/run.sh
- name: ATT&CK 레이어 ↔ 규칙 정합 테스트
run: detections/tests/attack/run.sh
- name: 탐지 지표 ↔ 상류 소스 일치 테스트 (상류 재동기화 드리프트 가드)
run: detections/tests/indicators/run.sh
- name: 지표 MISP 내보내기 ↔ CSV 동기화 테스트 (pymisp 로 MISP 형식 유효성 검증)
run: detections/tests/misp/run.sh
+33
View File
@@ -0,0 +1,33 @@
name: docs
# 추적되는 마크다운 문서의 저장소 내부 링크·이미지 참조가 실존 파일을 가리키는지,
# 그리고 문서 앵커 링크(#헤딩)가 대상 문서에 실제로 있는 헤딩을 가리키는지 푸시·PR
# 마다 검사해, 깨진 링크·이미지·앵커가 머지되는 것을 막는다. 링크가 가리키는
# 파일은 저장소 어디에나 있을 수 있어(예: ../LICENSE·screenshots/ko/*.png) paths
# 필터 없이 모든 변경에 돌린다. 외부 URL(http/https/mailto)은 네트워크에 의존해
# flaky 하므로 검사하지 않는다 — scripts/check-doc-links.py 가 내부 참조만 본다.
# 파이썬 표준 라이브러리만 쓰고 네트워크에 접속하지 않아 결정론적으로 끝나므로,
# Docker Hub 속도 제한 같은 외부 요인으로 깜빡이지 않는다. Go 빌드·정적 분석·테스트는
# ci.yml, 탐지 규칙 재현은 detections.yml, 한국어 UI 빌드는 web.yml 이 맡는다.
on:
push:
branches: ["**"]
pull_request:
permissions:
contents: read
# 같은 브랜치에 새 푸시가 오면 앞선 실행을 취소해 불필요한 실행이 쌓이지 않게 한다.
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true
jobs:
links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# python3 는 ubuntu-latest 러너에 기본 설치되어 있고, 스크립트는 표준
# 라이브러리만 쓰므로 별도 설치 단계가 필요 없다. -I 로 사용자 사이트·환경을
# 격리해 실행한다.
- name: 문서 내부 링크·이미지·앵커 무결성 검사
run: python3 -I scripts/check-doc-links.py
+36
View File
@@ -0,0 +1,36 @@
name: external-links
# 추적 마크다운이 가리키는 외부 링크(방어 가이드의 사고 신고 창구 boho.or.kr·privacy.go.kr·
# pipc.go.kr·fsec.or.kr, CISA KEV, OWASP·SigmaHQ·Suricata·MITRE·MISP, 원본 데모, GitHub
# 배지 등)가 아직 살아 있는지 점검한다. 외부 URL 생존은 네트워크·원격 서버 정책에 의존해
# flaky 하므로 내부 링크 머지 게이트(docs.yml)에서는 보지 않는다. 그래서 이 워크플로는
# 푸시·PR 이 아니라 주간 스케줄과 수동 실행(workflow_dispatch)으로만 돌아 **머지를 막지 않고**,
# 외부 링크가 변질·폐쇄되면 주기 실행이 빨갛게 드러낸다. 내부 링크·앵커는 docs.yml, Go 빌드·
# 분석·테스트는 ci.yml, 탐지 규칙은 detections.yml, 한국어 UI 빌드는 web.yml 이 맡는다.
#
# --strict 는 allowlist(scripts/external-links-allowlist.txt)에 없는 DOWN(404·410·5xx·
# DNS/연결 오류)이 하나라도 있을 때만 실패한다. RESTRICTED(봇 차단·속도 제한 등 호스트는
# 살아 있는 상태)와 ALLOWED(우리가 고칠 수 없는 상류 상속 죽은 링크)는 실패로 치지 않으므로,
# 빨간불은 "우리 문서가 큐레이션한 외부 링크가 새로 깨졌다 — 가서 보라"는 신호다. 스케줄
# 워크플로는 기본 브랜치(main)에서만 돈다.
on:
schedule:
- cron: "17 0 * * 1" # 매주 월요일 00:17 UTC
workflow_dispatch:
permissions:
contents: read
concurrency:
group: external-links-${{ github.ref }}
cancel-in-progress: true
jobs:
links:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
# python3 는 ubuntu-latest 러너에 기본 설치돼 있고 스크립트는 표준 라이브러리만
# 쓰므로 별도 설치가 없다. -I 로 사용자 사이트·환경을 격리해 실행한다.
- name: 외부 링크 생존 점검 (브라우저 UA·GET·리다이렉트 추적 · allowlist 제외 · 머지 비차단)
run: python3 -I scripts/check-external-links.py --strict
+159
View File
@@ -0,0 +1,159 @@
name: release
on:
push:
tags: ["v*"]
permissions:
contents: write # Release 생성에 필요
jobs:
# ① 프런트엔드 정적 내보내기(한 번 수행), 산출물을 각 플랫폼 크로스 컴파일에서 재사용
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: web/package-lock.json
- run: npm ci
working-directory: web
- run: npm run build:static
working-directory: web
- uses: actions/upload-artifact@v4
with:
name: web-dist
path: web/out
# ② 5개 플랫폼 크로스 컴파일(프런트엔드 내장) + zip 패키징
binaries:
needs: frontend
runs-on: ubuntu-latest
strategy:
matrix:
include:
- { goos: linux, goarch: amd64 }
- { goos: linux, goarch: arm64 }
- { goos: darwin, goarch: amd64 }
- { goos: darwin, goarch: arm64 }
- { goos: windows, goarch: amd64 }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
cache: true
- uses: actions/download-artifact@v4
with:
name: web-dist
path: server/webui/dist
- name: Build and package with build.sh
env:
ARTEX_TARGET_OS: ${{ matrix.goos }}
ARTEX_TARGET_ARCH: ${{ matrix.goarch }}
ARTEX_BUILD_VERSION: ${{ github.ref_name }}
ARTEX_SKIP_FRONTEND: "1"
ARTEX_SKIP_NPM_CI: "1"
# UPX 자가 압축 해제 ELF 는 일부 Linux 커널·가상화·보안 정책에서 크래시가 난다.
# Release 는 Go linker 스트립과 zip 압축을 사용해 이식성을 우선 보장한다.
ARTEX_COMPRESS: "0"
ARTEX_PACKAGE: "1"
ARTEX_PACKAGE_DIR: dist
run: |
./build.sh --target "${ARTEX_TARGET_OS}/${ARTEX_TARGET_ARCH}"
- name: Smoke test Linux amd64 binary
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
run: dist/artex-linux-amd64/artex -h
- uses: actions/upload-artifact@v4
with:
name: pkg-${{ matrix.goos }}-${{ matrix.goarch }}
path: "dist/*.zip"
# Linux 원본 바이너리를 따로 업로드해 docker job 에서 재사용한다(이미지 안에서 프런트엔드+Go 를 다시 빌드하지 않도록)
- if: matrix.goos == 'linux'
uses: actions/upload-artifact@v4
with:
name: bin-linux-${{ matrix.goarch }}
path: dist/artex-linux-${{ matrix.goarch }}/artex
# ③ 모든 zip 을 모아 → GitHub Release 생성
release:
needs: binaries
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: pkg-*
merge-multiple: true
path: dist
- name: Generate checksums
working-directory: dist
run: sha256sum *.zip > SHA256SUMS
- uses: softprops/action-gh-release@v2
with:
files: |
dist/*.zip
dist/SHA256SUMS
generate_release_notes: true
# ④-0 발행 자격증명 게이트: DOCKERHUB 시크릿이 설정된 경우에만 docker 잡을 실행한다.
# 시크릿이 없으면 docker 잡을 건너뛰어(skipped) 릴리스 CI 가 빨간 X 없이 끝나게 한다.
# GitHub Actions 는 잡 수준 if 에서 secrets 를 직접 참조할 수 없어, 시크릿 존재
# 여부를 이 잡의 출력으로 넘긴 뒤 docker 잡의 if 조건으로 쓴다.
# 어느 네임스페이스로 발행할지는 별도 결정 사안이다(G6·DECISIONS 8).
docker-gate:
runs-on: ubuntu-latest
outputs:
publish: ${{ steps.check.outputs.publish }}
steps:
- name: Docker Hub 발행 자격증명 확인
id: check
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
if [ -n "$DOCKERHUB_USERNAME" ] && [ -n "$DOCKERHUB_TOKEN" ]; then
echo "publish=true" >> "$GITHUB_OUTPUT"
else
echo "publish=false" >> "$GITHUB_OUTPUT"
echo "::notice::DOCKERHUB_USERNAME/DOCKERHUB_TOKEN 시크릿이 없어 Docker 이미지 발행(docker 잡)을 건너뜁니다. 바이너리 릴리스는 정상 진행됩니다."
fi
# ④ 다중 아키텍처 이미지 → Docker Hub 에 push(binaries 가 빌드한 Linux 바이너리를 재사용하고,
# 이미지에는 도구만 설치 + 바이너리만 배치; arm64 는 apt 계층만 에뮬레이션하면 되어 빌드가 훨씬 빠르다)
docker:
needs: [binaries, docker-gate]
if: needs.docker-gate.outputs.publish == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # Dockerfile 과 skills/ 를 가져온다
- uses: actions/download-artifact@v4
with:
name: bin-linux-amd64
path: dist/amd64
- uses: actions/download-artifact@v4
with:
name: bin-linux-arm64
path: dist/arm64
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/metadata-action@v5
id: meta
with:
images: autumn27/artex
tags: |
type=ref,event=tag
type=raw,value=latest
- uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+54
View File
@@ -0,0 +1,54 @@
name: web
# 한국어 UI(web/)의 정적 내보내기 빌드를 푸시·PR 마다 돌려, 프런트엔드 회귀를
# 머지 전에 잡는다. 릴리스 워크플로(release.yml)는 태그(v*)에서만 web 을 빌드하는데
# 이 포크에는 태그 푸시 이력이 없어 한 번도 실행된 적이 없다 — 그래서 일상적인 web
# 변경(TypeScript·React·i18n·next-intl)을 검증하는 역할은 이 워크플로가 맡는다. Go
# 쪽 빌드·테스트는 ci.yml 이 맡으므로, 여기서는 web/ 가 바뀔 때만 돈다(paths 필터).
on:
push:
branches: ["**"]
paths:
- "web/**"
- ".github/workflows/web.yml"
pull_request:
paths:
- "web/**"
- ".github/workflows/web.yml"
permissions:
contents: read
jobs:
# 정적 내보내기 빌드가 머지 게이트다. `NEXT_EXPORT=1 next build` 는 next.config 에
# typescript.ignoreBuildErrors 설정이 없어 TypeScript 타입 검사까지 함께 수행하므로,
# 타입 오류나 내보내기 실패가 있으면 이 잡이 빨갛게 멈춰 머지를 막는다.
#
# biome 린트도 머지 게이트다. 상류에서 딸려온 선재 린트 부채(G7)를 0 으로 정리한
# 뒤(오류 8→0: skills 트리 a11y 6 + package.json 포매터 1 + logo.svg noSvgWithoutTitle 1),
# 정보용 단계를 게이트로 승격했다. 이제 `npm run check`(biome check)가 오류를 내면 이
# 잡이 빨갛게 멈춰 머지를 막아, 깨끗해진 상태가 다시 더럽혀지는 회귀를 방지한다.
# 경고·정보는 종료 코드에 영향이 없어(biome 은 error 레벨에서만 비-0) 머지를 막지 않는다.
web:
runs-on: ubuntu-latest
defaults:
run:
working-directory: web
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: web/package-lock.json
- name: npm ci
run: npm ci
- name: build:static (머지 게이트 · 타입 검사 포함)
run: npm run build:static
- name: UI 한자 누출 가드 (머지 게이트 · out HTML 에 중국어 0)
# 이 포크의 핵심 성과는 "사용자에게 보이는 화면에 중국어가 없다"는 것이다.
# 기여자가 중국어를 하드코딩하거나 번역이 누락되는 회귀를 머지 전에 막는다.
# 스크립트는 git 루트의 web/out 를 스스로 찾으므로 작업 디렉터리와 무관하다.
run: python3 -I ../scripts/check-web-cjk.py
- name: biome check (머지 게이트 · lint·format·a11y 오류 0 유지)
if: always()
run: npm run check