Skip to main content

Sicherheitslücken in NodeJS-C/C++-Add-on-Erweiterungen

Artikel von
Headshot of Alessio Della Libera

Alessio Della Libera

feature snyk platform learn using snyk with CI CD

14. August 2024

0 Min. Lesezeit

Eines der Hauptziele dieser Untersuchung war es, C/C++-Sicherheitslücken im Kontext von NodeJS-npm-Paketen zu untersuchen. Im Mittelpunkt stehen die Untersuchung und Identifizierung klassischer Sicherheitslücken wie Buffer Overflows, Denial-of-Service-Angriffe (Abstürze von Prozessen, ungeprüfte Typen) und Speicherlecks im Kontext von NodeJS-C/C++-Add-ons sowie die Modellierung relevanter Quellen, Senken und Bereinigungsfunktionen mit Snyk Code (siehe Snyk bringt einen entwicklerorientierten AppSec-Ansatz für C/C++).

Ziel dieser Untersuchung sind NPM-Pakete, die im Rahmen ihrer Implementierung C/C++-Schnittstellen verwenden. Projekte, die nicht bei NPM gelistet sind, wurden nicht berücksichtigt.

Dieser Blogbeitrag gibt einen Überblick über häufige Sicherheitslücken und anfällige Muster, die beim Schreiben von C/C++-Add-ons in NodeJS auftreten können. Außerdem stellen wir Beispiele für Abhilfemaßnahmen und Empfehlungen für Open-Source-Maintainer vor.

Inspiriert wurde dieser Blogbeitrag von der Studie „Bilingual Problems: Studying the Security Risks Incurred by Native Extensions in Scripting Languages“ von Cristian-Alexandru Staicu, Sazzadur Rahaman, Àgnes Kiss und Michael Backes.[1] In ihrer ursprünglichen Arbeit analysierten die Autoren die Sicherheitsrisiken nativer Erweiterungen in verbreiteten Programmiersprachen, darunter JavaScript.

Hintergrund zu NodeJS-C/C++-Add-ons

NodeJS bietet verschiedene APIs zum Aufrufen nativen C/C++-Codes. Im Rahmen dieser Untersuchung wurden Sicherheitslücken betrachtet, die bei der Verwendung eines der folgenden Mechanismen auftreten können:

Ein gutes Beispiel für die Verwendung der oben genannten Bibliotheken finden Sie auf GitHub.

Eine vollständige Einführung in Add-ons und ihre Erstellung finden Sie in der offiziellen NodeJS-Dokumentation. 

Die folgenden Sicherheitslücken wurden untersucht und in mindestens einem Paket gefunden:

  • Speicherlecks

  • Ungeprüfter Typ (DoS)

  • Erreichbare Assertion (DoS)

  • Nicht behandelte Ausnahmen (DoS)

  • Buffer Overflow

  • Integer Overflow

In den folgenden Abschnitten werden Beispiele für anfällige Muster vorgestellt und die Bedingungen erläutert, die erfüllt sein müssen, damit sich die Sicherheitslücke ausnutzen lässt.

Beispiele für anfällige Muster

In diesem Abschnitt untersuchen wir, wie Add-on-spezifische APIs zu Sicherheitsproblemen führen können, wenn sie nicht ordnungsgemäß behandelt werden, und stellen einige im Rahmen dieser Studie identifizierte anfällige Muster vor. 

HINWEIS: Die folgenden Beispiele stellen keine vollständige Liste dar. Es kann weitere Szenarien geben

die zu Sicherheitsproblemen führen und in diesem Blogbeitrag nicht behandelt werden.

Einrichtung

