Chapter 13: Security

A production graph database must protect the data it holds. AstraeaDB provides layered security: token-based authentication with role-based access control, TLS encryption for data in transit, an experimental homomorphic encryption engine for privacy-sensitive workloads, and comprehensive audit logging. This chapter walks through each layer, from the simplest configuration to the most advanced.

13.1 Authentication (API Keys and RBAC)

AstraeaDB uses token-based authentication combined with Role-Based Access Control (RBAC). Every client connection must present a valid authentication token, and the token determines what operations the client is allowed to perform.

The Three Roles

AstraeaDB defines three built-in roles, each a strict superset of the one below it:

RolePermissionsTypical Use
Reader Query nodes and edges, run graph algorithms, execute vector searches, read properties Dashboard applications, reporting services, read-only API endpoints
Writer All Reader permissions plus create, update, and delete nodes and edges Application backends, ETL pipelines, data ingestion services
Admin All Writer permissions plus user management, configuration changes, audit log access, backup/restore Operations teams, database administrators

Connecting with Authentication

Pass your authentication token when creating the client connection. Here is how it looks in each supported language:

from astraeadb import AstraeaClient

# Connect with a Writer token
with AstraeaClient("127.0.0.1", 7687, auth_token="my-secret-token") as client:
    # This succeeds with Writer or Admin role
    client.create_node(["Person"], {"name": "Alice", "age": 30})

    # This succeeds with any role (Reader, Writer, or Admin)
    result = client.query(
        'MATCH (p:Person) RETURN p.name'
    )
    print(result)
source("r_client.R")

client <- AstraeaClient$new("127.0.0.1", 7687, auth_token = "my-secret-token")
client$connect()

# Create a node (requires Writer or Admin)
client$create_node(c("Person"), list(name = "Alice", age = 30))

# Query (any role)
result <- client$query("MATCH (p:Person) RETURN p.name")
print(result)
client$close()
package main

import (
    "context"
    "fmt"
    "github.com/AstraeaDB/AstraeaDB-Official"
)

func main() {
    client := astraeadb.NewClient(
        astraeadb.WithAddress("127.0.0.1", 7687),
        astraeadb.WithAuthToken("my-secret-token"),
    )
    ctx := context.Background()
    client.Connect(ctx)
    defer client.Close()

    // Create a node (requires Writer or Admin)
    client.CreateNode(ctx, []string{"Person"}, map[string]any{
        "name": "Alice",
        "age":  30,
    })

    // Query (any role)
    result, _ := client.Query(ctx, "MATCH (p:Person) RETURN p.name")
    fmt.Println(result)
}
import com.astraeadb.unified.UnifiedClient;
import java.util.List;
import java.util.Map;

try (var client = UnifiedClient.builder()
        .host("127.0.0.1").port(7687)
        .authToken("my-secret-token")
        .build()) {
    client.connect();

    // Create a node (requires Writer or Admin)
    client.createNode(
        List.of("Person"),
        Map.of("name", "Alice", "age", 30)
    );

    // Query (any role)
    var result = client.query("MATCH (p:Person) RETURN p.name");
    System.out.println(result);
}

Permission Denied Errors

If a client attempts an operation that exceeds its role, AstraeaDB returns a clear error. For example, a Reader token attempting to create a node:

# Python example: Reader trying to write
with AstraeaClient("127.0.0.1", 7687, auth_token="reader-only-token") as client:
    try:
        client.create_node(["Person"], {"name": "Bob"})
    except PermissionError as e:
        print(e)
        # PermissionError: role 'Reader' does not have 'write' permission
Token Management Store authentication tokens securely. Never hard-code tokens into application source code. Use environment variables, secret management systems (e.g., HashiCorp Vault, AWS Secrets Manager), or configuration files with restricted filesystem permissions.

13.2 TLS and Mutual TLS (mTLS)

