Back to Blog

HKDAP Stablecoin-Sicherheitsprüfung: Live, lizenziert, noch nicht bereit

Phalcon Security
August 14, 2026
25 min read
Key Insights
  • HKDAPs KYC-Widerruf ist toter Code und sein KYC-Nachweis wird nie on-chain validiert

  • Ein einziger Schlüssel kann prägen, verbrennen, pausieren oder einfrieren; zwei Schlüssel kontrollieren alle Upgrades und Rollen

  • Mehrere On-Chain-Eigenschaften weichen von den HKMA-Richtlinien für Stablecoin-Emittenten ab

A BlockSec Sicherheits- und Compliance-Überprüfung von HKDAP (Anchorpoint), Stand 13. August 2026.

TL;DR. Wir haben den deployed Vertrag von HKDAP überprüft, der ersten regulierten Stablecoin, die in Hongkong ausgegeben wurde und live im Ethereum-Mainnet ist — und er ist nicht produktionsreif. Seine KYC- und Widerrufskontrollen funktionieren nicht wie vorgesehen, seine Governance ist so stark konzentriert, dass ein einzelner Schlüssel minten, brennen oder einfrieren kann, und mehrere seiner On-Chain-Eigenschaften stehen im Widerspruch zur eigenen Leitlinie der HKMA. Dem Ganzen liegt ein gemeinsamer roter Faden zugrunde: Der Vertrag erfindet die Primitive, die das Ökosystem bereits bereitstellt und in großem Maßstab auditiert, von Grund auf neu (ein Multisig, eine Zugriffskontrollschicht, ein Timelock, der ERC-20 selbst), und die meisten Defekte stecken in dieser eigenen Mechanik, nicht in den Teilen, die Standardkomponenten wiederverwenden. Das Label „Beta Access“ schließt diese Lücke nicht.

Am 12. August 2026 startete Anchorpoint die erste Phase von HKDAP, eine Hongkong-Dollar-Stablecoin. Es ist ein bedeutender Start. Anchorpoint, ein Joint Venture unter der Führung der Standard Chartered Bank (Hongkong) mit HKT und Animoca Brands, hält eine von nur zwei Stablecoin-Emittentenlizenzen, die die HKMA vergeben hat, von 36 Bewerbern, und HKDAP gehört zu den ersten Stablecoins, die unter Hongkongs Stablecoins Ordinance ausgegeben wurden.

Anders als die meisten Produkte einer regulierten Bank ist HKDAP direkt prüfbar. Es läuft im Ethereum-Mainnet, und der Quellcode des Vertrags ist auf Etherscan verifiziert. Das ist eine nützliche Eigenschaft: Bei einer so ausgegebenen Stablecoin sind die Regeln für Ausgabe, Übertragung und Einfrieren nicht in einem Dokument beschrieben, sondern in Code implementiert, den jeder lesen kann und der exakt so ausgeführt wird, wie er geschrieben ist. Eine Lizenz ist eine Behauptung; der deployed Vertrag ist die Umsetzung dieser Behauptung, und er ist öffentlich.

Das macht eine konkrete Überprüfung möglich, und genau das haben wir getan. Wir haben den deployed Vertrag entlang zweier Achsen untersucht. Erstens als Software: Ist er korrekt und produktionsreif? Zweitens als regulierte Stablecoin: Entspricht sein On-Chain-Verhalten der Leitlinie zur Beaufsichtigung lizenzierter Stablecoin-Emittenten der HKMA?

Die Ergebnisse sind entlang beider Achsen konsistent. Der Vertrag enthält mehrere funktionale Defekte, darunter Compliance-Kontrollen, die nicht wie vorgesehen funktionieren. Seine Governance ist stark konzentriert; mehrere Hochrisiko-Operationen können mit einem einzigen Schlüssel ausgeführt werden. Und eine Reihe seiner On-Chain-Eigenschaften steht im Widerspruch zu konkreten Klauseln der HKMA-Leitlinie. Unsere Einschätzung ist, dass der Vertrag selbst als Beta nicht die Qualitätslatte erfüllt, die eine kommerzielle Stablecoin verlangt.

Der Rest dieses Artikels präsentiert diese Überprüfung, Stand 13. August 2026. Unsere Überprüfung basiert auf öffentlich bereitgestelltem Code und beobachtbaren On-Chain-Fakten. Wir treffen keine Aussagen über Dinge, die die Blockchain nicht belegen kann, etwa Reservehinterlegung oder Off-Chain-Schlüsselverwahrung.

Wie wir den Vertrag gefunden haben

Wir sind vom Emittenten ausgegangen, nicht von einer Token-Liste. Anchorpoints Unternehmenspräsenz verlinkt auf seine Website unter anchorpoint.hk. Die Beta-Access-Seite nennt das Deployment: Ethereum-Mainnet, Proxy 0x87622385F960fcCB3121d6D0A9513bd1D9Bed6cA, dessen Quellcode auf Etherscan verifiziert ist.

Von dort aus haben wir das gesamte System on-chain kartiert: den Token-Proxy und seine Implementierung (ControllableAHKD, 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc), den Governance-Vertrag, der ihn verwaltet (0x47dd47a776902bee03abdd5aeff43f41b0ffbc2b), das Top-Level-Rollenregister (0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c) und fünf Compliance-Module, jedes mit einem eigenen Governance-Vertrag. Jede der folgenden Beziehungen wurde durch Lesen von Storage-Slots und Aufrufen von View-Funktionen im Mainnet verifiziert, nicht nur aus dem Quellcode abgeleitet.

Wie der Vertrag aufgebaut ist

Die drei Schichten von HKDAP: Eine Governance-Steuerungsebene verwaltet den Token, und der Token fragt bei jeder Übertragung fünf Compliance-Module ab
Die drei Schichten von HKDAP: Eine Governance-Steuerungsebene verwaltet den Token, und der Token fragt bei jeder Übertragung fünf Compliance-Module ab

