marasi

package module
v0.0.0-...-46118c5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 20, 2026 License: MIT Imports: 41 Imported by: 1

README

Marasi

GoDoc

Marasi

A Go library for building application security testing proxies.

marasi.app

Features

  • HTTP/HTTPS Proxy: TLS-capable proxy server with certificate management
  • Request/Response Interception: Modify traffic in real-time
  • Lua Extensions: Scriptable proxy behavior with built-in extensions
  • Application Data Format: SQLite-based storage for all proxy data (requests, responses, metadata)
  • Launchpad: Replay and modify HTTP requests
  • Scope Management: Filter traffic with inclusion/exclusion rules
  • Waypoints: Override hostnames for request routing
  • Chrome Integration: Auto-configure Chrome with proxy settings

Core Components

Proxy Engine
  • Built on Google Martian Proxy
  • Automatic content decoding
  • Content prettification (JSON, XML, HTML)
  • Concurrent request/response processing
Extension System

Lua based extension support, three built-in extensions:

  • Compass: Scope management and traffic filtering
  • Checkpoint: Request/response interception rules
  • Workshop: Lua development environment

The ability to add custom extensions coming soon.

Application File Format

SQLite-based application file format stores:

  • Complete request/response data with timing
  • Extension management and settings
  • Launchpad entries for request replay
  • Comprehensive logging system
  • User notes and hostname waypoints

Library Usage

This library is designed for building web appilcation security proxies.

Basic integration (for full implementation see marasi-app):

// Create proxy with options
proxy, err := marasi.New(
    marasi.WithConfigDir("/path/to/config"),
    marasi.WithDatabase(repo),
)

// Set up handlers for your application
proxy.WithOptions(
    marasi.WithRequestHandler(func(req marasi.ProxyRequest) error {
        // Handle Requests in your application
        return nil
    }),
    marasi.WithResponseHandler(func(res marasi.ProxyResponse) error {
        // Handle Responses in your application
        return nil
    }),
    marasi.WithLogHandler(func(logItem marasi.Log) error {
        // Handle log messages
        return nil
    }),
    marasi.WithInterceptHandler(func(intercepted *marasi.Intercepted) error {
        switch intercepted.Type {
        case "request":
        // Handle Request interception
        case "response":
        // Handle Response interception
        }
        return nil
    }),
)

// Start proxy server
listener, err := proxy.GetListener("127.0.0.1", "8080")
proxy.Serve(listener)

GitHub Discussion

Use the GitHub Discussion to provide feedback, ask questions or discuss the project.

Documentation

Overview

Package marasi provides an HTTP/HTTPS proxy server with extension support, request/response interception, and SQLite database storage. It is designed to be decoupled from GUI implementations and provides methods to load handlers for building security testing tools, traffic analysis, and HTTP manipulation applications.

The core functionality includes:

  • HTTP/HTTPS proxy server with TLS certificate management
  • Lua-based extension system for request/response processing
  • Request/response interception and modification
  • SQLite database storage for traffic analysis
  • Scope-based filtering system
  • Chrome browser integration for testing
  • Launchpad system for organizing test requests

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrDropped is returned when the request / response should be dropped completely.
	// The request / response will not be processed by any modifier and will not continue to the client / server
	ErrDropped = errors.New("item dropped by extension or user")

	// ErrSkipPipeline is returned to stop the modifier pipeline for a request / response.
	// The request / response will still continue but won't be processed by any future modifiers
	ErrSkipPipeline = errors.New("stop processing item")

	// ErrMetadata is returned when metadata is invalid or missing
	ErrMetadataNotFound = errors.New("invalid or missing metadata")

	// ErrExtensionNotFound is returned when extension is invalid or missing
	ErrExtensionNotFound = errors.New("invalid or missing extension")

	// ErrRequestIDNotFound is returned when requestID is not found
	ErrRequestIDNotFound = errors.New("invalid or missing requestID")

	// ErrRebuildRequest is returned when the request is malformed and cannot be rebuilt
	ErrRebuildRequest = errors.New("cannot rebuild request")

	// ErrRebuildResponse is returned when the response is malformed and cannot be rebuilt
	ErrRebuildResponse = errors.New("cannot rebuild response")

	// ErrRequestHandlerUndefined is returned when the request handler is undefined
	ErrRequestHandlerUndefined = errors.New("no request handler defined")

	// ErrResponseHandlerUndefined is returned when the response handler is undefined
	ErrResponseHandlerUndefined = errors.New("no response handler defined")

	// ErrProxyRequest is returned when ProxyRequest cannot be created
	ErrProxyRequest = errors.New("failed to create proxyrequest")

	// ErrProxyResponse is returned when ErrProxyResponse cannot be created
	ErrProxyResponse = errors.New("failed to create proxyresponse")

	// ErrReadBody is returned when there is an error with reading the response body
	ErrReadBody = errors.New("failed to read the body")
)
View Source
var (
	// ErrConfigDirNotSet is returned when the configuration directory is not set.
	ErrConfigDirNotSet = errors.New("config dir not set")
	// ErrScopeNotFound is returned when the scope is not found in the proxy.
	ErrScopeNotFound = errors.New("scope field is not found")
	// ErrClientNotFound is returned when the HTTP client is not found in the proxy.
	ErrClientNotFound = errors.New("http client field not found")
	// ErrExtensionRepoNotFound is returned when the extension repository is not found.
	ErrExtensionRepoNotFound = errors.New("extension repo not found")
)
View Source
var (
	// ErrSessionContext is returned when the session could not be fetch from context
	ErrSessionContext = errors.New("failed to get session from context")
)