By default, AstraeaDB communicates over unencrypted TCP. For production deployments, you should enable TLS (Transport Layer Security) to encrypt all traffic between clients and the server.

Standard TLS

Standard TLS encrypts the connection and verifies the server's identity. The client trusts the server based on its certificate, but the server accepts any client that connects.

Configure TLS in your astraeadb.toml:

# astraeadb.toml - TLS Configuration

[tls]
cert_path = "/path/to/server.crt"    # Server certificate (PEM format)
key_path  = "/path/to/server.key"    # Server private key (PEM format)

With TLS enabled, the server only accepts encrypted connections. Clients must connect using TLS:

from astraeadb import AstraeaClient

with AstraeaClient(
    "db.example.com", 7687,
    auth_token="my-secret-token",
    tls=True,
    ca_cert="/path/to/ca.crt"    # CA that signed the server cert
) as client:
    result = client.ping()
    print(result)
source("r_client.R")

client <- AstraeaClient$new(
    "db.example.com", 7687,
    auth_token = "my-secret-token",
    tls = TRUE,
    ca_cert = "/path/to/ca.crt"
)
client$connect()
result <- client$ping()
print(result)
client$close()
package main

import (
    "context"
    "fmt"
    "github.com/AstraeaDB/AstraeaDB-Official"
)

func main() {
    client := astraeadb.NewClient(
        astraeadb.WithAddress("db.example.com", 7687),
        astraeadb.WithAuthToken("my-secret-token"),
        astraeadb.WithTLS("/path/to/ca.crt"),
    )
    ctx := context.Background()
    client.Connect(ctx)
    defer client.Close()

    result, _ := client.Ping(ctx)
    fmt.Println(result)
}
import com.astraeadb.unified.UnifiedClient;

try (var client = UnifiedClient.builder()
        .host("db.example.com").port(7687)
        .authToken("my-secret-token")
        .tls(true)
        .caCert("/path/to/ca.crt")
        .build()) {
    client.connect();
    var result = client.ping();
    System.out.println(result);
}

Mutual TLS (mTLS)

Standard TLS verifies the server to the client. Mutual TLS goes further: both the client and the server present certificates. This provides two-way identity verification and is the gold standard for zero-trust network architectures.

With mTLS, the client certificate's Common Name (CN) is mapped to an RBAC role. This eliminates the need for separate API tokens—the certificate itself is the credential.

Server Configuration

# astraeadb.toml - Mutual TLS Configuration

[tls]
cert_path    = "/path/to/server.crt"    # Server certificate
key_path     = "/path/to/server.key"    # Server private key
ca_cert_path = "/path/to/ca.crt"       # CA cert to verify client certs

[tls.client_role_mapping]
# Map certificate CN to RBAC role
"analytics-service" = "Reader"
"ingestion-pipeline" = "Writer"
"ops-team" = "Admin"

How mTLS Authentication Works

  1. The client connects and presents its certificate (signed by the trusted CA).
  2. The server verifies the client certificate against the CA certificate at ca_cert_path.
  3. The server extracts the Common Name (CN) from the client certificate.
  4. The CN is matched against the client_role_mapping to determine the RBAC role.
  5. If the CN is not in the mapping, the connection is rejected.
When to use mTLS Use mTLS when your services run in a zero-trust network, when you need to authenticate services (not just users), or when your organization already has a PKI (Public Key Infrastructure) in place. mTLS is especially valuable for inter-service communication in microservice architectures where API tokens can be difficult to rotate securely.

13.3 Homomorphic Encryption

AstraeaDB includes an experimental homomorphic encryption engine for workloads where even the database server should not see the unencrypted data. This is a cutting-edge research feature designed for privacy-sensitive domains such as banking and healthcare.

The Concept

In a traditional database, the server must decrypt data to query it. This means anyone with access to the server (administrators, attackers who breach the perimeter) can read the data. Homomorphic encryption allows computations on encrypted data without ever decrypting it. The server stores encrypted values, performs matching on encrypted values, and returns encrypted results that only the client can decrypt.

