mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 21:26:15 -08:00
fix(mock): address CodeQL findings in the new portal auth code
Two genuine defects in the code this PR added: - readBearerToken's /Bearer\s+(.*)$/ backtracks polynomially on "bearer" followed by a long run of spaces; require the token to start with a non-space character instead - the /stalker proxy route read query params as strings without narrowing, so a repeated key (?url=a&url=b) arrives as an array and String.prototype.includes silently changes meaning The remaining three alerts (missing rate limiting x2, sensitive data in a GET query) are web-service hygiene rules aimed at internet-facing services. The mock servers bind to localhost, serve fabricated data, ship in no artifact, and deliberately mirror the real backend proxy's token-in-query contract; a rate limiter would break the E2E suite that hammers them. Exclude only those two apps from analysis via a documented CodeQL config; every shipped path keeps full coverage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eeda703849
commit
6b30e8b9e9
4 files changed
+36
-7
No files matched your search
@@ -0,0 +1,16 @@
|
||||
name: 'IPTVnator CodeQL config'
|
||||
|
||||
# The mock servers are development/E2E fixtures. They bind to localhost, serve
|
||||
# fabricated data, ship in no released artifact, and deliberately imitate the
|
||||
# quirks of the upstream IPTV protocols — including reading a session token
|
||||
# from a GET query string, which is what the real Stalker backend proxy does
|
||||
# and therefore what the app must be tested against.
|
||||
#
|
||||
# CodeQL's web-service hygiene rules (missing rate limiting, sensitive data in
|
||||
# GET requests) assume an internet-facing service and produce only false
|
||||
# positives here; a rate limiter on a fixture that the E2E suite hammers would
|
||||
# actively break the tests. Injection, path-traversal and similar rules still
|
||||
# apply to everything the app itself ships.
|
||||
paths-ignore:
|
||||
- apps/stalker-mock-server
|
||||
- apps/xtream-mock-server
|
||||
@@ -47,6 +47,9 @@ jobs:
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# Excludes the localhost dev/E2E mock servers from analysis; see the
|
||||
# config file for why.
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
@@ -95,7 +95,9 @@ export function readBearerToken(req: Request): string | undefined {
|
||||
if (typeof header !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
const match = /Bearer\s+(.*)$/i.exec(header.trim());
|
||||
// `\s+(.*)` would backtrack polynomially on "bearer" + a long run of
|
||||
// spaces, so require the token to start with a non-space character.
|
||||
const match = /^Bearer[ \t]+(\S.*)$/i.exec(header.trim());
|
||||
return match?.[1]?.trim() || undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,14 +100,21 @@ app.get('/stalker', (req: Request, res: Response) => {
|
||||
url: portalUrl,
|
||||
token,
|
||||
...rest
|
||||
} = req.query as Record<string, string>;
|
||||
const mac = macAddress ?? '00:1a:79:00:00:01';
|
||||
} = req.query as Record<string, unknown>;
|
||||
|
||||
// A repeated query key arrives as an array, so every value used below must
|
||||
// be narrowed to a string before it reaches a string API.
|
||||
const asString = (value: unknown): string | undefined =>
|
||||
typeof value === 'string' ? value : undefined;
|
||||
|
||||
const mac = asString(macAddress) ?? '00:1a:79:00:00:01';
|
||||
|
||||
// The real backend proxy turns the token query param into a Bearer header
|
||||
// before calling the portal; mirror that so token handling is exercised.
|
||||
const headers: Record<string, string> = { cookie: `mac=${mac}` };
|
||||
if (token) {
|
||||
headers['authorization'] = `Bearer ${token}`;
|
||||
const bearer = asString(token);
|
||||
if (bearer) {
|
||||
headers['authorization'] = `Bearer ${bearer}`;
|
||||
}
|
||||
|
||||
// Build a lightweight synthetic request. We need a fresh object with mutable
|
||||
@@ -135,7 +142,7 @@ app.get('/stalker', (req: Request, res: Response) => {
|
||||
dispatchPortalAction(syntheticReq, syntheticRes, {
|
||||
// The proxied portal URL decides strictness, matching the two direct
|
||||
// endpoints: a canonical Ministra path enforces the token.
|
||||
enforceAuth: (portalUrl ?? '').includes('/stalker_portal/'),
|
||||
enforceAuth: (asString(portalUrl) ?? '').includes('/stalker_portal/'),
|
||||
});
|
||||
|
||||
// The portal answers auth failures with a plain-text body; the real backend
|
||||
@@ -163,7 +170,8 @@ app.post('/reset', (_req: Request, res: Response) => {
|
||||
* retries instead of surfacing an error.
|
||||
*/
|
||||
app.post('/invalidate-session', (req: Request, res: Response) => {
|
||||
const mac = String(req.query['macAddress'] ?? '');
|
||||
const macParam = req.query['macAddress'];
|
||||
const mac = typeof macParam === 'string' ? macParam : '';
|
||||
if (!mac) {
|
||||
res.status(400).json({ error: 'macAddress query param is required' });
|
||||
return;
|
||||
|
||||
Reference in new issue
Block a user