feat: SafeLine CE 9.3.9 with pro features
MCP Docker / build-and-push (push) Has been cancelled

- Remove telemetry: disabled all phone-home (installation notify, false positives, behavior tracking, upgrade tips)
- Version branding: removed 'ce-' prefix, product name changed to '长亭雷池 WAF'
- Detection engine enhancement: strict preset enables all 17 modules with aggressive configs
- Multi-user RBAC: admin/operator/viewer roles with password + TOTP 2FA
- Rate limiting: nginx limit_req per website (RPS, burst, action)
- JS Challenge: browser fingerprint verification page with cookie-based bypass
- Security fixes: removed InsecureSkipVerify, hardcoded credentials, NO_AUTH backdoor
This commit is contained in:
2026-07-04 07:59:04 +08:00
commit b8788c239e
259 changed files with 23055 additions and 0 deletions
@@ -0,0 +1,47 @@
package analyze
import (
"context"
"fmt"
"github.com/chaitin/SafeLine/mcp_server/internal/api"
)
type GetEventListRequest struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
IP string `json:"ip"`
Start int64 `json:"start"`
End int64 `json:"end"`
}
type GetEventListResponse struct {
Nodes []Event `json:"nodes"`
Total int64 `json:"total"`
}
type Event struct {
ID uint `json:"id"`
IP string `json:"ip"`
Protocol int `json:"protocol"`
Host string `json:"host"`
DstPort uint64 `json:"dst_port"`
UpdatedAt int64 `json:"updated_at"`
StartAt int64 `json:"start_at"`
EndAt int64 `json:"end_at"`
DenyCount int64 `json:"deny_count"`
PassCount int64 `json:"pass_count"`
Finished bool `json:"finished"`
Country string `json:"country"`
Province string `json:"province"`
City string `json:"city"`
}
func GetEventList(ctx context.Context, req *GetEventListRequest) (*GetEventListResponse, error) {
var resp api.Response[GetEventListResponse]
err := api.Service().Get(ctx, fmt.Sprintf("/api/open/events?page=%d&page_size=%d&ip=%s&start=%d&end=%d", req.Page, req.PageSize, req.IP, req.Start, req.End), &resp)
if err != nil {
return nil, err
}
return &resp.Data, nil
}
@@ -0,0 +1,34 @@
package app
import (
"context"
"github.com/chaitin/SafeLine/mcp_server/internal/api"
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
)
type CreateAppRequest struct {
ServerNames []string `json:"server_names"`
Ports []string `json:"ports"`
Upstreams []string `json:"upstreams"`
Comment string `json:"comment"`
}
// CreateApp Create new website or app
func CreateApp(ctx context.Context, req *CreateAppRequest) (int64, error) {
if req == nil {
return 0, errors.New("request is required")
}
var resp api.Response[int64]
err := api.Service().Post(ctx, "/api/open/site", req, &resp)
if err != nil {
return 0, errors.Wrap(err, "failed to create app")
}
if resp.Err != nil {
return 0, errors.New(resp.Msg)
}
return resp.Data, nil
}
+157
View File
@@ -0,0 +1,157 @@
package api
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
)
// Client API client
type Client struct {
baseURL string
httpClient *http.Client
headers map[string]string
}
// ClientOption Client configuration options
type ClientOption func(*Client)
// WithTimeout Set timeout duration
func WithTimeout(timeout time.Duration) ClientOption {
return func(c *Client) {
c.httpClient.Timeout = timeout
}
}
// WithHeader Set request header
func WithHeader(key, value string) ClientOption {
return func(c *Client) {
c.headers[key] = value
}
}
// WithBaseURL Set base URL
func WithBaseURL(baseURL string) ClientOption {
return func(c *Client) {
c.baseURL = baseURL
}
}
// WithInsecureSkipVerify Set whether to skip certificate verification
func WithInsecureSkipVerify(skip bool) ClientOption {
return func(c *Client) {
if transport, ok := c.httpClient.Transport.(*http.Transport); ok {
if transport.TLSClientConfig == nil {
transport.TLSClientConfig = &tls.Config{}
}
transport.TLSClientConfig.InsecureSkipVerify = skip
}
}
}
// NewClient Create new API client
func NewClient(opts ...ClientOption) *Client {
transport := &http.Transport{
TLSClientConfig: &tls.Config{},
}
c := &Client{
httpClient: &http.Client{
Timeout: 30 * time.Second,
Transport: transport,
},
headers: make(map[string]string),
}
for _, opt := range opts {
opt(c)
}
return c
}
// Request Send request
func (c *Client) Request(ctx context.Context, method, path string, body interface{}, result interface{}) error {
reqURL := fmt.Sprintf("%s%s", c.baseURL, path)
var bodyReader io.Reader
if body != nil {
bodyBytes, err := json.Marshal(body)
if err != nil {
return errors.Wrap(err, "marshal request body failed")
}
bodyReader = bytes.NewReader(bodyBytes)
}
logger.With("url", reqURL).Debug("request url")
req, err := http.NewRequestWithContext(ctx, method, reqURL, bodyReader)
if err != nil {
return errors.Wrap(err, "create request failed")
}
// Set common headers
req.Header.Set("Content-Type", "application/json")
for k, v := range c.headers {
req.Header.Set(k, v)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return errors.Wrap(err, "send request failed")
}
defer resp.Body.Close()
// Read response body
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "read response body failed")
}
// Check status code
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return errors.New(fmt.Sprintf("request failed with status %d: %s", resp.StatusCode, string(respBody)))
}
// Parse response
if result != nil {
if err := json.Unmarshal(respBody, result); err == nil {
return nil
}
var respData map[string]interface{}
if err := json.Unmarshal(respBody, &respData); err != nil {
return errors.Wrap(err, "unmarshal response failed")
}
if respData["err"] != nil || respData["msg"] != nil {
return errors.New(respData["msg"].(string))
}
}
return nil
}
// Get Send GET request
func (c *Client) Get(ctx context.Context, path string, result interface{}) error {
return c.Request(ctx, http.MethodGet, path, nil, result)
}
// Post Send POST request
func (c *Client) Post(ctx context.Context, path string, body interface{}, result interface{}) error {
return c.Request(ctx, http.MethodPost, path, body, result)
}
// Put Send PUT request
func (c *Client) Put(ctx context.Context, path string, body interface{}, result interface{}) error {
return c.Request(ctx, http.MethodPut, path, body, result)
}
// Delete Send DELETE request
func (c *Client) Delete(ctx context.Context, path string, result interface{}) error {
return c.Request(ctx, http.MethodDelete, path, nil, result)
}
+11
View File
@@ -0,0 +1,11 @@
package api
// Response Common API response structure
type Response[T any] struct {
// Response data
Data T `json:"data"`
// Error message
Err any `json:"err"`
// Prompt message
Msg string `json:"msg"`
}
@@ -0,0 +1,35 @@
package rule
import (
"context"
"github.com/chaitin/SafeLine/mcp_server/internal/api"
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
)
type CreateRuleRequest struct {
Name string `json:"name"`
IP []string `json:"ip"`
IsEnabled bool `json:"is_enabled"`
Pattern [][]api.Pattern `json:"pattern"`
Action int `json:"action"`
}
// CreateRule Create new rule
func CreateRule(ctx context.Context, req *CreateRuleRequest) (int64, error) {
if req == nil {
return 0, errors.New("request is required")
}
var resp api.Response[int64]
err := api.Service().Post(ctx, "/api/open/policy", req, &resp)
if err != nil {
return 0, errors.Wrap(err, "failed to create policy rule")
}
if resp.Err != nil {
return 0, errors.New(resp.Msg)
}
return resp.Data, nil
}
+100
View File
@@ -0,0 +1,100 @@
package api
import (
"context"
"sync"
"time"
"github.com/chaitin/SafeLine/mcp_server/internal/config"
"github.com/chaitin/SafeLine/mcp_server/pkg/errors"
"github.com/chaitin/SafeLine/mcp_server/pkg/logger"
)
// APIClient API client implementation
type APIClient struct {
client *Client
config *config.APIConfig
}
var (
instance *APIClient
once sync.Once
)
// Init Initialize API service
func Init(cfg *config.APIConfig) error {
var err error
once.Do(func() {
instance, err = newAPIClient(cfg)
if err != nil {
logger.With("error", err).Error("failed to initialize API service")
return
}
logger.Info("API service initialized successfully")
})
return err
}
// Service Get API service instance
func Service() *APIClient {
if instance == nil {
logger.Error("API service not initialized")
panic("API service not initialized")
}
return instance
}
// newAPIClient Create new API client
func newAPIClient(config *config.APIConfig) (*APIClient, error) {
if config == nil {
return nil, errors.New("config is required")
}
if config.BaseURL == "" {
return nil, errors.New("base_url is required")
}
timeout := 30
if config.Timeout > 0 {
timeout = config.Timeout
}
opts := []ClientOption{
WithBaseURL(config.BaseURL),
WithTimeout(time.Duration(timeout) * time.Second),
WithHeader("User-Agent", "SafeLine-MCP/1.0"),
WithInsecureSkipVerify(config.InsecureSkipVerify),
}
// If token is configured, add authentication header
if config.Token != "" {
opts = append(opts, WithHeader("X-SLCE-API-TOKEN", config.Token))
}
client := NewClient(opts...)
return &APIClient{
client: client,
config: config,
}, nil
}
// Post Send POST request
func (c *APIClient) Post(ctx context.Context, path string, body interface{}, result interface{}) error {
return c.client.Request(ctx, "POST", path, body, result)
}
// Get Send GET request
func (c *APIClient) Get(ctx context.Context, path string, result interface{}) error {
return c.client.Request(ctx, "GET", path, nil, result)
}
// Put Send PUT request
func (c *APIClient) Put(ctx context.Context, path string, body interface{}, result interface{}) error {
return c.client.Request(ctx, "PUT", path, body, result)
}
// Delete Send DELETE request
func (c *APIClient) Delete(ctx context.Context, path string, result interface{}) error {
return c.client.Request(ctx, "DELETE", path, nil, result)
}
+50
View File
@@ -0,0 +1,50 @@
package api
type PolicyRuleAction int
const (
PolicyRuleActionAllow PolicyRuleAction = iota
PolicyRuleActionDeny
PolicyRuleActionMax
)
type Key = string
const (
KeySrcIP Key = "src_ip"
KeyURI Key = "uri"
KeyURINoQuery Key = "uri_no_query"
KeyHost Key = "host"
KeyMethod Key = "method"
KeyReqHeader Key = "req_header"
KeyReqBody Key = "req_body"
KeyGetParam Key = "get_param"
KeyPostParam Key = "post_param"
)
type Op = string
const (
OpEq Op = "eq" // equal
OpNotEq Op = "not_eq" // not equal
OpMatch Op = "match" // match
OpCIDR Op = "cidr" // cidr
OpHas Op = "has" // has
OpNotHas Op = "not_has" // not has
OpPrefix Op = "prefix" // prefix
OpRe Op = "re" // regex
OpIn Op = "in" // in
OpNotIn Op = "not_in" // not in
OpNotCIDR Op = "not_cidr" // not cidr
OpExist Op = "exist" // exist
OpNotExist Op = "not_exist" // not exist
OpGeoEq Op = "geo_eq" // geo equal
OpGeoNotEq Op = "geo_not_eq" // geo not equal
)
type Pattern struct {
K Key `json:"k"`
Op Op `json:"op"`
V []string `json:"v"`
SubK string `json:"sub_k"`
}