Befunde aus dem Sicherheitsreview des Branches: - MEDIUM: Die Sperre lief vor der Schlüsselsuche und zählte auch falsch aufgebaute Tokens. Wer die gemeinsame Adresse (Cluster-Ausgang, Proxy) nutzt oder per X-Forwarded-For vortäuscht, hätte ERPNext und den Shop 15 Minuten aussperren können. Jetzt: erst Suche über den Hash; ein gültiger, nicht widerrufener, nicht abgelaufener Schlüssel kommt immer durch; 429 nur für unbekannte, widerrufene und abgelaufene Schlüssel; falsch aufgebaute Tokens sofort 401 ohne Zählung. Beim Start warnt ApiCore, wenn TRUST_PROXY in Produktion fehlt (im Cluster gesetzt). - LOW: Bei vollem Speicher verdrängt der Limiter zuerst nicht gesperrte Einträge, gesperrte erst zuletzt. - LOW: Bewegungs-Cursor mit Jahr außerhalb 2000-2100 ergab 500, jetzt 400. - LOW/INFO: Vertrag legt fest, dass ERPNext Produkte und Chargen bei jedem Lauf vollständig neu liest (Voll-Spiegel) und Bewegungen inkrementell; remainingQuantity ist live, Bewegungen kommen 5 Minuten verzögert. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
121 lines
3.8 KiB
TypeScript
121 lines
3.8 KiB
TypeScript
import { Logger } from '@nestjs/common';
|
|
import type { NestExpressApplication } from '@nestjs/platform-express';
|
|
import {
|
|
configureApp,
|
|
describeTrustProxy,
|
|
trustProxySetting,
|
|
} from './app.config';
|
|
|
|
describe('trustProxySetting', () => {
|
|
const original = process.env.TRUST_PROXY;
|
|
afterEach(() => {
|
|
if (original === undefined) delete process.env.TRUST_PROXY;
|
|
else process.env.TRUST_PROXY = original;
|
|
});
|
|
|
|
it('defaults to one hop when unset or blank', () => {
|
|
delete process.env.TRUST_PROXY;
|
|
expect(trustProxySetting()).toBe(1);
|
|
process.env.TRUST_PROXY = ' ';
|
|
expect(trustProxySetting()).toBe(1);
|
|
});
|
|
|
|
it('parses a number as hop count and anything else as a list', () => {
|
|
process.env.TRUST_PROXY = '2';
|
|
expect(trustProxySetting()).toBe(2);
|
|
process.env.TRUST_PROXY = 'loopback, 10.42.0.0/16';
|
|
expect(trustProxySetting()).toBe('loopback, 10.42.0.0/16');
|
|
});
|
|
});
|
|
|
|
describe('describeTrustProxy', () => {
|
|
it('reports the hop count mode and the default fallback', () => {
|
|
expect(describeTrustProxy(1, false)).toBe(
|
|
'TRUST_PROXY not set, using default of 1 trusted hop',
|
|
);
|
|
expect(describeTrustProxy(2, true)).toBe('TRUST_PROXY: 2 trusted hop(s)');
|
|
});
|
|
|
|
it('reports the list mode with entry count but not the entries', () => {
|
|
const text = describeTrustProxy(
|
|
'loopback, 10.42.0.0/16, 2400:cb00::/32',
|
|
true,
|
|
);
|
|
expect(text).toBe('TRUST_PROXY: list of 3 trusted proxy range(s)');
|
|
});
|
|
});
|
|
|
|
describe('configureApp trust proxy logging', () => {
|
|
it('logs the active mode once at startup', () => {
|
|
const log = jest.spyOn(Logger.prototype, 'log').mockImplementation();
|
|
const app = {
|
|
set: jest.fn(),
|
|
useBodyParser: jest.fn(),
|
|
use: jest.fn(),
|
|
useGlobalFilters: jest.fn(),
|
|
enableCors: jest.fn(),
|
|
setGlobalPrefix: jest.fn(),
|
|
enableVersioning: jest.fn(),
|
|
useGlobalPipes: jest.fn(),
|
|
};
|
|
configureApp(app as unknown as NestExpressApplication);
|
|
expect(
|
|
log.mock.calls.filter(([m]) => String(m).startsWith('TRUST_PROXY')),
|
|
).toHaveLength(1);
|
|
log.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('configureApp trust proxy warning', () => {
|
|
const originalProxy = process.env.TRUST_PROXY;
|
|
const originalEnv = process.env.NODE_ENV;
|
|
afterEach(() => {
|
|
if (originalProxy === undefined) delete process.env.TRUST_PROXY;
|
|
else process.env.TRUST_PROXY = originalProxy;
|
|
if (originalEnv === undefined) delete process.env.NODE_ENV;
|
|
else process.env.NODE_ENV = originalEnv;
|
|
});
|
|
|
|
function configure(): jest.SpyInstance {
|
|
const warn = jest.spyOn(Logger.prototype, 'warn').mockImplementation();
|
|
jest.spyOn(Logger.prototype, 'log').mockImplementation();
|
|
const app = {
|
|
set: jest.fn(),
|
|
useBodyParser: jest.fn(),
|
|
use: jest.fn(),
|
|
useGlobalFilters: jest.fn(),
|
|
enableCors: jest.fn(),
|
|
setGlobalPrefix: jest.fn(),
|
|
enableVersioning: jest.fn(),
|
|
useGlobalPipes: jest.fn(),
|
|
};
|
|
configureApp(app as unknown as NestExpressApplication);
|
|
return warn;
|
|
}
|
|
|
|
function trustProxyWarnings(warn: jest.SpyInstance): unknown[][] {
|
|
const calls = warn.mock.calls as unknown[][];
|
|
return calls.filter(([m]) => String(m).includes('TRUST_PROXY'));
|
|
}
|
|
|
|
afterEach(() => jest.restoreAllMocks());
|
|
|
|
it('warnt in Produktion, wenn TRUST_PROXY fehlt', () => {
|
|
process.env.NODE_ENV = 'production';
|
|
delete process.env.TRUST_PROXY;
|
|
expect(trustProxyWarnings(configure())).toHaveLength(1);
|
|
});
|
|
|
|
it('warnt nicht, wenn TRUST_PROXY gesetzt ist', () => {
|
|
process.env.NODE_ENV = 'production';
|
|
process.env.TRUST_PROXY = 'loopback, 10.42.0.0/16';
|
|
expect(trustProxyWarnings(configure())).toHaveLength(0);
|
|
});
|
|
|
|
it('warnt nicht in der Entwicklung', () => {
|
|
process.env.NODE_ENV = 'development';
|
|
delete process.env.TRUST_PROXY;
|
|
expect(trustProxyWarnings(configure())).toHaveLength(0);
|
|
});
|
|
});
|