Functions

func BufferStreamingBodyModifier

func BufferStreamingBodyModifier(proxy *Proxy, res *http.Response) error

BufferStreamingBodyModifier reads the entire streaming response body into memory and replaces the `res.Body` with a new `io.NopCloser` on the full body. It will remove the `Transfer-Encoding` and update the `Content-Length` to reflect the new body.

func CheckpointRequestModifier

func CheckpointRequestModifier(proxy *Proxy, req *http.Request) error

CheckpointRequestModifier will intercept requests if the global `proxy.InterceptFlag` is set or the `interceptRequest` function returns true. If a request is intercepted, the modifier will block until the user decides to resume or drop the request. If the request is resumed it will be rebuilt with the same context and metadata from the modified raw request. The metadata will be updated to include "intercepted", "original-request", and "dropped" based on the user action. If the modifier receives `ShouldInterceptResponse` the flag is added to the context so that the response is intercepted regardless of the `processResponse` or `proxy.InterceptFlag`

func CheckpointResponseModifier

func CheckpointResponseModifier(proxy *Proxy, res *http.Response) error

CheckpointResponseModifier will intercept response if the global `proxy.InterceptFlag` is set, `interceptResponse` function returns true, or if the context has an intercept flag set as true. If a response is intercepted, the modifier will block until the user decides to resume or drop the response. If the response is resumed it will be rebuilt with the same context and metadata from the modified raw response. The metadata will be updated to include "intercepted", "original-response", and "dropped" based on the user action.

func CompassRequestModifier

func CompassRequestModifier(proxy *Proxy, req *http.Request) error

CompassRequestModifier will run the `processRequest` function in the compass extension to determine if the request is in scope. After `processRequest`, it will check if the request is passed through (nil), skipped (`ErrSkipPipeline`), or dropped (`ErrDropped`). If the compass extension is not found the modifier will return `ErrExtensionNotFound` as "compass" is considered a core extension.

func CompassResponseModifier

func CompassResponseModifier(proxy *Proxy, res *http.Response) error

CompassResponseModifier will run the `processResponse` function in the compass extension to determine if the response is in scope. After `processResponse`, it will check if the response is passed through (nil), skipped (`ErrSkipPipeline`), or dropped (`ErrDropped`). If the compass extension is not found the modifier will return `ErrExtensionNotFound` as "compass" is considered a core extension.

func CompressedResponseModifier

func CompressedResponseModifier(proxy *Proxy, res *http.Response) error

CompressedResponseModifier decompresses the response bodies and replaces the `res.Body` with the decompressed data. It will remove the "Content-Encoding" header and update the "Content-Length" to the new length. Currently the modifier handles gzip and br compressed bodies.

func ExtensionsRequestModifier

func ExtensionsRequestModifier(proxy *Proxy, req *http.Request) error

ExtensionsRequestModifier will run the `processRequest` function (if it is defined) for all the loaded extensions (except compass and checkpoint). Initially the modifier will check if the request originated from an extension by reading the "x-extension-id" header. This extension ID will be set in the context so that the response modifier will be able to read it. After processRequest, it will check if the request is passed through (nil), skipped (`ErrSkipPipeline`), or dropped (`ErrDropped`).

func ExtensionsResponseModifier

func ExtensionsResponseModifier(proxy *Proxy, res *http.Response) error

ExtensionsResponseModifier will run the `processResponse` function (if it is defined) for all the loaded extensions (except compass and checkpoint). The modifier will check if the extension ID in request context matches the current extension and skip execution if it does. After `processResponse`, it will check if the request is passed through (nil), skipped (`ErrSkipPipeline`), or dropped (`ErrDropped`).

func NewProxyRequest

func NewProxyRequest(req *http.Request, requestId uuid.UUID) (*domain.ProxyRequest, error)

NewProxyRequest creates a new domain.ProxyRequest from an http.Request. It extracts metadata from the request context and dumps the raw request.

func NewProxyResponse

func NewProxyResponse(res *http.Response) (*domain.ProxyResponse, error)

NewProxyResponse creates a new domain.ProxyResponse from an http.Response. It extracts metadata from the response context and dumps the raw response.

func OverrideWaypointsModifier

func OverrideWaypointsModifier(proxy *Proxy, req *http.Request) error

OverrideWaypointsModifier checks if a Waypoint (host override) is defined for this host:port. If a waypoint exists it will write the "original_host" and "override_host" to the metadata. These values are used later in the `DialContext` function. If the metadata is not found the modifier will return `ErrMetadataNotFound` TODO should allow TLS -> Non TLS override

func PreventLoopModifier

func PreventLoopModifier(proxy *Proxy, req *http.Request) error

PreventLoopModifier skips processing a request if it is made to marasi's active listener address and port, preventing an infinite loop It will normalize localhost & 127.0.0.1 when checking the host and port

func ResponseFilterModifier

func ResponseFilterModifier(proxy *Proxy, res *http.Response) error

ResponseFilterModifier will perform an initial filtering round on responses. It will skip processing for responses to CONNECT requests, responses where the skip flag was set, or SkipRoundTrip is true. It will also add the response time to the context

func SetupRequestModifier

func SetupRequestModifier(proxy *Proxy, req *http.Request) error

SetupRequestModifier initializes the request context. It will generate and set the request ID, set the request time, initial and set the metadata map, and stores the Martian session. If the request is coming from launchpad, it will set the launchapd ID in the context

func SkipConnectRequestModifier

func SkipConnectRequestModifier(proxy *Proxy, req *http.Request) error

SkipConnectRequestModifier will skip processing for CONNECT requests

func WithBasePipeline

func WithBasePipeline() func(*Proxy) error

WithBasePipeline will setup the base modifier pipeline for marasi It will define the main Request & Response modifiers that will execute the attached modifiers and hande `ErrDropped` and `ErrSkipPipeline`. If a response is dropped the `martian.Session` is read from the context and hijacked to close the `conn`

func WithConfigDir

func WithConfigDir(appConfigDir string) func(*Proxy) error

WithConfigDir configures the proxy to use the specified configuration directory. It creates the directory if it doesn't exist and initializes the configuration file using Viper.

Parameters:

  • appConfigDir: Path to the configuration directory

Returns:

  • func(*Proxy) error: Configuration function that sets up the config directory

func WithConfigRepository

func WithConfigRepository(repo domain.ConfigRepository) func(*Proxy) error

WithConfigRepository injects the config repository implementation.

func WithDBCloser

func WithDBCloser(closer io.Closer) func(*Proxy) error

WithDBCloser injects the database closer.

func WithDefaultModifierPipeline

func WithDefaultModifierPipeline() func(*Proxy) error

WithDefaultPipeline will apply the default modifier pipelines The default processing order is: waypoint overrides → extensions → interception → database storage. WithDefaultModifierPipeline will apply the default modifier pipelines for Requests & Responses. The processing order is: (Request): Compass -> Waypoint -> Extensions -> Checkpoint -> Database Write (Response): Buffer Streaming -> Decompress -> Compass -> Extensions -> Checkpoint -> Database Write

func WithDefaultRepositories

func WithDefaultRepositories(repo RepositoryProvider) func(*Proxy) error

