| Crates.io | sentinel-agent-js |
| lib.rs | sentinel-agent-js |
| version | 0.1.0 |
| created_at | 2026-01-13 16:15:33.864461+00 |
| updated_at | 2026-01-13 16:15:33.864461+00 |
| description | JavaScript scripting agent for Sentinel reverse proxy - embed custom JS logic |
| homepage | https://sentinel.raskell.io |
| repository | https://github.com/raskell-io/sentinel-agent-js |
| max_upload_size | |
| id | 2040532 |
| size | 152,541 |
JavaScript scripting agent for Sentinel reverse proxy. Write custom request/response processing logic in JavaScript.
cargo install sentinel-agent-js
git clone https://github.com/raskell-io/sentinel-agent-js
cd sentinel-agent-js
cargo build --release
sentinel-js-agent --socket /var/run/sentinel/js.sock \
--script /etc/sentinel/scripts/handler.js
| Option | Environment Variable | Description | Default |
|---|---|---|---|
--socket |
AGENT_SOCKET |
Unix socket path | /tmp/sentinel-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 |
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" };
}
| Hook | Description |
|---|---|
on_request_headers(request) |
Called when request headers are received |
on_response_headers(response) |
Called when response headers are received |
{
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..."
}
}
{
status: 200, // HTTP status code
correlation_id: "abc123", // Request correlation ID
headers: { // Response headers (name -> value)
"Content-Type": "application/json",
"X-Custom": "value"
}
}
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" };
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"
}
};
}
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" };
}
function on_request_headers(request) {
console.log("Processing request:", request.uri);
console.warn("Warning message");
console.error("Error message");
return { decision: "allow" };
}
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" };
}
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
}
};
}
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" };
}
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"
}
};
}
agents {
agent "js" {
type "custom"
transport "unix_socket" {
path "/var/run/sentinel/js.sock"
}
events ["request_headers", "response_headers"]
timeout-ms 100
failure-mode "open"
}
}
When --fail-open is enabled, script errors will:
js-error and fail-open tags to audit metadataWhen --fail-open is disabled (default), script errors will:
js-error tag to audit metadata| Feature | sentinel-agent-js | sentinel-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 sentinel-agent-js for:
Use sentinel-agent-lua for:
# Run with debug logging
RUST_LOG=debug cargo run -- --socket /tmp/test.sock --script ./test.js --verbose
# Run tests
cargo test
Apache-2.0