fix(api-keys): gültige Schlüssel nie durch die Fehlversuchs-Sperre abweisen

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]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-12 12:00:03 +02:00
1 parent e28cc8e0f3
commit fe7b61c3c1
11 files changed
+300 -81

No files matched your search

+53
View File
@@ -65,3 +65,56 @@ describe('configureApp trust proxy logging', () => {
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);
});
});