WithDefaultRepositories is a convenience option to apply all repository implementations from a single provider.

func WithExtension

func WithExtension(extension *domain.Extension, options ...func(*extensions.Runtime) error) func(*Proxy) error

TODO: Need to fix this, currently if the extension exists it does not overrite it, this breaks opening new projects WithExtension loads a single extension into the proxy. It prepares the extension's Lua state and adds it to the proxy's extension list.

func WithExtensionRepository

func WithExtensionRepository(repo domain.ExtensionRepository) func(*Proxy) error

WithExtensionRepository injects the extension repository implementation.

func WithExtensions

func WithExtensions(exts []*domain.Extension, options ...func(*extensions.Runtime) error) func(*Proxy) error

WithExtensions loads multiple extensions into the proxy. It iterates through the provided extensions and prepares each one.

func WithInterceptHandler

func WithInterceptHandler(handler func(intercepted *Intercepted) error) func(*Proxy) error

WithInterceptHandler takes a handler function that will be executed on each intercept (Request / Response)

func WithLaunchpadRepository

func WithLaunchpadRepository(repo domain.LaunchpadRepository) func(*Proxy) error

WithLaunchpadRepository injects the launchpad repository implementation.

func WithLogHandler

func WithLogHandler(handler func(log domain.Log) error) func(*Proxy) error

WithLogHandler takes a handler function that will be executed on each Log

func WithLogRepository

func WithLogRepository(repo domain.LogRepository) func(*Proxy) error

WithLogRepository injects the log repository implementation.

func WithLogger

func WithLogger(logger *slog.Logger) func(*Proxy) error

WithLogger sets the structured logger for the proxy. It performs a nil check to ensure the proxy always has a valid logger.

func WithRequestHandler

func WithRequestHandler(handler func(req domain.ProxyRequest) error) func(*Proxy) error

WithRequestHandler takes a handler function that will be executed on each Request

func WithResponseHandler

func WithResponseHandler(handler func(res domain.ProxyResponse) error) func(*Proxy) error

WithResponseHandler takes a handler function that will be executed on each response

func WithStatsRepository

func WithStatsRepository(repo domain.StatsRepository) func(*Proxy) error

WithStatsRepository injects the stats repository implementation.

func WithTLS

func WithTLS() func(*Proxy) error

WithTLS will configure the proxy CA based on the proxy.ConfigDir It will also configure the http.Client that is used for the launchpad requests TODO - Check if the certificate expired

func WithTrafficRepository

func WithTrafficRepository(repo domain.TrafficRepository) func(*Proxy) error

WithTrafficRepository injects the traffic repository implementation.

func WithWaypointRepository

func WithWaypointRepository(repo domain.WaypointRepository) func(*Proxy) error

WithWaypointRepository injects the waypoint repository implementation.

func WriteRequestModifier

func WriteRequestModifier(proxy *Proxy, req *http.Request) error

WriteRequestModifier is the final modifier in the default request pipeline. It will create a `ProxyRequest` struct and queue it for database insertion. If the request came from launchpad, it will create a `LaunchpadRequest` struct and queue it for database insertion as well. If the `proxy.OnRequest` handler is defined, it will be called with the `ProxyRequest` otherwise the modifier will return `ErrRequestHandlerUndefined`

func WriteResponseModifier

func WriteResponseModifier(proxy *Proxy, res *http.Response) error

WriteResponseModifier is the final modifier in the default response pipeline. It will create a `ProxyResponse` struct and queue it for database insertion. If the `proxy.OnResponse` handler is defined, it will be called with the `ProxyResponse` otherwise the modifier will return `ErrResponseHandlerUndefined`

Types

type ChromePathConfig

type ChromePathConfig struct {
	OS   string `mapstructure:"os"`   // OS for the given path
	Path string `mapstructure:"path"` // Custom chrome path

}

type Config

type Config struct {
	ConfigDir  string             `mapstructure:"config_dir"` // Current config dir
	DesktopOS  string             `mapstructure:"desktop_os"` // Operating system identifier
	ChromeDirs []ChromePathConfig `mapstructure:"chrome_dirs"`
	// contains filtered or unexported fields
}

func (*Config) AddChromePath

func (cfg *Config) AddChromePath(path, os string) error

func (*Config) DeleteChromePath

