{"record":{"id":"82538fb6d3e0da83","repo":"txthinking/brook","slug":"expired-request-82538f","errorCode":null,"errorMessage":"Expired request","messagePattern":"Expired request","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"streamserver.go","lineNumber":90,"sourceCode":"\ts.ca, err = cipher.NewGCM(cb)\n\tif err != nil {\n\t\tx.BP12.Put(s.cn)\n\t\treturn nil, err\n\t}\n\n\ts.RB = x.BP2048.Get().([]byte)\n\tl, err := s.Read()\n\tif err != nil {\n\t\tx.BP12.Put(s.cn)\n\t\tx.BP2048.Put(s.RB)\n\t\treturn nil, err\n\t}\n\ti := int64(binary.BigEndian.Uint32(s.RB[2+16 : 2+16+4]))\n\tif time.Now().Unix()-i > 60 {\n\t\tx.BP12.Put(s.cn)\n\t\tx.BP2048.Put(s.RB)\n\t\tWaitReadErr(s.Client)\n\t\treturn nil, errors.New(\"Expired request\")\n\t}\n\tif i%2 == 0 {\n\t\ts.network = \"tcp\"\n\t}\n\tif i%2 == 1 {\n\t\ts.network = \"udp\"\n\t\ts.Timeout = udptimeout\n\t}\n\n\ts.sn = x.BP12.Get().([]byte)\n\tif _, err := io.ReadFull(rand.Reader, s.sn); err != nil {\n\t\tx.BP12.Put(s.cn)\n\t\tx.BP2048.Put(s.RB)\n\t\tx.BP12.Put(s.sn)\n\t\treturn nil, err\n\t}\n\tsk := x.BP32.Get().([]byte)\n\tif _, err := io.ReadFull(hkdf.New(sha256.New, password, s.sn, ServerHKDFInfo), sk); err != nil {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/txthinking/brook/blob/5cd13ef3b1fb574e88ebf2c1b5d95f2ebe1342c8/streamserver.go#L72-L108","documentation":"The stream server's handshake embeds a 4-byte big-endian Unix timestamp in the authenticated request; NewStreamServer rejects the request if the timestamp is more than 60 seconds in the past. This is a replay-protection window: old captured handshakes cannot be reused. A rejected handshake leaves the connection to be closed via WaitReadErr.","triggerScenarios":"A client embeds a stale timestamp in its handshake (clock skew > 60s between client and server, or a replayed/captured handshake), so time.Now().Unix()-i > 60 on the server.","commonSituations":"Client machine clock drifting (VM suspend, RTC drift, wrong timezone/NTP disabled); replaying a recorded handshake; queuing a pre-built handshake for more than a minute before sending.","solutions":["Sync the client clock with NTP and retry - the timestamp must be within 60s of the server clock","Build the handshake immediately before sending; never cache a prepared request for reuse","Check for replayed connections (e.g. a proxy re-sending a captured handshake) and generate a fresh one","If clock skew is unavoidable in the environment, increase the 60s window in the server code"],"exampleFix":"// before: handshake built once at startup and reused\nvar hs = buildHandshake() // timestamp frozen\nconn.Write(hs)\n\n// after: rebuild per connection so the timestamp is fresh\nconn.Write(buildHandshake()) // timestamp = time.Now().Unix()","handlingStrategy":"retry","validationCode":"func tsFresh(b []byte) bool {\n\ti := int64(binary.BigEndian.Uint32(b[2+16 : 2+16+4]))\n\treturn time.Now().Unix()-i <= 60\n}","typeGuard":null,"tryCatchPattern":"for attempt := 0; attempt < 2; attempt++ {\n\tc, err := NewStreamClient(...) // rebuild handshake each attempt\n\tif err == nil { break }\n\tntp.Sync() // clock may have drifted\n}","preventionTips":["Run NTP on clients","Build the handshake immediately before each connection","Never cache or replay prepared handshakes"],"tags":["authentication","timestamp","replay-protection"],"backgroundTag":"request-timeout","analyzedSha":"5cd13ef3b1fb574e88ebf2c1b5d95f2ebe1342c8","analyzedAt":"2026-09-06T04:35:00.432Z","contentChangedAt":"2026-09-06T04:35:00.432Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}