diff --git a/go.mod b/go.mod index 28a4a43d..9af1ba31 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/gorilla/websocket v1.5.3 github.com/labstack/echo-contrib/v5 v5.0.1 github.com/labstack/echo-jwt/v5 v5.0.2 - github.com/labstack/echo/v5 v5.3.0 + github.com/labstack/echo/v5 v5.4.0 github.com/lestrrat-go/jwx/v3 v3.1.1 github.com/prometheus/client_golang v1.23.2 github.com/r3labs/sse/v2 v2.10.0 diff --git a/go.sum b/go.sum index 85989ba3..7d773670 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/labstack/echo-jwt/v5 v5.0.2 h1:ECmHEjwbR30fv90LvpQ0XZbEJWhCF/c3Zp8ug/ github.com/labstack/echo-jwt/v5 v5.0.2/go.mod h1:jNxekWlI4+M+UoV8dozKwIHN9MJ2psUR+U1B0nEutIg= github.com/labstack/echo/v5 v5.3.0 h1:KT74Mprk053PQEHwSZdeCDIz1BigTZOZhavMD0c9Fjs= github.com/labstack/echo/v5 v5.3.0/go.mod h1:Q3j2+clBRgJr0O3DDONQeXNsM7RHgSwUhcuo47unqm8= +github.com/labstack/echo/v5 v5.4.0 h1:iY674460IvSmUcj7MziL3YrgyTClghz5BpdZoKiGxR4= +github.com/labstack/echo/v5 v5.4.0/go.mod h1:4iEGNQiPPZnkfYpNR/L6fINd3NLiGWUD5+eBotFALas= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= github.com/lestrrat-go/dsig v1.2.1 h1:MwxzZhE4+4fguHi+uDALKVlC3Cn+O1QU1Q/F8D7hVIc= diff --git a/reference/go.mod b/reference/go.mod index f390045d..595652a6 100644 --- a/reference/go.mod +++ b/reference/go.mod @@ -2,6 +2,6 @@ module github.com/labstack/echox/reference go 1.25.0 -require github.com/labstack/echo/v5 v5.3.0 +require github.com/labstack/echo/v5 v5.4.0 require golang.org/x/time v0.15.0 // indirect diff --git a/reference/go.sum b/reference/go.sum index 2929860c..bfc55272 100644 --- a/reference/go.sum +++ b/reference/go.sum @@ -2,14 +2,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/labstack/echo/v5 v5.3.0 h1:KT74Mprk053PQEHwSZdeCDIz1BigTZOZhavMD0c9Fjs= github.com/labstack/echo/v5 v5.3.0/go.mod h1:Q3j2+clBRgJr0O3DDONQeXNsM7RHgSwUhcuo47unqm8= +github.com/labstack/echo/v5 v5.4.0 h1:iY674460IvSmUcj7MziL3YrgyTClghz5BpdZoKiGxR4= +github.com/labstack/echo/v5 v5.4.0/go.mod h1:4iEGNQiPPZnkfYpNR/L6fINd3NLiGWUD5+eBotFALas= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/site/echo-source.json b/site/echo-source.json index bb5affe7..932305d6 100644 --- a/site/echo-source.json +++ b/site/echo-source.json @@ -1,5 +1,5 @@ { "repository": "https://github.com/labstack/echo.git", - "revision": "caa018246b840091e71a1d61ea9ee1176ab81333", - "release": "v5.3.0" + "revision": "fc410094b2f3415cf2bf5204082137b205bfd5af", + "release": "v5.4.0" } diff --git a/site/next-source.json b/site/next-source.json index d90df8ac..9e199c6a 100644 --- a/site/next-source.json +++ b/site/next-source.json @@ -1,5 +1,5 @@ { "repository": "https://github.com/labstack/echo.git", - "revision": "e804f422d2b0546df620b7f73b7563f509368972", + "revision": "5196b9b0ad8f683fa4bf7172a37805f4651a7975", "release": "next" } diff --git a/site/package.json b/site/package.json index 085a1414..27496165 100644 --- a/site/package.json +++ b/site/package.json @@ -12,6 +12,8 @@ "source:accept": "node scripts/accept-source.mjs", "translations:status": "node scripts/translation-status.mjs", "translations:accept": "node scripts/accept-translations.mjs", + "security-translations:status": "node scripts/security-translation-status.mjs", + "security-translations:accept": "node scripts/security-translation-status.mjs --accept", "site:check": "node scripts/check-site.mjs", "site:accept": "node scripts/check-site.mjs --accept", "performance:check": "node scripts/check-performance.mjs", diff --git a/site/route-baseline.json b/site/route-baseline.json index 6e48a8e9..2c81f217 100644 --- a/site/route-baseline.json +++ b/site/route-baseline.json @@ -78,6 +78,7 @@ "/docs/middleware/static/", "/docs/middleware/trailing-slash/", "/docs/quick-start/", + "/docs/request-scheme/", "/docs/request/", "/docs/response/", "/docs/routing/", @@ -114,6 +115,7 @@ "/es/guide/installation/", "/es/guide/ip-address/", "/es/guide/quickstart/", + "/es/guide/request-scheme/", "/es/guide/request/", "/es/guide/response/", "/es/guide/routing/", @@ -154,6 +156,7 @@ "/guide/installation/", "/guide/ip-address/", "/guide/quickstart/", + "/guide/request-scheme/", "/guide/request/", "/guide/response/", "/guide/routing/", @@ -189,6 +192,7 @@ "/ja/guide/installation/", "/ja/guide/ip-address/", "/ja/guide/quickstart/", + "/ja/guide/request-scheme/", "/ja/guide/request/", "/ja/guide/response/", "/ja/guide/routing/", @@ -298,6 +302,7 @@ "/next/es/guide/installation/", "/next/es/guide/ip-address/", "/next/es/guide/quickstart/", + "/next/es/guide/request-scheme/", "/next/es/guide/request/", "/next/es/guide/response/", "/next/es/guide/routing/", @@ -338,6 +343,7 @@ "/next/guide/installation/", "/next/guide/ip-address/", "/next/guide/quickstart/", + "/next/guide/request-scheme/", "/next/guide/request/", "/next/guide/response/", "/next/guide/routing/", @@ -373,6 +379,7 @@ "/next/ja/guide/installation/", "/next/ja/guide/ip-address/", "/next/ja/guide/quickstart/", + "/next/ja/guide/request-scheme/", "/next/ja/guide/request/", "/next/ja/guide/response/", "/next/ja/guide/routing/", @@ -460,6 +467,7 @@ "/next/pt-br/guide/installation/", "/next/pt-br/guide/ip-address/", "/next/pt-br/guide/quickstart/", + "/next/pt-br/guide/request-scheme/", "/next/pt-br/guide/request/", "/next/pt-br/guide/response/", "/next/pt-br/guide/routing/", @@ -521,6 +529,7 @@ "/next/zh-cn/guide/installation/", "/next/zh-cn/guide/ip-address/", "/next/zh-cn/guide/quickstart/", + "/next/zh-cn/guide/request-scheme/", "/next/zh-cn/guide/request/", "/next/zh-cn/guide/response/", "/next/zh-cn/guide/routing/", @@ -582,6 +591,7 @@ "/pt-br/guide/installation/", "/pt-br/guide/ip-address/", "/pt-br/guide/quickstart/", + "/pt-br/guide/request-scheme/", "/pt-br/guide/request/", "/pt-br/guide/response/", "/pt-br/guide/routing/", @@ -644,6 +654,7 @@ "/zh-cn/guide/installation/", "/zh-cn/guide/ip-address/", "/zh-cn/guide/quickstart/", + "/zh-cn/guide/request-scheme/", "/zh-cn/guide/request/", "/zh-cn/guide/response/", "/zh-cn/guide/routing/", diff --git a/site/scripts/build-site.mjs b/site/scripts/build-site.mjs index 086a5009..73cdb9cc 100644 --- a/site/scripts/build-site.mjs +++ b/site/scripts/build-site.mjs @@ -16,6 +16,7 @@ function run(args, channel, sourceDir = '') { run(['run', 'source:check'], 'stable'); run(['run', 'translations:status'], 'stable'); +run(['run', 'security-translations:status'], 'stable'); run(['run', 'astro', '--', 'build'], 'stable'); run(['run', 'source:check'], 'next', proposedEcho); run(['run', 'astro', '--', 'build'], 'next', proposedEcho); diff --git a/site/scripts/security-translation-status.mjs b/site/scripts/security-translation-status.mjs new file mode 100644 index 00000000..92312d3a --- /dev/null +++ b/site/scripts/security-translation-status.mjs @@ -0,0 +1,55 @@ +import { createHash } from 'node:crypto'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const siteDir = fileURLToPath(new URL('..', import.meta.url)); +const docsDir = join(siteDir, 'src/content/docs'); +const baselinePath = join(siteDir, 'security-translation-baseline.json'); +const locales = ['es', 'ja', 'pt-br', 'zh-cn']; +const pages = [ + 'guide/ip-address.md', + 'guide/request-scheme.md', + 'guide/response.md', + 'guide/static-files.md', + 'guide/testing.md', + 'middleware/redirect.mdx', + 'middleware/secure.mdx', + 'middleware/proxy.mdx', + 'middleware/method-override.mdx', + 'middleware/static.mdx', + 'middleware/trailing-slash.mdx', + 'cookbook/reverse-proxy.md', + 'cookbook/jsonp.md', +]; + +function hash(path) { + return createHash('sha256').update(readFileSync(path, 'utf8')).digest('hex'); +} + +const actual = Object.fromEntries(locales.map((locale) => [ + locale, + Object.fromEntries(pages.map((page) => [page, { + source: hash(join(docsDir, page)), + translation: hash(join(docsDir, locale, page)), + }])), +])); + +if (process.argv.includes('--accept')) { + writeFileSync(baselinePath, `${JSON.stringify(actual, null, 2)}\n`); + console.log(`Recorded ${pages.length} reviewed security pages in ${locales.length} locales`); +} else { + const baseline = JSON.parse(readFileSync(baselinePath, 'utf8')); + const stale = []; + for (const locale of locales) { + for (const page of pages) { + const before = baseline[locale]?.[page]; + const now = actual[locale][page]; + if (!before || before.source !== now.source || before.translation !== now.translation) { + stale.push(`${locale}/${page}: ${!before ? 'not reviewed' : before.source !== now.source ? 'English changed' : 'translation changed'}`); + } + } + } + if (stale.length) throw new Error(`${stale.length} security translations need review:\n${stale.join('\n')}`); + console.log(`${pages.length} security pages match reviewed translations in ${locales.length} locales`); +} diff --git a/site/security-translation-baseline.json b/site/security-translation-baseline.json new file mode 100644 index 00000000..2c02ab9c --- /dev/null +++ b/site/security-translation-baseline.json @@ -0,0 +1,218 @@ +{ + "es": { + "guide/ip-address.md": { + "source": "be4ce687caa56e2283c69f103c446b041acacc37d97a52f02fd07b733dd6381c", + "translation": "4bf85d5f5634ca1b6d5fc7428f8c788da32115f5bc97ca4991dc16f68c82f336" + }, + "guide/request-scheme.md": { + "source": "1529ab0504b491afd01b3f345e5ca5e90c41528d76b8ac06c64d2b8fb1f952f7", + "translation": "b0d013a4815b476b79bec8b4d0d31b6fe3af791dc174b3974ab6008d53e83f37" + }, + "guide/response.md": { + "source": "762d690e149acfab27231c767db5a55f6019c39c7899f160e9ae7b6fe408d385", + "translation": "3659f9bf0527d65ee19b6c05472122b718c291c2faa16e70430981be8de334aa" + }, + "guide/static-files.md": { + "source": "e806b7ae28bd328dcedb61d4a52f52da96d310a139989540459c12c9894078ef", + "translation": "b33c9c62e0f8398c260561f01b8a9134207d5f882818eda6d28b34b888e68f77" + }, + "guide/testing.md": { + "source": "03fa2dcda2db2b47e95d3bffd29f8229787b12ec0177f4d7a934d5f81e19fa36", + "translation": "42df109f65690eec3d69634e2cfe2a0f40f8c6e39eeae633193d1f3f2e688542" + }, + "middleware/redirect.mdx": { + "source": "ba113d4f531097bde33682e39e80e062659df8501b01be3b96d0b17144b25aa2", + "translation": "1af1638dd602cb93f7b38e782b841588e4db0f716fdcc18d90bebf919e02d8b3" + }, + "middleware/secure.mdx": { + "source": "a7eae9b3c59d0774428aa7db8798eb858a4dded9df4a68766c4329b8308a00f5", + "translation": "509a4b3889fd25bbcfc6a211532fdc5b6e69864e63250e6abaf4a1bde6f9980d" + }, + "middleware/proxy.mdx": { + "source": "91fd6bb11d5cae3376ba20c3946b24bab8a9938d77eba30eabc7f83d49c24233", + "translation": "378e9dca9bf609409df19a303e717b097e39d4724fbd6a181c6703990bcd75a0" + }, + "middleware/method-override.mdx": { + "source": "b44455bea89c9116ac41169b429f9b3ed83d80e1cec53056087434cc21a7c4eb", + "translation": "ca8aa2747af797cf0cbee69fc58db39367984bd38b5b18d3bcd234b87549be05" + }, + "middleware/static.mdx": { + "source": "b66aaa717fe8ad1b3d72dd67584a5412b5f8a1d877e0434faaa06e9ef7e2ab2b", + "translation": "ab19c296698dd296ed6d394bb4c1c3f63708b2ca2b75d16bc40d9ec1aff93011" + }, + "middleware/trailing-slash.mdx": { + "source": "6b9e0e2174a325d36dbcd94ef51490fdda626e1166d439d1cbad64fccc485396", + "translation": "1000ae874beea68b73adc5fd2bf4d57d355d7c20c6f4fff237b0ad6a2343b973" + }, + "cookbook/reverse-proxy.md": { + "source": "e7e9a24046aa733ba091c6dd74bc77172c43c2e0a0fbbcff22414351a36be56c", + "translation": "f48815712e93087a351a7e2b9862578225af48e7c7633608e3f9202731c65266" + }, + "cookbook/jsonp.md": { + "source": "97aa36ed1fdd95d22c65143cfa792463f9194cee05526856b418e6022a7d69f8", + "translation": "8e69468d19f85ccf66ddf5030d6af65e3513b0ca278f7c00f34f34a5978d6bd8" + } + }, + "ja": { + "guide/ip-address.md": { + "source": "be4ce687caa56e2283c69f103c446b041acacc37d97a52f02fd07b733dd6381c", + "translation": "fafdb95e60ab10d58be3d7f040400c2b72de80e742a2d5b7ac32acb879fc4a44" + }, + "guide/request-scheme.md": { + "source": "1529ab0504b491afd01b3f345e5ca5e90c41528d76b8ac06c64d2b8fb1f952f7", + "translation": "dc08bf5dba081f97bd330eded3e6670b84221385276f9d0ab86f255974e4118d" + }, + "guide/response.md": { + "source": "762d690e149acfab27231c767db5a55f6019c39c7899f160e9ae7b6fe408d385", + "translation": "16e25e3222975a3215c7f3038056eb37f84430cdecaabc9445f3b515edd0a0fa" + }, + "guide/static-files.md": { + "source": "e806b7ae28bd328dcedb61d4a52f52da96d310a139989540459c12c9894078ef", + "translation": "7eee7efa8ad71d06b6a7f87b6baa3f8c15c61d3c787b22c5311de82460b182cc" + }, + "guide/testing.md": { + "source": "03fa2dcda2db2b47e95d3bffd29f8229787b12ec0177f4d7a934d5f81e19fa36", + "translation": "ccc24654907f5a3cde66c9baa5ce3f157242194e7b12bb5582d0b387886514d8" + }, + "middleware/redirect.mdx": { + "source": "ba113d4f531097bde33682e39e80e062659df8501b01be3b96d0b17144b25aa2", + "translation": "e2320a78b42e348c7c00a6bfcb097617afae93c576f8e18131b5ab1fbca5c02c" + }, + "middleware/secure.mdx": { + "source": "a7eae9b3c59d0774428aa7db8798eb858a4dded9df4a68766c4329b8308a00f5", + "translation": "a5005ed235541112eacdd7e7fec7c5ddec29038cc6439eba2aa1b4cdc1aeea8b" + }, + "middleware/proxy.mdx": { + "source": "91fd6bb11d5cae3376ba20c3946b24bab8a9938d77eba30eabc7f83d49c24233", + "translation": "8f7e197058c300bc4231cfb2a9e711c276be19af30cf2a597985f7388a972896" + }, + "middleware/method-override.mdx": { + "source": "b44455bea89c9116ac41169b429f9b3ed83d80e1cec53056087434cc21a7c4eb", + "translation": "6f3e9b7d61237a1ff8807f2d843ca18b80dacc4976c13f16c2ac5fed1c9675be" + }, + "middleware/static.mdx": { + "source": "b66aaa717fe8ad1b3d72dd67584a5412b5f8a1d877e0434faaa06e9ef7e2ab2b", + "translation": "cb235433118488fa6cd70fc360d43c1527a517b8dacad44f5b4ba5c094ec4360" + }, + "middleware/trailing-slash.mdx": { + "source": "6b9e0e2174a325d36dbcd94ef51490fdda626e1166d439d1cbad64fccc485396", + "translation": "27b4cf00b4d74eb47e428b675911add9b52fd485612f11a6474f770e8f7b4a6d" + }, + "cookbook/reverse-proxy.md": { + "source": "e7e9a24046aa733ba091c6dd74bc77172c43c2e0a0fbbcff22414351a36be56c", + "translation": "650913efffc3c072798b000db4e07e5451c2446752763f3928c7903e66d5635f" + }, + "cookbook/jsonp.md": { + "source": "97aa36ed1fdd95d22c65143cfa792463f9194cee05526856b418e6022a7d69f8", + "translation": "055df4301b6609bce245493493017416b8f2dce8d602ed6a3d8d0f55550a4d76" + } + }, + "pt-br": { + "guide/ip-address.md": { + "source": "be4ce687caa56e2283c69f103c446b041acacc37d97a52f02fd07b733dd6381c", + "translation": "9d1a5007bfc8b011007f0d9a0b621f8ebcceb4f463f9984e3e1eb3ba16d19d43" + }, + "guide/request-scheme.md": { + "source": "1529ab0504b491afd01b3f345e5ca5e90c41528d76b8ac06c64d2b8fb1f952f7", + "translation": "11cb3bd839085831d7e1c6411bbeb7f3a41955bb9b860dc4a8ffc22562e594e5" + }, + "guide/response.md": { + "source": "762d690e149acfab27231c767db5a55f6019c39c7899f160e9ae7b6fe408d385", + "translation": "363131a48dfa60947c3657855240e5e87b7346878e79f579e26727bc15c3b75f" + }, + "guide/static-files.md": { + "source": "e806b7ae28bd328dcedb61d4a52f52da96d310a139989540459c12c9894078ef", + "translation": "5493d26780a7408fad43227e2bc5415cab0bffe967200540ae6bfd597b75f7a3" + }, + "guide/testing.md": { + "source": "03fa2dcda2db2b47e95d3bffd29f8229787b12ec0177f4d7a934d5f81e19fa36", + "translation": "008657c6c532fb635959df1da8c1b843fbc199d8421df2392a1b11054e6998ae" + }, + "middleware/redirect.mdx": { + "source": "ba113d4f531097bde33682e39e80e062659df8501b01be3b96d0b17144b25aa2", + "translation": "cc3bf0fd8a832ce3c2d8fa0ea684cd40331c4058d3a334bae63c003131ab6b27" + }, + "middleware/secure.mdx": { + "source": "a7eae9b3c59d0774428aa7db8798eb858a4dded9df4a68766c4329b8308a00f5", + "translation": "dfb13d64799e7701f0d01fba472e218546fa2cf3694818127a4bacbdb41c4438" + }, + "middleware/proxy.mdx": { + "source": "91fd6bb11d5cae3376ba20c3946b24bab8a9938d77eba30eabc7f83d49c24233", + "translation": "dc786587f573113e88d8e352088b06878d5534d9174e4cf76fbfdc6f50420754" + }, + "middleware/method-override.mdx": { + "source": "b44455bea89c9116ac41169b429f9b3ed83d80e1cec53056087434cc21a7c4eb", + "translation": "215de80a0506d8dc89c716ca5933f86ad905bf043e270eb86dd3f88e2c30460d" + }, + "middleware/static.mdx": { + "source": "b66aaa717fe8ad1b3d72dd67584a5412b5f8a1d877e0434faaa06e9ef7e2ab2b", + "translation": "b6784c53a7589035165eb27b3daab7b2c78b26fff3c3224458b9c88531c61229" + }, + "middleware/trailing-slash.mdx": { + "source": "6b9e0e2174a325d36dbcd94ef51490fdda626e1166d439d1cbad64fccc485396", + "translation": "dc1c527c81f4cce0bdb165c75327692db4b0ec09b09a9d4c8f4b094ab159b077" + }, + "cookbook/reverse-proxy.md": { + "source": "e7e9a24046aa733ba091c6dd74bc77172c43c2e0a0fbbcff22414351a36be56c", + "translation": "269e8af6257653675c6740500bfbc23b68e42cb267cd9db88ec4ed2d2a3d6a57" + }, + "cookbook/jsonp.md": { + "source": "97aa36ed1fdd95d22c65143cfa792463f9194cee05526856b418e6022a7d69f8", + "translation": "e0445348fb29c72e06c817229ae37d3743e1de14779fff074375f2626b645078" + } + }, + "zh-cn": { + "guide/ip-address.md": { + "source": "be4ce687caa56e2283c69f103c446b041acacc37d97a52f02fd07b733dd6381c", + "translation": "cc3f196e8cd5c1a20951e62c503ed94e0b771aee700194f99938062cb05c9a40" + }, + "guide/request-scheme.md": { + "source": "1529ab0504b491afd01b3f345e5ca5e90c41528d76b8ac06c64d2b8fb1f952f7", + "translation": "6203b83359a9ce3616690ff504617845b1c2b079d47548117e99b919321eb96d" + }, + "guide/response.md": { + "source": "762d690e149acfab27231c767db5a55f6019c39c7899f160e9ae7b6fe408d385", + "translation": "9a73fdb1d34581919c7e0b7850ac68e18fc7d3b82db97a3a615bfe247c39ec32" + }, + "guide/static-files.md": { + "source": "e806b7ae28bd328dcedb61d4a52f52da96d310a139989540459c12c9894078ef", + "translation": "fe20eac04216bf00a1142a42da0188aed4bcf89864d0fb9b9cc9c849fd182071" + }, + "guide/testing.md": { + "source": "03fa2dcda2db2b47e95d3bffd29f8229787b12ec0177f4d7a934d5f81e19fa36", + "translation": "bb7390ab33e2053e869ffbfb6d1aded99e7d67943f524d6c5eeaa619b7bdfc29" + }, + "middleware/redirect.mdx": { + "source": "ba113d4f531097bde33682e39e80e062659df8501b01be3b96d0b17144b25aa2", + "translation": "6a42ea17562f4952bbda10528cc356f898500639c20b5025817694a6bb4f3b1c" + }, + "middleware/secure.mdx": { + "source": "a7eae9b3c59d0774428aa7db8798eb858a4dded9df4a68766c4329b8308a00f5", + "translation": "82f6279614055c0871c9f6bdb3007d85253cc5adfd7bb2e4fea53a2eb37b1b7a" + }, + "middleware/proxy.mdx": { + "source": "91fd6bb11d5cae3376ba20c3946b24bab8a9938d77eba30eabc7f83d49c24233", + "translation": "fcbb1f45ec40572d0590a33a54449b56fdedf58e98741f995ffc9e432e254fb6" + }, + "middleware/method-override.mdx": { + "source": "b44455bea89c9116ac41169b429f9b3ed83d80e1cec53056087434cc21a7c4eb", + "translation": "8f198271f1b87620d9ad75e9482bcf904d5a90d7835315205c3bc8edf01a6928" + }, + "middleware/static.mdx": { + "source": "b66aaa717fe8ad1b3d72dd67584a5412b5f8a1d877e0434faaa06e9ef7e2ab2b", + "translation": "dd310934b23267a58dc4b8249519ca672611cb306d3fd537e543e5c9dc55bd0c" + }, + "middleware/trailing-slash.mdx": { + "source": "6b9e0e2174a325d36dbcd94ef51490fdda626e1166d439d1cbad64fccc485396", + "translation": "88a96458914bf772e223d3d17581de947ceb0f13f2176fa958ddcd2d2f2ec7e0" + }, + "cookbook/reverse-proxy.md": { + "source": "e7e9a24046aa733ba091c6dd74bc77172c43c2e0a0fbbcff22414351a36be56c", + "translation": "b8c7702e1a3f6c2a4832c96d622eff00a47d778d00ab19443266d2ceeaf77d20" + }, + "cookbook/jsonp.md": { + "source": "97aa36ed1fdd95d22c65143cfa792463f9194cee05526856b418e6022a7d69f8", + "translation": "51e6aee7b7983a9e5af900e585726ceba2365bdab9594e97676bcfeae4a854b6" + } + } +} diff --git a/site/src/content/docs/cookbook/jsonp.md b/site/src/content/docs/cookbook/jsonp.md index 1b8f4857..6dfe6c88 100644 --- a/site/src/content/docs/cookbook/jsonp.md +++ b/site/src/content/docs/cookbook/jsonp.md @@ -9,6 +9,16 @@ JSONP is a technique that allows cross-domain server calls from the browser. Ech serves JSONP responses with `c.JSONP()`, which wraps the JSON payload in a call to the callback function named in the request. +The callback must be empty, a JavaScript identifier, or a dot-separated path of +identifiers using ASCII letters, digits, `_`, and `$`. An invalid callback returns +HTTP 400 with an error wrapping `ErrInvalidJSONPCallback`; Echo writes no JSONP +body. JSONP responses include `X-Content-Type-Options: nosniff`. + +:::caution +Any website can read a JSONP response using the user's cookies. Never serve +authenticated or private data with JSONP; use JSON with CORS instead. +::: + ## Server ```go diff --git a/site/src/content/docs/cookbook/reverse-proxy.md b/site/src/content/docs/cookbook/reverse-proxy.md index d4650fa0..fea30c5d 100644 --- a/site/src/content/docs/cookbook/reverse-proxy.md +++ b/site/src/content/docs/cookbook/reverse-proxy.md @@ -9,6 +9,13 @@ This recipe demonstrates how to use Echo as a reverse proxy and load balancer in front of your applications, such as WordPress, Node.js, Java, Python, Ruby, or Go. For simplicity, the upstreams here are Go servers that also handle WebSocket. +Proxy forwards `X-Forwarded-Proto` from `Context#Scheme()` and drops the older +`X-Forwarded-Ssl`, `X-Forwarded-Protocol`, and `X-Url-Scheme` headers. In v5 it +sets `X-Real-IP` from `Context#RealIP()`. If another proxy sits before Echo, set +the [scheme extractor](/guide/request-scheme/) and [IP extractor](/guide/ip-address/) +for that trusted proxy; otherwise the upstream may see the proxy's IP or the wrong +scheme. + ## 1) Identify upstream target URL(s) ```go diff --git a/site/src/content/docs/es/cookbook/jsonp.md b/site/src/content/docs/es/cookbook/jsonp.md index 106b927d..239d2e1a 100644 --- a/site/src/content/docs/es/cookbook/jsonp.md +++ b/site/src/content/docs/es/cookbook/jsonp.md @@ -93,3 +93,7 @@ func main() { ``` + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +El callback debe estar vacío, ser un identificador JavaScript o una ruta de identificadores separados por puntos (letras ASCII, dígitos, `_` y `$`). Un valor inválido devuelve HTTP 400 con `ErrInvalidJSONPCallback` y no escribe JSONP. La respuesta incluye `X-Content-Type-Options: nosniff`. **Cualquier sitio puede leer JSONP con las cookies del usuario**: no sirvas datos privados o autenticados; usa JSON con CORS. diff --git a/site/src/content/docs/es/cookbook/reverse-proxy.md b/site/src/content/docs/es/cookbook/reverse-proxy.md index 918c310e..c3d75b22 100644 --- a/site/src/content/docs/es/cookbook/reverse-proxy.md +++ b/site/src/content/docs/es/cookbook/reverse-proxy.md @@ -200,3 +200,7 @@ func main() { } } ``` + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +Proxy reenvía `X-Forwarded-Proto` desde `Context#Scheme()` y elimina las otras cabeceras de esquema. En v5, `X-Real-IP` procede de `Context#RealIP()`. Si hay otro proxy delante de Echo, configura los extractores de [esquema](/es/guide/request-scheme/) e [IP](/es/guide/ip-address/) para que el upstream reciba valores correctos. diff --git a/site/src/content/docs/es/guide/ip-address.md b/site/src/content/docs/es/guide/ip-address.md index 8b160976..fe2c9ce7 100644 --- a/site/src/content/docs/es/guide/ip-address.md +++ b/site/src/content/docs/es/guide/ip-address.md @@ -16,10 +16,7 @@ engañado. **Esto es un riesgo de seguridad.** Para obtener la IP de forma fiable y segura, tu aplicación debe conocer toda su infraestructura. En Echo, esto se configura mediante `Echo#IPExtractor`. -:::caution -Si no estableces `Echo#IPExtractor` explícitamente, Echo vuelve al comportamiento legacy, -que no es un valor por defecto seguro. -::: +En v5, `Context#RealIP()` usa por defecto la dirección del par directo desde v5.1.0. En v4, el valor por defecto todavía confía en `X-Forwarded-For` y `X-Real-IP` enviados por cualquier cliente: **configura siempre `Echo#IPExtractor`** según tus proxies. El esquema HTTP/HTTPS se configura aparte en [Esquema de la solicitud y proxies de confianza](/es/guide/request-scheme/). Empieza con dos preguntas para encontrar el enfoque correcto: @@ -109,8 +106,4 @@ la puerta al fraude. ## Comportamiento por defecto -Por defecto, Echo considera al mismo tiempo el primer header XFF, el header X-Real-IP y la IP -de la capa de red. - -Como este artículo debería dejar claro, esa no es una buena elección. Sigue siendo el valor -por defecto solo por compatibilidad hacia atrás. +Sin `Echo#IPExtractor`, **v5 usa la dirección del par directo** e ignora las cabeceras de IP enviadas por el cliente. **v4 conserva el valor legacy**, que puede confiar en `X-Forwarded-For` o `X-Real-IP` sin comprobar el proxy. En v4, usa `echo.ExtractIPDirect()` si no hay proxy, o un extractor de cabeceras con opciones de confianza para tus proxies. diff --git a/site/src/content/docs/es/guide/request-scheme.md b/site/src/content/docs/es/guide/request-scheme.md new file mode 100644 index 00000000..523e1e4b --- /dev/null +++ b/site/src/content/docs/es/guide/request-scheme.md @@ -0,0 +1,55 @@ +--- +title: Esquema de la solicitud y proxies de confianza +description: Configura cómo Echo determina HTTP o HTTPS detrás de un proxy de confianza. +sidebar: + order: 15 +--- + +`Context#Scheme()` indica si la solicitud usa HTTP o HTTPS. Las redirecciones a +HTTPS, HSTS y el middleware Proxy dependen de este valor. Desde Echo v5.4.0 y +v4.16.0, Echo solo usa `X-Forwarded-Proto`, `X-Forwarded-Protocol`, +`X-Forwarded-Ssl` o `X-Url-Scheme` si el **par directo** tiene una dirección de +bucle local, de enlace local o privada, o si usa un socket Unix. En los demás +casos, la conexión determina el esquema. Así, un cliente público no puede enviar +`X-Forwarded-Proto: https` por HTTP para evitar la redirección. + +## Elegir el extractor + +`Echo#SchemeExtractor` controla esta decisión; en v5 también existe +`Config.SchemeExtractor`. El valor predeterminado es +`echo.ExtractSchemeFromHeaders()` con las direcciones de confianza anteriores. +`echo.ExtractSchemeDirect()` ignora las cabeceras de reenvío. +`echo.LegacySchemeExtractor()` restaura el comportamiento antiguo y no es seguro +si un cliente no confiable puede acceder a la aplicación o el proxy deja pasar +una cabecera enviada por el cliente. + +Si existe `X-Forwarded-Proto`, Echo utiliza **solo su último valor** y devuelve +el esquema en minúsculas. Un valor inválido produce `http`; Echo no prueba otras +cabeceras de esquema. El proxy de confianza debe **sobrescribir** la cabecera, +no reenviar la que envió el cliente. En nginx, usa +`proxy_set_header X-Forwarded-Proto $scheme;`. + +## Proxies con direcciones públicas + +Confía explícitamente en los rangos de un proxy que se conecte desde una +dirección pública o desde `100.64.0.0/10`. De lo contrario, Echo ignorará su +`X-Forwarded-Proto`: las redirecciones HTTPS pueden entrar en bucle y Secure +puede dejar de enviar HSTS. Esto afecta, por ejemplo, a Cloudflare, CloudFront, +Azure Front Door y a los balanceadores HTTP(S) externos de GCP o GKE Ingress. +Confía solo en los rangos que utilice tu despliegue. Para los rangos de GCP: + +```go +_, gclb1, _ := net.ParseCIDR("35.191.0.0/16") +_, gclb2, _ := net.ParseCIDR("130.211.0.0/22") +e.SchemeExtractor = echo.ExtractSchemeFromHeaders( + echo.TrustIPRange(gclb1), + echo.TrustIPRange(gclb2), +) +``` + +Importa `net` y `github.com/labstack/echo/v5` para este ejemplo; en v4 usa +`github.com/labstack/echo/v4`. Los proxies en el mismo equipo, en una red +privada o en un socket Unix funcionan con la configuración predeterminada. +Si un adaptador reemplaza `RemoteAddr` por la dirección del cliente, configura +el extractor según la topología real. La IP del cliente se configura por +separado en [Dirección IP](/es/guide/ip-address/). diff --git a/site/src/content/docs/es/guide/response.md b/site/src/content/docs/es/guide/response.md index 17938d4e..74be8fcf 100644 --- a/site/src/content/docs/es/guide/response.md +++ b/site/src/content/docs/es/guide/response.md @@ -316,3 +316,7 @@ e.GET("/hooks", func(c *echo.Context) error { :::tip Puedes registrar varias funciones `Before` y `After`. ::: + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +`Context#JSONP` acepta un callback vacío, un identificador JavaScript o una ruta de identificadores separados por puntos. Los demás valores devuelven HTTP 400 con `ErrInvalidJSONPCallback`; la respuesta válida incluye `X-Content-Type-Options: nosniff`. No uses JSONP para datos privados: cualquier sitio puede leer la respuesta con las cookies del usuario. Usa JSON con CORS. diff --git a/site/src/content/docs/es/guide/static-files.md b/site/src/content/docs/es/guide/static-files.md index bc326d7c..b5f4033c 100644 --- a/site/src/content/docs/es/guide/static-files.md +++ b/site/src/content/docs/es/guide/static-files.md @@ -87,3 +87,7 @@ e.File("/favicon.ico", "app/assets/favicon.ico") // The file path must not have Un `/` inicial en el path del archivo no funciona con la mayoría de implementaciones de `fs.FS`. Usa un path relativo. ::: + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +Las rutas estáticas con segmentos `.`, `..` o vacíos devuelven 404; en modo `HTML5` puede servirse el índice. La codificación no estándar de nombres requiere `StaticConfig.EnablePathUnescaping` para el middleware o `Config.EnablePathUnescapingStaticFiles` (v5) o `Echo#EnablePathUnescapingStaticFiles` (v4) para `Echo#Static` y `Echo#StaticFS`. Estas opciones también decodifican barras codificadas: no las combines con control de acceso basado en rutas. `e.Use(middleware.Static(...))` se ejecuta antes que las guardas de rutas y grupos; guarda los archivos protegidos fuera de su raíz o usa una ruta `Echo#Static` protegida. diff --git a/site/src/content/docs/es/guide/testing.md b/site/src/content/docs/es/guide/testing.md index b71db7e1..51af409b 100644 --- a/site/src/content/docs/es/guide/testing.md +++ b/site/src/content/docs/es/guide/testing.md @@ -262,3 +262,7 @@ func TestMiddleware(t *testing.T) { Para más ejemplos, consulta los [casos de prueba de middleware](https://github.com/labstack/echo/tree/master/middleware) en el código fuente de Echo. ::: + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +`httptest.NewRequest` establece `RemoteAddr` en `192.0.2.1:1234`, una dirección que el extractor de esquema predeterminado no considera de confianza. Si una prueba establece `X-Forwarded-Proto: https`, usa también `req.RemoteAddr = "10.0.0.1:1234"` para representar un proxy privado de confianza, o configura `e.SchemeExtractor = echo.LegacySchemeExtractor()` solo para probar el comportamiento antiguo. Consulta [Esquema de la solicitud](/es/guide/request-scheme/). diff --git a/site/src/content/docs/es/middleware/method-override.mdx b/site/src/content/docs/es/middleware/method-override.mdx index 2bf06393..239af3ac 100644 --- a/site/src/content/docs/es/middleware/method-override.mdx +++ b/site/src/content/docs/es/middleware/method-override.mdx @@ -49,3 +49,7 @@ DefaultMethodOverrideConfig = MethodOverrideConfig{ Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), } ``` + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +Registra MethodOverride con `Echo#Pre`, antes del enrutamiento y del middleware CSRF. Un `POST` ya no puede convertirse en `GET`, `HEAD`, `OPTIONS`, `TRACE` ni `CONNECT`; si se solicita uno de esos métodos, la petición sigue siendo `POST`. diff --git a/site/src/content/docs/es/middleware/proxy.mdx b/site/src/content/docs/es/middleware/proxy.mdx index 3fd867cd..b151c5f6 100644 --- a/site/src/content/docs/es/middleware/proxy.mdx +++ b/site/src/content/docs/es/middleware/proxy.mdx @@ -77,3 +77,7 @@ e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{ ``` Consulta el recetario de [reverse proxy](/es/cookbook/reverse-proxy/) para ver un ejemplo completo. + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +Proxy establece `X-Forwarded-Proto` desde `Context#Scheme()` y elimina `X-Forwarded-Ssl`, `X-Forwarded-Protocol` y `X-Url-Scheme` antes de reenviar. En v5 también establece `X-Real-IP` desde `Context#RealIP()`. En una cadena nginx → Echo Proxy → servidor upstream, configura los extractores de [esquema](/es/guide/request-scheme/) e [IP](/es/guide/ip-address/) para los proxies de confianza. diff --git a/site/src/content/docs/es/middleware/redirect.mdx b/site/src/content/docs/es/middleware/redirect.mdx index daab0e66..df7b32fc 100644 --- a/site/src/content/docs/es/middleware/redirect.mdx +++ b/site/src/content/docs/es/middleware/redirect.mdx @@ -98,3 +98,7 @@ RedirectConfig{ Code: http.StatusMovedPermanently, } ``` + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +Las redirecciones HTTPS dependen de `Context#Scheme()`. [Configura los rangos de proxies de confianza](/es/guide/request-scheme/): si el proxy tiene una dirección pública no confiable, puede producirse un bucle de redirección. diff --git a/site/src/content/docs/es/middleware/secure.mdx b/site/src/content/docs/es/middleware/secure.mdx index c4959613..f894f1a4 100644 --- a/site/src/content/docs/es/middleware/secure.mdx +++ b/site/src/content/docs/es/middleware/secure.mdx @@ -55,3 +55,7 @@ var DefaultSecureConfig = SecureConfig{ HSTSPreloadEnabled: false, } ``` + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +HSTS se envía cuando `Context#Scheme()` es `https`, no por una cabecera `X-Forwarded-Proto: https` sin verificar. Si un proxy público termina TLS, [confía en su rango](/es/guide/request-scheme/) y haz que sobrescriba esa cabecera; de lo contrario puede faltar HSTS. diff --git a/site/src/content/docs/es/middleware/static.mdx b/site/src/content/docs/es/middleware/static.mdx index 94d6bb9c..0c3c94c0 100644 --- a/site/src/content/docs/es/middleware/static.mdx +++ b/site/src/content/docs/es/middleware/static.mdx @@ -23,7 +23,7 @@ La página importa el mismo archivo que compila la CI de documentación. El ejem `Root` indica el directorio que se sirve. `Browse` permite listar directorios, `HTML5` reenvía las rutas no encontradas al archivo índice y `Filesystem` acepta un `fs.FS`. Usa `IgnoreBase` cuando el prefijo URL de un grupo no debe formar parte de la ruta del archivo. -La respuesta alternativa varía según la revisión. Echo v5.3.0 sirve el archivo índice cuando un manejador posterior devuelve 404. En la revisión `next` registrada, solo lo sirve si el 404 procede del enrutador; un 404 de una ruta coincidente se conserva. Esta diferencia importa cuando una SPA y las rutas de API comparten servidor. +En Echo v5.4.0, `HTML5` sirve el índice solo ante un 404 del enrutador. Un 404 devuelto por una ruta coincidente se conserva; esto importa cuando una SPA comparte servidor con una API. #### Ejemplo 1 @@ -44,3 +44,7 @@ Esta tabla procede de los campos exportados de la revisión indicada de Echo. Ma `Index` usa `index.html` por defecto. `EnablePathUnescaping` es `false` por defecto: las barras codificadas en la ruta comodín permanecen codificadas. `DisablePathUnescaping` está obsoleto y se ignora; usa `EnablePathUnescaping` si necesitas habilitar la decodificación. Habilítala solo si necesitas caracteres codificados en los nombres de archivo y tus rutas no restringen el acceso a subdirectorios. Decodificar una barra después del enrutamiento puede eludir una ruta de protección. + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +Por defecto, Static resuelve archivos desde la misma forma de ruta que usó el enrutador. Los nombres pedidos con codificación no estándar (`%2C`, `%40` o hexadecimales en minúsculas) requieren `EnablePathUnescaping`. Los segmentos `.`, `..` o vacíos (por ejemplo `/assets//app.js`) devuelven 404; `HTML5` aún puede servir el índice. Al activar la decodificación también se decodifican las barras codificadas: no la combines con control de acceso basado en rutas. **`e.Use(middleware.Static(...))` se ejecuta antes que los middlewares de rutas y grupos**; sus guardas no protegen esos archivos. Mantén los archivos protegidos fuera de su raíz o sírvelos mediante `Echo#Static` detrás de una guarda. diff --git a/site/src/content/docs/es/middleware/trailing-slash.mdx b/site/src/content/docs/es/middleware/trailing-slash.mdx index ff92998c..cac2887d 100644 --- a/site/src/content/docs/es/middleware/trailing-slash.mdx +++ b/site/src/content/docs/es/middleware/trailing-slash.mdx @@ -52,3 +52,7 @@ El ejemplo anterior agrega una trailing slash al URI del request y redirige con + +## Actualización de seguridad de Echo (v5.4.0 / v4.16.0) + +Cuando redirigen, los middlewares de barra final codifican porcentualmente los caracteres de control de la ruta antes de construir la cabecera `Location`. diff --git a/site/src/content/docs/guide/ip-address.md b/site/src/content/docs/guide/ip-address.md index fb5f9542..edfdf723 100644 --- a/site/src/content/docs/guide/ip-address.md +++ b/site/src/content/docs/guide/ip-address.md @@ -17,10 +17,15 @@ risk being deceived. **This is a security risk.** To retrieve the IP reliably and securely, your application must be aware of your entire infrastructure. In Echo, you configure this through `Echo#IPExtractor`. -:::caution -If you do not set `Echo#IPExtractor` explicitly, Echo falls back to legacy behavior, -which is not a secure default. -::: +In v5, `Context#RealIP()` uses the direct peer address by default (since v5.1.0). +Set `Echo#IPExtractor` only when a trusted proxy supplies the client IP in a header. +In v4, the default still trusts `X-Forwarded-For` and `X-Real-IP` from any client; +**always set an extractor** that matches your deployment, especially when using +rate limiting or Proxy middleware. See the [v4.16.0 release notes](https://github.com/labstack/echo/releases/tag/v4.16.0). + +The request's HTTP/HTTPS scheme has a separate trust setting. See +[Request Scheme and Trusted Proxies](/guide/request-scheme/) when configuring a +reverse proxy, HTTPS redirect, or HSTS. Start with two questions to find the right approach: @@ -111,8 +116,9 @@ forge them, opening the door to fraud. ## Default behavior -By default, Echo considers the first XFF header, the X-Real-IP header, and the IP -from the network layer all at once. - -As this article should make clear, that is not a good choice. It remains the default -only for backward compatibility. +Without `Echo#IPExtractor`, **v5 uses the direct peer address** from the network +layer and ignores client-supplied IP headers. In **v4**, the legacy default can use +`X-Forwarded-For` or `X-Real-IP` without checking whether the sender is a trusted +proxy. Configure v4 explicitly, for example with `echo.ExtractIPDirect()` when no +proxy sits in front of the app, or a header extractor with suitable trust options +when a proxy does. diff --git a/site/src/content/docs/guide/request-scheme.md b/site/src/content/docs/guide/request-scheme.md new file mode 100644 index 00000000..3eed27c1 --- /dev/null +++ b/site/src/content/docs/guide/request-scheme.md @@ -0,0 +1,58 @@ +--- +title: Request Scheme and Trusted Proxies +description: Configure how Echo determines HTTP or HTTPS behind a trusted proxy. +sidebar: + order: 15 +--- + +`Context#Scheme()` tells Echo whether a request used HTTP or HTTPS. HTTPS redirects, +HSTS, and the Proxy middleware rely on it. Since Echo v5.4.0 and v4.16.0, Echo only +uses `X-Forwarded-Proto`, `X-Forwarded-Protocol`, `X-Forwarded-Ssl`, or +`X-Url-Scheme` when the **direct peer** is a loopback, link-local, or private address, +or a Unix socket. Otherwise, the connection itself determines the scheme. This +prevents a public client from sending `X-Forwarded-Proto: https` over HTTP to bypass +an HTTPS redirect. See the [v5.4.0 release notes](https://github.com/labstack/echo/releases/tag/v5.4.0). + +## Choose a scheme extractor + +`Echo#SchemeExtractor` controls this behavior. In v5, `Config.SchemeExtractor` can +set it when creating Echo. The default is `echo.ExtractSchemeFromHeaders()` with the +trusted direct-peer addresses above. `echo.ExtractSchemeDirect()` ignores forwarding +headers. `echo.LegacySchemeExtractor()` restores the older behavior, which is unsafe +if an untrusted client can reach the app or a proxy forwards a client-supplied scheme +header. + +When `X-Forwarded-Proto` is present, Echo uses **only its last value** and returns +the scheme in lowercase. An invalid value becomes `http`; Echo does not fall back +to another forwarded-scheme header. Your trusted proxy must **overwrite** +`X-Forwarded-Proto` with the scheme it observed. It must not pass through a value +sent by the client. For example, configure nginx with +`proxy_set_header X-Forwarded-Proto $scheme;`. + +## Proxies with public addresses + +If the direct proxy has a public address, or uses `100.64.0.0/10`, explicitly trust +its address ranges. Otherwise Echo ignores its `X-Forwarded-Proto`: HTTPS redirects +can loop and Secure middleware will not send HSTS. This applies to Cloudflare, +CloudFront, and Azure Front Door connecting to a public origin, and to GCP external +HTTP(S) load balancers or GKE Ingress. Trust only the ranges your deployment uses. + +For the GCP load balancer ranges: + +```go +_, gclb1, _ := net.ParseCIDR("35.191.0.0/16") +_, gclb2, _ := net.ParseCIDR("130.211.0.0/22") +e.SchemeExtractor = echo.ExtractSchemeFromHeaders( + echo.TrustIPRange(gclb1), + echo.TrustIPRange(gclb2), +) +``` + +Import `net` and `github.com/labstack/echo/v5` for this v5 example. For v4, use +`github.com/labstack/echo/v4` instead. Proxies on the same host or a private +network, and Unix sockets, keep working with the default extractor. If an adapter +replaces `RemoteAddr` with the client's address, configure the extractor for the +actual deployment rather than assuming Echo sees the proxy address. + +The scheme and client IP are separate decisions. Configure `Echo#IPExtractor` for +client IPs as described in [IP Address](/guide/ip-address/). diff --git a/site/src/content/docs/guide/response.md b/site/src/content/docs/guide/response.md index 04b64819..f4674b42 100644 --- a/site/src/content/docs/guide/response.md +++ b/site/src/content/docs/guide/response.md @@ -130,6 +130,12 @@ func handler(c *echo.Context) error { See the [JSONP cookbook](/cookbook/jsonp/). +The callback must be empty, a JavaScript identifier, or a dot-separated path of +identifiers. Invalid callbacks return HTTP 400 with an error wrapping +`ErrInvalidJSONPCallback`; JSONP responses include `X-Content-Type-Options: nosniff`. +Because any site can read JSONP with the user's cookies, do not return private or +authenticated data through JSONP. Use JSON with CORS for that case. + ## Send XML `Context#XML(code int, i any)` encodes a Go value as XML and sends it with a status diff --git a/site/src/content/docs/guide/static-files.md b/site/src/content/docs/guide/static-files.md index b2782be7..caee04dc 100644 --- a/site/src/content/docs/guide/static-files.md +++ b/site/src/content/docs/guide/static-files.md @@ -8,6 +8,16 @@ sidebar: Echo can serve static assets such as images, JavaScript, CSS, PDFs, and fonts from the filesystem or an embedded filesystem. +In Echo v5.4.0 and v4.16.0, Static middleware uses the same form of the path +that the router matched; `Echo#Static` and `Echo#StaticFS` already used this +behavior in earlier updates. Paths with `.`, `..`, or empty segments (such as +`/assets//app.js`) return 404; HTML5 mode can still serve the index. Non-default +escaping in file names (such as `%2C`, `%40`, or lowercase hex) needs +`StaticConfig.EnablePathUnescaping` for middleware or +`Config.EnablePathUnescapingStaticFiles` (v5) or `Echo#EnablePathUnescapingStaticFiles` (v4) for `Echo#Static` and `Echo#StaticFS`. +Those options decode encoded slashes as well, so do not combine them with +route-based access control for subdirectories. + ## Default filesystem Echo uses `os.DirFS(".")` as its default filesystem, rooted at the current working @@ -22,6 +32,10 @@ e.Filesystem = os.DirFS("assets") See [Static middleware](/middleware/static/). +If registered with `e.Use`, Static middleware runs before route and group +middleware. Route guards do not protect its files. Keep protected files outside +its root, or use a guarded `Echo#Static` route instead. + ## Using Echo#Static() `Echo#Static(prefix, root string)` registers a route that serves static files under diff --git a/site/src/content/docs/guide/testing.md b/site/src/content/docs/guide/testing.md index 2efd3260..638f5126 100644 --- a/site/src/content/docs/guide/testing.md +++ b/site/src/content/docs/guide/testing.md @@ -9,6 +9,23 @@ Echo handlers and middleware are plain functions over an `echo.Context`, so they straightforward to test with the standard `net/http/httptest` package. The `echotest` package provides helpers that cut down on boilerplate. +## Testing forwarded schemes + +`httptest.NewRequest` sets `RemoteAddr` to `192.0.2.1:1234`, which the default +scheme extractor does not trust. A test that sets `X-Forwarded-Proto: https` +therefore sees `http` unless it also represents a trusted proxy: + +```go +req := httptest.NewRequest(http.MethodGet, "/", nil) +req.RemoteAddr = "10.0.0.1:1234" // Trusted private proxy in this test. +req.Header.Set(echo.HeaderXForwardedProto, "https") +``` + +Alternatively, a test can explicitly set +`e.SchemeExtractor = echo.LegacySchemeExtractor()`. Use the latter only to test +legacy behavior; configure trusted proxies in production as described in +[Request Scheme and Trusted Proxies](/guide/request-scheme/). + ## Testing a handler Consider two handlers: diff --git a/site/src/content/docs/ja/cookbook/jsonp.md b/site/src/content/docs/ja/cookbook/jsonp.md index fa580c1e..5fd49aad 100644 --- a/site/src/content/docs/ja/cookbook/jsonp.md +++ b/site/src/content/docs/ja/cookbook/jsonp.md @@ -93,3 +93,7 @@ func main() { ``` + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +コールバックは空文字、JavaScript 識別子、または識別子をドットでつないだ名前に限られます(ASCII 英字、数字、`_`、`$`)。不正な値では `ErrInvalidJSONPCallback` を含む HTTP 400 を返し、JSONP 本文は書き込みません。正常な応答には `X-Content-Type-Options: nosniff` が付きます。**どのサイトもユーザーの Cookie とともに JSONP を読み取れる**ため、認証が必要なデータには使わず、CORS を設定した JSON を使用してください。 diff --git a/site/src/content/docs/ja/cookbook/reverse-proxy.md b/site/src/content/docs/ja/cookbook/reverse-proxy.md index 78ec7d2c..5623b957 100644 --- a/site/src/content/docs/ja/cookbook/reverse-proxy.md +++ b/site/src/content/docs/ja/cookbook/reverse-proxy.md @@ -200,3 +200,7 @@ func main() { } } ``` + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +Proxy は `Context#Scheme()` から `X-Forwarded-Proto` を設定し、古いスキームヘッダーを削除します。v5 の `X-Real-IP` は `Context#RealIP()` から設定されます。Echo の前段に別のプロキシがある場合は[スキーム](/ja/guide/request-scheme/)と [IP](/ja/guide/ip-address/) の抽出器を設定し、上流へ正しい値を渡してください。 diff --git a/site/src/content/docs/ja/guide/ip-address.md b/site/src/content/docs/ja/guide/ip-address.md index fdf710df..ca7eba7c 100644 --- a/site/src/content/docs/ja/guide/ip-address.md +++ b/site/src/content/docs/ja/guide/ip-address.md @@ -16,10 +16,7 @@ HTTP 経由でアプリへ伝えられる必要があります。ただし HTTP IP を信頼性高く安全に取得するには、アプリケーションがインフラ全体を把握している必要があります。 Echo では `Echo#IPExtractor` でこれを設定します。 -:::caution -`Echo#IPExtractor` を明示的に設定しない場合、Echo は従来の挙動にフォールバックします。 -これは安全なデフォルトではありません。 -::: +v5 の `Context#RealIP()` は v5.1.0 以降、既定で直接接続元のアドレスを使います。v4 の既定動作は依然として任意のクライアントが送った `X-Forwarded-For` と `X-Real-IP` を信頼するため、プロキシ構成に合わせて **必ず `Echo#IPExtractor` を設定**してください。HTTP/HTTPS の判定は[リクエストのスキーム](/ja/guide/request-scheme/)で別途設定します。 適切な方法を見つけるため、まず 2 つの質問から始めます。 @@ -107,6 +104,4 @@ XFF と同様に、デフォルトでは内部 IP アドレスを信頼し、同 ## デフォルトの挙動 -デフォルトでは、Echo は最初の XFF header、X-Real-IP header、ネットワーク層の IP を同時に考慮します。 - -この記事から分かるように、これは良い選択ではありません。後方互換性のためだけにデフォルトとして残されています。 +`Echo#IPExtractor` 未設定時、**v5 は直接接続元のアドレス**を使い、クライアントが送った IP ヘッダーを無視します。**v4 は旧動作のまま**で、信頼できるプロキシか確認せずに `X-Forwarded-For` や `X-Real-IP` を使用する場合があります。v4 ではプロキシがなければ `echo.ExtractIPDirect()`、プロキシがあれば適切な信頼オプションを付けたヘッダー抽出器を設定してください。 diff --git a/site/src/content/docs/ja/guide/request-scheme.md b/site/src/content/docs/ja/guide/request-scheme.md new file mode 100644 index 00000000..81defacc --- /dev/null +++ b/site/src/content/docs/ja/guide/request-scheme.md @@ -0,0 +1,54 @@ +--- +title: リクエストのスキームと信頼できるプロキシ +description: 信頼できるプロキシの背後で HTTP と HTTPS を判定する方法を設定します。 +sidebar: + order: 15 +--- + +`Context#Scheme()` はリクエストの HTTP/HTTPS を返します。HTTPS リダイレクト、 +HSTS、Proxy ミドルウェアがこの値を使用します。Echo v5.4.0 と v4.16.0 以降、 +`X-Forwarded-Proto`、`X-Forwarded-Protocol`、`X-Forwarded-Ssl`、 +`X-Url-Scheme` が使われるのは、**直接接続した相手**がループバック、リンクローカル、 +プライベートアドレス、または Unix ソケットの場合だけです。それ以外では接続自体から +スキームを判定します。公開クライアントが HTTP で `X-Forwarded-Proto: https` を +送って HTTPS リダイレクトを回避することはできません。 + +## 抽出方法を選ぶ + +`Echo#SchemeExtractor` で判定方法を設定します。v5 では +`Config.SchemeExtractor` も使えます。既定の +`echo.ExtractSchemeFromHeaders()` は上記の直接接続元だけを信頼します。 +`echo.ExtractSchemeDirect()` は転送ヘッダーを無視します。 +`echo.LegacySchemeExtractor()` は旧動作に戻しますが、信頼できないクライアントが +アプリに到達できる場合や、プロキシがクライアントのヘッダーをそのまま渡す場合は危険です。 + +`X-Forwarded-Proto` がある場合は**最後の値だけ**を使い、小文字のスキームを +返します。不正な値は `http` となり、別の転送スキームヘッダーには切り替わりません。 +信頼するプロキシはクライアントから届いた値を渡さず、観測したスキームで +`X-Forwarded-Proto` を**上書き**してください。nginx では +`proxy_set_header X-Forwarded-Proto $scheme;` を設定します。 + +## 公開アドレスのプロキシ + +直接接続するプロキシが公開アドレスまたは `100.64.0.0/10` を使う場合は、 +そのアドレス範囲を明示的に信頼してください。設定しないと転送スキームが無視され、 +HTTPS リダイレクトがループしたり、Secure ミドルウェアが HSTS を送らなくなったり +します。Cloudflare、CloudFront、Azure Front Door、GCP 外部 HTTP(S) +ロードバランサー、GKE Ingress などが該当します。実際に使う範囲だけを信頼します。 +GCP の範囲を指定する例: + +```go +_, gclb1, _ := net.ParseCIDR("35.191.0.0/16") +_, gclb2, _ := net.ParseCIDR("130.211.0.0/22") +e.SchemeExtractor = echo.ExtractSchemeFromHeaders( + echo.TrustIPRange(gclb1), + echo.TrustIPRange(gclb2), +) +``` + +この v5 の例では `net` と `github.com/labstack/echo/v5` をインポートします。 +v4 では `github.com/labstack/echo/v4` を使います。同一ホスト、プライベート +ネットワーク、Unix ソケットのプロキシは既定の設定で動作します。アダプターが +`RemoteAddr` をクライアントのアドレスに置き換える場合は、実際の構成に合わせて +抽出方法を設定してください。クライアント IP は +[IP アドレス](/ja/guide/ip-address/)で別途設定します。 diff --git a/site/src/content/docs/ja/guide/response.md b/site/src/content/docs/ja/guide/response.md index 42611b35..f41e0e52 100644 --- a/site/src/content/docs/ja/guide/response.md +++ b/site/src/content/docs/ja/guide/response.md @@ -316,3 +316,7 @@ e.GET("/hooks", func(c *echo.Context) error { :::tip 複数の `Before` 関数と `After` 関数を登録できます。 ::: + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +`Context#JSONP` のコールバックは空文字、JavaScript 識別子、またはドット区切りの識別子だけです。不正な値は `ErrInvalidJSONPCallback` を含む HTTP 400 を返し、正常な応答には `X-Content-Type-Options: nosniff` が付きます。JSONP は他サイトからユーザーの Cookie とともに読めるため、非公開データには使わず CORS を設定した JSON を使ってください。 diff --git a/site/src/content/docs/ja/guide/static-files.md b/site/src/content/docs/ja/guide/static-files.md index 83d69779..85dfb504 100644 --- a/site/src/content/docs/ja/guide/static-files.md +++ b/site/src/content/docs/ja/guide/static-files.md @@ -86,3 +86,7 @@ e.File("/favicon.ico", "app/assets/favicon.ico") // The file path must not have :::caution ファイルパス先頭の `/` は、ほとんどの `fs.FS` 実装では機能しません。相対パスを使ってください。 ::: + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +`.`、`..`、空のパスセグメントは 404 になり、`HTML5` モードではインデックスを返す場合があります。標準以外のエスケープを使うファイル名には、ミドルウェアで `StaticConfig.EnablePathUnescaping`、`Echo#Static` と `Echo#StaticFS` で `Config.EnablePathUnescapingStaticFiles` (v5) または `Echo#EnablePathUnescapingStaticFiles` (v4) が必要です。これらはエンコードされたスラッシュも復元するため、ルート単位のアクセス制御と併用しないでください。`e.Use(middleware.Static(...))` はルートのガードより先に実行されます。保護するファイルはルート外に置くか、ガード付きの `Echo#Static` で配信してください。 diff --git a/site/src/content/docs/ja/guide/testing.md b/site/src/content/docs/ja/guide/testing.md index 8e146cda..32426929 100644 --- a/site/src/content/docs/ja/guide/testing.md +++ b/site/src/content/docs/ja/guide/testing.md @@ -262,3 +262,7 @@ func TestMiddleware(t *testing.T) { その他の例は、Echo ソース内の [ミドルウェアテストケース](https://github.com/labstack/echo/tree/master/middleware)を参照してください。 ::: + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +`httptest.NewRequest` の `RemoteAddr` は `192.0.2.1:1234` で、既定のスキーム抽出器は信頼しません。`X-Forwarded-Proto: https` を設定するテストでは、信頼するプライベートプロキシを表す `req.RemoteAddr = "10.0.0.1:1234"` も設定してください。旧動作のテストだけなら `e.SchemeExtractor = echo.LegacySchemeExtractor()` も使えます。詳しくは[リクエストのスキーム](/ja/guide/request-scheme/)を参照してください。 diff --git a/site/src/content/docs/ja/middleware/method-override.mdx b/site/src/content/docs/ja/middleware/method-override.mdx index 6b609393..71d09f06 100644 --- a/site/src/content/docs/ja/middleware/method-override.mdx +++ b/site/src/content/docs/ja/middleware/method-override.mdx @@ -49,3 +49,7 @@ DefaultMethodOverrideConfig = MethodOverrideConfig{ Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), } ``` + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +MethodOverride はルーティングと CSRF ミドルウェアより前に `Echo#Pre` で登録してください。`POST` を `GET`、`HEAD`、`OPTIONS`、`TRACE`、`CONNECT` に変更することはできません。これらが指定された場合、リクエストは `POST` のままです。 diff --git a/site/src/content/docs/ja/middleware/proxy.mdx b/site/src/content/docs/ja/middleware/proxy.mdx index 59c4f7b0..748cc88c 100644 --- a/site/src/content/docs/ja/middleware/proxy.mdx +++ b/site/src/content/docs/ja/middleware/proxy.mdx @@ -77,3 +77,7 @@ e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{ ``` 完全な例は[リバースプロキシ](/ja/cookbook/reverse-proxy/) cookbook を参照してください。 + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +Proxy は `Context#Scheme()` から `X-Forwarded-Proto` を設定し、`X-Forwarded-Ssl`、`X-Forwarded-Protocol`、`X-Url-Scheme` を削除して転送します。v5 では `Context#RealIP()` から `X-Real-IP` も設定します。nginx → Echo Proxy → 上流サーバーの構成では、信頼するプロキシに合わせて[スキーム](/ja/guide/request-scheme/)と [IP](/ja/guide/ip-address/) の抽出器を設定してください。 diff --git a/site/src/content/docs/ja/middleware/redirect.mdx b/site/src/content/docs/ja/middleware/redirect.mdx index d33fcf4b..cf903a97 100644 --- a/site/src/content/docs/ja/middleware/redirect.mdx +++ b/site/src/content/docs/ja/middleware/redirect.mdx @@ -99,3 +99,7 @@ RedirectConfig{ Code: http.StatusMovedPermanently, } ``` + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +HTTPS リダイレクトは `Context#Scheme()` に依存します。公開アドレスのプロキシを使う場合は[信頼するアドレス範囲](/ja/guide/request-scheme/)を設定してください。未設定だとリダイレクトがループすることがあります。 diff --git a/site/src/content/docs/ja/middleware/secure.mdx b/site/src/content/docs/ja/middleware/secure.mdx index 0f700ddd..182f5e73 100644 --- a/site/src/content/docs/ja/middleware/secure.mdx +++ b/site/src/content/docs/ja/middleware/secure.mdx @@ -55,3 +55,7 @@ var DefaultSecureConfig = SecureConfig{ HSTSPreloadEnabled: false, } ``` + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +HSTS は `Context#Scheme()` が `https` の場合に送信されます。未検証の `X-Forwarded-Proto: https` だけでは有効になりません。公開プロキシで TLS を終端する場合は[その範囲を信頼](/ja/guide/request-scheme/)し、プロキシがヘッダーを上書きするようにしてください。 diff --git a/site/src/content/docs/ja/middleware/static.mdx b/site/src/content/docs/ja/middleware/static.mdx index 14345b66..88b93959 100644 --- a/site/src/content/docs/ja/middleware/static.mdx +++ b/site/src/content/docs/ja/middleware/static.mdx @@ -23,7 +23,7 @@ Static ミドルウェアはルートディレクトリからファイルを配 `Root` は配信するディレクトリです。`Browse` はディレクトリ一覧を有効にし、`HTML5` は見つからないパスをインデックスファイルに転送し、`Filesystem` には `fs.FS` を指定できます。グループの URL プレフィックスをファイルパスに含めたくない場合は `IgnoreBase` を使います。 -フォールバックの動作はリビジョンによって異なります。Echo v5.3.0 は後続のハンドラーが 404 を返すとインデックスを配信します。記録された `next` リビジョンでは、ルーターでルートが見つからない 404 の場合にだけインデックスを配信し、一致したルートが返す 404 はそのまま返します。SPA と API のルートを同じサーバーで扱う場合に重要です。 +Echo v5.4.0 の `HTML5` は、ルーターが返した 404 の場合だけインデックスを配信します。一致したルート自身が返す 404 はそのまま返します。SPA と API を同じサーバーで扱う場合に重要です。 #### 例 1 @@ -44,3 +44,7 @@ Static ミドルウェアはルートディレクトリからファイルを配 `Index` の既定値は `index.html` です。`EnablePathUnescaping` の既定値は `false` で、ワイルドカードパス内のエンコードされたスラッシュをデコードしません。`DisablePathUnescaping` は非推奨で、現在の Echo では無視されます。明示的にデコードが必要な場合は `EnablePathUnescaping` を使います。 ファイル名に URL エンコードされた文字が必要で、ルートでサブディレクトリへのアクセスを制限していない場合にのみ有効にしてください。ルーティング後にスラッシュをデコードすると、保護用ルートを回避できる場合があります。 + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +既定では、Static はルーターが照合した形式のパスからファイルを解決します。標準以外のエスケープを使ったファイル名(`%2C`、`%40`、小文字の 16 進数など)には `EnablePathUnescaping` が必要です。`.`、`..`、空のセグメント(例: `/assets//app.js`)は 404 になり、`HTML5` モードではインデックスを返す場合があります。パスのデコードはエンコードされたスラッシュも復元するため、サブディレクトリのルート単位のアクセス制御と併用しないでください。**`e.Use(middleware.Static(...))` はルート・グループのミドルウェアより先に実行**されます。保護するファイルはそのルート外に置くか、ガード付きの `Echo#Static` で配信してください。 diff --git a/site/src/content/docs/ja/middleware/trailing-slash.mdx b/site/src/content/docs/ja/middleware/trailing-slash.mdx index 0985aec6..a31dcda6 100644 --- a/site/src/content/docs/ja/middleware/trailing-slash.mdx +++ b/site/src/content/docs/ja/middleware/trailing-slash.mdx @@ -51,3 +51,7 @@ e.Use(middleware.AddTrailingSlashWithConfig(middleware.AddTrailingSlashConfig{ + +## Echo のセキュリティ更新 (v5.4.0 / v4.16.0) + +末尾のスラッシュを変更するミドルウェアは、リダイレクト時にパス内の制御文字をパーセントエンコードしてから `Location` ヘッダーを作成します。 diff --git a/site/src/content/docs/middleware/method-override.mdx b/site/src/content/docs/middleware/method-override.mdx index 3af493dc..16af8166 100644 --- a/site/src/content/docs/middleware/method-override.mdx +++ b/site/src/content/docs/middleware/method-override.mdx @@ -11,7 +11,9 @@ Method Override middleware reads the overridden method from the request and uses instead of the original method. :::note -For security reasons, only the `POST` method can be overridden. +For security reasons, only the `POST` method can be overridden. It cannot be +overridden to `GET`, `HEAD`, `OPTIONS`, `TRACE`, or `CONNECT`; the request remains +`POST` if one of those methods is requested. ::: All core middleware lives in the `middleware` package: @@ -26,6 +28,9 @@ import "github.com/labstack/echo/v5/middleware" e.Pre(middleware.MethodOverride()) ``` +Register Method Override with `Echo#Pre`, before routing and the CSRF middleware. +This prevents a form or query `_method` value from bypassing a CSRF check. + ## Custom configuration ```go diff --git a/site/src/content/docs/middleware/proxy.mdx b/site/src/content/docs/middleware/proxy.mdx index ea527f8e..8e7bc401 100644 --- a/site/src/content/docs/middleware/proxy.mdx +++ b/site/src/content/docs/middleware/proxy.mdx @@ -10,6 +10,14 @@ import ConfigReference from '../../../components/ConfigReference.astro'; Proxy provides an HTTP/WebSocket reverse proxy middleware. It forwards a request to an upstream server using a configured load balancing technique. +In Echo v5.4.0 and v4.16.0, Proxy sets `X-Forwarded-Proto` from +`Context#Scheme()` and removes `X-Forwarded-Ssl`, `X-Forwarded-Protocol`, and +`X-Url-Scheme` before forwarding. Configure the [scheme extractor](/guide/request-scheme/) +for your trusted proxy chain. In v5, Proxy also sets `X-Real-IP` from +`Context#RealIP()` rather than forwarding a client-supplied value. For a chain such +as nginx → Echo Proxy → upstream, configure `Echo#IPExtractor` for the trusted +incoming IP header as explained in [IP Address](/guide/ip-address/). + All core middleware lives in the `middleware` package: ```go diff --git a/site/src/content/docs/middleware/redirect.mdx b/site/src/content/docs/middleware/redirect.mdx index 556d84a1..111b250b 100644 --- a/site/src/content/docs/middleware/redirect.mdx +++ b/site/src/content/docs/middleware/redirect.mdx @@ -25,6 +25,11 @@ e := echo.New() e.Pre(middleware.HTTPSRedirect()) ``` +HTTPS redirects use `Context#Scheme()`. Behind a proxy, configure the +[scheme extractor and trusted proxy ranges](/guide/request-scheme/) so Echo can +recognize HTTPS. A public proxy address that has not been trusted can cause a +redirect loop. + ## HTTPS WWW Redirect HTTPS WWW redirect redirects HTTP requests to www HTTPS. For example, diff --git a/site/src/content/docs/middleware/secure.mdx b/site/src/content/docs/middleware/secure.mdx index ec69389b..f8a8af71 100644 --- a/site/src/content/docs/middleware/secure.mdx +++ b/site/src/content/docs/middleware/secure.mdx @@ -22,6 +22,11 @@ import "github.com/labstack/echo/v5/middleware" e.Use(middleware.Secure()) ``` +HSTS is sent when `Context#Scheme()` is `https`. A raw +`X-Forwarded-Proto: https` header from an untrusted client does not enable HSTS. +If a public proxy terminates TLS, [trust its address range](/guide/request-scheme/) +and make sure it overwrites `X-Forwarded-Proto`; otherwise HSTS may be missing. + ## Custom configuration ```go diff --git a/site/src/content/docs/middleware/static.mdx b/site/src/content/docs/middleware/static.mdx index 0715e661..e519e368 100644 --- a/site/src/content/docs/middleware/static.mdx +++ b/site/src/content/docs/middleware/static.mdx @@ -23,7 +23,9 @@ The page imports the same file that documentation CI compiles. The example keeps Set `Root` to the directory to serve. `Browse` enables directory listings, `HTML5` forwards missing paths to the index file for a single-page application, and `Filesystem` lets you provide an `fs.FS`. Use `IgnoreBase` when a group's URL prefix should not become part of the file path. -The fallback differs by revision. Echo v5.3.0 serves the index when a downstream handler returns 404. In the recorded `next` revision, it serves the index only for a router-level 404; a matched route's 404 passes through. This matters when an SPA and API routes share a server. +In Echo v5.4.0, HTML5 mode serves the index for a router-level 404. A matched +route's own 404 passes through. This matters when an SPA and API routes share a +server. #### Example 1 @@ -41,6 +43,22 @@ The table comes from the exported fields in the recorded Echo revision. It ident ### Default configuration -`Index` defaults to `index.html`. `EnablePathUnescaping` defaults to `false`, so encoded slashes in the wildcard path stay encoded. `DisablePathUnescaping` is deprecated and ignored by current Echo; use `EnablePathUnescaping` when you explicitly need unescaping. - -Enable unescaping only when you need URL-encoded characters in filenames and your route rules do not restrict access to subdirectories. Decoding an encoded slash after routing can bypass a route guard that matched the encoded path differently. Review the linked source in the table before enabling it. +`Index` defaults to `index.html`. `EnablePathUnescaping` defaults to `false`, so +encoded slashes in the wildcard path stay encoded. `DisablePathUnescaping` is +deprecated and ignored by current Echo; use `EnablePathUnescaping` when you +explicitly need unescaping. + +## Security in Echo v5.4.0 + +By default, files are resolved from the same form of the path that the router +matched. File names requested with non-default escaping, such as `%2C`, `%40`, or +lowercase hex, need `EnablePathUnescaping`. Paths containing `.`, `..`, or empty +segments (for example `/assets//app.js`) return 404; HTML5 mode still serves the +index. Enabling path unescaping decodes encoded slashes too, so do not combine it +with route-based access control for subdirectories. + +:::caution +`e.Use(middleware.Static(...))` runs before route and group middleware. Route +guards do not protect the files it serves. Keep protected files outside its root, +or serve them with `Echo#Static` behind the guard. +::: diff --git a/site/src/content/docs/middleware/trailing-slash.mdx b/site/src/content/docs/middleware/trailing-slash.mdx index ed89b0d4..a6ca8afa 100644 --- a/site/src/content/docs/middleware/trailing-slash.mdx +++ b/site/src/content/docs/middleware/trailing-slash.mdx @@ -13,6 +13,9 @@ All core middleware lives in the `middleware` package: import "github.com/labstack/echo/v5/middleware" ``` +When configured to redirect, the trailing slash middlewares percent-encode +control characters in the path before constructing the `Location` header. + ## Add trailing slash Add trailing slash middleware adds a trailing slash to the request URI. diff --git a/site/src/content/docs/pt-br/cookbook/jsonp.md b/site/src/content/docs/pt-br/cookbook/jsonp.md index 1123a225..2775ca0d 100644 --- a/site/src/content/docs/pt-br/cookbook/jsonp.md +++ b/site/src/content/docs/pt-br/cookbook/jsonp.md @@ -93,3 +93,7 @@ func main() { ``` + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +O callback deve ser vazio, um identificador JavaScript ou um caminho de identificadores separados por pontos (letras ASCII, dígitos, `_` e `$`). Um valor inválido retorna HTTP 400 com `ErrInvalidJSONPCallback` e não escreve o corpo JSONP. A resposta válida inclui `X-Content-Type-Options: nosniff`. **Qualquer site pode ler JSONP com os cookies do usuário**: não sirva dados privados ou autenticados; use JSON com CORS. diff --git a/site/src/content/docs/pt-br/cookbook/reverse-proxy.md b/site/src/content/docs/pt-br/cookbook/reverse-proxy.md index 97ee9c3e..c73e1ce9 100644 --- a/site/src/content/docs/pt-br/cookbook/reverse-proxy.md +++ b/site/src/content/docs/pt-br/cookbook/reverse-proxy.md @@ -200,3 +200,7 @@ func main() { } } ``` + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +Proxy define `X-Forwarded-Proto` a partir de `Context#Scheme()` e remove os outros cabeçalhos de esquema. No v5, `X-Real-IP` vem de `Context#RealIP()`. Se houver outro proxy antes do Echo, configure os extratores de [esquema](/pt-br/guide/request-scheme/) e [IP](/pt-br/guide/ip-address/) para transmitir valores corretos ao upstream. diff --git a/site/src/content/docs/pt-br/guide/ip-address.md b/site/src/content/docs/pt-br/guide/ip-address.md index 76c18740..296527d6 100644 --- a/site/src/content/docs/pt-br/guide/ip-address.md +++ b/site/src/content/docs/pt-br/guide/ip-address.md @@ -16,10 +16,7 @@ corre o risco de ser enganado. **Isso é um risco de segurança.** Para recuperar o IP de forma confiável e segura, sua aplicação precisa conhecer toda a sua infraestrutura. No Echo, você configura isso por meio de `Echo#IPExtractor`. -:::caution -Se você não definir `Echo#IPExtractor` explicitamente, Echo recorre ao comportamento legado, -que não é um padrão seguro. -::: +No v5, `Context#RealIP()` usa por padrão o endereço do par direto desde v5.1.0. O v4 ainda confia por padrão em `X-Forwarded-For` e `X-Real-IP` enviados por qualquer cliente; por isso, **sempre configure `Echo#IPExtractor`** conforme seus proxies. O esquema HTTP/HTTPS é configurado separadamente em [Esquema da requisição](/pt-br/guide/request-scheme/). Comece com duas perguntas para encontrar a abordagem correta: @@ -110,8 +107,4 @@ forjá-los, abrindo espaço para fraude. ## Comportamento padrão -Por padrão, Echo considera ao mesmo tempo o primeiro header XFF, o header X-Real-IP e o IP -da camada de rede. - -Como este artigo deve deixar claro, essa não é uma boa escolha. Ela continua sendo o padrão -apenas por compatibilidade retroativa. +Sem `Echo#IPExtractor`, **o v5 usa o endereço do par direto** e ignora cabeçalhos de IP enviados pelo cliente. **O v4 mantém o comportamento legado**, que pode usar `X-Forwarded-For` ou `X-Real-IP` sem verificar o proxy. No v4, use `echo.ExtractIPDirect()` se não houver proxy, ou um extrator de cabeçalho com opções de confiança adequadas aos seus proxies. diff --git a/site/src/content/docs/pt-br/guide/request-scheme.md b/site/src/content/docs/pt-br/guide/request-scheme.md new file mode 100644 index 00000000..a5b96875 --- /dev/null +++ b/site/src/content/docs/pt-br/guide/request-scheme.md @@ -0,0 +1,55 @@ +--- +title: Esquema da requisição e proxies confiáveis +description: Configure como o Echo identifica HTTP ou HTTPS atrás de um proxy confiável. +sidebar: + order: 15 +--- + +`Context#Scheme()` informa se a requisição usa HTTP ou HTTPS. Redirecionamentos +HTTPS, HSTS e o middleware Proxy dependem desse valor. Desde o Echo v5.4.0 e +v4.16.0, o Echo só usa `X-Forwarded-Proto`, `X-Forwarded-Protocol`, +`X-Forwarded-Ssl` ou `X-Url-Scheme` quando o **par conectado diretamente** usa +um endereço de loopback, link-local ou privado, ou um socket Unix. Caso contrário, +a própria conexão determina o esquema. Assim, um cliente público não pode enviar +`X-Forwarded-Proto: https` por HTTP para evitar o redirecionamento. + +## Escolha do extrator + +`Echo#SchemeExtractor` controla essa decisão; no v5 também há +`Config.SchemeExtractor`. O padrão `echo.ExtractSchemeFromHeaders()` confia apenas +nos pares diretos citados acima. `echo.ExtractSchemeDirect()` ignora os cabeçalhos +de encaminhamento. `echo.LegacySchemeExtractor()` restaura o comportamento antigo, +que é inseguro se um cliente não confiável puder acessar a aplicação ou se o proxy +repassar um cabeçalho enviado pelo cliente. + +Quando `X-Forwarded-Proto` está presente, o Echo usa **somente o último valor** +e retorna o esquema em letras minúsculas. Um valor inválido resulta em `http`; +o Echo não tenta outro cabeçalho de esquema. O proxy confiável deve +**sobrescrever** `X-Forwarded-Proto` com o esquema observado, nunca repassar o +valor do cliente. No nginx, configure +`proxy_set_header X-Forwarded-Proto $scheme;`. + +## Proxies com endereços públicos + +Confie explicitamente nas faixas de um proxy que se conecta de um endereço +público ou de `100.64.0.0/10`. Sem isso, o Echo ignora seu `X-Forwarded-Proto`: +redirecionamentos HTTPS podem entrar em loop e o middleware Secure pode deixar +de enviar HSTS. Isso inclui Cloudflare, CloudFront, Azure Front Door, +balanceadores HTTP(S) externos do GCP e GKE Ingress. Confie apenas nas faixas +usadas pela sua implantação. Para as faixas do GCP: + +```go +_, gclb1, _ := net.ParseCIDR("35.191.0.0/16") +_, gclb2, _ := net.ParseCIDR("130.211.0.0/22") +e.SchemeExtractor = echo.ExtractSchemeFromHeaders( + echo.TrustIPRange(gclb1), + echo.TrustIPRange(gclb2), +) +``` + +Importe `net` e `github.com/labstack/echo/v5` para este exemplo; no v4, use +`github.com/labstack/echo/v4`. Proxies no mesmo host, em rede privada ou em +socket Unix continuam funcionando com a configuração padrão. Se um adaptador +substitui `RemoteAddr` pelo endereço do cliente, configure o extrator para a +topologia real. O IP do cliente é configurado separadamente em +[Endereço IP](/pt-br/guide/ip-address/). diff --git a/site/src/content/docs/pt-br/guide/response.md b/site/src/content/docs/pt-br/guide/response.md index eaeac053..854841f9 100644 --- a/site/src/content/docs/pt-br/guide/response.md +++ b/site/src/content/docs/pt-br/guide/response.md @@ -320,3 +320,7 @@ e.GET("/hooks", func(c *echo.Context) error { :::tip Você pode registrar várias funções `Before` e `After`. ::: + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +`Context#JSONP` aceita callback vazio, identificador JavaScript ou caminho de identificadores separados por pontos. Os demais valores retornam HTTP 400 com `ErrInvalidJSONPCallback`; uma resposta válida inclui `X-Content-Type-Options: nosniff`. Não use JSONP para dados privados: qualquer site pode ler a resposta com os cookies do usuário. Use JSON com CORS. diff --git a/site/src/content/docs/pt-br/guide/static-files.md b/site/src/content/docs/pt-br/guide/static-files.md index 434a3483..76240cee 100644 --- a/site/src/content/docs/pt-br/guide/static-files.md +++ b/site/src/content/docs/pt-br/guide/static-files.md @@ -87,3 +87,7 @@ e.File("/favicon.ico", "app/assets/favicon.ico") // The file path must not have Um `/` inicial no caminho do arquivo não funciona com a maioria das implementações de `fs.FS`. Use um caminho relativo. ::: + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +Caminhos com segmentos `.`, `..` ou vazios retornam 404; no modo `HTML5` o índice ainda pode ser servido. Nomes com escape não padrão exigem `StaticConfig.EnablePathUnescaping` no middleware ou `Config.EnablePathUnescapingStaticFiles` (v5) ou `Echo#EnablePathUnescapingStaticFiles` (v4) em `Echo#Static` e `Echo#StaticFS`. Essas opções também decodificam barras codificadas; não as combine com controle de acesso baseado em rotas. `e.Use(middleware.Static(...))` executa antes das proteções de rota e grupo; mantenha arquivos protegidos fora da raiz ou use uma rota `Echo#Static` protegida. diff --git a/site/src/content/docs/pt-br/guide/testing.md b/site/src/content/docs/pt-br/guide/testing.md index e08bcfce..5d25d71c 100644 --- a/site/src/content/docs/pt-br/guide/testing.md +++ b/site/src/content/docs/pt-br/guide/testing.md @@ -262,3 +262,7 @@ func TestMiddleware(t *testing.T) { Para mais exemplos, veja os [casos de teste de middleware](https://github.com/labstack/echo/tree/master/middleware) no código-fonte do Echo. ::: + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +`httptest.NewRequest` define `RemoteAddr` como `192.0.2.1:1234`, endereço que o extrator de esquema padrão não considera confiável. Se o teste define `X-Forwarded-Proto: https`, defina também `req.RemoteAddr = "10.0.0.1:1234"` para simular um proxy privado confiável. Use `e.SchemeExtractor = echo.LegacySchemeExtractor()` apenas para testar o comportamento antigo. Veja [Esquema da requisição](/pt-br/guide/request-scheme/). diff --git a/site/src/content/docs/pt-br/middleware/method-override.mdx b/site/src/content/docs/pt-br/middleware/method-override.mdx index 19efb9e7..6a202cc4 100644 --- a/site/src/content/docs/pt-br/middleware/method-override.mdx +++ b/site/src/content/docs/pt-br/middleware/method-override.mdx @@ -49,3 +49,7 @@ DefaultMethodOverrideConfig = MethodOverrideConfig{ Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), } ``` + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +Registre MethodOverride com `Echo#Pre`, antes do roteamento e do middleware CSRF. Um `POST` não pode mais ser convertido em `GET`, `HEAD`, `OPTIONS`, `TRACE` ou `CONNECT`; nesses casos a requisição continua como `POST`. diff --git a/site/src/content/docs/pt-br/middleware/proxy.mdx b/site/src/content/docs/pt-br/middleware/proxy.mdx index 66e8c2d0..e6cf5492 100644 --- a/site/src/content/docs/pt-br/middleware/proxy.mdx +++ b/site/src/content/docs/pt-br/middleware/proxy.mdx @@ -77,3 +77,7 @@ e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{ ``` Veja a receita de [reverse proxy](/pt-br/cookbook/reverse-proxy/) para um exemplo completo. + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +Proxy define `X-Forwarded-Proto` a partir de `Context#Scheme()` e remove `X-Forwarded-Ssl`, `X-Forwarded-Protocol` e `X-Url-Scheme` antes de encaminhar. No v5, também define `X-Real-IP` a partir de `Context#RealIP()`. Em uma cadeia nginx → Echo Proxy → servidor upstream, configure os extratores de [esquema](/pt-br/guide/request-scheme/) e [IP](/pt-br/guide/ip-address/) para os proxies confiáveis. diff --git a/site/src/content/docs/pt-br/middleware/redirect.mdx b/site/src/content/docs/pt-br/middleware/redirect.mdx index 29e570e6..dc918cef 100644 --- a/site/src/content/docs/pt-br/middleware/redirect.mdx +++ b/site/src/content/docs/pt-br/middleware/redirect.mdx @@ -98,3 +98,7 @@ RedirectConfig{ Code: http.StatusMovedPermanently, } ``` + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +Redirecionamentos HTTPS dependem de `Context#Scheme()`. [Configure as faixas de proxies confiáveis](/pt-br/guide/request-scheme/): um proxy de endereço público não confiável pode causar um loop de redirecionamento. diff --git a/site/src/content/docs/pt-br/middleware/secure.mdx b/site/src/content/docs/pt-br/middleware/secure.mdx index 85c88b56..0d311234 100644 --- a/site/src/content/docs/pt-br/middleware/secure.mdx +++ b/site/src/content/docs/pt-br/middleware/secure.mdx @@ -55,3 +55,7 @@ var DefaultSecureConfig = SecureConfig{ HSTSPreloadEnabled: false, } ``` + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +HSTS é enviado quando `Context#Scheme()` é `https`, não apenas por um `X-Forwarded-Proto: https` não verificado. Se um proxy público termina TLS, [confie na faixa dele](/pt-br/guide/request-scheme/) e faça o proxy sobrescrever o cabeçalho; caso contrário, HSTS pode faltar. diff --git a/site/src/content/docs/pt-br/middleware/static.mdx b/site/src/content/docs/pt-br/middleware/static.mdx index b243bb4d..04ad824c 100644 --- a/site/src/content/docs/pt-br/middleware/static.mdx +++ b/site/src/content/docs/pt-br/middleware/static.mdx @@ -23,7 +23,7 @@ A página importa o mesmo arquivo compilado pela CI da documentação. O exemplo `Root` define o diretório servido. `Browse` habilita a listagem de diretórios, `HTML5` encaminha caminhos não encontrados ao arquivo de índice e `Filesystem` aceita um `fs.FS`. Use `IgnoreBase` quando o prefixo da URL de um grupo não deve entrar no caminho do arquivo. -O fallback varia entre as revisões. O Echo v5.3.0 serve o arquivo de índice quando o próximo handler retorna 404. Na revisão `next` registrada, isso só ocorre para um 404 do roteador; um 404 retornado por uma rota correspondente é preservado. A diferença importa quando uma SPA e rotas de API compartilham o servidor. +No Echo v5.4.0, `HTML5` serve o arquivo de índice apenas para um 404 do roteador. Um 404 retornado por uma rota correspondente é preservado; isso importa quando uma SPA e uma API compartilham o servidor. #### Exemplo 1 @@ -44,3 +44,7 @@ A tabela vem dos campos exportados da revisão indicada do Echo. Ela marca campo `Index` usa `index.html` por padrão. `EnablePathUnescaping` é `false` por padrão: barras codificadas no caminho curinga continuam codificadas. `DisablePathUnescaping` está obsoleto e é ignorado; use `EnablePathUnescaping` quando precisar habilitar a decodificação. Habilite-a somente se precisar de caracteres codificados em nomes de arquivos e se suas rotas não restringirem o acesso a subdiretórios. Decodificar uma barra após o roteamento pode contornar uma rota de proteção. + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +Por padrão, Static resolve arquivos a partir da mesma forma de caminho usada pelo roteador. Nomes pedidos com escape não padrão (`%2C`, `%40` ou hexadecimal minúsculo) exigem `EnablePathUnescaping`. Segmentos `.`, `..` ou vazios (por exemplo `/assets//app.js`) retornam 404; `HTML5` ainda pode servir o índice. Ativar a decodificação também decodifica barras codificadas: não combine isso com controle de acesso baseado em rotas. **`e.Use(middleware.Static(...))` executa antes dos middlewares de rota e grupo**; suas proteções não cobrem esses arquivos. Mantenha arquivos protegidos fora da raiz ou sirva-os com `Echo#Static` atrás de uma proteção. diff --git a/site/src/content/docs/pt-br/middleware/trailing-slash.mdx b/site/src/content/docs/pt-br/middleware/trailing-slash.mdx index 40df4c49..8524109c 100644 --- a/site/src/content/docs/pt-br/middleware/trailing-slash.mdx +++ b/site/src/content/docs/pt-br/middleware/trailing-slash.mdx @@ -52,3 +52,7 @@ O exemplo acima adiciona uma barra final à URI do request e redireciona com + +## Atualização de segurança do Echo (v5.4.0 / v4.16.0) + +Ao redirecionar, os middlewares de barra final codificam os caracteres de controle no caminho em porcentagem antes de criar o cabeçalho `Location`. diff --git a/site/src/content/docs/zh-cn/cookbook/jsonp.md b/site/src/content/docs/zh-cn/cookbook/jsonp.md index 670bbbec..75d4d1b4 100644 --- a/site/src/content/docs/zh-cn/cookbook/jsonp.md +++ b/site/src/content/docs/zh-cn/cookbook/jsonp.md @@ -92,3 +92,7 @@ func main() { ``` + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +回调必须为空、JavaScript 标识符,或由点分隔的标识符路径(ASCII 字母、数字、`_`、`$`)。无效值返回包含 `ErrInvalidJSONPCallback` 的 HTTP 400,且不写入 JSONP 正文。正常响应带有 `X-Content-Type-Options: nosniff`。**任何网站都能携带用户 Cookie 读取 JSONP**;不要用它返回需要认证或私密的数据,应使用带 CORS 的 JSON。 diff --git a/site/src/content/docs/zh-cn/cookbook/reverse-proxy.md b/site/src/content/docs/zh-cn/cookbook/reverse-proxy.md index a3b2a60c..e3991eb6 100644 --- a/site/src/content/docs/zh-cn/cookbook/reverse-proxy.md +++ b/site/src/content/docs/zh-cn/cookbook/reverse-proxy.md @@ -198,3 +198,7 @@ func main() { } } ``` + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +Proxy 从 `Context#Scheme()` 设置 `X-Forwarded-Proto` 并删除旧协议标头。v5 的 `X-Real-IP` 来自 `Context#RealIP()`。如果 Echo 前面还有代理,请配置[协议](/zh-cn/guide/request-scheme/)和 [IP](/zh-cn/guide/ip-address/) 提取器,确保上游收到正确的值。 diff --git a/site/src/content/docs/zh-cn/guide/ip-address.md b/site/src/content/docs/zh-cn/guide/ip-address.md index 2e74e20f..d256399d 100644 --- a/site/src/content/docs/zh-cn/guide/ip-address.md +++ b/site/src/content/docs/zh-cn/guide/ip-address.md @@ -15,9 +15,7 @@ Echo 暴露 `Context#RealIP()` 来获取它。 要可靠且安全地获取 IP,你的应用必须了解整个基础设施。在 Echo 中,你通过 `Echo#IPExtractor` 配置这一点。 -:::caution -如果没有显式设置 `Echo#IPExtractor`,Echo 会回退到旧行为,而这不是安全的默认值。 -::: +从 v5.1.0 开始,v5 的 `Context#RealIP()` 默认使用直接对端地址。v4 仍会默认信任任意客户端发送的 `X-Forwarded-For` 和 `X-Real-IP`,因此请按代理拓扑**始终配置 `Echo#IPExtractor`**。HTTP/HTTPS 的判断需另行配置,参见[请求协议与可信代理](/zh-cn/guide/request-scheme/)。 从两个问题开始,找到正确方法: @@ -100,6 +98,4 @@ e.IPExtractor = echo.ExtractIPFromRealIPHeader() ## 默认行为 -默认情况下,Echo 会同时考虑第一个 XFF header、X-Real-IP header 和网络层 IP。 - -正如本文应当说明的那样,这并不是好的选择。它仅为了向后兼容而保留为默认值。 +未配置 `Echo#IPExtractor` 时,**v5 使用直接对端地址**,忽略客户端提供的 IP 标头。**v4 保留旧行为**,可能在不验证代理的情况下使用 `X-Forwarded-For` 或 `X-Real-IP`。v4 在无代理时使用 `echo.ExtractIPDirect()`;有代理时使用带适当信任选项的标头提取器。 diff --git a/site/src/content/docs/zh-cn/guide/request-scheme.md b/site/src/content/docs/zh-cn/guide/request-scheme.md new file mode 100644 index 00000000..7729923d --- /dev/null +++ b/site/src/content/docs/zh-cn/guide/request-scheme.md @@ -0,0 +1,48 @@ +--- +title: 请求协议与可信代理 +description: 配置 Echo 如何在可信代理后判断 HTTP 或 HTTPS。 +sidebar: + order: 15 +--- + +`Context#Scheme()` 返回请求使用的 HTTP 或 HTTPS。HTTPS 重定向、HSTS 和 Proxy +中间件都依赖它。从 Echo v5.4.0 和 v4.16.0 开始,只有**直接连接的对端**是 +环回、链路本地或私有地址,或通过 Unix 套接字连接时,Echo 才使用 +`X-Forwarded-Proto`、`X-Forwarded-Protocol`、`X-Forwarded-Ssl` 和 +`X-Url-Scheme`。否则由连接本身决定协议。这样,公网客户端无法通过 HTTP 发送 +`X-Forwarded-Proto: https` 来绕过 HTTPS 重定向。 + +## 选择协议提取器 + +使用 `Echo#SchemeExtractor` 配置判断方式;v5 还支持 +`Config.SchemeExtractor`。默认的 `echo.ExtractSchemeFromHeaders()` 仅信任上述 +直接对端。`echo.ExtractSchemeDirect()` 忽略转发标头。 +`echo.LegacySchemeExtractor()` 恢复旧行为;如果不可信客户端能直接访问应用, +或代理透传客户端提供的标头,旧行为并不安全。 + +存在 `X-Forwarded-Proto` 时,Echo **只使用最后一个值**,并以小写形式返回协议。 +无效值会得到 `http`,不会回退到其他转发协议标头。可信代理必须用自己观察到的 +协议**覆盖**该标头,不能透传客户端的值。nginx 可配置 +`proxy_set_header X-Forwarded-Proto $scheme;`。 + +## 公网地址的代理 + +如果直接连接的代理使用公网地址或 `100.64.0.0/10`,需要明确设置信任的地址段。 +否则 Echo 会忽略其 `X-Forwarded-Proto`,导致 HTTPS 重定向循环或 Secure +中间件不发送 HSTS。Cloudflare、CloudFront、Azure Front Door、GCP 外部 +HTTP(S) 负载均衡器和 GKE Ingress 都可能属于这种情况。只信任部署实际使用的 +地址段。以下是 GCP 地址段示例: + +```go +_, gclb1, _ := net.ParseCIDR("35.191.0.0/16") +_, gclb2, _ := net.ParseCIDR("130.211.0.0/22") +e.SchemeExtractor = echo.ExtractSchemeFromHeaders( + echo.TrustIPRange(gclb1), + echo.TrustIPRange(gclb2), +) +``` + +这个 v5 示例需要导入 `net` 和 `github.com/labstack/echo/v5`;v4 使用 +`github.com/labstack/echo/v4`。同一主机、私有网络和 Unix 套接字上的代理可沿用 +默认设置。如果适配器把 `RemoteAddr` 改为客户端地址,请按实际拓扑配置提取器。 +客户端 IP 需要另外配置,参见 [IP 地址](/zh-cn/guide/ip-address/)。 diff --git a/site/src/content/docs/zh-cn/guide/response.md b/site/src/content/docs/zh-cn/guide/response.md index 05d920ff..b070945e 100644 --- a/site/src/content/docs/zh-cn/guide/response.md +++ b/site/src/content/docs/zh-cn/guide/response.md @@ -314,3 +314,7 @@ e.GET("/hooks", func(c *echo.Context) error { :::tip 你可以注册多个 `Before` 和 `After` 函数。 ::: + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +`Context#JSONP` 的回调只能为空、JavaScript 标识符或点分隔的标识符。无效回调返回包含 `ErrInvalidJSONPCallback` 的 HTTP 400;正常响应带有 `X-Content-Type-Options: nosniff`。任何网站都可携带用户 Cookie 读取 JSONP,所以私密数据应使用带 CORS 的 JSON。 diff --git a/site/src/content/docs/zh-cn/guide/static-files.md b/site/src/content/docs/zh-cn/guide/static-files.md index a8e10dba..db92e260 100644 --- a/site/src/content/docs/zh-cn/guide/static-files.md +++ b/site/src/content/docs/zh-cn/guide/static-files.md @@ -83,3 +83,7 @@ e.File("/favicon.ico", "app/assets/favicon.ico") // The file path must not have :::caution 文件路径中前导 `/` 不适用于大多数 `fs.FS` 实现。请使用相对路径。 ::: + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +含 `.`、`..` 或空路径段的请求返回 404;`HTML5` 模式仍可返回索引文件。非默认转义的文件名需要中间件的 `StaticConfig.EnablePathUnescaping`,或 `Echo#Static`、`Echo#StaticFS` 的 `Config.EnablePathUnescapingStaticFiles` (v5) 或 `Echo#EnablePathUnescapingStaticFiles` (v4)。这些设置也会解码编码斜杠,请勿与基于路由的访问控制一起使用。`e.Use(middleware.Static(...))` 先于路由保护运行;应将受保护文件放在根目录外,或使用带保护的 `Echo#Static` 路由。 diff --git a/site/src/content/docs/zh-cn/guide/testing.md b/site/src/content/docs/zh-cn/guide/testing.md index 9c003139..545ddd35 100644 --- a/site/src/content/docs/zh-cn/guide/testing.md +++ b/site/src/content/docs/zh-cn/guide/testing.md @@ -260,3 +260,7 @@ func TestMiddleware(t *testing.T) { 更多示例请参见 Echo 源码中的 [中间件测试用例](https://github.com/labstack/echo/tree/master/middleware)。 ::: + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +`httptest.NewRequest` 将 `RemoteAddr` 设为 `192.0.2.1:1234`,默认协议提取器不信任此地址。测试设置 `X-Forwarded-Proto: https` 时,还应设 `req.RemoteAddr = "10.0.0.1:1234"` 以模拟可信私有代理。仅测试旧行为时可使用 `e.SchemeExtractor = echo.LegacySchemeExtractor()`。参见[请求协议](/zh-cn/guide/request-scheme/)。 diff --git a/site/src/content/docs/zh-cn/middleware/method-override.mdx b/site/src/content/docs/zh-cn/middleware/method-override.mdx index aff9c24b..94bbc52d 100644 --- a/site/src/content/docs/zh-cn/middleware/method-override.mdx +++ b/site/src/content/docs/zh-cn/middleware/method-override.mdx @@ -48,3 +48,7 @@ DefaultMethodOverrideConfig = MethodOverrideConfig{ Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride), } ``` + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +请在路由和 CSRF 中间件之前通过 `Echo#Pre` 注册 MethodOverride。`POST` 不能再改写为 `GET`、`HEAD`、`OPTIONS`、`TRACE` 或 `CONNECT`;请求这些方法时仍保持 `POST`。 diff --git a/site/src/content/docs/zh-cn/middleware/proxy.mdx b/site/src/content/docs/zh-cn/middleware/proxy.mdx index bdf181fb..84685c1e 100644 --- a/site/src/content/docs/zh-cn/middleware/proxy.mdx +++ b/site/src/content/docs/zh-cn/middleware/proxy.mdx @@ -75,3 +75,7 @@ e.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{ ``` 完整示例请参见[反向代理](/zh-cn/cookbook/reverse-proxy/) cookbook。 + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +Proxy 从 `Context#Scheme()` 设置 `X-Forwarded-Proto`,并在转发前删除 `X-Forwarded-Ssl`、`X-Forwarded-Protocol` 和 `X-Url-Scheme`。v5 还从 `Context#RealIP()` 设置 `X-Real-IP`。对于 nginx → Echo Proxy → 上游服务的链路,请按可信代理配置[协议](/zh-cn/guide/request-scheme/)和 [IP](/zh-cn/guide/ip-address/) 提取器。 diff --git a/site/src/content/docs/zh-cn/middleware/redirect.mdx b/site/src/content/docs/zh-cn/middleware/redirect.mdx index d8c8baa4..f550529a 100644 --- a/site/src/content/docs/zh-cn/middleware/redirect.mdx +++ b/site/src/content/docs/zh-cn/middleware/redirect.mdx @@ -99,3 +99,7 @@ RedirectConfig{ Code: http.StatusMovedPermanently, } ``` + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +HTTPS 重定向依赖 `Context#Scheme()`。使用公网地址的代理时,请[配置可信地址段](/zh-cn/guide/request-scheme/);否则可能出现重定向循环。 diff --git a/site/src/content/docs/zh-cn/middleware/secure.mdx b/site/src/content/docs/zh-cn/middleware/secure.mdx index 4937bec2..c93bee45 100644 --- a/site/src/content/docs/zh-cn/middleware/secure.mdx +++ b/site/src/content/docs/zh-cn/middleware/secure.mdx @@ -54,3 +54,7 @@ var DefaultSecureConfig = SecureConfig{ HSTSPreloadEnabled: false, } ``` + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +只有 `Context#Scheme()` 为 `https` 时才发送 HSTS,未经验证的 `X-Forwarded-Proto: https` 不会启用 HSTS。如果公网代理终止 TLS,请[信任其地址段](/zh-cn/guide/request-scheme/),并确保代理覆盖该标头。 diff --git a/site/src/content/docs/zh-cn/middleware/static.mdx b/site/src/content/docs/zh-cn/middleware/static.mdx index 771d53a0..c889ef2e 100644 --- a/site/src/content/docs/zh-cn/middleware/static.mdx +++ b/site/src/content/docs/zh-cn/middleware/static.mdx @@ -23,7 +23,7 @@ Static 中间件从根目录提供文件。下面的示例在 `/` 提供 `public `Root` 指定要提供的目录。`Browse` 启用目录列表,`HTML5` 将未找到的路径转到索引文件,`Filesystem` 可接收 `fs.FS`。如果分组的 URL 前缀不应成为文件路径的一部分,请使用 `IgnoreBase`。 -回退行为因修订版本而异。Echo v5.3.0 会在后续处理器返回 404 时提供索引文件。所记录的 `next` 修订版本仅在路由器未匹配到路由而返回 404 时提供索引文件;已匹配路由返回的 404 会原样返回。在同一服务器中同时提供 SPA 和 API 路由时,这一区别很重要。 +Echo v5.4.0 的 `HTML5` 模式仅对路由器返回的 404 提供索引文件。已匹配路由自身返回的 404 会原样返回;这对共用服务器的 SPA 和 API 很重要。 #### 示例 1 @@ -44,3 +44,7 @@ Static 中间件从根目录提供文件。下面的示例在 `/` 提供 `public `Index` 默认为 `index.html`。`EnablePathUnescaping` 默认为 `false`,因此通配符路径中的编码斜杠不会被解码。`DisablePathUnescaping` 已弃用,当前 Echo 会忽略它;确实需要解码时请使用 `EnablePathUnescaping`。 只有在文件名需要 URL 编码字符,且路由规则不限制子目录访问时才启用解码。在路由匹配后解码斜杠可能绕过保护性路由。 + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +默认情况下,Static 根据路由器匹配时使用的路径形式查找文件。非默认转义的文件名(如 `%2C`、`%40` 或小写十六进制)需要 `EnablePathUnescaping`。包含 `.`、`..` 或空段的路径(如 `/assets//app.js`)返回 404;`HTML5` 模式仍可返回索引文件。启用路径解码也会解码编码斜杠,因此不要与基于子目录路由的访问控制一起使用。**`e.Use(middleware.Static(...))` 先于路由和分组中间件运行**;路由保护无法保护其文件。请将受保护文件放在根目录外,或通过带保护的 `Echo#Static` 提供。 diff --git a/site/src/content/docs/zh-cn/middleware/trailing-slash.mdx b/site/src/content/docs/zh-cn/middleware/trailing-slash.mdx index eefa04f7..ed765438 100644 --- a/site/src/content/docs/zh-cn/middleware/trailing-slash.mdx +++ b/site/src/content/docs/zh-cn/middleware/trailing-slash.mdx @@ -51,3 +51,7 @@ e.Use(middleware.AddTrailingSlashWithConfig(middleware.AddTrailingSlashConfig{ + +## Echo 安全更新 (v5.4.0 / v4.16.0) + +尾部斜杠中间件执行重定向时,会先对路径中的控制字符进行百分号编码,再构造 `Location` 标头。 diff --git a/site/translation-baseline.json b/site/translation-baseline.json index 9d16852b..078fea3a 100644 --- a/site/translation-baseline.json +++ b/site/translation-baseline.json @@ -70,8 +70,8 @@ }, { "heading": "Custom configuration", - "source": "aff0a36a30fca26a52b57828a9009e13e7925f328842474f3531df689c700c34", - "translation": "8c65dde7d33f7ffdf280541469b922a58807f6c9ee0eb93dbb9f11af18d1c929" + "source": "78822d82c9cf9601386c75a7a70f5ef2d4d3617390dd06a00c60336a19dc2540", + "translation": "e28acaa0f6fa9e34ead172a52f33caf3b79663a6d73042a9171091b556d7d694" }, { "heading": "Configuration", @@ -80,8 +80,13 @@ }, { "heading": "Default configuration", - "source": "4178c79309946e059e92d5a255cd0695542664baccae90f140c4ad326e54159a", + "source": "87dd7f64edb5703afeb14e3d5233f40f62da2e9932f3ac1b72286f773754543a", "translation": "7379ff2ee6b57d2b8be2c6054b763fcc3d16068ba7a2a3f5e2d56491bef37ca3" + }, + { + "heading": "Security in Echo v5.4.0", + "source": "67848fc24e49ecc6992ac990d86744c001dfef843d5d075548741d3a4c3fd092", + "translation": "44dd2afdd19adb70b4647f4d1ecfc4868d33f221fdf59d46e614e4a295882f6a" } ], "index": [ @@ -173,8 +178,8 @@ }, { "heading": "Custom configuration", - "source": "aff0a36a30fca26a52b57828a9009e13e7925f328842474f3531df689c700c34", - "translation": "0f78deb38c9ff5c8f1492e19ba8130d3451db5f4aae93b6564d870bf5add14ec" + "source": "78822d82c9cf9601386c75a7a70f5ef2d4d3617390dd06a00c60336a19dc2540", + "translation": "1d903fd2553d3d2e68ec202951cc532c958d3e1efd583654d9e05ef14cf665db" }, { "heading": "Configuration", @@ -183,8 +188,13 @@ }, { "heading": "Default configuration", - "source": "4178c79309946e059e92d5a255cd0695542664baccae90f140c4ad326e54159a", + "source": "87dd7f64edb5703afeb14e3d5233f40f62da2e9932f3ac1b72286f773754543a", "translation": "155968c41190d2e099d7f1b1d075e0db37dd4ad1a22c3dba54e3470d4ec8acff" + }, + { + "heading": "Security in Echo v5.4.0", + "source": "67848fc24e49ecc6992ac990d86744c001dfef843d5d075548741d3a4c3fd092", + "translation": "93c0cf348b27531e4ab29ea99439496fb4f0c25a82bd49c07a70df9b17533936" } ], "index": [ @@ -276,8 +286,8 @@ }, { "heading": "Custom configuration", - "source": "aff0a36a30fca26a52b57828a9009e13e7925f328842474f3531df689c700c34", - "translation": "a5da6b6451516398676fc185e6923ced23f8734d3473fb8c78a3ce9eaeb01427" + "source": "78822d82c9cf9601386c75a7a70f5ef2d4d3617390dd06a00c60336a19dc2540", + "translation": "88362601c99a010274754dded588c510e014fc12c700849d27025cdc489b2c18" }, { "heading": "Configuration", @@ -286,8 +296,13 @@ }, { "heading": "Default configuration", - "source": "4178c79309946e059e92d5a255cd0695542664baccae90f140c4ad326e54159a", + "source": "87dd7f64edb5703afeb14e3d5233f40f62da2e9932f3ac1b72286f773754543a", "translation": "83052e253e1a91debf48ac4305b0daf4f24897b41f2f4cf301fe0c811461b052" + }, + { + "heading": "Security in Echo v5.4.0", + "source": "67848fc24e49ecc6992ac990d86744c001dfef843d5d075548741d3a4c3fd092", + "translation": "13d890b92c55214864c94b7b344f48cdfe73d6cbdbefc2105152aad0b9b40e7d" } ], "index": [ @@ -379,8 +394,8 @@ }, { "heading": "Custom configuration", - "source": "aff0a36a30fca26a52b57828a9009e13e7925f328842474f3531df689c700c34", - "translation": "2ede0c0c72d3ac78b9948b8548de77083a7a89d177b242aaee85579664df4dd8" + "source": "78822d82c9cf9601386c75a7a70f5ef2d4d3617390dd06a00c60336a19dc2540", + "translation": "af5cf3dfb04bfb8b93fef34b024592b6c4867e80507be03b80a8303250e158ad" }, { "heading": "Configuration", @@ -389,8 +404,13 @@ }, { "heading": "Default configuration", - "source": "4178c79309946e059e92d5a255cd0695542664baccae90f140c4ad326e54159a", + "source": "87dd7f64edb5703afeb14e3d5233f40f62da2e9932f3ac1b72286f773754543a", "translation": "efc9a058c6b155a27dbc26bd1911399795449ad7e9967a1473eb744fd67c3d44" + }, + { + "heading": "Security in Echo v5.4.0", + "source": "67848fc24e49ecc6992ac990d86744c001dfef843d5d075548741d3a4c3fd092", + "translation": "4333359b1ef72fc7d86087ce1ffae3539ad7736ed7c8fe5e7a24760b0e56a9cf" } ], "index": [