From b3d32fd8d0284d995d0b750bda56b68ec73df2d5 Mon Sep 17 00:00:00 2001 From: Chiloute <35150997+Chiloute@users.noreply.github.com> Date: Tue, 21 Jul 2026 18:56:02 +0200 Subject: [PATCH 1/3] feat: add plugin JWT Decoder Signed-off-by: Chiloute <35150997+Chiloute@users.noreply.github.com> --- plugins/jwt_decoder.lua | 80 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 plugins/jwt_decoder.lua diff --git a/plugins/jwt_decoder.lua b/plugins/jwt_decoder.lua new file mode 100644 index 0000000..f2baca2 --- /dev/null +++ b/plugins/jwt_decoder.lua @@ -0,0 +1,80 @@ +Plugin = { + name = "JWT Decoder", + description = [[ +Decodes JWTs found in request headers and exposes the decoded payload inline. + +For every request header whose value looks like a JWT (three dot-separated +base64url segments), the base64url-encoded payload is decoded and added back to +the request as a new header: + +``` +Authorization: Bearer eyJ... -> X-JWT-Decoded-Authorization: {"sub":"123",...} +X-Auth-Token: eyJ... -> X-JWT-Decoded-X-Auth-Token: {"sub":"123",...} +``` + +A leading `Bearer ` prefix is stripped from any header value before decoding, +so custom authorization headers (`Authorization-Test`, `X-Auth-Token`, ...) +are handled too. The decoded header is added to the outbound request, so it is visible +in the intercept, history and replay views (and forwarded upstream). + +Pure Lua, no external dependencies. + ]], + on_request = { sync = true }, +} + +-- Reverse lookup table: base64url character -> 6-bit value (built once at load time). +local B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" +local B64_DEC = {} +for i = 1, #B64_ALPHABET do + B64_DEC[B64_ALPHABET:sub(i, i)] = i - 1 +end + +-- Decode an unpadded base64url string. Returns the decoded string, or nil if +local function b64url_decode(s) + if #s % 4 == 1 then + return nil + end + local out, acc, bits = {}, 0, 0 + for c in s:gmatch(".") do + local v = B64_DEC[c] + if not v then + return nil + end + acc, bits = acc * 64 + v, bits + 6 + if bits >= 8 then + bits = bits - 8 + out[#out + 1] = string.char(math.floor(acc / 2 ^ bits)) + acc = acc % 2 ^ bits + end + end + return table.concat(out) +end + +-- If value is a JWT, return its decoded payload +local function decode_jwt_payload(value) + -- Three dot-separated base64url segments; the signature may be empty (alg:none). + local payload = value:match("^[A-Za-z0-9_-]+%.([A-Za-z0-9_-]+)%.[A-Za-z0-9_-]*$") + if not payload then + return nil + end + local decoded = b64url_decode(payload) + if not decoded then + return nil + end + -- Only accept plausible JSON payloads. + if decoded:match("^%s*{") == nil then + return nil + end + decoded = decoded:gsub("[\r\n]+", " ") + return decoded +end + +function on_request(req) + for name, value in pairs(req.headers) do + local token = value:gsub("^[Bb][Ee][Aa][Rr][Ee][Rr]%s+", "") + local payload = decode_jwt_payload(token) + if payload then + req:set_header("X-JWT-Decoded-" .. name, payload) + end + end +end From 984e2f88c885d7d315e5007b04f77305c6926861 Mon Sep 17 00:00:00 2001 From: chiloute <35150997+Chiloute@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:21:19 +0200 Subject: [PATCH 2/3] Fix comment Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- plugins/jwt_decoder.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/jwt_decoder.lua b/plugins/jwt_decoder.lua index f2baca2..eb9bbd5 100644 --- a/plugins/jwt_decoder.lua +++ b/plugins/jwt_decoder.lua @@ -29,7 +29,7 @@ for i = 1, #B64_ALPHABET do B64_DEC[B64_ALPHABET:sub(i, i)] = i - 1 end --- Decode an unpadded base64url string. Returns the decoded string, or nil if +-- Decode an unpadded base64url string. Returns the decoded string, or nil on invalid input. local function b64url_decode(s) if #s % 4 == 1 then return nil From 717ea26b94a0c816fd51ada6e3d316b3d2f5b522 Mon Sep 17 00:00:00 2001 From: chiloute <35150997+Chiloute@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:17:47 +0200 Subject: [PATCH 3/3] copilot fix --- plugins/jwt_decoder.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/jwt_decoder.lua b/plugins/jwt_decoder.lua index eb9bbd5..ce67c57 100644 --- a/plugins/jwt_decoder.lua +++ b/plugins/jwt_decoder.lua @@ -54,7 +54,7 @@ end local function decode_jwt_payload(value) -- Three dot-separated base64url segments; the signature may be empty (alg:none). local payload = value:match("^[A-Za-z0-9_-]+%.([A-Za-z0-9_-]+)%.[A-Za-z0-9_-]*$") - if not payload then + if not payload or #payload > 8192 then return nil end local decoded = b64url_decode(payload) @@ -65,7 +65,7 @@ local function decode_jwt_payload(value) if decoded:match("^%s*{") == nil then return nil end - decoded = decoded:gsub("[\r\n]+", " ") + decoded = decoded:gsub("[%c]+", " ") return decoded end