Skip to content

Commit 5404e24

Browse files
committed
fix: address code review safety findings
1 parent 3b50d21 commit 5404e24

28 files changed

Lines changed: 431 additions & 70 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ trace_flow(qualified_name: "webhook.WebhookHandler.ServeHTTP")
219219
```
220220
search(query: "authentication")
221221
→ internal/adapters/inbound/webhook/handler.go (HMAC signature validation)
222-
→ cmd/ccg-server/main.go (--webhook-secret flag)
222+
→ cmd/ccg-server/main.go (CCG_WEBHOOK_SECRET / --webhook-secret)
223223
```
224224

225225
## MCP Server

cmd/ccg-server/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func newRootCmd(rt *ccgruntime.Runtime, serviceVersion string) *cobra.Command {
141141
cmd.Flags().DurationVar(&cfg.WebhookRetryBaseDelay, "webhook-retry-base-delay", cfg.WebhookRetryBaseDelay, "Initial webhook sync retry delay")
142142
cmd.Flags().DurationVar(&cfg.WebhookRetryMaxDelay, "webhook-retry-max-delay", cfg.WebhookRetryMaxDelay, "Maximum webhook sync retry delay")
143143
cmd.Flags().StringSliceVar(&cfg.AllowRepo, "allow-repo", nil, "Allowed repo patterns for webhook sync (repeatable, e.g. org/*, !org/private)")
144-
cmd.Flags().StringVar(&cfg.WebhookSecret, "webhook-secret", "", "HMAC secret for GitHub webhook signature verification")
144+
cmd.Flags().StringVar(&cfg.WebhookSecret, "webhook-secret", cfg.WebhookSecret, "HMAC secret for GitHub webhook signature verification (env: CCG_WEBHOOK_SECRET)")
145145
cmd.Flags().BoolVar(&cfg.InsecureWebhook, "insecure-webhook", false, "Allow unsigned webhook requests (unsafe; testing only)")
146146
cmd.Flags().StringArrayVar(&cfg.RepoCloneBaseURLs, "repo-clone-base-url", nil, "Canonical base URL used to reconstruct clone targets for allowed repos (repeatable)")
147147
cmd.Flags().StringVar(&cfg.RepoRoot, "repo-root", cfg.RepoRoot, "Root directory for cloned repositories")

cmd/ccg-server/main_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package main
2+
3+
import (
4+
"log/slog"
5+
"testing"
6+
7+
ccgruntime "github.com/tae2089/code-context-graph/internal/runtime"
8+
)
9+
10+
func TestWebhookSecretFlagUsesEnvironmentDefaultAndAllowsExplicitOverride(t *testing.T) {
11+
t.Setenv("CCG_WEBHOOK_SECRET", "environment-test-value")
12+
rt := ccgruntime.NewRuntime(slog.Default())
13+
defer rt.Close()
14+
cmd := newRootCmd(rt, "test")
15+
16+
got, err := cmd.Flags().GetString("webhook-secret")
17+
if err != nil {
18+
t.Fatalf("get webhook-secret flag: %v", err)
19+
}
20+
if got != "environment-test-value" {
21+
t.Fatal("webhook-secret flag did not preserve the environment default")
22+
}
23+
if err := cmd.Flags().Set("webhook-secret", "explicit-test-value"); err != nil {
24+
t.Fatalf("set webhook-secret flag: %v", err)
25+
}
26+
got, err = cmd.Flags().GetString("webhook-secret")
27+
if err != nil {
28+
t.Fatalf("get overridden webhook-secret flag: %v", err)
29+
}
30+
if got != "explicit-test-value" {
31+
t.Fatal("explicit webhook-secret flag did not override the environment default")
32+
}
33+
}

guide/cli-reference.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ HTTP MCP and webhook hosting now live in the dedicated `ccg-server` binary:
141141
| `ccg-server --wiki-dir <dir>` | Enable the browser Wiki UI at `/wiki` using a built React dist directory; `/wiki/api/*` uses the same bearer token as `/mcp` |
142142
| `ccg-server --namespace-root <dir>` | Root directory for file namespaces (default `namespaces`) |
143143
| `ccg-server --allow-repo <pat>` | Allowed repo patterns for webhook sync (e.g. `org/*`, `org/api:main,develop`) |
144-
| `ccg-server --webhook-secret <s>` | HMAC secret for webhook signature verification |
144+
| `ccg-server --webhook-secret <s>` | HMAC secret for webhook signature verification (prefer `CCG_WEBHOOK_SECRET` to avoid argv exposure) |
145145
| `ccg-server --insecure-webhook` | Allow unsigned webhook requests for local testing only |
146146
| `ccg-server --repo-clone-base-url <url>` | Canonical base URL used to reconstruct webhook clone targets (repeatable) |
147147
| `ccg-server --repo-root <dir>` | Root directory for cloned repositories |
@@ -156,7 +156,7 @@ HTTP MCP and webhook hosting now live in the dedicated `ccg-server` binary:
156156
| `ccg-server --max-total-parsed-bytes <bytes>` | Maximum total bytes parsed across source files (`0` disables the limit) |
157157

158158
Webhook-related server flags can also be configured with matching environment
159-
variables where supported: `CCG_WEBHOOK_WORKERS`,
159+
variables where supported: `CCG_WEBHOOK_SECRET`, `CCG_WEBHOOK_WORKERS`,
160160
`CCG_WEBHOOK_MAX_TRACKED_REPOS`, `CCG_WEBHOOK_ATTEMPT_TIMEOUT`,
161161
`CCG_WEBHOOK_RETRY_ATTEMPTS`, `CCG_WEBHOOK_RETRY_BASE_DELAY`,
162162
`CCG_WEBHOOK_RETRY_MAX_DELAY`, and `CCG_REPO_ROOT`.

guide/docker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ docker run -d -p 8080:8080 \
6363
-e CCG_DB_DRIVER=postgres \
6464
-e CCG_DB_DSN="$CCG_DB_DSN" \
6565
-e CCG_REPO_ROOT=/data/repos \
66+
-e CCG_WEBHOOK_SECRET \
6667
-v ccg-repos:/data/repos \
6768
--entrypoint ccg-server ccg \
6869
--http-addr :8080 \
6970
--allow-repo "acme/*" \
70-
--webhook-secret "$WEBHOOK_SECRET" \
7171
--repo-clone-base-url https://github.com
7272
```
7373

guide/ko/cli-reference.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ HTTP MCP와 웹훅 호스팅은 전용 `ccg-server` 바이너리에서 제공합
131131
| `ccg-server --wiki-dir <dir>` | 빌드된 React dist 디렉터리로 `/wiki` 브라우저 Wiki UI 활성화; `/wiki/api/*``/mcp`와 같은 Bearer 토큰 사용 |
132132
| `ccg-server --namespace-root <dir>` | 파일 네임스페이스의 루트 디렉토리 (기본값 `namespaces`) |
133133
| `ccg-server --allow-repo <pat>` | 웹훅 동기화가 허용된 저장소 패턴 (예: `org/*`, `org/api:main,develop`) |
134-
| `ccg-server --webhook-secret <s>` | 웹훅 서명 검증을 위한 HMAC 비밀키 |
134+
| `ccg-server --webhook-secret <s>` | 웹훅 서명 검증을 위한 HMAC 비밀키(argv 노출 방지를 위해 `CCG_WEBHOOK_SECRET` 권장) |
135135
| `ccg-server --insecure-webhook` | 로컬 테스트 전용으로 서명되지 않은 웹훅 요청 허용 |
136136
| `ccg-server --repo-clone-base-url <url>` | 웹훅 복제 대상을 재구성하는 데 사용되는 정규 베이스 URL (반복 가능) |
137137
| `ccg-server --repo-root <dir>` | 복제된 저장소의 루트 디렉토리 |
@@ -145,7 +145,7 @@ HTTP MCP와 웹훅 호스팅은 전용 `ccg-server` 바이너리에서 제공합
145145
| `ccg-server --max-file-bytes <bytes>` | 파싱된 소스 파일당 허용되는 최대 바이트 수 (`0`은 제한 없음) |
146146
| `ccg-server --max-total-parsed-bytes <bytes>` | 소스 파일 전체에서 파싱된 최대 총 바이트 수 (`0`은 제한 없음) |
147147

148-
웹훅 관련 server 플래그는 지원되는 경우 일치하는 환경 변수로도 설정할 수 있습니다: `CCG_WEBHOOK_WORKERS`, `CCG_WEBHOOK_MAX_TRACKED_REPOS`, `CCG_WEBHOOK_ATTEMPT_TIMEOUT`, `CCG_WEBHOOK_RETRY_ATTEMPTS`, `CCG_WEBHOOK_RETRY_BASE_DELAY`, `CCG_WEBHOOK_RETRY_MAX_DELAY`, `CCG_REPO_ROOT`.
148+
웹훅 관련 server 플래그는 지원되는 경우 일치하는 환경 변수로도 설정할 수 있습니다: `CCG_WEBHOOK_SECRET`, `CCG_WEBHOOK_WORKERS`, `CCG_WEBHOOK_MAX_TRACKED_REPOS`, `CCG_WEBHOOK_ATTEMPT_TIMEOUT`, `CCG_WEBHOOK_RETRY_ATTEMPTS`, `CCG_WEBHOOK_RETRY_BASE_DELAY`, `CCG_WEBHOOK_RETRY_MAX_DELAY`, `CCG_REPO_ROOT`.
149149

150150
`CCG_HTTP_BEARER_TOKEN``--http-bearer-token`에 대해서도 지원되며, `CCG_OTEL_ENDPOINT``--otel-endpoint`에 대해서도 지원됩니다. 이 토큰은 `/mcp`의 MCP HTTP 엔드포인트를 보호하지만, `/health`, `/ready`, `/status`, `/webhook` 자체를 비공개로 만들지는 않습니다.
151151

guide/ko/docker.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ docker run -d -p 8080:8080 \
4747
-e CCG_DB_DRIVER=postgres \
4848
-e CCG_DB_DSN="$CCG_DB_DSN" \
4949
-e CCG_REPO_ROOT=/data/repos \
50+
-e CCG_WEBHOOK_SECRET \
5051
-v ccg-repos:/data/repos \
5152
--entrypoint ccg-server ccg \
5253
--http-addr :8080 \
5354
--allow-repo "acme/*" \
54-
--webhook-secret "$WEBHOOK_SECRET" \
5555
--repo-clone-base-url https://github.com
5656
```
5757

guide/ko/operations.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,12 @@ CCG가 Ingress, 리버스 프록시 또는 로드 밸런서 뒤에 있는 경우
159159
웹훅 배포는 다음 사항들을 설정해야 합니다:
160160

161161
- 명시적인 저장소 및 브랜치 허용 목록을 위한 `--allow-repo`
162-
- HMAC 검증을 위한 `--webhook-secret`
162+
- HMAC 검증을 위한 `CCG_WEBHOOK_SECRET`(권장) 또는 `--webhook-secret`
163163
- 복제 URL이 페이로드로부터 신뢰받는 대신 허용된 저장소 이름으로부터 재구성되도록 하는 `--repo-clone-base-url`
164164
- 지속성 있는 로컬 스토리지를 위한 `--repo-root`
165165
- 팀용 또는 상시 가동 배포를 위한 `--db-driver postgres`
166166

167-
웹훅 네임스페이스 추출은 마지막 저장소 이름을 사용합니다. 예를 들어 `acme/api``api` 네임스페이스에 저장되고 `$REPO_ROOT/api`에 checkout됩니다. 이 전략은 단일 owner 웹훅 배포를 위한 것입니다. 허용 목록이 여러 owner를 포함하면 서버는 경고를 남깁니다. `acme/api``external/api`가 같은 네임스페이스 및 checkout 경로에서 충돌할 수 있기 때문입니다.
167+
웹훅 네임스페이스 추출은 마지막 저장소 이름을 사용합니다. 예를 들어 `acme/api``api` 네임스페이스에 저장되고 `$REPO_ROOT/api`에 checkout됩니다. 이 전략은 단일 owner 웹훅 배포를 위한 것입니다. 허용 목록이 여러 owner를 포함하면 `acme/api``external/api`가 같은 네임스페이스 및 checkout 경로에서 충돌할 수 있으므로 서버는 시작을 거부합니다.
168168

169169
권장 정책:
170170

@@ -253,7 +253,7 @@ CCG를 업그레이드한 후, 기존의 기본 `ccg.db` 또한 기존 스키마
253253
| 증상 | 가능성 높은 원인 | 확인 / 해결 방법 |
254254
|---------|--------------|-------------|
255255
| `401` 또는 MCP 초기화 실패 | Bearer 토큰 누락 또는 오류 | `Authorization: Bearer ...``CCG_HTTP_BEARER_TOKEN`을 확인하십시오. |
256-
| 웹훅이 권한 없음(unauthorized) 반환 | HMAC 서명 누락/유효하지 않음 | `--webhook-secret` 및 제공자의 서명 헤더를 확인하십시오. |
256+
| 웹훅이 권한 없음(unauthorized) 반환 | HMAC 서명 누락/유효하지 않음 | `CCG_WEBHOOK_SECRET`(또는 `--webhook-secret`) 및 제공자의 서명 헤더를 확인하십시오. |
257257
| 웹훅이 금지됨(forbidden) 반환 | 허용되지 않은 저장소 또는 브랜치 | `--allow-repo` 패턴 및 브랜치 ref를 확인하십시오. |
258258
| 웹훅이 너무 많은 요청 반환 | 동기화 큐가 가득 참 | `/status`를 확인하고, push 볼륨을 줄이거나 PostgreSQL에서 워커를 늘리십시오. |
259259
| `/ready``not_ready`임 | DB 또는 큐 차단 조건 | `/status` 및 서비스 로그를 조사하십시오. |

guide/ko/runtime-layout.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,13 @@ ccg-server --http-addr 0.0.0.0:8080 --http-bearer-token "$CCG_HTTP_BEARER_TOKEN"
8383

8484
Webhook sync:
8585

86+
이 명령을 시작하기 전에 배포 secret store를 통해 `CCG_WEBHOOK_SECRET`을 설정하십시오.
87+
8688
```bash
8789
ccg-server \
8890
--http-addr 0.0.0.0:8080 \
8991
--http-bearer-token "$CCG_HTTP_BEARER_TOKEN" \
9092
--allow-repo "org/api:main,develop" \
91-
--webhook-secret "$WEBHOOK_SECRET" \
9293
--repo-clone-base-url https://github.com \
9394
--repo-root /data/repos
9495
```

guide/ko/webhook.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ GitHub 또는 Gitea로부터 push 이벤트를 수신하여 자동으로 복제(
66

77
## 설정 (Setup)
88

9+
서버 시작 전에 배포 환경의 secret store를 통해 `CCG_WEBHOOK_SECRET`을 설정하십시오. 환경 변수 기반 설정은 값을 프로세스 인자에 노출하지 않습니다.
10+
911
```bash
1012
ccg-server \
1113
--allow-repo "org/api:main,develop" \
1214
--allow-repo "org/web:main" \
13-
--webhook-secret "your-secret" \
1415
--repo-clone-base-url https://github.com \
1516
--repo-root /data/repos
1617
```
@@ -80,7 +81,7 @@ ccg-server \
8081

8182
웹훅 동기화는 `org/repo` 중 마지막 저장소 이름을 그래프 네임스페이스와 checkout 디렉터리로 사용합니다. 예를 들어 `acme/api``api` 네임스페이스와 `/data/repos/api` checkout으로 매핑됩니다.
8283

83-
이 방식은 단일 조직 배포에서 짧고 예측 가능한 이름을 유지하기 위한 전략이며, 권장 운영 모델입니다. 허용 목록이 `acme/*``external/shared`처럼 여러 owner를 포함하거나 `*/*`를 사용하면, 같은 마지막 저장소 이름이 충돌할 수 있으므로 CCG는 시작 시 경고 로그를 남깁니다.
84+
이 방식은 단일 조직 배포에서 짧고 예측 가능한 이름을 유지하기 위한 전략이며, 필수 운영 모델입니다. 허용 목록이 `acme/*``external/shared`처럼 여러 owner를 포함하거나 `*/*`를 사용하면 같은 마지막 저장소 이름이 충돌할 수 있으므로 CCG는 시작을 거부합니다.
8485

8586
| 저장소 | 파생 네임스페이스 |
8687
|------|-------------------|
@@ -89,7 +90,7 @@ ccg-server \
8990

9091
운영 정책:
9192

92-
- 웹훅 CCG 인스턴스 하나에는 하나의 owner/조직을 사용하는 것을 권장합니다.
93+
- 웹훅 CCG 인스턴스 하나에는 하나의 owner/조직만 사용하십시오. 여러 owner 또는 wildcard owner는 시작 시 거부됩니다.
9394
- 같은 인스턴스 안에 마지막 저장소 이름이 같은 저장소를 허용하지 마십시오.
9495
- 여러 owner의 동기화가 필요하면 별도 CCG 인스턴스를 사용하거나, 네임스페이스 전략을 변경한 뒤 활성화하십시오.
9596

@@ -102,9 +103,9 @@ HMAC-SHA256을 사용하여 웹훅 페이로드를 검증합니다.
102103
| GitHub | `X-Hub-Signature-256` | `sha256=<hex>` |
103104
| Gitea | `X-Gitea-Signature` | `<hex>` |
104105

105-
기본적으로 `--webhook-secret`이 설정되지 않으면 웹훅 요청은 실패(fail closed) 처리됩니다.
106+
기본적으로 `CCG_WEBHOOK_SECRET` 또는 `--webhook-secret`이 설정되지 않으면 웹훅 요청은 실패(fail closed) 처리됩니다.
106107

107-
- `--webhook-secret`은 HMAC 검증을 활성화합니다.
108+
- `CCG_WEBHOOK_SECRET`(권장) 또는 `--webhook-secret`은 HMAC 검증을 활성화합니다. 환경 변수를 사용하면 프로세스 인자에 값이 노출되지 않습니다.
108109
- `--insecure-webhook`은 명시적인 테스트 전용 옵션이며 `--webhook-secret`과 함께 사용할 수 없습니다.
109110
- 보안 모드에서 실행할 때는 `--repo-clone-base-url`이 필수이며, 서버는 웹훅 페이로드의 `clone_url`을 신뢰하는 대신 허용된 저장소 이름을 기반으로 복제 URL을 재구성합니다.
110111

0 commit comments

Comments
 (0)