Skip to content

About

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

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

28 Commits

Folders and files

Repository files navigation

Zentinel

JavaScript agent

Runs one JavaScript file (QuickJS) on request and response headers to allow, block, redirect or rewrite headers.

CI License: Apache-2.0 Rust: 1.94.1+ Agent protocol: v2

Registry · Docs · All repositories

For request logic that is too specific for a built-in filter and too small for a compiled agent. The agent loads one script at startup and calls its on_request_headers(request) and on_response_headers(response) functions; whatever object they return becomes the decision. A function the script does not define allows. A script error blocks with 500 unless the agent runs with --fail-open; if the agent itself is unreachable, the filter's failure-mode decides.

Quick start

zentinel bundle install js

cat > handler.js <<'EOF'
function on_request_headers(request) {
    if (request.uri.startsWith("/admin")) {
        return { decision: "block", status: 403, body: "Forbidden" };
    }
    return { decision: "allow", add_request_headers: { "X-Processed-By": "js-agent" } };
}
EOF

zentinel-js-agent --script handler.js --socket /tmp/zentinel-js.sock
system {
    worker-threads 0
}

listeners {
    listener "http" {
        address "127.0.0.1:8080"
        protocol "http"
    }
}

agents {
    agent "js" type="custom" {
        unix-socket "/tmp/zentinel-js.sock"
        events "request_headers" "response_headers"
        timeout-ms 100
    }
}

filters {
    filter "js" {
        type "agent"
        agent "js"
        failure-mode "closed"
    }
}

routes {
    route "default" {
        matches {
            path-prefix "/"
        }
        upstream "backend"
        filters "js"
    }
}

upstreams {
    upstream "backend" {
        target "127.0.0.1:3000"
    }
}

Install

zentinel bundle install js

Or from the repository (the crate is not on crates.io):

cargo install --git https://github.com/zentinelproxy/zentinel-agent-js

Release binaries (zentinel-js-agent-<version>-<platform>.tar.gz) for linux-x86_64, linux-aarch64 and darwin-aarch64 are on the releases page. From source: cargo build --release.

Configuration

Option Default Meaning
--script / JS_SCRIPT required JavaScript file to load at startup
--socket / AGENT_SOCKET /tmp/zentinel-js.sock Unix socket to serve agent protocol v2 on
--grpc-address / GRPC_ADDRESS unset Serve gRPC on this address (e.g. 127.0.0.1:50052) instead of the Unix socket; the proxy then connects with grpc "http://..."
--fail-open / FAIL_OPEN false On a script error, allow (tags js-error, fail-open) instead of blocking with 500
--verbose, -v / JS_VERBOSE false Debug logging

Up to 0.3.0 the agent served gRPC only, on 0.0.0.0:50052 by default; it now serves the Unix socket unless --grpc-address is given. The script is read once; restart the agent to change it. Zentinel 26.10_8 and later send the agent block's config { } to the agent on every connection (releases up to 0.6.50 never did; over gRPC only a build against zentinel-agent-protocol 0.6.51 or later receives it, which the lockfile's 0.6.50 is not). The agent applies it: script (inline JavaScript, replacing the file's), fail-open and max-memory (bytes). A block that leaves out fail-open sets it to false, whatever --fail-open says. A block the agent cannot parse, or whose script fails to load, is refused; Zentinel then does not use the agent and its filters' failure-mode applies. Without a config { } block the flags above are all there is.

Script API

The request argument has method, uri (path and query), client_ip, correlation_id and headers. The response argument has status, correlation_id and headers. Header names are lowercase (user-agent, not User-Agent), repeated headers are joined with ", ", and the request headers include the pseudo-headers :method and :path.

The returned object:

Field Meaning
decision "allow" (default), "block" / "deny", or "redirect"
status Status for block (default 403) or redirect (default 302)
body Response body for block; target URL for redirect
add_request_headers, remove_request_headers Object of headers to set / array of names to remove upstream
add_response_headers, remove_response_headers The same for the response to the client
tags Audit tags

console.log, console.warn and console.error write to the agent's log. There is no other host API (no network, no filesystem).

function on_request_headers(request) {
    const ua = (request.headers["user-agent"] || "").toLowerCase();
    for (const bot of ["sqlmap", "nikto", "masscan"]) {
        if (ua.includes(bot)) {
            return { decision: "block", status: 403, tags: ["bot-blocked", bot] };
        }
    }
    if (!request.uri.startsWith("/public/") && !request.headers["authorization"]) {
        return { decision: "block", status: 401, body: "Authentication required" };
    }
    return { decision: "allow" };
}

function on_response_headers(response) {
    return {
        decision: "allow",
        add_response_headers: { "X-Content-Type-Options": "nosniff" }
    };
}

New since release 0.3.0 (whose binary was built from 0.2.0 code): on_request_body_chunk, on_response_body_chunk and on_websocket_frame hooks, a cached script context, a 64 MiB QuickJS memory limit, and the Unix socket transport.

Development

cargo test
cargo clippy --all-targets -- -D warnings

scripts/smoke-test.sh runs the release binary behind a real Zentinel with the shared harness (agent-smoke/smoke.py from zentinelproxy/.github), loading scripts/smoke/block-admin.js: /admin and paths under it get 403, other paths are forwarded with the header the script adds, and stopping the agent changes the answer. CI runs it on every push; a release runs it on every platform before anything is published.

AGENT_SMOKE=../.github/agent-smoke/smoke.py scripts/smoke-test.sh target/release/zentinel-js-agent ./zentinel

Related

License

Apache-2.0, see LICENSE.


Part of Zentinel, the security-first reverse proxy. The shisa guards the gate.

About

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

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages