Skip to content

A simple, multi-stage, WebAssembly-based security framework to protect web APIs from automated threats.

Notifications You must be signed in to change notification settings

SSL-ACTX/Sigilus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sigilus Logo

Sigilus Security

A simple, multi-stage, WebAssembly-based security framework to protect web APIs from automated threats.

License Python Version Node Version Emscripten


Sigilus is a simple multi-stage, WebAssembly-based security framework designed to protect web application APIs from automated threats, bots, and tampering. It establishes a dynamic, short-lived cryptographic trust between the client and server, ensuring that API requests originate from a legitimate, unmodified frontend application.


Architecture Overview

Sigilus employs a two-stage initialization process to provision a client-side request-signing VM. This ensures that only clients capable of executing the initial challenge can receive the primary security module.

sequenceDiagram
  autonumber

  participant Client as Browser Client
  participant Server as Server
  participant Redis as Redis

  Note over Client, Server: Stage 1: Proof of Execution & Provisioning

  Client ->> Server: GET /sigilus.js
  Server -->> Client: Dynamic JS Bundle (los.js, sigilus_client.js, etc.)

  Client ->> Client: 1. Initialize Loom of Secrets VM (los.wasm)
  Client ->> Client: 2. Execute bytecode → Generate "Woven Sigil" (Proof of Execution)

  Client ->> Server: POST /sigilus/meta (Headers: X-Sigilus-Dynamic-Key, Chrono, Phantom)

  Server ->> Server: 3. validate_woven_sigil() mirrors VM logic
  Server ->> Redis: 4. Check if Woven Sigil is already used

  alt Sigil is valid and unused
    Server ->> Redis: Burn Woven Sigil (prevent replay)
    Server -->> Client: 200 OK (Provisioning Data: PhantomID, Encrypted VM URL, Decryption Runes)
  else Sigil is invalid or replayed
    Server -->> Client: 403 Forbidden
  end

  Note over Client, Server: Stage 2: Main VM Operation (Request Signing)

  Client ->> Server: 5. GET /.../frame.enc (Encrypted VM)
  Server -->> Client: Encrypted WASM + Secret Seed

  Client ->> Client: 6. Decrypt frame.enc using Runes
  Client ->> Client: 7. Initialize Main Sigilus VM (sigilus_vm.wasm) with Secret Seed

  loop For every protected API request
    Client ->> Client: 8. Generate headers (Sigilus, OblivionSig, ChronoMark)
    Client ->> Server: POST /api/data (Headers: Sigilus, PhantomID, etc.)

    Server ->> Server: 9. Validate all headers
    Server ->> Redis: 10. Check if PhantomID is already used

    alt Request is valid and not replayed
      Server ->> Redis: Burn PhantomID
      Server -->> Client: 200 OK (API Data)
    else Invalid or replayed request
      Server -->> Client: 403 Forbidden
    end
  end
Loading

Key Features

  • WASM-Based Obfuscation: Moves critical security logic from easily readable JavaScript into a sandboxed, low-level WebAssembly binary, making reverse-engineering significantly more difficult.
  • Mini-VM Execution: Both WASM modules are minimalist, custom-built virtual machines with a very small, purpose-built instruction set, making static analysis and instrumentation challenging.
  • Multi-Stage VM Provisioning: A lightweight "Loom of Secrets" VM provides proof-of-execution before the server provisions the main, more powerful request-signing VM.
  • Dynamic VM Encryption & Key Rotation: The main WASM VM is encrypted on the server with a daily-rotating key. The client decrypts it at runtime, preventing static analysis of the security module.
  • Replay Attack Prevention: Utilizes a Redis-backed cache to "burn" unique identifiers (PhantomID for requests, Woven Sigil for provisioning), ensuring each cryptographic token is used only once.
  • Payload Integrity (OblivionSig): API request bodies are cryptographically signed using AES-GCM, preventing man-in-the-middle tampering between the client and server.
  • Timestamp Validation (ChronoMark): Requests are timestamped and validated against a server-defined time-drift tolerance, mitigating simple record-and-playback attacks.
  • Drop-in Integration: Provides simple integrators for Flask and Quart, along with an automatic JavaScript hook for fetch and XMLHttpRequest, requiring minimal changes to existing application code.
  • Automated Build & Packing: Includes Python scripts to compile the JS bundle and to pack/encrypt the WASM frame, simplifying the development and deployment workflow.

How It Works: The Security Flow

1. The Loom of Secrets (Proof of Execution)

  1. When the client first loads, it fetches the Sigilus JavaScript bundle, which includes the logic for a small "Loom of Secrets" WASM module (los.wasm).
  2. The client executes a predefined bytecode program (rune_pattern.js) inside this module. This bytecode is processed by a minimalist, custom-built virtual machine with a very small instruction set. The opcodes are designed specifically for the required cryptographic operations (e.g., string manipulation, HMAC, Base64 encoding), with a few decoy instructions to hinder analysis.
  3. The result is a Woven Sigil, a unique, short-lived token sent in the X-Sigilus-Dynamic-Key header. This serves as proof that the client successfully executed the initial WASM challenge.

2. Provisioning the Main VM

  1. The client sends the Woven Sigil to the /sigilus/meta endpoint.
  2. The server's sigilus_vm_validator.py meticulously simulates the exact same bytecode logic on the server side, mirroring the mini-VM's behavior.
  3. If the server's calculated Sigil matches the client's submitted Sigil (and it hasn't been seen before), the server trusts the client.
  4. The server then responds with provisioning data, which includes:
    • A server-authoritative PhantomID for the session.
    • The URL for the main, encrypted VM (frame.enc).
    • The necessary metadata ("runes") to decrypt it (date and salt).

3. Decryption and Initialization

  1. The client fetches the encrypted frame.enc.
  2. Using the Web Crypto API, it derives the AES decryption key from the runes and decrypts the frame.
  3. The decrypted payload is a JSON object containing the main sigilus_vm.wasm binary (as a Base64 string) and the secret rune_seed (the HMAC key).
  4. The client initializes the main Sigilus VM instance with this binary and securely stores the rune_seed in memory.

4. Signing API Requests

  1. The SigilusAutoHook automatically intercepts all outgoing fetch or XHR calls to protected routes.
  2. For each request, it calls the main WASM VM to generate a fresh set of security headers:
    • ChronoMark: A current timestamp.
    • Sigilus: An HMAC-based signature of the request path, PhantomID, and ChronoMark.
    • OblivionSig: An AES-GCM encrypted signature of the JSON request body, preventing tampering.
    • PhantomID: The persistent session identifier provided by the server.
  3. The server validates these headers on every request, checking signatures and using Redis to prevent PhantomID reuse.

Project Structure

Sigilus/
├── backend/
│   ├── sigilus_flask_integrator.py  # Drop-in Flask support
│   ├── sigilus_quart_integrator.py  # Drop-in Quart support
│   ├── sigilus_vm_validator.py      # Server-side validation of the Woven Sigil
│   ├── phantom_cache.py             # Redis/in-memory cache for replay protection
│   ├── phantom_core.py              # Core Python crypto logic
│   ├── wasm_packer.py               # Script to encrypt and pack the main VM
│   └── vm/
│       ├── los.cpp                  # C++ source for the Loom of Secrets VM
│       ├── sigilus_vm.cpp           # C++ source for the main request-signing VM
│       └── wasm_dist/               # Compiled WASM binaries
│
└── frontend/
    ├── js/
    │   ├── sigilus_client.js        # Main client orchestrator
    │   ├── sigilus_autohook.js      # Intercepts fetch/XHR
    │   ├── los.js                   # Emscripten wrapper for los.wasm
    │   ├── sigilus_vm.js            # Emscripten wrapper for sigilus_vm.wasm
    │   ├── aes_decrypt.js           # Web Crypto API decryption logic
    │   ├── frame_loader.js          # Logic to fetch and decrypt frame.enc
    │   ├── rune_pattern.js          # Bytecode for the Loom of Secrets
    │   └── phantom.bundle.min.js    # Final compiled JS bundle
    │
    ├── wasm/
    │   ├── frame.enc                # The encrypted main VM package
    │   └── runeset.json             # Metadata for decrypting frame.enc
    │
    └── build_phantom_bundle.py      # Script to build and minify the JS bundle

Installation & Setup

Prerequisites

  • Python 3.8+
  • Node.js and npx (for JS bundling/obfuscation)
  • Emscripten SDK (for compiling C++ to WASM)
  • A running Redis server

1. Backend Setup

  1. Install dependencies:

    pip install -r requirements.txt
    # requirements.txt should include:
    # Flask / Quart, python-dotenv, redis, pycryptodome, importlib-resources
  2. Configure Environment: Create a .env file in your project root with the following keys. These must be strong, randomly generated secrets.

    # Secret for generating PhantomIDs and other server-side tokens
    SIGILUS_KEY=your_strong_random_32_char_secret_key
    
    # The HMAC secret seed embedded inside the encrypted main VM
    SIGILUS_SEED=another_strong_random_32_char_secret
    
    # (Optional) An API key for future administrative functions
    SIGILUS_API_KEY=your_admin_api_key
    
    # Your Redis connection string
    REDIS_URL_URI=redis://localhost:6379/0

2. Frontend & WASM Build Process

This process must be run during your application's build/deployment pipeline.

  1. Compile WASM Modules (if not pre-compiled): Navigate to Sigilus/backend/vm/ and use Emscripten to compile los.cpp and sigilus_vm.cpp into their respective .wasm and .js wrappers in the wasm_dist/ directory.

  2. Pack the Main VM Frame: This script encrypts sigilus_vm.wasm and the SIGILUS_SEED into frame.enc.

    # From your project root
    python -m Sigilus.backend.wasm_packer

    This generates Sigilus/frontend/wasm/frame.enc and runeset.json.

  3. Build the JavaScript Bundle: This script concatenates, minifies, and optionally obfuscates all necessary JS files into a single bundle.

    # From your project root, navigate to the frontend directory
    cd Sigilus/frontend/
    
    # Basic build (minify only) and compress for serving
    python build_phantom_bundle.py --compress
    
    # For production (obfuscate, silence logs, and compress)
    python build_phantom_bundle.py --obs --silent --compress

Usage

Backend Integration (Quart Example)

# In your app.py
from quart import Quart, jsonify
from Sigilus.backend.sigilus_quart_integrator import SigilusQuartIntegrator

app = Quart(__name__)

# Initialize Sigilus and automatically protect all routes
# The integrator handles serving /sigilus.js, /sigilus/meta, and WASM assets.
sigilus = SigilusQuartIntegrator(app, protect_all_routes=True)

# Add the dynamic JS boot URL to your template context
@app.context_processor
def inject_sigilus():
    return dict(sigilus_js_boot_url=sigilus.js_boot_url)

# This route is now automatically protected by Sigilus
@app.route("/api/v1/user", methods=["POST"])
async def update_user():
    data = await request.get_json()
    # Your API logic here...
    return jsonify({"status": "ok", "user_id": data.get("id")})

# To protect a specific route, set protect_all_routes=False
# and use the decorator.
# sigilus_manual = SigilusQuartIntegrator(app, protect_all_routes=False)
# @app.route("/api/protected")
# @sigilus_manual.sigilus_guard
# async def protected_endpoint():
#     return jsonify({"secret": "data"})

Note: The sigilus_flask_integrator provides an identical API for Flask applications.

Frontend Integration

In your main HTML template (e.g., index.html), simply include the script tag pointing to the dynamic JS endpoint.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Secure App</title>
</head>
<body>
    <h1>Welcome</h1>
    <script src="{{ sigilus_js_boot_url }}"></script>
    <script src="/path/to/your/app.js"></script>
</body>
</html>

The Sigilus client will initialize automatically. The SigilusAutoHook will intercept fetch and XMLHttpRequest calls, adding the necessary security headers.

For advanced use cases where you need to ensure Sigilus is ready before making a manual request, you can use the ready() promise:

// in your app.js
async function doSomethingSecure() {
    try {
        await SigilusClient.ready(); // Wait for initialization to complete
        const response = await fetch('/api/v1/user', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ id: 123, name: 'Alice' })
        });
        const data = await response.json();
        console.log('Server response:', data);
    } catch (error) {
        console.error('Sigilus failed to initialize or request failed:', error);
    }
}

doSomethingSecure();

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

About

A simple, multi-stage, WebAssembly-based security framework to protect web APIs from automated threats.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published