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:
1 parent
e28cc8e0f3
commit
fe7b61c3c1
11 files changed
+300
-81
No files matched your search
@@ -74,4 +74,39 @@ describe('FailedAttemptLimiter', () => {
|
||||
small.recordFailure('c');
|
||||
expect(small.trackedSources()).toBe(2);
|
||||
});
|
||||
it('verdrängt bei voller Liste zuerst nicht gesperrte Quellen', () => {
|
||||
const small = new FailedAttemptLimiter({
|
||||
maxFailures: 2,
|
||||
windowMs: 60_000,
|
||||
maxTrackedSources: 2,
|
||||
clock: () => now,
|
||||
});
|
||||
small.recordFailure('angreifer');
|
||||
small.recordFailure('angreifer'); // gesperrt, ältester Eintrag
|
||||
small.recordFailure('harmlos'); // ein Fehlversuch, nicht gesperrt
|
||||
small.recordFailure('neu');
|
||||
|
||||
expect(small.trackedSources()).toBe(2);
|
||||
// Die Sperre bleibt, obwohl sie der älteste Eintrag ist.
|
||||
expect(small.isBlocked('angreifer')).toBe(true);
|
||||
// Verdrängt wurde der nicht gesperrte Eintrag.
|
||||
expect(small.recordFailure('harmlos')).toBe(false);
|
||||
});
|
||||
|
||||
it('verdrängt gesperrte Quellen erst, wenn alle gesperrt sind (älteste zuerst)', () => {
|
||||
const small = new FailedAttemptLimiter({
|
||||
maxFailures: 1,
|
||||
windowMs: 60_000,
|
||||
maxTrackedSources: 2,
|
||||
clock: () => now,
|
||||
});
|
||||
small.recordFailure('a');
|
||||
small.recordFailure('b');
|
||||
small.recordFailure('c');
|
||||
|
||||
expect(small.trackedSources()).toBe(2);
|
||||
expect(small.isBlocked('a')).toBe(false);
|
||||
expect(small.isBlocked('b')).toBe(true);
|
||||
expect(small.isBlocked('c')).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user