func (cfg *Config) DeleteChromePath(path, os string) error

type Intercepted

type Intercepted struct {
	Type    string                 // "request" or "response"
	Raw     string                 // Raw HTTP data that can be modified
	Channel chan InterceptionTuple // Channel for receiving user decisions
}

Intercepted represents a request or response that has been intercepted for manual inspection and modification before being allowed to continue.

type InterceptionTuple

type InterceptionTuple struct {
	Resume                  bool // Whether to resume the intercepted item
	ShouldInterceptResponse bool // Whether to intercept the corresponding response
}

InterceptionTuple contains the user's decision when an intercepted item is resumed, indicating whether to continue and whether to intercept the corresponding response.

type Proxy

type Proxy struct {
	ConfigDir        string                               // The configuration directory (defaults to the marasi folder under the user configuration directory)
	Config           *Config                              // The marasi proxy configuration (separate from the GUI config)
	Modifiers        *fifo.Group                          // Modifier group pipeline
	DBWriteChannel   chan any                             // DB Write Channel
	InterceptedQueue []*Intercepted                       // Queue of intercepted requests / responses
	OnRequest        func(req domain.ProxyRequest) error  // Function to be ran on each request - used by the GUI application to handle the new requests
	OnResponse       func(res domain.ProxyResponse) error // Function to be ran on each response - used by the GUI application to handle the new responses
	OnIntercept      func(intercepted *Intercepted) error // Function to be ran on each intercept - used by the GUI application to handle the new intercepted items
	OnLog            func(log domain.Log) error           // Function to be ran on each log event - used by the GUI application to handle new log entries
	Addr             string                               // IP Address of the proxy
	Port             string                               // Port of the proxy
	Client           *http.Client                         // HTTP Client that is used by the repeater functionality (autoconfigured to use the proxy)
	Extensions       []*extensions.Runtime                // Slice of loaded extensions
	SPKIHash         string                               // SPKI Hash of the current certificate
	Cert             *x509.Certificate                    // The proxy's TLS certificate.

	MarasiClientTLSConfig *tls.Config       // TLSConfig for the proxy.Client
	Scope                 *compass.Scope    // Proxy scope configuration through Compass
	Waypoints             map[string]string // Map of host:port overrides
	InterceptFlag         bool              // Global intercept flag

	TrafficRepo   domain.TrafficRepository   // Repository for traffic data.
	LaunchpadRepo domain.LaunchpadRepository // Repository for launchpad data.
	WaypointRepo  domain.WaypointRepository  // Repository for waypoint data.
	StatsRepo     domain.StatsRepository     // Repository for statistics data.
	ConfigRepo    domain.ConfigRepository    // Repository for configuration data.
	LogRepo       domain.LogRepository       // Repository for log data.
	ExtensionRepo domain.ExtensionRepository // Repository for extension data.
	DBCloser      io.Closer                  // Closer for the database connection.
	Logger        *slog.Logger               // Logger for Marasi
	// contains filtered or unexported fields
}

Proxy is the main struct that orchestrates all proxy functionality including request/response processing, extension management, database operations, and TLS handling. It serves as the central coordinator for the Marasi proxy server.

func New

func New(options ...func(*Proxy) error) (*Proxy, error)

New creates a new Proxy instance with default configuration and applies any provided options. It initializes the underlying martian proxy, database write channel, extensions map, HTTP client, scope, waypoints, and sets up default log modifiers.

Parameters:

  • options: Variadic list of option functions to configure the proxy

Returns:

  • *Proxy: Configured proxy instance
  • error: Configuration error if any option fails

func (*Proxy) AddRequestModifier

func (proxy *Proxy) AddRequestModifier(modifier RequestModifierFunc)

AddRequestModifier accepts RequestModifierFunc and wraps it in a reqAdapter

func (*Proxy) AddResponseModifier

func (proxy *Proxy) AddResponseModifier(modifier ResponseModifierFunc)

AddResponseModifier accepts ResponseModifierFunc and wraps it in a resAdapter

func (*Proxy) Close

func (proxy *Proxy) Close()

Close shuts down the proxy and closes the database connection.

func (*Proxy) GetClient

func (proxy *Proxy) GetClient() (*http.Client, error)

GetClient returns the proxy's HTTP client. It returns an error if the client is not set.

