diff --git a/internal/waf/action_js_cookie.go b/internal/waf/action_js_cookie.go
new file mode 100644
index 0000000..3c62f07
--- /dev/null
+++ b/internal/waf/action_js_cookie.go
@@ -0,0 +1,128 @@
+// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
+
+package waf
+
+import (
+	"crypto/md5"
+	"fmt"
+	"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
+	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
+	"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
+	"github.com/TeaOSLab/EdgeNode/internal/waf/requests"
+	"github.com/iwind/TeaGo/types"
+	"net/http"
+	"time"
+)
+
+type JSCookieAction struct {
+	BaseAction
+
+	Life             int32  `yaml:"life" json:"life"`
+	MaxFails         int    `yaml:"maxFails" json:"maxFails"`                 // 最大失败次数
+	FailBlockTimeout int    `yaml:"failBlockTimeout" json:"failBlockTimeout"` // 失败拦截时间
+	Scope            string `yaml:"scope" json:"scope"`
+}
+
+func (this *JSCookieAction) Init(waf *WAF) error {
+	this.Scope = firewallconfigs.FirewallScopeGlobal
+
+	return nil
+}
+
+func (this *JSCookieAction) Code() string {
+	return ActionJavascriptCookie
+}
+
+func (this *JSCookieAction) IsAttack() bool {
+	return false
+}
+
+func (this *JSCookieAction) WillChange() bool {
+	return true
+}
+
+func (this *JSCookieAction) Perform(waf *WAF, group *RuleGroup, set *RuleSet, req requests.Request, writer http.ResponseWriter) (allow bool) {
+	// 是否在白名单中
+	if SharedIPWhiteList.Contains("set:"+types.String(set.Id), this.Scope, req.WAFServerId(), req.WAFRemoteIP()) {
+		return true
+	}
+
+	nodeConfig, err := nodeconfigs.SharedNodeConfig()
+	if err != nil {
+		return true
+	}
+
+	var life = this.Life
+	if life <= 0 {
+		life = 3600
+	}
+
+	// 检查Cookie
+	var cookieName = "ge_js_validator_" + types.String(set.Id)
+	cookie, err := req.WAFRaw().Cookie(cookieName)
+	if err == nil && cookie != nil {
+		var cookieValue = cookie.Value
+		if len(cookieValue) > 10 {
+			var timestamp = cookieValue[:10]
+			if types.Int64(timestamp) >= time.Now().Unix()-int64(life) && fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+nodeConfig.NodeId))) == cookieValue[10:] {
+				return true
+			}
+		}
+	}
+
+	writer.Header().Set("Content-Type", "text/html; charset=utf-8")
+	writer.Header().Set("Cache-Control", "no-cache")
+
+	var timestamp = types.String(time.Now().Unix())
+
+	var cookieValue = timestamp + fmt.Sprintf("%x", md5.Sum([]byte(timestamp+"@"+nodeConfig.NodeId)))
+
+	_, _ = writer.Write([]byte(`
+
+
+
+
+
+
+
+
+`))
+
+	// 记录失败次数
+	this.increaseFails(req, waf.Id, group.Id, set.Id)
+
+	return false
+}
+
+func (this *JSCookieAction) increaseFails(req requests.Request, policyId int64, groupId int64, setId int64) (goNext bool) {
+	var maxFails = this.MaxFails
+	var failBlockTimeout = this.FailBlockTimeout
+
+	if maxFails <= 0 {
+		maxFails = 10 // 默认10次
+	} else if maxFails <= 3 {
+		maxFails = 3 // 不能小于3,防止意外刷新出现
+	}
+	if failBlockTimeout <= 0 {
+		failBlockTimeout = 1800 // 默认1800s
+	}
+
+	var key = "JS_COOKIE:FAILS:" + req.WAFRemoteIP() + ":" + types.String(req.WAFServerId())
+
+	var countFails = ttlcache.SharedCache.IncreaseInt64(key, 1, time.Now().Unix()+300, true)
+	if int(countFails) >= maxFails {
+		var useLocalFirewall = false
+
+		if this.Scope == firewallconfigs.FirewallScopeGlobal {
+			useLocalFirewall = true
+		}
+
+		SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "JS_COOKIE验证连续失败超过"+types.String(maxFails)+"次")
+		return false
+	}
+
+	return true
+}
diff --git a/internal/waf/action_types.go b/internal/waf/action_types.go
index 75aeace..2ce8778 100644
--- a/internal/waf/action_types.go
+++ b/internal/waf/action_types.go
@@ -5,18 +5,19 @@ import "reflect"
 type ActionString = string
 
 const (
-	ActionLog      ActionString = "log"       // allow and log
-	ActionBlock    ActionString = "block"     // block
-	ActionCaptcha  ActionString = "captcha"   // block and show captcha
-	ActionNotify   ActionString = "notify"    // 告警
-	ActionGet302   ActionString = "get_302"   // 针对GET的302重定向认证
-	ActionPost307  ActionString = "post_307"  // 针对POST的307重定向认证
-	ActionRecordIP ActionString = "record_ip" // 记录IP
-	ActionTag      ActionString = "tag"       // 标签
-	ActionPage     ActionString = "page"      // 显示网页
-	ActionAllow    ActionString = "allow"     // allow
-	ActionGoGroup  ActionString = "go_group"  // go to next rule group
-	ActionGoSet    ActionString = "go_set"    // go to next rule set
+	ActionLog              ActionString = "log"       // allow and log
+	ActionBlock            ActionString = "block"     // block
+	ActionCaptcha          ActionString = "captcha"   // block and show captcha
+	ActionJavascriptCookie ActionString = "js_cookie" // js cookie
+	ActionNotify           ActionString = "notify"    // 告警
+	ActionGet302           ActionString = "get_302"   // 针对GET的302重定向认证
+	ActionPost307          ActionString = "post_307"  // 针对POST的307重定向认证
+	ActionRecordIP         ActionString = "record_ip" // 记录IP
+	ActionTag              ActionString = "tag"       // 标签
+	ActionPage             ActionString = "page"      // 显示网页
+	ActionAllow            ActionString = "allow"     // allow
+	ActionGoGroup          ActionString = "go_group"  // go to next rule group
+	ActionGoSet            ActionString = "go_set"    // go to next rule set
 )
 
 var AllActions = []*ActionDefinition{
@@ -44,6 +45,12 @@ var AllActions = []*ActionDefinition{
 		Instance: new(CaptchaAction),
 		Type:     reflect.TypeOf(new(CaptchaAction)).Elem(),
 	},
+	{
+		Name:     "JS Cookie验证",
+		Code:     ActionJavascriptCookie,
+		Instance: new(JSCookieAction),
+		Type:     reflect.TypeOf(new(JSCookieAction)).Elem(),
+	},
 	{
 		Name:     "告警",
 		Code:     ActionNotify,
diff --git a/internal/waf/captcha_counter.go b/internal/waf/captcha_counter.go
index 7007bc3..8a974d1 100644
--- a/internal/waf/captcha_counter.go
+++ b/internal/waf/captcha_counter.go
@@ -34,7 +34,7 @@ func CaptchaIncreaseFails(req requests.Request, actionConfig *CaptchaAction, pol
 				useLocalFirewall = true
 			}
 
-			SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "CAPTCHA验证连续失败")
+			SharedIPBlackList.RecordIP(IPTypeAll, firewallconfigs.FirewallScopeService, req.WAFServerId(), req.WAFRemoteIP(), time.Now().Unix()+int64(failBlockTimeout), policyId, useLocalFirewall, groupId, setId, "CAPTCHA验证连续失败超过"+types.String(maxFails)+"次")
 			return false
 		}
 	}