ruleshttp

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 5, 2026 License: MIT Imports: 10 Imported by: 0

README

ruleshttp

CI Go Reference

[!NOTE] ruleshttp is currently a beta-quality library.

ruleshttp wraps Go HTTP clients and enforces rules on outgoing HTTP requests and incoming responses using the Expr expression language. It is intended to be used in cases where scoped credentials are not sufficiently locked down.

Example integration

GitHub's Personal Access Tokens (PATs) can be granted read and write permissions for pull requests which enables both approving and requesting changes. Perhaps you have a code review setup where approved PRs are automatically merged and you want some LLM to request changes but not approve PRs. PAT permissions alone cannot express that! With ruleshttp you can solve this with the following rules:

# rules.yaml
pre_request:
  - name: allow-request-changes
    match: 'Method == "POST" && Host == "api.github.com" && Path contains "/pulls/" && Path endsWith "/reviews"'
    authorize: 'fromJSON(Body)["event"] == "REQUEST_CHANGES"'

pre_response:
  - name: all-responses
    match: ALL
    authorize: ALL

The ruleshttp.New functions return a Go http.RoundTripper which can be used in an http.Client like so:

// Load the rules and create a http.RoundTripper/http.Client.Transport
transport, err := ruleshttp.NewFromFile("rules.yaml")
if err != nil {
    log.Fatal(err)
}

// Using github.com/google/go-github/v84 as an example
client := github.NewClient(&http.Client{Transport: transport}).WithAuthToken(token)
_, _, err = client.PullRequests.CreateReview(ctx, owner, repo, number, &github.PullRequestReviewRequest{
		Body:  github.Ptr("An approval attempt that will be rejected by rules.yaml"),
		Event: github.Ptr("APPROVE"),
	})
if errors.Is(err, ruleshttp.ErrDenied) {
    // request or response was rejected by rules
}

How it works

Rules are defined in YAML and compiled at startup. Each rule has a match expression and an authorize expression. For each request or response, rules are evaluated in order: the first rule whose match expression is true and whose authorize expression is true allows the traffic. If no rule allows, the call is rejected.

Rules are an allowlist-only system. A request/response must be explicitly authorized to be allowed. An empty rules list denies all traffic.

Two hook points are supported:

  • pre_request — evaluated before the request is sent.
  • pre_response — evaluated after the response is received. Has access to the original request via Request.

Note that if a request is allowed, a response can still be rejected. This is to support cases where you want to allow read-only requests but you may want to reject certain responses. A consequence of this decision is that responses always have to be explicitly allowed.

Additional Docs

Documentation

Overview

Package ruleshttp provides an http.RoundTripper that uses the Expr expression language (github.com/expr-lang/expr) to enforce rules on HTTP requests and responses.

Two hook types are supported:

  • pre_request: rules evaluated against the outgoing request before it is sent. The default decision is deny; the first rule whose match expression is true and whose authorize expression is true allows the request to proceed.

  • pre_response: rules evaluated against the incoming response after it is received. The same logic applies.

If no rules are configured for a phase, that phase denies all traffic.

Configuration is loaded from a YAML file via NewFromFile or built programmatically with a Config value passed to New.

Index

Constants

This section is empty.

Variables

View Source
var ErrDenied = errors.New("ruleshttp: denied")

ErrDenied is returned when a request or response is denied by rules. Callers can use errors.Is to detect this case.

Functions

This section is empty.

Types

type Config

type Config struct {
	PreRequest  []Rule `yaml:"pre_request"`
	PreResponse []Rule `yaml:"pre_response"`
}

Config holds the pre_request and pre_response rule lists loaded from YAML.

type Option

type Option func(*Transport)

Option configures a Transport. If multiple logger options are provided, the last one takes effect.

func WithAllLogger

func WithAllLogger(logger *slog.Logger) Option

WithAllLogger attaches a slog.Logger that records every roundtrip — rule allows and denials at Info level, non-rule errors at Error level.

func WithAllowLogger

func WithAllowLogger(logger *slog.Logger) Option

WithAllowLogger attaches a slog.Logger that records rule allows at Info level in addition to non-rule errors at Error level.

func WithDenialLogger

func WithDenialLogger(logger *slog.Logger) Option

