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
@@ -69,13 +69,26 @@ export class FailedAttemptLimiter {
|
||||
this.windows.delete(source);
|
||||
}
|
||||
}
|
||||
// Immer noch voll: den ältesten Eintrag verdrängen (Map behält die
|
||||
// Einfügereihenfolge). Lieber eine Zählung verlieren als unbegrenzt wachsen.
|
||||
// Immer noch voll: lieber eine Zählung verlieren als unbegrenzt wachsen.
|
||||
// Zuerst den ältesten nicht gesperrten Eintrag verdrängen - sonst könnte
|
||||
// ein Angreifer mit vielen Adressen seine eigene Sperre hinausschieben.
|
||||
// Nur wenn alle gesperrt sind, geht der älteste gesperrte (die Map behält
|
||||
// die Einfügereihenfolge).
|
||||
if (this.windows.size >= this.options.maxTrackedSources) {
|
||||
for (const oldest of this.windows.keys()) {
|
||||
this.windows.delete(oldest);
|
||||
break;
|
||||
}
|
||||
const victim =
|
||||
this.oldestWhere(
|
||||
(window) => window.failures < this.options.maxFailures,
|
||||
) ?? this.oldestWhere(() => true);
|
||||
if (victim !== undefined) this.windows.delete(victim);
|
||||
}
|
||||
}
|
||||
|
||||
private oldestWhere(
|
||||
matches: (window: Window) => boolean,
|
||||
): string | undefined {
|
||||
for (const [source, window] of this.windows) {
|
||||
if (matches(window)) return source;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user