Installieren Sie node-gyp (https://github.com/nodejs/node-gyp).

Die folgenden Dateien werden verwendet, um die Beispiele im nächsten Abschnitt auszuführen:

package.json

{
  "main": "main.js",
  "private": true,
  "gypfile": true,
  "dependencies": {
    "bindings": "^1.5.0",
    "nan": "^2.18.0",
    "node-addon-api": "^7.0.0"
  }
}

binding.gyp

{
  "targets": [
    {
      "target_name": "test_napi_exceptions",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [ "test_napi_exceptions.cpp" ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], # if this line is commented, all the tests in test_napi_exceptions.cpp will not crash the process
    },
    {
      "target_name": "test_node_api_assert",
      "sources": [ "test_node_api_assert.c" ]
    },
    {
      "target_name": "test_napi_unchecked_type",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [ "test_napi_unchecked_type.cpp" ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], # if this line is commented, all the tests in test_napi_unchecked_type.cpp will not crash the process
    },
    {
      "target_name": "test_napi_memory_leak",
      "sources": [ "test_napi_memory_leak.c" ]
    }
  ]
}

Führen Sie die folgenden Befehle aus, um die C/C++-Erweiterungen zu erstellen:

  • node-gyp configure

  • node-gyp build

Ein bestimmtes Beispiel ausführen:

node main.js <test1|test2|...>

main.js

const test_napi_exceptions = require('bindings')('test_napi_exceptions');
const test_node_api_assert = require('bindings')('test_node_api_assert');
const test_napi_unchecked_type = require('bindings')('test_napi_unchecked_type');
const test_napi_memory_leak = require('bindings')('test_napi_memory_leak');

function test1(){
    console.log('[+] Running test1');
    try {
        console.log(test_napi_exceptions.test1('foo', 'bar')); // TEST1 - OK
        console.log(test_napi_exceptions.test1('foo')); // throws an exception
    } catch (e) {
        // executed
        console.log(e); // TypeError: TEST3 - Err1
    }

    try {
        test_napi_exceptions.test1(1); 
        /*
            FATAL ERROR: Error::ThrowAsJavaScriptException napi_throw
            ...
            Aborted
        */
    } catch (e) {
        console.log(e);
    }
}

function test2(){
    console.log('[+] Running test2');
    try {
        console.log(test_napi_exceptions.test2('foo', 'bar')); // TEST2 - OK

        console.log(test_napi_exceptions.test2('foo'));
         /*
        terminate called after throwing an instance of 'Napi::Error'
        Aborted
        */

    } catch (e) {
        console.log(e);
    }

}

function test3(){
    console.log('[+] Running test3');
    console.log(test_napi_exceptions.test3('foo', 'bar', 'baz')); // TEST3 - OK

    try {
        console.log(test_napi_exceptions.test3('foo', 'bar')); 
    } catch (e) {
        console.log(e); // TypeError: TEST3 - Error2
    }

    console.log(test_napi_exceptions.test3('foo')); 
    /*
        FATAL ERROR: Error::ThrowAsJavaScriptException napi_throw
        ...
        Aborted
    */
}

function test4(){
    console.log('[+] Running test4');
    try {
        console.log(test_node_api_assert.test1());
    } catch (e) {
        console.log(e); // TypeError: Wrong number of arguments
    }

    try {
        console.log(test_node_api_assert.test1(1)); // 2

        console.log(test_node_api_assert.test1('1'));
        /*
        node: ../test_Assert.c:24: Test1: Assertion `status == napi_ok' failed.
        Aborted
        */
    } catch (e) {
        console.log(e);
    }
}

function test5(){
    console.log('[+] Running test5');

    console.log(test_napi_unchecked_type.test1('foo')); 
    // foo
    // TEST1 - OK

    console.log(test_napi_unchecked_type.test1({'foo': 'bar'})); 
    // [object Object]
    // TEST1 - OK

    try {
        test_napi_unchecked_type.test1({'toString': 'foo'});
        /*
        FATAL ERROR: Error::New napi_get_last_error_info
        ...
        Aborted
        */
    } catch (e) {
        console.log(e);
    }

}

function test6(){
    console.log('[+] Running test6');

    console.log(test_napi_unchecked_type.test2({'foo': 'bar'})); 
    // bar
    // TEST2 - OK

    try {
        test_napi_unchecked_type.test2({'foo': {'toString': 'foo'}});
        /*
        FATAL ERROR: Error::New napi_get_last_error_info
        ...
        Aborted
        */
    } catch (e) {
        console.log(e);
    }

}

function test7(){
    console.log('[+] Running test7');

    console.log(test_napi_unchecked_type.test3(1)); 
    // 1
    // TEST3 - OK

    console.log(test_napi_unchecked_type.test3({'foo': 'bar'})); 
    // nan
    // TEST3 - OK

    try {
        test_napi_unchecked_type.test3({'toString': 'foo'});
        /*
        FATAL ERROR: Error::New napi_get_last_error_info
        ...
        Aborted
        */
    } catch (e) {
        console.log(e);
    }

}

function test8(){
    console.log('[+] Running test8');
    console.log(test_napi_memory_leak.test1(10)); // Xtest1In
    console.log(test_napi_memory_leak.test1(30)); // Xtest1InitTest14

}

const tests = new Map();
tests.set('test1', test1);
tests.set('test2', test2);
tests.set('test3', test3);
tests.set('test4', test4);
tests.set('test5', test5);
tests.set('test6', test6);
tests.set('test7', test7);
tests.set('test8', test8);

function poc() {
    const args = process.argv.slice(2);

    const t = args[0];

    const test = tests.get(t) || test1;
    test();

    // never executed
    console.log('Done');
}

poc();

Nicht behandelte Ausnahmen

Auswirkung: Denial of Service (DoS)

napi

Die napi-API bietet verschiedene Funktionen zum Behandeln von Ausnahmen und Auslösen von Fehlern. Je nach verwendetem Flag in der Datei binding.gyp ist jedoch besondere Vorsicht geboten, um unerwartete Abstürze zu vermeiden.

Ist beispielsweise das Flag NAPI_DISABLE_CPP_EXCEPTIONS in der Datei binding.gyp gesetzt, können die folgenden Szenarien zum Absturz eines Prozesses führen (DoS):

  1. Napi::TypeError::New(env, "").ThrowAsJavaScriptException(); sowie andere Funktionen, die einen Fehler auslösen können (zum Beispiel ein Argument mit falschem Typ)

  2. throw Napi::Error::New ohne umgebendes try/catch

  3. Mehrere Aufrufe von Napi::TypeError::New(env, "").ThrowAsJavaScriptException(); ohne return, die innerhalb derselben Funktion erreichbar sind

Wie in der Dokumentation erläutert: „Nach dem Auslösen einer JavaScript-Ausnahme sollte der Code normalerweise sofort aus dem nativen Callback zurückkehren, nachdem alle erforderlichen Bereinigungen durchgeführt wurden.“ . 

test_napi_exceptions.cpp

#include <napi.h>

Napi::Value Test1(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    std::string data = info[0].As<Napi::String>().Utf8Value();

    if (info.Length() < 2) {
        Napi::TypeError::New(env, "TEST1 - Error").ThrowAsJavaScriptException();
    }
    return Napi::String::New(env, "TEST1 - OK");

}

Napi::Value Test2(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    if (info.Length() < 2) {
        throw Napi::Error::New(env, "TEST2 - Error");
        // missing try-catch
    }
    return Napi::String::New(env, "TEST2 - OK");

}

Napi::Value Test3(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    // multiple reachable ThrowAsJavaScriptException
    if (info.Length() < 2) {
        Napi::TypeError::New(env, "TEST3 - Error1").ThrowAsJavaScriptException();
    }

    if (info.Length() < 3) {
        Napi::TypeError::New(env, "TEST3 - Error2").ThrowAsJavaScriptException();
    }

    return Napi::String::New(env, "TEST3 - OK");

}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
    exports.Set(Napi::String::New(env, "test1"), Napi::Function::New(env, Test1));
    exports.Set(Napi::String::New(env, "test2"), Napi::Function::New(env, Test2));
    exports.Set(Napi::String::New(env, "test3"), Napi::Function::New(env, Test3));
    return exports;
}

