Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/node/hooks/express/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,8 +868,9 @@ const getApiRootForVersion = (version:string, style:any = APIPathStyle.FLAT): st
const generateServerForApiVersion = (apiRoot:string, req:any): {
url:string
} => ({
url: `${settings.ssl ? 'https' : 'http'}://${req.headers.host}${apiRoot}`,
url: `${req.protocol}://${req.headers.host}${apiRoot}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Proxy-spoofed openapi scheme 🐞 Bug ⛨ Security

generateServerForApiVersion() now uses req.protocol to select the scheme, which (when
settings.trustProxy is enabled) can be influenced by X-Forwarded-Proto and cause the generated
OpenAPI servers[0].url to advertise the wrong scheme under misconfigured/over-trusting proxy
setups. Prefer settings.publicURL when configured (operator-trusted), and otherwise harden the
fallback by whitelisting http/https and using Express’s host accessor (and optionally host
validation) instead of raw headers.
Agent Prompt
### Issue description
`generateServerForApiVersion()` now builds the OpenAPI server URL using `req.protocol`, which becomes proxy-header-derived when `trustProxy` is enabled. This can cause the served OpenAPI document to advertise an incorrect scheme if proxy headers are missing/spoofed or the instance is reachable without a trusted proxy in front.

### Issue Context
The codebase already has an operator-trusted `settings.publicURL` intended to avoid client-controlled origin values, and `socialMeta.ts` demonstrates a hardened approach (prefer `publicURL`, otherwise validate host/proto).

### Fix Focus Areas
- src/node/hooks/express/openapi.ts[868-872]
- src/node/utils/Settings.ts[411-421]
- src/node/utils/socialMeta.ts[139-155]
- src/node/hooks/express.ts[157-165]

### Suggested fix
1) If `settings.publicURL` is set and valid, use it as the origin for `servers[0].url` (append `apiRoot`).
2) Else, keep the request-derived fallback but harden it:
   - Allow only `http` or `https` (fallback to `'http'` if unexpected).
   - Use `req.get('host')` instead of `req.headers.host` for consistency with other code paths.
   - (Optional but safer) validate/sanitize the host similarly to `socialMeta.ts`’s `sanitizeHost()` before emitting it into the OpenAPI document.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

});

exports.generateDefinitionForVersion = generateDefinitionForVersion;
exports.APIPathStyle = APIPathStyle;
exports.generateServerForApiVersion = generateServerForApiVersion;
25 changes: 25 additions & 0 deletions src/tests/backend/specs/openapi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const assert = require('assert').strict;

describe('openapi server URL generation', function () {
let generateServerForApiVersion: (apiRoot: string, req: any) => {url: string};

before(function () {
({generateServerForApiVersion} = require('../../../node/hooks/express/openapi'));
});

const mockReq = (protocol: string, host: string) => ({protocol, headers: {host}});

it('emits http:// for a plain HTTP request', function () {
assert.deepEqual(
generateServerForApiVersion('/api/1.2.15', mockReq('http', 'pad.example.com')),
{url: 'http://pad.example.com/api/1.2.15'});
});

it('emits https:// when the request protocol is https (TLS or reverse proxy)', function () {
assert.deepEqual(
generateServerForApiVersion('/api/1.2.15', mockReq('https', 'pad.example.com')),
{url: 'https://pad.example.com/api/1.2.15'});
});
});
Loading