Skip to content

zentinelproxy/zentinel-agent-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zentinel-agent-js

JavaScript scripting agent for Zentinel reverse proxy. Write custom request/response processing logic in JavaScript.

Features

  • Execute JavaScript scripts on request/response lifecycle events
  • Fast, lightweight QuickJS engine (via rquickjs)
  • Console API for logging (console.log, console.warn, console.error)
  • Return-based decision model (allow, block, redirect)
  • Header manipulation (add/remove request and response headers)
  • Fail-open mode for graceful error handling

Installation

Using Bundle (Recommended)

# Install just this agent
zentinel bundle install js

# Or install all bundled agents
zentinel bundle install

The bundle command downloads the correct binary for your platform and places it in the standard location. See the bundle documentation for details.

Using Cargo

cargo install zentinel-agent-js

From Source

git clone https://github.com/zentinelproxy/zentinel-agent-js
cd zentinel-agent-js
cargo build --release

Usage

zentinel-js-agent --socket /var/run/zentinel/js.sock \
  --script /etc/zentinel/scripts/handler.js

Command Line Options

Option Environment Variable Description Default
--socket AGENT_SOCKET Unix socket path /tmp/zentinel-js.sock
--script JS_SCRIPT JavaScript script file (required)
--verbose JS_VERBOSE Enable debug logging false
--fail-open FAIL_OPEN Allow requests on script errors false

Writing Scripts

Basic Example

function on_request_headers(request) {
    // Block admin access
    if (request.uri.includes("/admin")) {
        return { decision: "block", status: 403, body: "Forbidden" };
    }

    // Allow all other requests
    return { decision: "allow" };
}

Available Hooks

Hook Description
on_request_headers(request) Called when request headers are received
on_response_headers(response) Called when response headers are received

Request Object

{
    method: "GET",           // HTTP method
    uri: "/api/users",       // Request URI with query string
    client_ip: "192.168.1.1", // Client IP address
    correlation_id: "abc123", // Request correlation ID
    headers: {               // Request headers (name -> value)
        "Content-Type": "application/json",
        "User-Agent": "Mozilla/5.0..."
    }
}

Response Object

{
    status: 200,             // HTTP status code
    correlation_id: "abc123", // Request correlation ID
    headers: {               // Response headers (name -> value)
        "Content-Type": "application/json",
        "X-Custom": "value"
    }
}

Return Values

Scripts should return a decision object:

// Allow the request
return { decision: "allow" };

// Block with custom status and body
return { decision: "block", status: 403, body: "Access Denied" };

// Redirect to another URL
return { decision: "redirect", status: 302, body: "https://example.com/login" };

Header Manipulation

function on_request_headers(request) {
    return {
        decision: "allow",
        add_request_headers: {
            "X-Processed-By": "js-agent",
            "X-Client-IP": request.client_ip
        },
        remove_request_headers: ["X-Debug"],
        add_response_headers: {
            "X-Frame-Options": "DENY"
        }
    };
}

Audit Tags

Add tags for logging and analytics:

function on_request_headers(request) {
    if (request.headers["User-Agent"]?.includes("bot")) {
        return {
            decision: "allow",
            tags: ["bot-detected", "monitoring"]
        };
    }
    return { decision: "allow" };
}

Console Logging

function on_request_headers(request) {
    console.log("Processing request:", request.uri);
    console.warn("Warning message");
    console.error("Error message");
    return { decision: "allow" };
}

Examples

Block Bad User-Agents

function on_request_headers(request) {
    const ua = request.headers["User-Agent"] || "";
    const badBots = ["sqlmap", "nikto", "nessus", "masscan"];

    for (const bot of badBots) {
        if (ua.toLowerCase().includes(bot)) {
            return {
                decision: "block",
                status: 403,
                tags: ["bot-blocked", bot]
            };
        }
    }
    return { decision: "allow" };
}

Rate Limit by Path

function on_request_headers(request) {
    // Add rate limit tier based on path
    let tier = "standard";
    if (request.uri.startsWith("/api/v1/")) {
        tier = "api";
    } else if (request.uri.startsWith("/admin/")) {
        tier = "admin";
    }

    return {
        decision: "allow",
        add_request_headers: {
            "X-Rate-Limit-Tier": tier
        }
    };
}

Require Authentication

function on_request_headers(request) {
    // Skip for public paths
    if (request.uri.startsWith("/public/") || request.uri === "/health") {
        return { decision: "allow" };
    }

    // Check for auth header
    if (!request.headers["Authorization"]) {
        return {
            decision: "block",
            status: 401,
            body: "Authentication required"
        };
    }

    return { decision: "allow" };
}

Add Security Headers

function on_response_headers(response) {
    return {
        decision: "allow",
        add_response_headers: {
            "X-Content-Type-Options": "nosniff",
            "X-Frame-Options": "DENY",
            "X-XSS-Protection": "1; mode=block",
            "Strict-Transport-Security": "max-age=31536000"
        }
    };
}

Zentinel Proxy Configuration

agents {
    agent "js" {
        type "custom"
        transport "unix_socket" {
            path "/var/run/zentinel/js.sock"
        }
        events "request_headers" "response_headers"
        timeout-ms 100
        failure-mode "open"
    }
}

Error Handling

When --fail-open is enabled, script errors will:

  • Log the error
  • Allow the request to proceed
  • Add js-error and fail-open tags to audit metadata

When --fail-open is disabled (default), script errors will:

  • Log the error
  • Block the request with 500 status
  • Add js-error tag to audit metadata

Comparison with zentinel-agent-lua

Feature zentinel-agent-js zentinel-agent-lua
Engine QuickJS mlua (Lua 5.4)
Scripting Single script file Multiple scripts with metadata
Hot Reload No Yes
VM Pooling No Yes
Resource Limits Minimal Comprehensive (memory, CPU, time)
Standard Library Basic (console) Rich (JSON, crypto, regex, etc.)
Use Case Simple scripts Production workloads

Use zentinel-agent-js for:

  • Simple request filtering logic
  • Quick prototyping
  • Lightweight deployments

Use zentinel-agent-lua for:

  • Complex processing logic
  • Production environments with strict resource limits
  • Multiple scripts with hot reload

Development

# Run with debug logging
RUST_LOG=debug cargo run -- --socket /tmp/test.sock --script ./test.js --verbose

# Run tests
cargo test

License

Apache-2.0

About

JavaScript scripting agent for Zentinel reverse proxy - embed custom JS logic

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors