Skip to content

Commit 31c934c

Browse files
author
molty3000
committed
fix: lint compliance and CodeQL remediation
- Fix standard.js linting: double→single quotes, remove semicolons - Replace SSRF error message with generic 'Bad Request' response per CodeQL warning about user-influenced text in HTTP response
1 parent 993d07d commit 31c934c

2 files changed

Lines changed: 104 additions & 104 deletions

File tree

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function fastProxy (opts = {}) {
3737
url = getReqUrl(source || req.url, cache, base, opts)
3838
} catch (err) {
3939
res.statusCode = 400
40-
res.end(err.message)
40+
res.end('Bad Request')
4141
return
4242
}
4343
const sourceHttp2 = req.httpVersionMajor === 2

test/13.security.test.js

Lines changed: 103 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,124 @@
11
/* global describe, it */
2-
"use strict";
2+
'use strict'
33

4-
const { expect } = require("chai");
5-
const net = require("net");
6-
const http = require("http");
4+
const { expect } = require('chai')
5+
const net = require('net')
6+
const http = require('http')
77

8-
describe("Security: buildURL SSRF prevention", () => {
9-
const { buildURL } = require("../lib/utils");
8+
describe('Security: buildURL SSRF prevention', () => {
9+
const { buildURL } = require('../lib/utils')
1010

11-
it("should allow relative paths within base", () => {
12-
const url = buildURL("/hi", "http://localhost:3000");
13-
expect(url.origin).to.equal("http://localhost:3000");
14-
});
11+
it('should allow relative paths within base', () => {
12+
const url = buildURL('/hi', 'http://localhost:3000')
13+
expect(url.origin).to.equal('http://localhost:3000')
14+
})
1515

16-
it("should block absolute URL bypassing base", () => {
17-
expect(() => buildURL("http://evil.com/admin", "http://127.0.0.1:3000"))
18-
.to.throw(/SSRF prevention/);
19-
});
16+
it('should block absolute URL bypassing base', () => {
17+
expect(() => buildURL('http://evil.com/admin', 'http://127.0.0.1:3000'))
18+
.to.throw(/SSRF prevention/)
19+
})
2020

21-
it("should block HTTPS absolute URL bypass", () => {
22-
expect(() => buildURL("https://internal/api", "http://127.0.0.1:3000"))
23-
.to.throw(/SSRF prevention/);
24-
});
21+
it('should block HTTPS absolute URL bypass', () => {
22+
expect(() => buildURL('https://internal/api', 'http://127.0.0.1:3000'))
23+
.to.throw(/SSRF prevention/)
24+
})
2525

26-
it("should allow absolute URL when no base", () => {
27-
const url = buildURL("http://target.com/api");
28-
expect(url.href).to.equal("http://target.com/api");
29-
});
26+
it('should allow absolute URL when no base', () => {
27+
const url = buildURL('http://target.com/api')
28+
expect(url.href).to.equal('http://target.com/api')
29+
})
3030

31-
it("should sanitize protocol-relative within base", () => {
32-
const url = buildURL("//evil.com/hi", "http://localhost");
33-
expect(url.origin).to.equal("http://localhost");
34-
});
35-
});
31+
it('should sanitize protocol-relative within base', () => {
32+
const url = buildURL('//evil.com/hi', 'http://localhost')
33+
expect(url.origin).to.equal('http://localhost')
34+
})
35+
})
3636

37-
describe("Security: hop-by-hop header stripping", () => {
38-
const { stripHttp1ConnectionHeaders } = require("../lib/utils");
37+
describe('Security: hop-by-hop header stripping', () => {
38+
const { stripHttp1ConnectionHeaders } = require('../lib/utils')
3939

40-
it("should strip transfer-encoding", () => {
41-
const h = { "transfer-encoding": "gzip, chunked", "x-custom": "val" };
42-
const r = stripHttp1ConnectionHeaders(h);
43-
expect(r).to.not.have.property("transfer-encoding");
44-
expect(r).to.have.property("x-custom", "val");
45-
});
40+
it('should strip transfer-encoding', () => {
41+
const h = { 'transfer-encoding': 'gzip, chunked', 'x-custom': 'val' }
42+
const r = stripHttp1ConnectionHeaders(h)
43+
expect(r).to.not.have.property('transfer-encoding')
44+
expect(r).to.have.property('x-custom', 'val')
45+
})
4646

47-
it("should strip connection and keep-alive", () => {
48-
const h = { connection: "close", "keep-alive": "t=5", "x-data": "ok" };
49-
const r = stripHttp1ConnectionHeaders(h);
50-
expect(r).to.not.have.property("connection");
51-
expect(r).to.not.have.property("keep-alive");
52-
expect(r).to.have.property("x-data", "ok");
53-
});
47+
it('should strip connection and keep-alive', () => {
48+
const h = { connection: 'close', 'keep-alive': 't=5', 'x-data': 'ok' }
49+
const r = stripHttp1ConnectionHeaders(h)
50+
expect(r).to.not.have.property('connection')
51+
expect(r).to.not.have.property('keep-alive')
52+
expect(r).to.have.property('x-data', 'ok')
53+
})
5454

55-
it("should strip host header from response", () => {
56-
const h = { host: "evil.com", "content-type": "text/plain" };
57-
const r = stripHttp1ConnectionHeaders(h);
58-
expect(r).to.not.have.property("host");
59-
expect(r).to.have.property("content-type", "text/plain");
60-
});
61-
});
55+
it('should strip host header from response', () => {
56+
const h = { host: 'evil.com', 'content-type': 'text/plain' }
57+
const r = stripHttp1ConnectionHeaders(h)
58+
expect(r).to.not.have.property('host')
59+
expect(r).to.have.property('content-type', 'text/plain')
60+
})
61+
})
6262

