Skip to content

web.guard

web 守卫面:CSRF 双提交中间件 + 令牌桶限流中间件(Plan 2c Task 2; http/frm/csrf + http/frm/limit 薄收编,调用不复制;纪律 §14-1;§8-A5 时钟注入)

use 面:web.core(Resp/Req/取头)+ web.router(测试装配链)+ std.str(index_of/trim/ starts_with/contains)+ http.frm.csrf + http.frm.limit(两独立叶,零共享; std.crypto 不直 use —— csrf 单叶口径,mw.ct auth 同款,消费方再入即同叶双径 E5020)。

== csrf_mw:无状态双提交(HMAC 签名硬化,frm/csrf 全语义面) == 安全方法(GET/HEAD/OPTIONS 及一切非写法)→ next 直通;写方法(POST/PUT/DELETE/ PATCH)→ cookie 端(csrf_cookie_get 读 Cookie 头)vs 提交端(csrf_form_get 读 form-urlencoded 体,字段名 csrf)→ csrf_verify(双提交等值 + nonce 域 + hex 形 + HMAC 重算)→ 不合 403(体 "csrf rejected",frm csrf_resp_403 同串;wire 串形态 不入 Resp 世界,以 status() 值构造 —— 语义随 frm,形态随框架)。通过面(安全+ 已验写)响应侧恒发新 csrf cookie(csrf_issue 签发 + csrf_set_cookie 行剥壳取值, SameSite=Lax 随 frm;HttpOnly 不设 —— 双提交 JS 可读,frm 头注权衡)。 nonce 熵源 = 闭包持原子序号十进制补零至 16(frm 头注「uuid_v4/random 序号/会话 指纹」取「序号」支;抗伪造在 HMAC 签名面,不依赖 nonce 保密;序号确定性 → 测试 面可断言;真熵装配归 composition,志向)。表单解析双面登记:csrf_form_get 零解码 (frm 口径)—— 签发域 [A-Za-z0-9.] 无 %/+ 出现,与 web.core form() 的 pdec 在 令牌域恒等,两侧取值等价。

== rate_limit_mw:令牌桶 per-key + max-in-flight 闸(frm/limit 全语义面) == 判定序随 frm:in-flight 门先于令牌桶(shed 优先 —— 满载不再耗桶令牌)。桶补充/ 消费/Retry-After 全走 lim_take(cap 封顶、整步前进、ceil 算 retry);装配期 lim_cfg 校验,参数非正 panic(失败在启动,不在半夜 —— timeout_ms 先例)。 超限 → 429 + Retry-After(lt_retry;绑定口径);shed → 503 + Retry-After 1 (frm lim_resp_503 常量礼让值;wire 串同不入 Resp 世界,以 status() 值构造)。

—— v1 结构性边界(如实登记,非临时态)——

① per-key 口径:Req 无 remote 字段(v1)→ 键 = X-Forwarded-For 首段(trim), 缺省 "local"。XFF 客户端可伪造 → 伪造键各得新桶,per-key 限流可被绕过且键表 增长攻击面由此而生 —— 真键面挂 Req.remote/serve 取 peer 字段落位(§13 候选/ P8),本件头注即闸。 ② 键表容量:maxk()=1024,线性扫描(v1)。满表 miss 并入 "local" 共享桶(降级 为全局限流);表满且 "local" 未入表(全 XFF 流量)→ 溢出键不记放行(fail-open, bkt_plan 纯面测试钉死)。原位压缩/淘汰归志向。 ③ 键表载体 = Mutex[List[BktEnt]](键唯一不变式:命中原位 Index 替换,miss 才 push)—— 绑定速记 Mutex[Map[Str,LimBkt]] 的证据化调整:std/map put 函数式 (值语义)+ with_mut 闭包内整体重绑不写回 Mutex(web_todo README 在册实证), 函数式 Map 无写回位;List 原位 Index 替换写回经探针实证(整元素替换写回 9/ I64 位写回 5;复合 l[i].f 写回被拒「assign target:Member」—— 故整元素替换形)。 ④ max-in-flight:load→acq→store 三步非原子(v1 阻塞串行档 §8.2 无并发窗口; 并发波落地需 CAS/fetch_add 形信号量,志向);释放点 = next 返回即 rel(链形 fn 无 post-return 钩子,frm/limit 头注同款登记 —— 较「响应写出后」略早,诚实口径)。 ⑤ csrf 令牌不绑会话(frm v1 无会话绑定)+ SameSite=Lax 为跨站主防;子域 cookie 注入面 = 双提交经典弱点,frm 头注已在册,随。

接线说明:本件纯判定面(无二进制 body/载体问题),可正常挂链上线 —— compress_mw 的「勿裸上线闸」(2c Task 1 载体边界)不适用本件。 E3070 口径:两中间件闭包体全 let/return 形(无 var 声明、无 Ident 赋值);状态 变更全经原子方法(store/fetch_add)、Mutex.with_mut 闭包内 Index 替换/push 方法位;可变逻辑全提顶层 fn(E3070 全文件 var 名交集纪律,mw.ct 先例同款)。

pub struct

struct Pair

Field Type
k Str
v Str

struct Resp

Field Type
status I32
headers List[Pair]
body Str

struct JObj

Field Type
parts List[Str]

struct Req[S]

Field Type
method Str
path Str
query Str
body Str
headers List[Pair]
cookies List[Pair]
params List[Pair]
session List[Pair]
state S

struct RouteSeg

Field Type
kind Str
text Str

struct Route[S]

Field Type
m Str
segs List[RouteSeg]
chain List[fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp]
h fn(&Req[S]) -> Resp

struct Router[S]

Field Type
state S
routes List[Route[S]]
mws List[fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp]
not_found_h List[fn(&Req[S]) -> Resp]
not_allowed_h List[fn(&Req[S]) -> Resp]