NODE_API_MODULE(addon, Init)

Führen Sie diese Beispiele aus:

node main.js test1
node main.js test2
node main.js test3

Erreichbare Assertion

Auswirkung: Denial of Service (DoS)

node_api

Betrachtet man die bereitgestellten Beispiele, sieht man, dass in einigen Beispielen assert verwendet wird, um den Rückgabewert bestimmter Funktionen zu prüfen. Wird eine assert-Anweisung während der Programmausführung jedoch durch nicht vertrauenswürdige Werte (aus dem JavaScript-Code) erreicht, kann dies zu einem Absturz führen (DoS). Bei der Überprüfung einiger Projekte fanden wir mehrere erreichbare Assertions in der Programmlogik. Daher hielt ich es für wichtig, sie in die oben stehende Liste aufzunehmen.

Eine mögliche Lösung für dieses Szenario wäre, den Rückgabewert in einer if-Anweisung zu prüfen und anschließend den entsprechenden Wert zurückzugeben (abhängig von der Programmlogik), statt assert zu verwenden.

test_node_api_assert.c

#include <assert.h>
#include <node_api.h>
#include <stdlib.h>

static napi_value Test1(napi_env env, napi_callback_info info) {
    napi_status status;

    size_t argc = 1;
    napi_value args[1];
    status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
    assert(status == napi_ok);

    if (argc < 1) {
        napi_throw_type_error(env, NULL, "Wrong number of arguments");
        return NULL;
    }

    double value0;
    status = napi_get_value_double(env, args[0], &value0);
    assert(status == napi_ok); // if value0 is not double, the assert will fail

    // potential fix
    // if (status != napi_ok) {
    //     return NULL;
    // }

    napi_value sum;
    status = napi_create_double(env, value0 + value0, &sum);
    assert(status == napi_ok);

    return sum;
}

#define DECLARE_NAPI_METHOD(name, func){ name, 0, func, 0, 0, 0, napi_default, 0 }

static napi_value Init(napi_env env, napi_value exports) {
    napi_status status;
    napi_property_descriptor desc = DECLARE_NAPI_METHOD("test1", Test1);
    status = napi_define_properties(env, exports, 1, &desc);
    assert(status == napi_ok);
    return exports;
}

NAPI_MODULE(addon, Init)

Führen Sie dieses Beispiel aus:

node main.js test4

Ungeprüfter Datentyp

Auswirkung: Denial of Service (DoS)

napi

napi bietet mehrere APIs zum Umwandeln von JavaScript-Typen. Zum Beispiel:

Napi::Value::ToString() „gibt den in einen JavaScript-String umgewandelten Napi::Value zurück“. Ebenso „Napi::Value::ToNumber() gibt den in eine JavaScript-Zahl umgewandelten Napi::Value zurück“. 

Die napi-API Napi::Value::ToString() ruft intern napi_coerce_to_string aus der Node-API auf:

inline MaybeOrValue<String> Value::ToString() const {
  napi_value result;
  napi_status status = napi_coerce_to_string(_env, _value, &result);
  NAPI_RETURN_OR_THROW_IF_FAILED(
      _env, status, Napi::String(_env, result), Napi::String);
}

Referenz 

Ebenso ruft die napi-API Napi::Value::ToNumber() intern napi_coerce_to_number aus der Node-API auf:

inline MaybeOrValue<Number> Value::ToNumber() const {
  napi_value result;
  napi_status status = napi_coerce_to_number(_env, _value, &result);
  NAPI_RETURN_OR_THROW_IF_FAILED(
      _env, status, Napi::Number(_env, result), Napi::Number);
}

Referenz

In der offiziellen Dokumentation zu napi_coerce_to_string heißt es: „Diese API implementiert die abstrakte Operation ToString() gemäß Abschnitt 7.1.13 der ECMAScript-Sprachspezifikation. Diese Funktion führt möglicherweise JavaScript-Code aus, wenn der übergebene Wert ein Objekt ist.“ Das bedeutet: Wenn die Benutzereingabe eine toString-Eigenschaft definiert, wird der Wert dieser Eigenschaft zurückgegeben (anstatt toString() aufzurufen), was zu unerwarteten Ergebnissen führen kann. 

Rufen wir andere Methoden für die von Napi::Value::ToString() zurückgegebenen Werte auf und definiert die Eingabe eine toString-Eigenschaft, kann eine Ausnahme auftreten, die meist zum Absturz des Prozesses führt. Dasselbe gilt für napi_coerce_to_number.

Anfälliges Muster:

  • Aufrufe wie Napi::String::Utf8Value() für einen Napi::Value, der aus ToString() oder ToNumber stammt, ohne vorherige ordnungsgemäße Typprüfung