WithDenialLogger attaches a slog.Logger that records rule denials at Info level in addition to non-rule errors at Error level.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger attaches a slog.Logger that records only non-rule errors (transport failures, body read errors) at Error level. Rule outcomes (allows and denials) are not logged.

Every log entry includes the fields:

  • err – the error returned by RoundTrip, if any
  • pre_request_allowed – whether the pre_request rules passed
  • pre_request_allowed_rule – name of the rule that allowed the request
  • pre_response_allowed – whether the pre_response rules passed
  • pre_response_allowed_rule – name of the rule that allowed the response

func WithRoundTripper

func WithRoundTripper(rt http.RoundTripper) Option

WithRoundTripper sets the underlying RoundTripper used to make real HTTP calls. Defaults to http.DefaultTransport when not provided.

type RequestEnv

type RequestEnv struct {
	// Method is the HTTP method (GET, POST, …).
	Method string `yaml:"method"`
	// Scheme is the URL scheme (http or https).
	Scheme string `yaml:"scheme"`
	// Path is the URL path component.
	Path string `yaml:"path"`
	// Host is the request host (from the Host header or URL).
	Host string `yaml:"host"`
	// Headers contains canonicalized header names mapped to all their values.
	// Keys follow Go's http.CanonicalHeaderKey format, e.g. "Content-Type".
	Headers map[string][]string `yaml:"headers"`
	// Body is the full request body decoded as a UTF-8 string.
	Body string `yaml:"body"`
	// Query contains URL query parameters mapped to all their values.
	Query map[string][]string `yaml:"query"`
}

RequestEnv is the environment exposed to pre_request Expr expressions. Field names are used directly in expression strings, e.g. Method == "GET".

type ResponseEnv

type ResponseEnv struct {
	// StatusCode is the numeric HTTP status code, e.g. 200.
	StatusCode int `yaml:"status_code"`
	// Headers contains canonicalized response header names mapped to all their values.
	Headers map[string][]string `yaml:"headers"`
	// Body is the full response body decoded as a UTF-8 string.
	Body string `yaml:"body"`
	// Request is the original request environment.
	Request RequestEnv `yaml:"request"`
}

ResponseEnv is the environment exposed to pre_response Expr expressions.

type Rule

type Rule struct {
	Name      string `yaml:"name"`
	Match     string `yaml:"match"`
	Authorize string `yaml:"authorize"`
}

Rule is a named pair of match and authorize Expr expressions. A rule applies to a request when match returns true. It allows the request when authorize returns true.

type Transport

type Transport struct {
	// contains filtered or unexported fields
}

Transport is an http.RoundTripper that evaluates Expr expressions before sending requests and before returning responses.

func New

func New(cfg Config, opts ...Option) (*Transport, error)

New creates a Transport from cfg. It compiles all Expr expressions eagerly so that configuration errors are surfaced immediately rather than at request-time.

func NewFromFile

func NewFromFile(path string, opts ...Option) (*Transport, error)

NewFromFile reads a YAML config file at path and creates a Transport from it. It parses and validates the YAML, then calls New.

func (*Transport) CheckRequest

func (t *Transport) CheckRequest(env RequestEnv) (bool, string, error)

CheckRequest evaluates the pre_request rules against env and returns whether the request is allowed, the name of the matching rule, and any expression evaluation error.

func (*Transport) CheckResponse

func (t *Transport) CheckResponse(env ResponseEnv) (bool, string, error)

CheckResponse evaluates the pre_response rules against env and returns whether the response is allowed, the name of the matching rule, and any expression evaluation error.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

RoundTrip implements http.RoundTripper.

When pre_request rules are configured, they are evaluated in order; the first rule whose match expression is true and whose authorize expression is true allows the request. If no rule allows, the request is rejected with ErrDenied. An empty rules list denies all traffic. The same logic applies to pre_response rules.

If a logger is configured via WithLogger, a single wide log entry is emitted after the roundtrip completes.

Directories

Path Synopsis
cmd
ruleshttp-test command
Command ruleshttp-test evaluates ruleshttp rules against a test file.
Command ruleshttp-test evaluates ruleshttp rules against a test file.
Package rulestest provides test helpers for evaluating ruleshttp.Transport rules against declarative test cases.
Package rulestest provides test helpers for evaluating ruleshttp.Transport rules against declarative test cases.

Jump to

Keyboard shortcuts

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