63-
describe("Security: SSRF end-to-end proxy", () => {
64-
let gateway, service, close, proxy, gHttpServer;
63+
describe('Security: SSRF end-to-end proxy', () => {
64+
let gateway, service, close, proxy, gHttpServer
6565

66-
it("setup", async () => {
67-
const fastProxy = require("../index")({ base: "http://127.0.0.1:3000" });
68-
close = fastProxy.close;
69-
proxy = fastProxy.proxy;
70-
gateway = require("restana")();
71-
gateway.all("/*", (req, res) => proxy(req, res, req.url, {}));
72-
gHttpServer = await gateway.start(8080);
73-
service = require("restana")();
74-
service.get("/service/get", (req, res) => res.send("OK"));
75-
service.get("/service/evil", (req, res) => {
76-
res.setHeader("transfer-encoding", "gzip, chunked");
77-
res.setHeader("keep-alive", "timeout=99");
78-
res.setHeader("x-custom", "downstream");
79-
res.end("evil");
80-
});
81-
await service.start(3000);
82-
});
66+
it('setup', async () => {
67+
const fastProxy = require('../index')({ base: 'http://127.0.0.1:3000' })
68+
close = fastProxy.close
69+
proxy = fastProxy.proxy
70+
gateway = require('restana')()
71+
gateway.all('/*', (req, res) => proxy(req, res, req.url, {}))
72+
gHttpServer = await gateway.start(8080)
73+
service = require('restana')()
74+
service.get('/service/get', (req, res) => res.send('OK'))
75+
service.get('/service/evil', (req, res) => {
76+
res.setHeader('transfer-encoding', 'gzip, chunked')
77+
res.setHeader('keep-alive', 'timeout=99')
78+
res.setHeader('x-custom', 'downstream')
79+
res.end('evil')
80+
})
81+
await service.start(3000)
82+
})
8383

84-
it("should block SSRF via absolute-form request", (done) => {
84+
it('should block SSRF via absolute-form request', (done) => {
8585
// Use raw http.createServer instead of restana because
8686
// restana routes cannot match absolute-form req.url values.
87-
const fastProxy = require("../index")({ base: "http://127.0.0.1:3000" });
88-
const { proxy, close } = fastProxy;
87+
const fastProxy = require('../index')({ base: 'http://127.0.0.1:3000' })
88+
const { proxy, close } = fastProxy
8989
const server = http.createServer((req, res) => {
90-
proxy(req, res, req.url, {});
91-
});
90+
proxy(req, res, req.url, {})
91+
})
9292
server.listen(0, () => {
93-
const port = server.address().port;
94-
const c = net.connect(port, "127.0.0.1", () => {
95-
c.write("GET http://169.254.169.254/latest HTTP/1.1\r\n");
96-
c.write("Host: 127.0.0.1\r\n");
97-
c.write("Connection: close\r\n\r\n");
98-
});
99-
let d = "";
100-
c.on("data", ch => { d += ch.toString(); });
101-
c.on("end", () => {
102-
expect(d).to.include("400");
93+
const port = server.address().port
94+
const c = net.connect(port, '127.0.0.1', () => {
95+
c.write('GET http://169.254.169.254/latest HTTP/1.1\r\n')
96+
c.write('Host: 127.0.0.1\r\n')
97+
c.write('Connection: close\r\n\r\n')
98+
})
99+
let d = ''
100+
c.on('data', ch => { d += ch.toString() })
101+
c.on('end', () => {
102+
expect(d).to.include('400')
103103
// 400 status + normal proxy still functional after SSRF attempt
104-
close();
105-
server.close();
106-
done();
107-
});
108-
c.on("error", done);
109-
});
110-
});it("should strip hop-by-hop headers end-to-end", async () => {
111-
const res = await require("supertest")(gHttpServer)
112-
.get("/service/evil")
113-
.expect(200);
114-
expect(res.headers["transfer-encoding"]).to.not.equal("gzip, chunked");
115-
expect(res.headers["keep-alive"]).to.not.equal("timeout=99");
116-
expect(res.headers["x-custom"]).to.equal("downstream");
117-
});
104+
close()
105+
server.close()
106+
done()
107+
})
108+
c.on('error', done)
109+
})
110+
}); it('should strip hop-by-hop headers end-to-end', async () => {
111+
const res = await require('supertest')(gHttpServer)
112+
.get('/service/evil')
113+
.expect(200)
114+
expect(res.headers['transfer-encoding']).to.not.equal('gzip, chunked')
115+
expect(res.headers['keep-alive']).to.not.equal('timeout=99')
116+
expect(res.headers['x-custom']).to.equal('downstream')
117+
})
118118

119-
it("teardown", async () => {
120-
close();
121-
await gateway.close();
122-
await service.close();
123-
});
124-
});
119+
it('teardown', async () => {
120+
close()
121+
await gateway.close()
122+
await service.close()
123+
})
124+
})

0 commit comments

Comments
 (0)