Um diese Szenarien zu vermeiden, können Sie prüfen, ob der von Napi::Value::ToString() oder Napi::Value::ToNumber() zurückgegebene Wert ein String beziehungsweise eine Zahl ist, bevor Sie andere Methoden darauf aufrufen.

HINWEIS: Wie bei den zuvor erwähnten Fällen nicht behandelter Ausnahmen treten diese Probleme auf, wenn das Flag NAPI_DISABLE_CPP_EXCEPTIONS in der Datei binding.gyp gesetzt ist.

test_napi_unchecked_type.cpp

#include <napi.h>
#include <iostream>

Napi::Value Test1(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    // possible fix
    /*
        if (!info[0].IsString()) {
            return Napi::String::New(env, "TEST1 - Input is not a string");
        }
    */

    std::string data = info[0].As<Napi::String>().ToString().Utf8Value();

    std::cout << data << "\n";

    return Napi::String::New(env, "TEST1 - OK");
}

Napi::Value Test2(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    Napi::Object obj = info[0].As<Napi::Object>();

    std::string data = obj.Get("foo").ToString().Utf8Value();

    std::cout << data << "\n";

    return Napi::String::New(env, "TEST2 - OK");
}

Napi::Value Test3(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    double data = info[0].As<Napi::String>().ToNumber().DoubleValue();
    std::cout << data << "\n";

    return Napi::String::New(env, "TEST3 - OK");
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
    exports.Set(Napi::String::New(env, "test1"),Napi::Function::New(env, Test1));
    exports.Set(Napi::String::New(env, "test2"),Napi::Function::New(env, Test2));
    exports.Set(Napi::String::New(env, "test3"),Napi::Function::New(env, Test3));
    return exports;
}

NODE_API_MODULE(addon, Init)

Führen Sie diese Beispiele aus:

node main.js test5
node main.js test6
node main.js test7

Speicherlecks

Auswirkung: Offenlegung von Informationen

napi

Die napi-API bietet mehrere Methoden, um aus einem UTF8-, UTF16-LE- oder ISO-8859-1-codierten C-String einen JavaScript-String zu erstellen. Diese APIs sind:

Alle diese Methoden haben dieselbe Signatur:

napi_create_string_*(napi_env env, const char* str, size_t length, napi_value* result)

Der Wert, der sorgfältig geprüft werden muss, ist [in] length, also die Länge des Strings in Bytes. Wird dieser Wert von einem Angreifer kontrolliert oder fest codiert und ist der Eingabewert nicht vertrauenswürdig, können unerwartete Speicherwerte im Wert result gespeichert werden.

Um solche Probleme zu vermeiden, verwenden Sie für den Wert size_t length NAPI_AUTO_LENGTH.

Anfälliges Muster:

  • napi_create_string_* mit einem Wert für size_t length, der größer ist als die Länge von const char* str 

test_napi_memory_leak.c

#include <assert.h>
#include <node_api.h>

napi_value Test1(napi_env env, napi_callback_info info) {
    napi_status status;

    size_t argc = 1;

    napi_value args[1];

    status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
    assert(status == napi_ok);

    int32_t n;
    status = napi_get_value_int32(env, args[0], &n);
    assert(status == napi_ok);

    napi_value result;

    // leak n bytes

    status = napi_create_string_utf8(env, "X", n, &result);  

    // status = napi_create_string_utf16(env, u"X", n, &result);

    // status = napi_create_string_latin1(env, "X", n, &result);

    assert(status == napi_ok);

    return result;
}

#define DECLARE_NAPI_METHOD(name, func){ name, 0, func, 0, 0, 0, napi_default, 0 }

static napi_value Init(napi_env env, napi_value exports) {
    napi_status status;

    napi_property_descriptor desc[] = {
        DECLARE_NAPI_METHOD("test1", Test1),
    };

    status = napi_define_properties(env, exports, sizeof(desc) / sizeof(*desc), desc);
    assert(status == napi_ok);
    return exports;
}

NAPI_MODULE(addon, Init)

Führen Sie dieses Beispiel aus:

node main.js test8

Methodik