Der Token von HKDAP verwendet nicht OpenZeppelins Standard-ERC-20-Referenzimplementierung; seine ERC-20-Logik ist von Grund auf neu geschrieben (hier entstehen einige der unten genannten Bugs). Das System hat drei Schichten:

  • Der Token. ControllableAHKD, hinter einem upgradebaren Proxy. Ein kontrollierter ERC-20 mit Mint, Burn, Pause und erzwungenem Destroy sowie Compliance-Prüfungen, die in jede Übertragung eingebunden sind.
  • Eine eigen entwickelte M-of-N-Governance-Engine. Jede privilegierte Aktion (Upgrade, Mint, Burn, Pause, Blacklist, Freeze, Änderung eines Compliance-Moduls) durchläuft eine Request-Approve-Execute-Zeremonie statt eines einfachen Multisigs.
  • Fünf Compliance-Module. Blacklist, Freezing, ein KYC-Aktivierungsdienst sowie Einzahlungs- und Einlösungs-Whitelists. Jedes Modul ist selbst ein Proxy, der von einem eigenen Control-Authority-Vertrag regiert wird.

Strukturell wiederholt sich dieselbe Einheit aus „Control Authority plus Proxy plus Implementierung“ sechsmal (der Token plus fünf Module), und alle sechs Control Authorities lösen ihre Rollen über ein einziges Register auf. Die gesamte Kontrolle im System läuft letztlich auf dieses eine Register zusammen, und wer die Rollen darin neu schreiben kann, läuft auf eine sehr kleine Gruppe von Signaturschlüsseln hinaus, wie wir unten im Detail zeigen.

Teil 1: Sicherheit und Bugs

Die Ergebnisse dieses Teils sind unten zusammengefasst; jede Zeile wird im angegebenen Abschnitt ausführlich behandelt.

Bereich Befund Wo Auswirkung
1.1 KYC- und Widerrufskontrollen versagen KYC-Widerruf ist toter Code TokenHolderActivationServerLibrary.sol:221-235 isActive ignoriert die Abmeldung von Anbietern (die Schleife läuft nie und verwendet == statt =); Fail-Open.
Verifier-Abmeldung setzt nie INACTIVE TokenHolderActivationServer.sol:504-511 Ein „abgemeldeter“ Verifier kann weiterhin Wallets anlegen und deaktivieren; ein zweiter Aufruf reverts.
KYC-Nachweis wird nie on-chain validiert TokenHolderActivationServer.sol:575-578 Der Nachweis wird übergeben, aber verworfen; jeder Nachweis, einschließlich eines leeren Strings, wird akzeptiert.
Die Ausnahme für freie Transfers kann durch Aufteilen umgangen werden ControllableAHKD.sol:337-535 freeTransferLimit wird pro Aufruf geprüft, nicht kumulativ; wenn es als Obergrenze für Nicht-KYC-Aktivität dient, kann es durch Aufteilen in Transfers unterhalb des Limits umgangen werden.
1.2 Governance ist überkonzentriert Hochrisiko-Operationen sind Einzelsignaturen authorizationMatrix; Mint-Tx 0xa7e53c…b33d7 Mint / Burn / Freeze / KYC-Deaktivierung (Rolle C), Pause / Destroy (Rolle D), Blacklist / Unfreeze (Rolle F) werden jeweils mit einem Schlüssel ausgeführt.
Ein Schlüsselpaar (A + B) aktualisiert alles authorizationMatrix upgradeTo für den Token und alle fünf Module erfordert Rolle A + B; zwei Personen können jede Implementierung ersetzen.
Dasselbe Paar schreibt alle Rollen neu Register 0xa728… authorizationMatrix grantRole / revokeRole im Register sind ebenfalls Rolle A + B (ADMIN_ROLE wird nur vom Vertrag gehalten; DEFAULT_ADMIN_ROLE = address(0)); kein externer Schlüssel kann Rollen direkt ändern.
Eine Adresse hält sechs Rollen Rollenregister 0xa728… 0x2f7f00… hält Rolle C sowie ADMIN_TOKEN_HOLDER und alle vier Auditor-Rollen; Ausführung und Prüfung überschneiden sich.
Widerruf ist nicht sofort; kein Timelock HybridControlEngine.sol:158-227 Eine gezählte Signatur wird nach einem Rollenentzug nicht erneut validiert; die Ausführung ist atomar mit der letzten Signatur, ohne Verzögerung.
Der Audit-Trail der Engine ist unzuverlässig HybridControlEngine.sol:36-129, 177-196 evtApprove emittiert address(0) als Unterzeichner; die Liste aktiver Requests verwendet Nonce 0 sowohl als echte ID als auch als Leermarker, sodass die Rückwärtsdurchquerung den ersten Request übersieht.
1.3 Inkonsistente Transferprüfungen transfer und transferFrom erzwingen unterschiedliche Regeln ControllableAHKD.sol:313-502 Der Whitelist-Frühreturn umgeht KYC nur über transferFrom; checkingMode prüft verschiedene Parteien; derselbe Transfer wird unterschiedlich geregelt.
1.4 Anzeichen eines Vorproduktions-Builds Debug-Logs, falscher Kommentar, Name/Hash-Mismatch, Tests hochgeladen UpgradeableProxy.sol:115-119; ControllableAHKD.sol:25 console.log im Proxy-Fallback (permanentes Gas); Proxy-Kommentar widerspricht dem Code; Rollenname/Hash-Mismatch; 117 Dateien inkl. Tests hochgeladen; Optimizer-Runs = 0.
1.5 Das einzige Upgrade hinterließ die Defekte Rückverfolgung des einzelnen Upgrades tx 0x742372…85136, 0xa7a400…630c Deploy 28. Apr → Upgrade 10. Jul; die zwei Transaktionen der A + B-Zeremonie lagen einen Block auseinander (~12s); jeder Defekt aus Teil 1 steckt in der installierten Implementierung.

1.1 Die KYC- und Widerrufskontrollen funktionieren nicht wie vorgesehen

Nach der Regulierung müssen die B-seitigen (institutionellen) Nutzer, die HKDAP bedient, KYC durchlaufen. Im Vertrag bedeutet das, er muss mindestens zwei Dinge tun: KYC bei Übertragungen durchsetzen, sodass ein Wallet, das KYC nicht bestanden hat, blockiert wird, und den Zugriff entziehen, wenn ein Identitätsanbieter oder ein Halter entfernt wird. Bei HKDAP ist dieser Pfad an drei unabhängigen Stellen kaputt.

