# 已知問題

本頁列出 v1.0.0 原始碼中已確認、會讓行為與設計不符的缺陷，以及在修正前可採取的因應方式。

## 會影響安全或可用性

| 問題 | 位置 | 影響 | 因應 |
|---|---|---|---|
| Redis 在評分階段失敗時 nil pointer panic | `instance.go:156` | `dynamicScore` 回傳 `nil, err`，`Check` 只記錄錯誤後讀取 `score.IsBlock` | Gin 使用 `gin.Recovery()`；`net/http` 會中斷該連線 |
| 用戶端 IP 直接信任代理標頭 | `device.go` `getClientIP` | 偽造 `X-Forwarded-For` 等標頭即可繞過速率限制、Block、Deny，或冒用 Allow 名單 IP | 由前端代理覆寫或清除標頭，見 [用戶端 IP 解析](/zh/client-ip) |
| `BlockTimeMin`／`BlockTimeMax` 為 0 時封鎖永久生效 | `block.go` `Add` | 未設定時封鎖紀錄永不過期 | 兩者都設為正值 |

## 設計上存在但不會執行的功能

| 功能 | 位置 | 原因 |
|---|---|---|
| `HighRiskCountry` 高風險國家加分 | `geo.go:248` | 歷史紀錄解析時把國家碼存進 `Country`，比對卻用永遠為空的 `CountryCode` |
| 分數過高自動 `Block.Add` | `score.go:196` | 條件是 `> 100`，但 `calcScore` 已把總分上限壓在 100 |
| 封鎖中持續請求達 `BlockToBan` 轉 Deny | `instance.go:141` | 前一步 `device.Is.Block` 已回 `403`，判斷式不會執行 |
| `too_frequent_requests` | `score.go:546` | 需要 16 個間隔，列表最多只保留 10 個；`ScoreFrequencyRequest` 也沒有預設值 |
| `ScoreNormal` | `type.go` | 欄位存在但沒有任何程式讀取 |

## 其他行為缺陷

| 問題 | 位置 | 影響 |
|---|---|---|
| Email 副本不會投遞 | `deny.go:157` | `smtp.SendMail` 的收件者只有 `To`，`CC` 只出現在標頭 |
| 只有 `CountryDB` 時地理偵測整段停用 | `score.go:441` | `calcGeo` 要求 `CityDB` 存在 |
| 每個請求輸出除錯字串 | `score.go:457` | 啟用 GeoLite2 時以標準 `log.Print` 印出位置與 key，不經 `Logger` |
| 預設值在請求路徑寫回 `Config` | `instance.go`、`score.go`、`geo.go` | 並行請求同時寫入相同欄位，`go test -race` 會回報資料競爭 |
| 內網與外網交替時誤判快速移動 | `geo.go` | 內網紀錄經緯度為 (0, 0)，見 [地理偵測](/zh/geo-detection) |
| `LoginFailure`／`NotFound404` 重複計入請求數 | `score.go` | 兩者內部再次執行裝置識別，同一請求計入兩次每分鐘計數 |

## 重現：Redis 中斷時的 panic

```go
package main

import (
	"fmt"
	"net/http/httptest"
	"strconv"
	"time"

	"github.com/alicebob/miniredis/v2"
	golangIPSentry "github.com/pardnchiu/golang-ip-sentry"
)

func main() {
	mr, err := miniredis.Run()
	if err != nil {
		panic(err)
	}
	port, _ := strconv.Atoi(mr.Port())
	sentry, err := golangIPSentry.New(golangIPSentry.Config{
		Redis:     golangIPSentry.Redis{Host: mr.Host(), Port: port},
		Parameter: golangIPSentry.Parameter{BlockTimeMin: time.Minute, BlockTimeMax: time.Hour},
	})
	if err != nil {
		panic(err)
	}
	mr.Close()

	defer func() { fmt.Println("recovered:", recover()) }()
	r := httptest.NewRequest("GET", "https://example.com/", nil)
	sentry.Check(r, httptest.NewRecorder())
}
```

實際輸出：

```
recovered: runtime error: invalid memory address or nil pointer dereference
```