Um möglichst viele Probleme automatisch zu testen und zu finden, habe ich den folgenden Ansatz verwendet, um die Leistungsfähigkeit von Snyk Code zu nutzen:

  1. Erstellen eines Datensatzes mit npm-Paketen, die über NodeJS-Add-on-APIs C/C++ aufrufen

  2. Schreiben von Sicherheitsregeln in Snyk Code zur Modellierung von:

    1. Quellen: In diesem Kontext sind Quellen Werte aus JavaScript-Code, etwa Daten aus Napi::CallbackInfo::Env() im Kontext von napi oder aus napi_get_value_* im Kontext von node_api

    2. Senken: Je nach Sicherheitsproblem modellierte ich das Vorhandensein mehrerer Aufrufe von ThrowAsJavaScriptException innerhalb derselben Funktion, die assert-Prüfung sowie verschiedene Methoden zur Erstellung von String-Werten (um nur einige zu nennen). Ich berücksichtigte auch Fälle, in denen der Code aufgrund bestimmter Argumente nicht anfällig ist, etwa durch NAPI_AUTO_LENGTH bei Speicherlecks.

  3. Schreiben von Regeln, die die definierten Senken und Quellen für eine Taint-Analyse verwenden, um den Datenfluss von den Quellen zu den Senken nachzuverfolgen

  4. Verwenden der Quellen aus den bestehenden unterstützten Regeln (zum Beispiel Buffer Overflow oder Integer Overflow), damit ich noch mehr C/C++-Sicherheitslücken abdecken kann, nicht nur solche, die speziell NodeJS-Add-on-APIs verwenden

  5. Ausführen dieser Regeln für den zuvor erstellten Datensatz

  6. Manuelles Überprüfen der Ergebnisse und gegebenenfalls Erstellen eines PoC

Mit diesem Ansatz konnte ich mehrere Probleme in npm-Paketen finden, indem ich die relevanten APIs für NodeJS-Add-ons mit Snyk Code modellierte.

Bei einigen der gefundenen Probleme wählte ich jedoch eine Stichprobe von Projekten aus dem erstellten Datensatz aus und überprüfte sie manuell.

Ergebnisse

Im Rahmen dieser Untersuchung wurden mehrere Sicherheitslücken in Paketen gefunden. Sie sind unten aufgeführt.

Fazit

Persönlich war diese Untersuchung aus mehreren Gründen eine unglaublich lehrreiche Erfahrung. Ich hatte die Gelegenheit, tief in die Welt der NodeJS-Add-ons einzutauchen, vorhandene Literatur zu bestehenden Problemen zu lesen und mithilfe von Snyk Code einige Szenarien zu modellieren, um Probleme in einer großen Zahl von Repositories zu finden.

Obwohl ich mit JavaScript und vielen anderen Programmiersprachen recht vertraut bin, habe ich erst vor Kurzem begonnen, C/C++ zu lernen – aufgrund unserer Arbeit an der Unterstützung mehrerer Sicherheitsregeln, die Snyk Code-Kunden jetzt zur Verfügung stehen. Die Verbindung dieser beiden Aspekte – das Lernen und die Möglichkeit, mit Snyk Code verschiedene Sicherheitsprobleme zu modellieren – hat mir an dieser Untersuchung besonders viel Freude bereitet. Dafür möchte ich Snyk für diese Gelegenheit danken.

Quellen

Weiterlesen

feature insights context
Blog

Autonome Angriffe sind bereits Realität. Die Verteidigung muss Schritt halten.

Autonome Angreifer verkürzen das Zeitfenster für die Verteidigung. Erfahren Sie, wie kontinuierliches Erkennen, Beheben, Validieren und Verhindern Sicherheitsteams helfen kann, Schritt zu halten.

illustration hero ai
Blog

Der KI-Hurrikan ist da

KI beschleunigt gleichermaßen die Softwareentwicklung und Cyberangriffe. Führungskräfte müssen Agents und Code von Anfang an absichern, zur Laufzeit Kontrollen durchsetzen und Abwehrmaßnahmen unabhängig validieren.

feature insights context
Blog

Ist Prävention im Grunde ein gelöstes Problem?

Die Prävention in von Agenten generiertem Code ist architektonisch gelöst – die Herausforderung besteht jedoch weiterhin darin, Kontrollen zu wählen, die die Sicherheit schützen, ohne die Entwicklung zu verlangsamen.