KYC-Widerruf ist toter Code. Der Token regelt Transfers, indem er isActive(address) auf dem KYC-Modul aufruft. isActive soll zwei Dinge tun: erstens einen Basiswert aus einem Pro-Wallet-Zähler setzen, aktiv, wenn dieses Wallet öfter KYC-aktiviert als deaktiviert wurde; zweitens diesen Wert auf false einschränken, wenn ein Identitätsanbieter, der für das Wallet gebürgt hat, inzwischen abgemeldet wurde. Die beiden decken zwei Arten des Widerrufs ab: den Widerruf des KYC eines einzelnen Halters (der Zähler) und den Widerruf eines ganzen Anbieters, sodass jedes von ihm angebundene Wallet mitfällt (die Schleife). Nur Ersteres passiert.

// contracts/hce/erc20/libs/TokenHolderActivationServerLibrary.sol
221    active = ( tokenHolderRegistration[_address].deactivationCount < tokenHolderRegistration[_address].activationCount ) ;
223    uint256 entryCount = 0 ;
224    uint256 index = chainedItemList.firstEntry ;        // 0 if such ChainedList is empty
226    while ( entryCount > chainedItemList.entryCount && active ) {
227        ChainedListLibrary.ChainedItem storage chainedItem = identityProvidersByStatuss[index] ; // deregistered provider
230        // NDLR: .... not too sure about that one to be frank
231        active == ( tokenHolderKYCProofDirectoryByProvider[_address][identityProviders[chainedItem.objectId].identifier] == 0 ) ;
233        entryCount++ ;
234        index = chainedItem.pointNext ;
235    }

Zeile 221 ist eine echte Zuweisung (=), und sie ist die einzige, die wirkt: active wird zu deactivationCount < activationCount. Die Schleife (Zeilen 226-235) soll diesen Wert einschränken, scheitert aber zweimal. Ihre Bedingung in Zeile 226, entryCount > chainedItemList.entryCount, ist 0 > N, der Schleifenkörper wird also nie ausgeführt; und selbst wenn er liefe, verwendet Zeile 231 ==, einen Vergleich, dessen Ergebnis verworfen wird, wo eigentlich mit = zugewiesen werden müsste. isActive gibt daher nur den Zählervergleich zurück und ignoriert den Anbieter-Widerruf vollständig. Das ist Fail-Open: Das Abmelden eines kompromittierten KYC-Anbieters stoppt die von ihm angebundenen Wallets nicht daran, Transaktionen durchzuführen. Und weil unregisterVerifier diese Zähler nicht anfasst, behalten die Wallets eines widerrufenen Anbieters einen positiven Zählerstand und bleiben aktiv.

Auch die Anbieter-Abmeldung ist auf der anderen Seite kaputt. unregisterVerifier verschiebt nur einen Knoten der verknüpften Liste; es setzt den Verzeichnisstatus des Anbieters nie auf INACTIVE:

// contracts/hce/erc20/modules/TokenHolderActivationServer.sol
504    function _unregisterVerifier(string calldata _verifierId) internal {
505        _removeToList(_verifierId, ACTIVE_IDENTIY_PROVIDER, "60a");
507        uint256 _ipIdx       = identityProviders.length - 1;
508        uint256 _newIdx      = identityProvidersByStatuss.length + 1;
510        _addToList(_ipIdx, _newIdx, INACTIVE_IDENTIY_PROVIDER, _verifierId, "60b");
511    }

Da der Status ACTIVE bleibt, kann ein „abgemeldeter“ Verifier weiterhin Halter registrieren und deaktivieren, der Zweig, der einen Anbieter reaktivieren soll, ist unerreichbar, und ein zweiter unregisterVerifier-Aufruf verursacht einen Underflow und reverts.

Der KYC-Nachweis wird nie on-chain validiert. Wenn ein autorisierter Verifier ein Wallet über registerOrRenew registriert oder erneuert (so eingeschränkt, dass nur ein aktuell aktiver Verifier ihn aufrufen kann), erreicht der Ablauf _checkKYCProof, das den eingereichten Nachweis gegen das Schema des Anbieters validieren soll. Laut Interface ist dieser Nachweis „eine URI für ein Orakel oder ein signierter Hash aus einer verifizierbaren Quelle“. Die Funktion ignoriert ihn:

// contracts/hce/erc20/modules/TokenHolderActivationServer.sol
575    function _checkKYCProof( string memory _ipIdentifier, string memory, string memory _rcCode ) internal view returns ( bool isVerified ) {
576        require( identityProviderDirectory[_ipIdentifier].status == ACTIVE_IDENTIY_PROVIDER, string.concat(ERROR_404, _rcCode, "41b")) ;
577        return true ;
578    }

Der Nachweis erreicht diese Funktion tatsächlich. registerOrRenew trägt den eingereichten kycProof durch _registerOrRenew nach unten und übergibt ihn als zweites Argument. Aber in Zeile 575 landet dieses Argument in einem Parameter ganz ohne Namen, den auch der Dokumentationskommentar über der Funktion weglässt, und der Funktionskörper liest ihn nie. Die Funktion bestätigt nur, dass der Anbieter aktiv ist, und gibt in Zeile 577 true zurück; der Nachweis wird also verworfen statt geprüft. Das ist kein Umgehen von außen, da nur ein aktiver Verifier ihn erreichen kann, aber on-chain führt der Vertrag keine Validierung des KYC-Nachweises durch, sodass seine Integrität vollständig vom Off-Chain-Verifier abhängt. Ein kompromittierter oder nachlässiger Verifier kann jedes Wallet mit jedem Nachweis aktivieren, auch mit einem leeren String.

Zusammen bedeuten diese drei Punkte, dass eine zentrale Compliance-Eigenschaft, die Möglichkeit, Zugriff zu gewähren und zu entziehen, nicht wie vorgesehen funktioniert.

Über diese drei Punkte hinaus gibt es eine damit zusammenhängende Schwäche: Die Ausnahme für freie Transfers kann durch Aufteilen umgangen werden. Der Token hat ein freeTransferLimit, und eine Übertragung darunter überspringt die isActive-(KYC-)Prüfung. Aber das Limit wird nur gegen den Betrag der aktuellen Übertragung verglichen; der Vertrag führt keine kumulative Summe pro Adresse oder Zeitraum. Wenn das Limit also als Obergrenze für Nicht-KYC-Aktivität dient, kann ein Halter einen beliebigen Gesamtbetrag bewegen, indem er ihn in wiederholte Transfers knapp unter dem Limit aufteilt, was die Obergrenze wirkungslos macht.

1.2 Governance ist überkonzentriert, und Hochrisiko-Operationen sind Einzelsignaturen

Wir haben jede Rolle und jeden Rolleninhaber on-chain erfasst. Zwei Dinge fallen vor den Details auf.

Erstens haben die Rollen, die die M-of-N-Zeremonien autorisieren, keine lesbaren Namen. In der deployed Konfiguration erscheinen sie nur als 32-Byte-Hashes, und keine von ihnen entspricht einer benannten Rollenkonstante im verifizierten Quellcode (die benannten Rollen wie SUPPLY_CONTROLLER_ROLE werden von den Verträgen selbst gehalten, nicht von den Signataren). Wir bezeichnen die sechs Signaturrollen als A bis F. Dass die mächtigsten Rollen im System undurchsichtige Identifikatoren sind, ist selbst eine Schwäche: Es macht die Governance schwerer zu prüfen als benannte Rollen.

Zweitens ist die Zahl der Inhaber klein. Die folgende Tabelle wird aus dem Rollenregister on-chain gelesen; Adressen sind abgekürzt.

Rolle (unser Label) On-Chain-Hash Inhaber Autorisiert
A 0x37f0f656… 0x747356d525bf47f495af7c6e42f3ab9b8ba87a4b erste Signatur für upgradeTo / changeAdmin / setControlAuthority
B 0x95c27f81… 0x8117a657df7a1c399231bfe26a096eb8f8ad5973, 0x5d9d9b28e83382e694916b0feeaf7a9badbb659c, 0xebb73f78e253539acceb4e6cc287095788eee5d4 die zweite Signatur bei fast jeder Zwei-Signatur-Aktion
C 0xfa2fe896… 0x2f7f00cc5334fe2861e485ff610f74890a0316ed mintToDeposit, burnFrom, freeze, KYC-Deaktivierung (einzeln)
D 0x3dce3265… 0x5092af62a1625fa57404557d3ad417474f3f494c pause, destroyBlackFunds, Registrieren/Abmelden von Einzahlungs- und Einlösungsadressen, registerVerifier (einzeln)
E 0xfd21a76d… 0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2 Ändern der Compliance-Module (setBlacklistServer usw.)
F 0x510ac1ff… 0x7daabe5092d3feea5cc19ddbb5380a4eac38f89c addBlackList, unfreeze (einzeln)

Aus der Tabelle ergeben sich mehrere Punkte.

Hochrisiko-Operationen mit nur einem Signatar. Die meisten Hochrisiko-Operationen erfordern eine einzelne Rolle mit einem Quorum von eins. Die folgende Tabelle wird on-chain aus der authorizationMatrix des Governance-Vertrags des Tokens und der fünf Modul-Governance-Verträge gelesen (Einzelsignatur, sofern nicht eine zweite Signatur + B angegeben ist):

Operation Geregelt durch Erforderliche Signaturen
mintToDeposit, burnFrom Token C x1
freeze, batchFreeze Freezing-Modul C x1
deactivate, adminDeactivate (KYC) KYC-Modul C x1
pause, destroyBlackFunds Token D x1
register, unregister (Einzahlung / Einlösung) Verzeichnismodule D x1
registerVerifier, unregisterVerifier KYC-Modul D x1
addBlackList, batchBlackList Blacklist-Modul F x1
unfreeze Freezing-Modul F x1
removeBlackList Blacklist-Modul F x1 + B x1
setBlacklistServer, setFreezingServer, setCheckingMode Token E x1 + B x1
Directory-Server- und Supply-Limit-Setter Token D x1 + B x1
upgradeTo / changeAdmin / setControlAuthority (Token und jedes Modul), unpause Token + Module A x1 + B x1

Ausgabe, Einfrieren und KYC-Deaktivierung sind Einzelsignaturen (alle unter Rolle C); Pause, Destroy, Verzeichnisänderungen und Verifier-Registrierung sind Einzelsignaturen unter Rolle D; Blacklisting und Unfreezing sind Einzelsignaturen unter Rolle F. Nur Upgrades und Konfigurationsänderungen benötigen eine zweite Signatur. Beachten Sie die Asymmetrie: freeze und addBlackList benötigen eine Signatur, während removeBlackList zwei benötigt; das Einschränken eines Kontos ist also einfacher als das Freigeben.

Das ist nicht nur eine Lektüre der Matrix; es ist in einem Live-Mint beobachtbar. Die jüngste Ausgabe zum Zeitpunkt des Schreibens, Tx 0xa7e53c…b33d7, ist eine einzelne Transaktion, die vom alleinigen Rolle-C-Konto (0x2f7f00cc5334fe2861e485ff610f74890a0316ed) an den Governance-Vertrag des Tokens gesendet wurde. Sie ruft request auf, und dieselbe Transaktion erreicht das Quorum und emittiert den Mint-Transfer von address(0), ohne separate Genehmigungstransaktion und ohne zweiten Signatar. Da die Ausgabe innerhalb der eigenen Transaktion des Anfragenden abgewickelt wird, hat ein einziger Schlüssel den Mint sowohl angefragt als auch ausgeführt.

Es gibt außerdem keinen Timelock in der Engine. In dem Moment, in dem die letzte erforderliche Signatur eintrifft, wird die Aktion in derselben Transaktion ausgeführt, ohne Verzögerung, in der sie geprüft, storniert oder angefochten werden könnte; die Engine zeichnet einen executedAt-Zeitstempel auf, prüft aber nie einen. Selbst die Zwei-Signatur-Operationen werden also sofort abgewickelt, sobald der zweite Schlüssel signiert.

Ein Schlüsselpaar aktualisiert alles. Das Upgraden des Tokens und aller fünf Compliance-Module verwendet dieselbe Anforderung, A plus B. Da A ein Konto ist und B drei Konten, die sich eine Rolle teilen, können zwei Personen jede Implementierung im System ersetzen.

Dasselbe Paar kontrolliert auch die Rollentabelle. Rollen können nur über das Register (HybridControlledAuthority, 0xa7287701f7ab8f38ef57cdb2a2a69a40128ec89c) in dessen eigener Zeremonie gewährt und entzogen werden; kein externer Schlüssel kann sie direkt ändern. Und seine authorizationMatrix, on-chain gelesen, verlangt für eine Rollenänderung dieselben Signaturen wie für ein Upgrade: Rolle A plus Rolle B. Die zwei Personen, die jede Implementierung ersetzen können, können also auch einen Schlüssel zu Rolle C hinzufügen, einen bestehenden Inhaber entfernen und die gesamte Rollentabelle neu schreiben. Dieses Zwei-Signatur-Gate ist besser als die Einzelsignatur-Operationen oben, aber es ist immer noch eine niedrige Hürde für die Wurzel des Systems, da Rolle A ein einziges Konto ohne Redundanz ist.

Eine Adresse, sechs Rollen. Der Inhaber von Rolle C (0x2f7f00cc5334fe2861e485ff610f74890a0316ed) hält außerdem die benannte ADMIN_TOKEN_HOLDER_ROLE (KYC-Verifier-Verwaltung) und ist Mitglied aller vier Auditor-Rollen (BLACKLIST_AUDITOR_ROLE, FREEZING_AUDITOR_ROLE, WHITELIST_AUDITOR_ROLE und AFL_TOKEN_AUDITOR_HOLDER_ROLE). Die Kompromittierung dieses einen Schlüssels bedeutet gleichzeitig den Verlust von Ausgabe, Einfrieren und KYC-Verwaltung.

Die Auditor-Rollen haben zwei eigene Probleme. Erstens beschränken sie die read-only Getter für die Compliance-Listen, offenbar um zu kontrollieren, wer sie lesen kann, aber in einer öffentlichen Chain ist das sinnlos: Der zugrunde liegende Storage ist für jeden lesbar (das Lesen von Storage-Slots ist die Methode, mit der wir dieses System kartiert haben), sodass die Listen so oder so öffentlich sind, und die Einschränkung zeigt, dass das Design die Tatsache, auf einer öffentlichen Chain zu sein, nicht berücksichtigt hat. Zweitens Konzentration: Alle vier Auditor-Rollen liegen bei denselben 23 Konten, und die Inhaber der Ausführungsrollen A, C, D und F gehören dazu, sodass dieselben Schlüssel, die minten, brennen, einfrieren und blacklisten, auch in der Gruppe sitzen, die diese Aktionen prüfen soll.

Widerruf ist nicht sofort. In der Zeremonien-Engine wird die Rolle eines Signatars einmal geprüft und das Quorum dekrementiert; frühere Signatare werden nie erneut validiert, sodass der spätere Entzug einer Rolle eine bereits gezählte Stimme nicht zurücknimmt:

// contracts/hce/HybridControlEngine.sol
158    for ( uint256 i=0; i < approvalRequest.ceremony.length; i++ ) {
159        if ( !_hasBeenMandated && !approvalRequest.ceremony[i].completed &&
160                IControlAuthority(authorityContract).hasRole( approvalRequest.ceremony[i].expectation.authority, _signer ) ) {
161            _hasBeenMandated = true ;
163            approvalRequest.ceremony[i].expectation.quota-- ;
167            approvalRequest.ceremony[i].completed = _approvalIntent && approvalRequest.ceremony[i].expectation.quota == 0 ;
168        }

Schließlich ist der Audit-Trail der Engine selbst unzuverlässig. Bei jeder Genehmigung emittiert das evtApprove-Event address(0) als Signatar statt des echten Genehmigenden; der tatsächliche Signatar bleibt nur im Transaktionssender und einem internen Datensatz erhalten, sodass Event-Logs nicht zuordnen können, wer einen Request genehmigt hat. Und die Liste aktiver Requests verwendet Nonce 0 sowohl als echte Request-ID als auch als Leermarker, sodass Monitoring- oder Genehmigungstools, die die Liste rückwärts durchlaufen, den Request bei Nonce 0 übersehen. Keines davon ist kritisch, aber für ein reguliertes System, das einen sauberen Audit-Trail braucht, mindern beide Punkte ihn.

1.3 Transferkontrollen sind zwischen transfer und transferFrom inkonsistent

Ein Teil des Unterschieds zwischen den beiden ist zu erwarten. Bei transferFrom ist der Initiator, msg.sender, ein genehmigter Spender und nicht die Quelle der Gelder, daher prüft der Code die echte Quelle from in jedem Modus explizit; bei transfer ist das nicht nötig, weil dort msg.sender die Quelle ist. Diese Anpassung ist vernünftig. Zwei andere Unterschiede lassen sich nicht durch den Initiator erklären, und sie führen dazu, dass dieselbe wirtschaftliche Handlung unterschiedlichen Regeln unterliegt.

Erstens werden die Einzahlungs- und Einlösungs-Whitelists nur im transferFrom-Pfad abgefragt, wo eine Mitgliedschaft einen frühen Return auslöst, der die isActive-(KYC-)Prüfung umgeht. transfer fragt sie nie ab. Ob ein Empfänger auf der Whitelist steht, hat nichts damit zu tun, wer die Übertragung initiiert hat; derselbe Empfänger unterliegt also bei transfer der KYC-Prüfung, kann sie aber bei transferFrom überspringen:

// contracts/hce/ControllableAHKD.sol : transfer(), "Source" mode gates only the sender
323            else if ( activateMode == ActivateMode.Source )
325                _transferCheckSourceMode(_value, "80h");

// contracts/hce/ControllableAHKD.sol : transferFrom() -> _transferFromCheckDestinationMode(_to)
428        try depositDirectoryServer.isRegistered(_to)
429                    returns ( bool isIt, IWhitelistServer.WalletAddress memory ) {
430            if ( isIt ) { return ; }
436        try redemptionDirectoryServer.isRegistered(_to)
437                    returns ( bool isIt, IWhitelistServer.WalletAddress memory ) {
438            if ( isIt ) { return ; }
444        try tokenHolderActivationServer.isActive(_to) returns ( bool isIt ) {
445            require(isIt || _value < freeTransferLimit,     string.concat(ERROR_404, _rcCode, "/86a" ) ) ;

Zweitens bedeutet checkingMode in den beiden Funktionen unterschiedliche Dinge. Bei transfer prüft der Modus „Source“ den Sender; bei transferFrom prüft der Modus „Source“ den Spender (msg.sender), während from in jedem Modus geprüft wird. Eine einzige Konfigurationseinstellung erzwingt also je nach Einstiegspunkt zwei verschiedene Richtlinien.

Die Wirkung ist, dass die Transferkontrollen eines regulierten Tokens davon abhängen, welche Funktion verwendet wird, was sie schwer durchschaubar und je nach Konfiguration vermeidbar macht.

1.4 Anzeichen eines Vorproduktions-Builds

Über die spezifischen Logikfehler hinaus deuten mehrere Eigenschaften der Codebasis darauf hin, dass ein Vorproduktions-Build ins Mainnet deployed wurde.

Debug-Logging in Produktion. hardhat/console.log-Aufrufe sind überall geblieben, auch im Fallback des Proxys, der bei jeder Nutzertransaktion läuft. Da der Proxy selbst nicht upgradebar ist, ist dieser Overhead dauerhaft:

// contracts/proxy/UpgradeableProxy.sol
115    function _beforeFallback() internal virtual override {
116        console.log("iam %s, entering fallback as %s", address(this), msg.sender) ;
117        // require(msg.sender != _getAdmin(), "[UPY]404/02");
118        super._beforeFallback();
119    }

Ein Proxy-Kopfkommentar, der das Gegenteil des Codes beschreibt. Dieselbe Datei enthält die Dokumentation von OpenZeppelins TransparentUpgradeableProxy, die besagt, dass der Admin niemals zur Implementierung durchfallen kann. Dieser Vertrag tut bewusst das Gegenteil: Die Schutzprüfung in Zeile 117 ist auskommentiert, und der Admin fällt sehr wohl durch. Ein Prüfer, der dem Kommentar vertraut, würde die Vertrauensgrenze falsch modellieren.

Rollenname und Hash passen nicht zusammen. Die „Elevated-Risk“-Rolle wird im Token und in den Modulen mit demselben Konstantennamen, aber einem anderen Keccak-String deklariert, was zwei verschiedene Rollen erzeugt:

// contracts/hce/ControllableAHKD.sol  (token)    hash = 0xd2b9...
25    bytes32 internal constant ELEVATEDRISK_OWNER_ROLE = keccak256('ELEVATED_RISK_OWNER_ROLE') ;
// contracts/hce/erc20/modules/BlackListServer.sol  (module)    hash = 0x4de4...
18    bytes32 internal constant ELEVATEDRISK_OWNER_ROLE = keccak256('ELEVATEDRISK_OWNER_ROLE') ;

Das Deployment vermeidet Probleme nur, weil jedes Modul seine eigene Kopie hält; ein zweiter Rollenname (AFL_TOKEN_HOLDER_AUDITOR_ROLE) ist auf dieselbe Weise vertauscht.

Weitere Anzeichen. Das Verifikationsbündel des Proxys hat 117 Dateien, einschließlich der Testsuite des Projekts, in den öffentlichen Explorer hochgeladen, was einem Leser die internen Tests und Randfälle ausliefert. Das jüngste Upgrade änderte nur Compiler-Warning-Bereinigungen, ohne ein externes Audit im Prozess. Und der Optimizer ist auf null Runs gesetzt, was die heißen Pfade eines stark genutzten Tokens teurer statt günstiger macht.

Keiner dieser Punkte ist für sich genommen schwerwiegend. Zusammen deuten sie darauf hin, dass der Code nicht die Release-Disziplin durchlaufen hat, die man von einem Vertrag erwartet, der im Mainnet Werte hält.

1.5 Das einzige Upgrade des Vertrags, zurückverfolgt

Der Proxy wurde einmal aktualisiert. Er wurde am 28. April 2026 mit der Implementierung 0x8ff12fe3bed22d9e40afb4b98ef4dee28d94699e deployed und am 10. Juli 2026 auf die aktuelle 0xe42d38b05d7ff702193ac5ccedb411b7298ffbfc aktualisiert. Da ein Upgrade eine On-Chain-Governance-Aktion ist, können wir genau sehen, wer es genehmigt hat.

upgradeTo erfordert Rolle A plus Rolle B. Das Upgrade bestand aus zwei Transaktionen in benachbarten Blöcken, etwa zwölf Sekunden auseinander:

  • der Request, Rolle A, von 0xa9315aadc89ba681f1fe5df3375ab1a87d37eca2, im Block 25500519 (Tx 0x742372…85136);
  • die Genehmigung, Rolle B, von 0x3795300b31429f9d37b0dc805528d9390ce87c50, im Block 25500520 (Tx 0xa7a400…630c), die das Quorum erreichte und das Upgrade in derselben Transaktion durchführte.

Zwei Dinge fallen auf. Erstens: Die gesamte Zwei-Signatur-Zeremonie wurde innerhalb eines einzigen Blockintervalls abgeschlossen. Vom Request bis zur Ausführung verging ein Block; es gab kein Fenster, in dem der zweite Signatar die Änderung vor ihrem Livegang unabhängig hätte prüfen können.

Zweitens ist keiner der Signatare aus dem aktuellen Rollenregister identifizierbar. Die Rollen wurden seither rotiert: Der Anfragende 0xa9315a… hält Rolle A nicht mehr (er hält heute Rolle E), und der zweite Signatar 0x3795300b… hält jetzt gar keine Rolle mehr. Ein Blick auf das Register in seinem jetzigen Zustand würde nicht verraten, wer das Upgrade autorisiert hat; das tut nur die Transaktionshistorie. Das ist die Eigenschaft „Widerruf ist nicht sofort“, von der anderen Seite betrachtet: Rollen wechseln, sodass eine Momentaufnahme davon, wer was hält, kein Protokoll darüber ist, wer was getan hat.

Und das Upgrade hat keinen der in dieser Überprüfung genannten Defekte behoben. Die von ihm installierte Implementierung, 0xe42d38b0…, ist genau die, die in Teil 1 beschrieben wird. Wir können der Chain nicht entnehmen, ob vor dem Upgrade ein externes Audit durchgeführt wurde; was wir sagen können, ist: Wenn eines durchgeführt wurde, haben die Defekte aus Teil 1 es überlebt.

Teil 2: Entspricht es Hongkongs Stablecoin-Rahmenwerk?

Hongkongs Stablecoins Ordinance trat am 1. August 2025 in Kraft, und lizenzierte Emittenten werden nach der Leitlinie der HKMA zur Beaufsichtigung lizenzierter Stablecoin-Emittenten beaufsichtigt. Wir haben nur die Klauseln verglichen, die ein Smart Contract allein erfüllen kann; Reservehinterlegung, Verwahrung und Off-Chain-Schlüsselzeremonien sind für eine On-Chain-Überprüfung nicht relevant. Zu jeder unten aufgeführten Klausel geben wir an, was die Leitlinie verlangt, was der Vertrag tut und wo die beiden voneinander abweichen. Der Vergleich ist hier zusammengefasst und in den folgenden Abschnitten ausgeführt.

HKMA-Klausel Was sie verlangt Wo der Vertrag abweicht Bewertung
6.5.3 Hochrisiko-Operationen dürfen nicht einseitig sein (Multisignatur und Mitigationsmaßnahmen wie Velocity-Limits oder Timelocks) Mint, Burn, Pause und Freeze werden jeweils mit einem Schlüssel ausgeführt; kein Timelock (Ausführung ist atomar mit der letzten Signatur) Weicht ab
6.5.4 Aufgabenverteilung zwischen autorisierten Personen; Autorität sofort entziehen Ein Konto hält sechs Rollen, sodass Ausführung und Prüfung sich überschneiden; eine bereits gezählte Genehmigung überlebt einen späteren Widerruf Weicht ab
6.5.5 Externes Audit für jede Codeänderung; korrekt, konsistent, frei von Schwachstellen Die Defekte aus Teil 1 sind in der deployed Implementierung live; isActive und der Anbieter-Widerruf tun nicht, was ihre Namen versprechen Weicht ab
Wirksamkeit der Compliance-Kontrollen Blacklist-, Freeze-, Whitelist- und KYC-Kontrollen müssen wirksam sein KYC-Gating und Anbieter-Widerruf sind im deployed Code funktionslos Weicht ab
2.2.3 Eingefrorene oder vernichtete Coins bleiben vollständig gedeckt und abstimmbar Destroy emittiert Transfer an address(this) statt an address(0) und lässt die Netto-Emissionszähler unberührt; aus Events rekonstruierter Supply driftet Bedenklich

Absatz 6.5.3: Hochrisiko-Operationen dürfen nicht einseitig sein

Was sie verlangt. Hochrisiko-Operationen sollten so gestaltet sein, dass keine einzelne Partei sie einseitig ausführen kann, zum Beispiel durch ein Multisignatur-Protokoll; die Leitlinie nennt weitere Mitigationsmaßnahmen wie Velocity-Limits und zeitverzögerte (Timelock-)Kontrollen.

Was der Vertrag tut. Gelesen aus der authorizationMatrix des Governance-Vertrags (die vollständige Matrix steht in der Tabelle in 1.2), erfordern die Supply- und Notfalloperationen jeweils eine einzelne Rolle mit einem Quorum von eins:

Operation Erforderliche Signatur
mintToDeposit ROLE_C x1
burnFrom ROLE_C x1
freeze ROLE_C x1
pause ROLE_D x1
destroyBlackFunds ROLE_D x1

Wo es abweicht. Minten, Brennen, Pausieren und Einfrieren können jeweils mit einem einzigen Schlüssel ausgeführt werden. Das erfüllt die Anforderung „keine einzelne Partei einseitig“ nicht. Es gibt auch keinen Timelock: Wie in 1.2 gezeigt, wird die Aktion, sobald das Quorum erreicht ist, in derselben Transaktion ausgeführt, sodass auch eine Zeitverzögerung, eine der von der Leitlinie genannten Mitigationsmaßnahmen, fehlt. Der Vertrag implementiert zwar ein Supply-Velocity-Limit (whenWithinRiskThresholds), diese Mitigationsmaßnahme ist also vorhanden, aber sie ersetzt keine Multisignatur-Kontrolle über die Operationen selbst.

Absatz 6.5.4: Aufgabenverteilung und sofortiger Widerruf

Was sie verlangt. Verschiedene Operationen sollten auf verschiedene autorisierte Personen aufgeteilt werden, und die Autorität einer autorisierten Person sollte sofort widerrufbar sein.

Was der Vertrag tut. Ein extern besessenes Konto (EOA) hält sechs Rollen (Ausgabe, Einfrieren, KYC-Verwaltung und alle vier Auditor-Rollen), sodass sich Ausführungs- und Prüfrollen überschneiden. Rollenänderungen selbst erfordern ebenfalls nur Rolle A plus Rolle B (siehe 1.2), sodass dieselbe kleine Gruppe entscheidet, wer autorisiert ist, und ausführen und upgraden kann. Und eine bereits gezählte Signatur wird nicht erneut validiert, wenn die Rolle des Signatars später entzogen wird.

Wo es abweicht. Aufgaben sind konzentriert statt getrennt, und der Widerruf ist nicht sofort: Die frühere Genehmigung eines widerrufenen Signatars zählt weiterhin zu einer späteren Ausführung.

Absatz 6.5.5: Jede Codeänderung auditieren; korrekt, konsistent, keine Schwachstellen

Was sie verlangt. Eine qualifizierte dritte Partei sollte die Smart Contracts bei jeder Codeänderung auditieren und bestätigen, dass sie (i) korrekt implementiert, (ii) mit der beabsichtigten Funktionalität konsistent und (iii) mit hohem Vertrauensniveau frei von Schwachstellen sind.

Was der Vertrag tut. Die aktuelle Implementierung ist die durch das Upgrade vom 10. Juli installierte (in 1.5 zurückverfolgt), und die Defekte aus Teil 1 sind darin live.

Wo es abweicht. Wir können off-chain nicht sehen, ob ein Audit durchgeführt wurde, aber das Ergebnis erfüllt den Standard in jedem Fall nicht. Da isActive und der Anbieter-Widerruf nicht tun, was ihre Namen besagen, sind die Bedingungen (i) und (ii) nicht erfüllt; und da die Defekte aus Teil 1 im deployed Code vorhanden sind, ist auch (iii) nicht erfüllt.

Wirksamkeit der Compliance-Kontrollen

Was sie verlangt. Das Lebenszyklusmodell der Leitlinie (Blacklist, Freeze, Whitelist, KYC) setzt voraus, dass diese Kontrollen wirksam sind.

Was der Vertrag tut. Wie in 1.1 gezeigt, sind KYC-Gating und Anbieter-Widerruf im deployed Code nicht funktionsfähig.

Wo es abweicht. Eine Kontrolle, die funktionieren muss, funktioniert nicht. Das ist eine substanzielle Lücke, keine Formalie.

Absatz 2.2.3: Eingefrorene oder vernichtete Coins bleiben vollständig gedeckt und abstimmbar

Was sie verlangt. Stablecoins, die durch Durchsetzungsmaßnahmen eingefroren oder vernichtet werden, sollten vollständig gedeckt bleiben, sodass Supply und Reserven abgestimmt werden können.

Was der Vertrag tut. Der übliche Sanierungsablauf für eine regulierte Stablecoin besteht darin, die Gelder auf einer schwarzen Adresse zu verbrennen und später dem Opfer einen gleich hohen Betrag als separaten Mint erneut auszugeben (so funktionieren USDTs destroyBlackFunds plus issue). HKDAPs Destroy reduziert _totalSupply, was ein Burn ist, aber es schreibt nie balances[address(this)] gut, es emittiert einen Transfer an address(this) statt an address(0), und es lässt die Netto-Emissionszähler, die das Mint-Limit verwendet, unberührt:

// contracts/hce/ControllableAHKD.sol
628    function destroyBlackFunds(address _blackListedUser) external override whenNotPaused onlySupplyDestroyer() {
636        uint dirtyFunds = balanceOf(_blackListedUser);
637        balances[_blackListedUser] = 0;
638        _totalSupply = _totalSupply - dirtyFunds ;
639        emit DestroyedBlackFunds(_blackListedUser, dirtyFunds);
640        emit Transfer(_blackListedUser, address(this), dirtyFunds);
641    }

Wo es abweicht. Die Zustandsänderung ist ein Burn, aber das Event sagt, die Token seien an den Vertrag übertragen worden, und dort wird nie etwas gehalten (balances[address(this)] bleibt null). Ein Indexer würde address(this) Token gutschreiben, die es nicht hält, und der aus Events rekonstruierte Gesamtsupply würde nicht zur Chain passen. Es verwirrt auch den Sanierungsprozess selbst: Da die Token verbrannt statt geparkt werden, muss eine Neuausgabe an das Opfer ein frischer Mint sein, doch das irreführende Transfer(..., address(this), ...) suggeriert, der Vertrag verwahre sie nun und könnte sie weiterleiten, was er nicht kann. Ein sauberer Burn an address(0) plus eine separate Neuausgabe wäre sowohl korrekt als auch abstimmbar. Wie geschrieben, driftet die On-Chain-Buchhaltung, von der eine Reserveabstimmung abhängt, vom wahren Zustand der Chain ab. Das ist eher bedenklich als einwandfrei.

Wir beschränken diese Ergebnisse auf das, was die Chain zeigt. Ob Reserven vollständig gedeckt sind, ob Schlüssel in einem HSM oder in einer luftabgeschirmten Umgebung liegen und ob Transaktionen vor der Signatur off-chain simuliert werden, ist vom Vertrag nicht sichtbar, und dazu treffen wir keine Aussage.

Fazit

Das Bild ist entlang beider Achsen der Überprüfung konsistent. Als Software enthält HKDAP funktionale Defekte, einschließlich Compliance-Kontrollen, die nicht wie vorgesehen ausgeführt werden, und zeigt mehrere Anzeichen eines Vorproduktions-Builds. Als regulierte Stablecoin stehen mehrere On-Chain-Eigenschaften im Widerspruch zu konkreten Klauseln der HKMA-Leitlinie. Aufgrund der On-Chain-Beweise und abgesehen von den Off-Chain-Dingen, die wir nicht sehen können, erfüllt der deployed Vertrag noch nicht den Standard, den eine kommerzielle Stablecoin erfüllen sollte.

Zwei Beobachtungen schließen sich an, und es lohnt sich, sie unmissverständlich zu formulieren.

Erstens verändert die Ausgabe auf einer öffentlichen Chain den Ort, an dem Compliance entschieden wird. Eine Anforderung wie „keine einzelne Partei sollte einseitig handeln können“ wird durch die Rollenprüfungen im deployed Code erfüllt oder nicht, und dieser Code ist öffentlich. Die Implementierung ist, nicht die Lizenz oder die Dokumentation, der Ort, an dem eine solche Anforderung tatsächlich erfüllt oder verfehlt wird, und jeder kann verifizieren, welches der Fall ist.

Zweitens ändert das „Beta Access“-Label das Risikoprofil des deployed Code nicht. Der Vertrag ist live im Ethereum-Mainnet, wird von echten Schlüsseln verwaltet und stellt eine Forderung auf Hongkong-Dollar dar. Er sollte daher unabhängig von seinem Label an Produktionsstandards gemessen werden.

Ein gemeinsamer roter Faden zieht sich durch die konkreten Ergebnisse. Die Architektur erfindet in maßgeschneiderter Form Primitive neu, die das Ökosystem bereits bereitstellt und in großem Maßstab auditiert hat: eine von Grund auf eigene Genehmigungs-Engine und Rollenschicht, wo ein Safe-Multisig mit OpenZeppelins AccessManager und TimelockController genügen würde; eine handgeschriebene Linked-List-Sammlung anstelle von EnumerableSet; einen modifizierten Proxy anstelle des Standard-TransparentUpgradeableProxy; und einen handgeschriebenen ERC-20 anstelle von OpenZeppelins. Die meisten Defekte in dieser Überprüfung stecken in dieser eigenen Mechanik, nicht in den Teilen, die Standardkomponenten wiederverwenden. Es liest sich wie ein System, das auf die Weise abstrahiert wurde, wie es bei Allzweck-Software üblich ist, statt aus den kleinen, auditierten Bausteinen komponiert zu werden, die die On-Chain-Entwicklung bevorzugt, wo jede Schicht eigener Abstraktion auch Gas, Angriffsfläche und Upgrade-Risiko bedeutet. Ein Design aus diesen Standardkomponenten wäre kleiner, sicherer und leichter zu prüfen und brächte die Dinge mit, die diesem System derzeit fehlen: ein Überlegungsfenster durch einen Timelock und benannte, lesbare Rollen.

Die hier beschriebenen Probleme sind behebbar. Die Wiederherstellung der Multisignatur bei Hochrisiko-Operationen, die Trennung von Ausführung und Audit, die Korrektur der KYC-Widerrufslogik, die Vereinheitlichung der Transferprüfungen, das Entfernen des Debug-Codes und die Forderung nach einem externen Audit vor jedem Upgrade würden die meisten davon lösen. Dass im Mainnet mit verifiziertem Quellcode deployed wurde, hat diese Überprüfung überhaupt erst ermöglicht, und es ist die richtige Standardeinstellung. Kontinuierliche On-Chain-Sicherheits- und Compliance-Überprüfung dieser Art ist das, was wir bei BlockSec tun, und wir helfen gern.

Get Real-Time Protection with Phalcon Security

Audits alone are not enough. Phalcon Security detects attacks in real time and blocks threats mid-flight.

phalcon security