struct Match[S]

Field Type
kind I32
route Route[S]
params List[Pair]
splat Str
allow Str

struct CsrfTok

Field Type
ok I64
why I64
tok Str

struct CsrfVer

Field Type
rc I64
why I64

struct LimCfg

Field Type
ok I64
err I64
cap I64
refil I64
ivl I64
maxif I64

struct LimBkt

Field Type
tks I64
last I64

struct LimTake

Field Type
ok I64
retry I64
tks I64
last I64

struct LimIf

Field Type
ok I64
n I64

pub fn

Signature Returns Description
csrf_mw[S](key: Str) fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp csrf_mw —— CSRF 双提交中间件(§4.5 形态 ③ 带状态工厂:原子序号随闭包)
rate_limit_mw[S](cap: I64, refil: I64, ivl: I64, maxif: I64, now: fn() -> I64) fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp rate_limit_mw —— 令牌桶限流中间件(§4.5 形态 ③:键表 + in-flight 计数随闭包;
now 注入 §8-A5,真钟归装配 \| \| now_ms() / 1000 形,测试面 fake 前进)
html(body: Str) Resp —
json(body: Str) Resp —
text(body: Str) Resp —
redirect(loc: Str) Resp —
status(n: I32, body: Str) Resp —
xml(body: Str) Resp —
yaml(body: Str) Resp —
err_json(n: I32, msg: Str) Resp —
attachment(r: Resp, filename: Str) Resp —
with(r: Resp, k: Str, v: Str) Resp —
with_status(r: Resp, n: I32) Resp —
resp_header(r: Resp, k: Str) Str —
resp_body(r: Resp) Str —
resp_status(r: Resp) I32 —
json_obj() JObj —
str(b: JObj, k: Str, v: Str) JObj —
i64(b: JObj, k: Str, v: I64) JObj —
bool(b: JObj, k: Str, v: Bool) JObj —
strs(b: JObj, k: Str, xs: List[Str]) JObj —
end(b: JObj) Str —
end_status(b: JObj, n: I32) Resp —
csv_rows(headers: List[Str], rows: List[List[Str]]) Str —
req_of[S](method: Str, path: Str, state: S) Req[S] —
req_full[S](method: Str, path: Str, body: Str, hdrs: List[Pair], state: S) Req[S] —
req_set_params[S](r: &Req[S], params: List[Pair]) Req[S] —
req_set_session[S](r: &Req[S], sess: List[Pair]) Req[S] —
pdec(s: Str) Str —
param[S](r: &Req[S], name: Str) Str —
pair_get(ps: List[Pair], k: Str) Str —
param_i64[S](r: &Req[S], name: Str) Option[I64] —
param_f64[S](r: &Req[S], name: Str) Option[F64] —
query[S](r: &Req[S], name: Str) Str —
query_all[S](r: &Req[S], name: Str) List[Str] —
form[S](r: &Req[S], name: Str) Str —
form_all[S](r: &Req[S], name: Str) List[Str] —
header[S](r: &Req[S], name: Str) Str —
cookie[S](r: &Req[S], name: Str) Str —
session[S](r: &Req[S], name: Str) Str —
router[S](state: S) Router[S] —
get[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) Router[S] —
post[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) Router[S] —
put[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) Router[S] —
middleware[S](r: Router[S], mw: fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp) Router[S] —
mount_at[S](r: Router[S], prefix: Str, sub: Router[S]) Router[S] —
has_conflict[S](r: Router[S]) Bool —
install[S](r: Router[S], p: fn(Router[S]) -> Router[S]) Router[S] —
not_found[S](r: Router[S], h: fn(&Req[S]) -> Resp) Router[S] —
method_not_allowed[S](r: Router[S], h: fn(&Req[S]) -> Resp) Router[S] —
test_call[S](r: Router[S], method: Str, path: Str, body: Str) Resp —
route_match[S](r: Router[S], m: Str, path: Str) Match[S] —
dispatch_h[S](r: Router[S], method: Str, path: Str, body: Str, hdrs: List[Pair]) Resp —
dispatch[S](r: Router[S], method: Str, path: Str, body: Str) Resp —
ct_rc(t: CsrfTok) I64 —
ct_why(t: CsrfTok) I64 —
ct_tok(t: CsrfTok) Str —
csrf_issue(key: Str, nonce: Str) CsrfTok —
cv_rc(v: CsrfVer) I64 —
cv_why(v: CsrfVer) I64 —
csrf_verify(key: Str, cookie_tok: Str, submit_tok: Str) CsrfVer —
csrf_cookie_get(cookie_hdr: Str, name: Str) Str —
csrf_form_get(body: Str, name: Str) Str —
csrf_set_cookie(tok: Str) Str —
lim_err_ok() I64 —
lim_err_param() I64 —
lcfg_ok(c: LimCfg) I64 —
lcfg_err(c: LimCfg) I64 —
lcfg_cap(c: LimCfg) I64 —
lim_cfg(cap: I64, refil: I64, ivl: I64, maxif: I64) LimCfg —
lim_bkt(tks: I64, last: I64) LimBkt —
lim_bkt_new(cap: I64, now: I64) LimBkt —
lt_ok(t: LimTake) I64 —
lt_retry(t: LimTake) I64 —
lt_tks(t: LimTake) I64 —
lt_last(t: LimTake) I64 —
lim_take(b: LimBkt, c: LimCfg, now: I64, cost: I64) LimTake —
lif_ok(x: LimIf) I64 —
lif_n(x: LimIf) I64 —
lim_acq(n: I64, maxif: I64) LimIf —
lim_rel(n: I64) I64 —
lim_ip_key(ip: Str) I64 —
lim_resp_503(retry: I64) Str —