diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 284db30..f3ca9ec 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -12,6 +12,6 @@ jobs: uses: node-modules/github-actions/.github/workflows/node-test.yml@master with: os: 'ubuntu-latest, macos-latest, windows-latest' - version: '14, 16, 18, 20, 22' + version: '14.20.0, 14, 16, 18, 20, 22' secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml new file mode 100644 index 0000000..bac3fac --- /dev/null +++ b/.github/workflows/pkg.pr.new.yml @@ -0,0 +1,23 @@ +name: Publish Any Commit +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - run: corepack enable + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install + + - name: Build + run: npm run prepublishOnly --if-present + + - run: npx pkg-pr-new publish diff --git a/README.md b/README.md index 28a36d1..9c63127 100644 --- a/README.md +++ b/README.md @@ -551,19 +551,12 @@ exports.security = { - Forbid `trace` `track` http methods. - - -## Contributors - -|[
dead-horse](https://github.com/dead-horse)
|[
fengmk2](https://github.com/fengmk2)
|[
jtyjty99999](https://github.com/jtyjty99999)
|[
popomore](https://github.com/popomore)
|[
shaoshuai0102](https://github.com/shaoshuai0102)
|[
whxaxes](https://github.com/whxaxes)
| -| :---: | :---: | :---: | :---: | :---: | :---: | -|[
atian25](https://github.com/atian25)
|[
ai](https://github.com/ai)
|[
Anemone95](https://github.com/Anemone95)
|[
guoshencheng](https://github.com/guoshencheng)
|[
p0sec](https://github.com/p0sec)
|[
pusongyang](https://github.com/pusongyang)
| -[
ShadyZOZ](https://github.com/ShadyZOZ)
|[
viko16](https://github.com/viko16)
|[
brizer](https://github.com/brizer)
|[
damujiangr](https://github.com/damujiangr)
|[
EliYao](https://github.com/EliYao)
+## License -This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Wed May 10 2023 16:36:13 GMT+0800`. +[MIT](https://github.com/eggjs/egg-security/blob/master/LICENSE) - +## Contributors -## License +[![Contributors](https://contrib.rocks/image?repo=eggjs/egg-security)](https://github.com/eggjs/egg-security/graphs/contributors) -[MIT](https://github.com/eggjs/egg-security/blob/master/LICENSE) +Made with [contributors-img](https://contrib.rocks). diff --git a/README.zh-CN.md b/README.zh-CN.md index 2188640..6f88144 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -426,19 +426,12 @@ console.log(cmd); * crossdomain.xml robots.txt 支持,默认都不加,系统可自行加,需要咨询项目安全工程师 * 禁止 trace track 两种类型请求 - +## License -## Contributors - -|[
dead-horse](https://github.com/dead-horse)
|[
fengmk2](https://github.com/fengmk2)
|[
jtyjty99999](https://github.com/jtyjty99999)
|[
popomore](https://github.com/popomore)
|[
shaoshuai0102](https://github.com/shaoshuai0102)
|[
whxaxes](https://github.com/whxaxes)
| -| :---: | :---: | :---: | :---: | :---: | :---: | -|[
atian25](https://github.com/atian25)
|[
ai](https://github.com/ai)
|[
Anemone95](https://github.com/Anemone95)
|[
guoshencheng](https://github.com/guoshencheng)
|[
p0sec](https://github.com/p0sec)
|[
pusongyang](https://github.com/pusongyang)
| -[
ShadyZOZ](https://github.com/ShadyZOZ)
|[
viko16](https://github.com/viko16)
|[
brizer](https://github.com/brizer)
|[
damujiangr](https://github.com/damujiangr)
|[
EliYao](https://github.com/EliYao)
- -This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Wed May 10 2023 16:36:13 GMT+0800`. +[MIT](https://github.com/eggjs/egg-security/blob/master/LICENSE)¬ - +## Contributors -## License¬ +[![Contributors](https://contrib.rocks/image?repo=eggjs/egg-security)](https://github.com/eggjs/egg-security/graphs/contributors) -[MIT](https://github.com/eggjs/egg-security/blob/master/LICENSE)¬ +Made with [contributors-img](https://contrib.rocks). diff --git a/app/extend/agent.js b/app/extend/agent.js index a9b7855..6207e09 100644 --- a/app/extend/agent.js +++ b/app/extend/agent.js @@ -1,7 +1,5 @@ -'use strict'; - -const safeCurl = require('../../lib/extend/safe_curl'); +const { safeCurlForApplication } = require('../../lib/extend/safe_curl'); module.exports = { - safeCurl, + safeCurl: safeCurlForApplication, }; diff --git a/app/extend/application.js b/app/extend/application.js index bf82e73..5f5d542 100644 --- a/app/extend/application.js +++ b/app/extend/application.js @@ -1,6 +1,4 @@ -'use strict'; - -const safeCurl = require('../../lib/extend/safe_curl'); +const { safeCurlForApplication } = require('../../lib/extend/safe_curl'); const INPUT_CSRF = '\r\n'; @@ -33,4 +31,4 @@ exports.injectHijackingDefense = function injectHijackingDefense(tmplStr) { return INJECTION_DEFENSE + tmplStr + INJECTION_DEFENSE; }; -exports.safeCurl = safeCurl; +exports.safeCurl = safeCurlForApplication; diff --git a/app/extend/context.js b/app/extend/context.js index a3f7520..4c8e255 100644 --- a/app/extend/context.js +++ b/app/extend/context.js @@ -3,7 +3,7 @@ const debug = require('node:util').debuglog('egg-security:context'); const { nanoid } = require('nanoid/non-secure'); const Tokens = require('csrf'); -const safeCurl = require('../../lib/extend/safe_curl'); +const { safeCurlForContext } = require('../../lib/extend/safe_curl'); const utils = require('../../lib/utils'); const tokens = new Tokens(); @@ -228,5 +228,5 @@ module.exports = { } }, - safeCurl, + safeCurl: safeCurlForContext, }; diff --git a/lib/extend/safe_curl.js b/lib/extend/safe_curl.js index b033deb..7212810 100644 --- a/lib/extend/safe_curl.js +++ b/lib/extend/safe_curl.js @@ -1,4 +1,4 @@ -'use strict'; +const SSRF_HTTPCLIENT = Symbol('SSRF_HTTPCLIENT'); /** * safe curl with ssrf protect @@ -6,13 +6,28 @@ * @param {Object} options request options * @return {Promise} response */ -module.exports = function safeCurl(url, options = {}) { - const config = this.config || this.app.config; - if (config.security.ssrf && config.security.ssrf.checkAddress) { - options.checkAddress = config.security.ssrf.checkAddress; +exports.safeCurlForApplication = function safeCurlForApplication(url, options = {}) { + const app = this; + const ssrfConfig = app.config.security.ssrf; + if (ssrfConfig?.checkAddress) { + options.checkAddress = ssrfConfig.checkAddress; } else { - this.logger.warn('[egg-security] please configure `config.security.ssrf` first'); + app.logger.warn('[egg-security] please configure `config.security.ssrf` first'); } - return this.curl(url, options); + if (app.config.httpclient.useHttpClientNext && ssrfConfig?.checkAddress) { + // use the new httpClient init with checkAddress + if (!app[SSRF_HTTPCLIENT]) { + app[SSRF_HTTPCLIENT] = app.createHttpClient({ + checkAddress: ssrfConfig.checkAddress, + }); + } + return app[SSRF_HTTPCLIENT].request(url, options); + } + + return app.curl(url, options); +}; + +exports.safeCurlForContext = function safeCurlForContext(url, options = {}) { + return this.app.safeCurl(url, options); }; diff --git a/package.json b/package.json index a230493..fa6e008 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,9 @@ { "name": "egg-security", "version": "3.3.1", + "engines": { + "node": ">=14.20.0" + }, "description": "security plugin in egg framework", "eggPlugin": { "name": "security", @@ -33,26 +36,21 @@ "devDependencies": { "beautify-benchmark": "^0.2.4", "benchmark": "^2.1.4", - "egg": "^3.15.0", + "egg": "^3.26.0", "egg-bin": "^6.4.0", "egg-mock": "^5.10.6", "egg-view-nunjucks": "^2.3.0", "eslint": "^8.40.0", "eslint-config-egg": "^12.2.1", - "git-contributor": "*", "spy": "^1.0.0", "supertest": "^6.3.3" }, - "engines": { - "node": ">=14.17.0" - }, "scripts": { "lint": "eslint .", "test": "npm run lint -- --fix && npm run test-local", "test-local": "egg-bin test", "cov": "egg-bin cov", - "ci": "npm run lint && npm run cov", - "contributor": "git-contributor" + "ci": "npm run lint && npm run cov" }, "repository": { "type": "git", diff --git a/test/fixtures/apps/ssrf-check-address-useHttpClientNext/config/config.default.js b/test/fixtures/apps/ssrf-check-address-useHttpClientNext/config/config.default.js new file mode 100644 index 0000000..80d11dd --- /dev/null +++ b/test/fixtures/apps/ssrf-check-address-useHttpClientNext/config/config.default.js @@ -0,0 +1,16 @@ +exports.security = { + ssrf: { + ipBlackList: [ + '10.0.0.0/8', + '127.0.0.1', + '0.0.0.0/32', + ], + checkAddress(ip) { + return ip !== '127.0.0.2'; + }, + }, +}; + +exports.httpclient = { + useHttpClientNext: true, +}; diff --git a/test/fixtures/apps/ssrf-check-address-useHttpClientNext/package.json b/test/fixtures/apps/ssrf-check-address-useHttpClientNext/package.json new file mode 100644 index 0000000..47b008c --- /dev/null +++ b/test/fixtures/apps/ssrf-check-address-useHttpClientNext/package.json @@ -0,0 +1,3 @@ +{ + "name": "ssrf-ip-check-address-useHttpClientNext" +} diff --git a/test/ssrf.test.js b/test/ssrf.test.js index 982d5f7..2840731 100644 --- a/test/ssrf.test.js +++ b/test/ssrf.test.js @@ -88,6 +88,27 @@ describe('test/ssrf.test.js', () => { }); }); + describe('checkAddress with useHttpClientNext = true', () => { + before(() => { + app = mm.app({ baseDir: 'apps/ssrf-check-address-useHttpClientNext' }); + return app.ready(); + }); + + it('should safeCurl work', async () => { + const urls = [ + 'https://127.0.0.2/foo', + 'https://www.google.com/foo', + ]; + mm.data(dns, 'lookup', '127.0.0.2'); + const ctx = app.createAnonymousContext(); + for (const url of urls) { + await checkIllegalAddressError(app, url); + await checkIllegalAddressError(app.agent, url); + await checkIllegalAddressError(ctx, url); + } + }); + }); + describe('ipExceptionList', () => { before(() => { app = mm.app({ baseDir: 'apps/ssrf-ip-exception-list' }); @@ -157,6 +178,7 @@ async function checkIllegalAddressError(instance, url) { await instance.safeCurl(url); throw new Error('should not execute'); } catch (err) { - assert(err.name === 'IllegalAddressError'); + assert.equal(err.name, 'IllegalAddressError'); + assert.match(err.message, /illegal address/); } }