How It Works in AstraeaDB

AstraeaDB supports deterministic encrypted label matching. The workflow is as follows:

Client Server ------ ------ 1. Encrypt node labels with secret key --- store encrypted ---> 2. Store encrypted labels on disk 3. Encrypt query labels with same key --- encrypted query ---> 4. Match encrypted query labels against encrypted stored labels (deterministic comparison) <--- encrypted results --- 5. Decrypt results with secret key

Because the encryption is deterministic (the same plaintext always produces the same ciphertext), the server can perform equality matching without ever knowing the underlying values.

Use Cases

DomainScenarioBenefit
Banking Query customer relationship graphs without exposing PII (names, account numbers) Database administrators cannot see customer data even with full server access
Healthcare Analyze patient interaction graphs while maintaining HIPAA compliance Research teams can run graph queries without being exposed to protected health information
Government Cross-agency intelligence sharing on classified graphs Each agency can query shared infrastructure without revealing sensitive entity names
Research Feature Homomorphic encryption in AstraeaDB currently supports basic label matching (equality comparisons on encrypted labels). Full query computation over encrypted data (range queries, aggregations, traversals over encrypted edge weights) is an active area of research and is not yet supported. Performance overhead is significant compared to plaintext operations. Use this feature only when the privacy requirements justify the cost.

Enabling Homomorphic Encryption

# astraeadb.toml - Homomorphic Encryption

[encryption]
homomorphic_enabled = true
# The encryption keys are managed client-side.
# The server never holds the decryption key.

13.4 Audit Logging

Every operation in AstraeaDB can be recorded in an audit log. This provides a tamper-evident record of who did what, when, and from where—essential for compliance, incident investigation, and security monitoring.

What Gets Logged

Each audit log entry contains:

Storage and Retention

Audit logs are stored in a circular buffer that maintains recent operation history in memory for fast access. Older entries are flushed to disk. The buffer size is configurable:

# astraeadb.toml - Audit Logging

[audit]
enabled       = true
buffer_size   = 100000    # Number of entries in the circular buffer
flush_to_disk = true
log_path      = "/var/log/astraeadb/audit.log"

Accessing the Audit Log

Admin-role users can query the audit log through the client API:

# Python: retrieve recent audit entries
with AstraeaClient("127.0.0.1", 7687, auth_token="admin-token") as client:
    entries = client.audit_log(limit=50)
    for entry in entries:
        print(f"[{entry['timestamp']}] "
              f"{entry['user']} "
              f"{entry['operation']} "
              f"{entry['result']}")

Example output:

[2026-02-21T14:30:01.123Z] writer-svc  CREATE_NODE  success
[2026-02-21T14:30:01.456Z] reader-app  QUERY        success
[2026-02-21T14:30:02.789Z] reader-app  CREATE_NODE  DENIED (insufficient permissions)
[2026-02-21T14:30:03.012Z] admin-ops   CONFIG_CHANGE success
Integration with SIEM The audit log output is structured JSON, making it easy to ingest into Security Information and Event Management (SIEM) platforms like Splunk, Elastic Security, or Microsoft Sentinel. Forward the log file using your preferred log shipper (Filebeat, Fluentd, Vector) for centralized monitoring and alerting.

Security Summary

AstraeaDB's security model is designed in layers. Each layer addresses a different threat:

LayerProtects AgainstMechanism
Authentication (RBAC) Unauthorized access Token-based identity with role-based permissions
TLS / mTLS Eavesdropping, man-in-the-middle attacks Encrypted transport with certificate-based identity
Homomorphic Encryption Insider threats, server compromise Encrypted-at-rest with server-side blind matching
Audit Logging Undetected misuse, compliance gaps Complete operation history with identity attribution
← Chapter 12: Graph Neural Networks Chapter 14: Performance and Scaling →