func (*Proxy) GetConfigDir

func (proxy *Proxy) GetConfigDir() (string, error)

GetConfigDir returns the configuration directory path. It returns an error if the configuration directory is not set.

func (*Proxy) GetExtension

func (proxy *Proxy) GetExtension(name string) (*extensions.Runtime, bool)

GetExtension retrieves a loaded extension by its name. It returns the extension and true if found, otherwise nil and false.

func (*Proxy) GetExtensionRepo

func (proxy *Proxy) GetExtensionRepo() (domain.ExtensionRepository, error)

GetExtensionRepo returns the extension repository. It returns an error if the repository is not set.

func (*Proxy) GetListener

func (proxy *Proxy) GetListener(address string, port string) (net.Listener, error)

func (*Proxy) GetScope

func (proxy *Proxy) GetScope() (*compass.Scope, error)

GetScope returns the current scope configuration. It returns an error if the scope is not set.

func (*Proxy) GetTrafficRepo

func (proxy *Proxy) GetTrafficRepo() (domain.TrafficRepository, error)

GetTrafficRepo returns the traffic repository. It returns an error if the repository is not set.

func (*Proxy) Launch

func (proxy *Proxy) Launch(raw string, launchpadId string, useHttps bool) error

Launch sends a raw HTTP request through the proxy client. It is used for the launchpad functionality to replay and test requests.

func (*Proxy) Serve

func (proxy *Proxy) Serve(listener net.Listener) error

Serve starts the proxy and begins accepting connections on the provided listener. It also starts the database writer goroutine.

func (*Proxy) StartChrome

func (proxy *Proxy) StartChrome() error

StartChrome launches Chrome with proxy configuration and security settings. It configures Chrome to use the proxy server, creates an isolated user profile, and disables various Chrome features that might interfere with testing.

Returns:

  • error: Chrome launch error if executable not found or process fails to start

func (*Proxy) SyncWaypoints

func (proxy *Proxy) SyncWaypoints() error

SyncWaypoints fetches the latest waypoints from the repository and updates the proxy's in-memory map.

func (*Proxy) WithOptions

func (proxy *Proxy) WithOptions(options ...func(*Proxy) error) error

WithOptions applies a series of configuration functions to the proxy instance. Each option function can modify the proxy configuration and return an error if it fails.

Parameters:

  • options: Variadic list of configuration functions

Returns:

  • error: First error encountered from any option function

func (*Proxy) WriteLog

func (proxy *Proxy) WriteLog(level string, message string, options ...func(log *domain.Log) error) error

WriteLog creates a new log entry and sends it to the DBWriteChannel. It accepts a level, a message, and optional functions to modify the log entry.

func (*Proxy) WriteToDB

func (proxy *Proxy) WriteToDB()

WriteToDB reads from the DBWriteChannel and writes items to their respective repositories. It handles ProxyRequest, ProxyResponse, LaunchpadRequest, and Log items.

type RepositoryProvider

RepositoryProvider defines the interface for a provider of all data repositories used by the proxy. This allows for easy injection of a database implementation.

type RequestModifierFunc

type RequestModifierFunc func(proxy *Proxy, req *http.Request) error

RequestModifierFunc is a signature for HTTP request modifiers, it takes in the request and *Proxy

type ResponseModifierFunc

type ResponseModifierFunc func(proxy *Proxy, res *http.Response) error

ResponseModifierFunc is a signature for HTTP response modifiers, it takes in the response and *Proxy

type Waypoint

type Waypoint struct {
	Hostname string // The hostname to match
	Override string // The destination to redirect to
}

Waypoint represents a hostname override mapping, allowing requests to specific hosts to be redirected to different destinations.

Directories

Path Synopsis
Package core provides fundamental utilities for the Marasi proxy, including context management for passing data through the request/response lifecycle.
Package core provides fundamental utilities for the Marasi proxy, including context management for passing data through the request/response lifecycle.
db
Package db provides the database layer for the Marasi application.
Package db provides the database layer for the Marasi application.
Package domain defines the core business logic and data structures of the Marasi application.
Package domain defines the core business logic and data structures of the Marasi application.
Package extensions provides a sandboxed Lua scripting environment for extending the functionality of the proxy.
Package extensions provides a sandboxed Lua scripting environment for extending the functionality of the proxy.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL