writegate

package
v2.25.3 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: GPL-3.0 Imports: 35 Imported by: 0

Documentation

Overview

Package writegate decides every write a browser sends: it lets the write through, or blocks it and keeps what it would have sent. A write is any request whose method is not GET, HEAD, or OPTIONS, and any request a policy declares a write.

step or session ── loopback HTTP + token ──▶ write gate process
                                               │ DevTools Fetch on every request
                                               ▼
                                             browser

The gate runs as a process of its own, beside the browser it guards, and answers each paused request at once: a request it held would be sent the moment its connection to the browser ended. It arms itself on the browser target before any page loads, so pages, frames, popups, workers, and service workers opened later are covered without arming each one. When its connection to a browser that still runs is lost, it closes that browser.

Index

Constants

View Source
const (
	// ModeOff runs no gate.
	ModeOff = "off"
	// ModeRecord sends only the writes the operation running may send, as
	// its approved commit points name them, and keeps each with its
	// answer; it blocks the others.
	ModeRecord = "record"
	// ModeAsk decides writes as ModeRecord does, and a step running in it
	// asks a person before each operation that is a commit point.
	ModeAsk = "ask"
	// ModeRehearse blocks every write and keeps what it would have sent.
	ModeRehearse = "rehearse"
)

Modes, from the most permissive. A browser runs in the stricter of the mode its step or session asks for and the host's floor.

View Source
const (
	// KindWrite is a write the gate decided.
	KindWrite = "write"
	// KindSocket is a WebSocket a service worker or shared worker opened,
	// whose writes the gate cannot see.
	KindSocket = "socket"
)

Kinds of Event.

View Source
const (
	// ClassWrite is a write of the work, one the gate exists for.
	ClassWrite = "write"
	// ClassApproved is a write of the work the operation running may send.
	ClassApproved = "approved"
	// ClassSignIn is a write to a sign-in endpoint the policy lists.
	ClassSignIn = "sign_in"
	// ClassNoise is a beacon, a report, a write to a noise host, or one to
	// an endpoint that only collects analytics or error reports.
	ClassNoise = "noise"
	// ClassPerson is a write sent while a person holds the browser.
	ClassPerson = "person"
)

Classes of a write.

View Source
const (
	OutcomeBlocked = "blocked"
	OutcomeSent    = "sent"
)

Outcomes of a write.

View Source
const (
	// DefaultBodyLimit is how much of a write's body the gate keeps.
	DefaultBodyLimit = 64 << 10
)

Variables

This section is empty.

Functions

func Allowed

func Allowed(points []CommitPoint, key string) []string

Allowed returns the signatures the approved points of an operation send.

func Begin

func Begin(ctx context.Context, handleURL string, signatures []string, lease time.Duration) error

Begin tells the gate at handleURL which writes the operation starting may send, for at most lease. In record mode every other write is blocked, and so is every write once End is called or the lease ends.

func End

func End(ctx context.Context, handleURL string) error

End tells the gate at handleURL that the operation that began ended.

func IsWrite

func IsWrite(method string) bool

IsWrite reports whether a request of method writes, before any policy.

func Main

func Main(ctx context.Context, stdin io.Reader, stdout io.WriteCloser) error

Main is the write-gate command. It reads a Config as JSON from stdin, writes one line to stdout once the gate decides the browser's requests, closes stdout, and serves until the browser is gone or the process is told to stop.

func ParseMode

func ParseMode(text string) (string, error)

ParseMode returns the mode text names; empty is ModeOff.

func Sends

func Sends(mode string) bool

Sends reports whether a gate in mode sends the writes an operation is allowed.

func Serve

func Serve(ctx context.Context, cfg Config, ready func(Ready)) error

Serve guards the browser at cfg.CDPURL until the browser is gone or ctx ends. It calls ready once every request the browser makes is decided, before any page loads. Losing the browser's connection while the browser still runs closes the browser, as does ctx ending.

func SetPerson

func SetPerson(ctx context.Context, handleURL string, on bool) error

SetPerson tells the gate at handleURL whether a person holds the browser, whose writes it then sends.

func Stricter

func Stricter(a, b string) string

Stricter returns the stricter of two modes.

Types

type CommitPoint

type CommitPoint struct {
	// Key identifies the operation as the step's replay cache does: its
	// position, words, and page.
	Key string `json:"key"`
	// Op is the operation's position in with.do, Operation its words, and
	// Page the page it ran on.
	Op        int    `json:"op"`
	Operation string `json:"operation"`
	Page      string `json:"page,omitempty"`
	Signature string `json:"signature"`
	Approved  bool   `json:"approved"`
	// Actions are what the operation did on the page, recorded as the
	// browser runtime replays them; an approved commit act replays them
	// and asks no model.
	Actions json.RawMessage `json:"actions,omitempty"`
	// Example is the write as it was last seen.
	Example    *Event     `json:"example,omitempty"`
	SeenAt     time.Time  `json:"seen_at"`
	ApprovedAt *time.Time `json:"approved_at,omitempty"`
}

CommitPoint is an operation of a step that sends a write, and the kind of write it sends. A rehearsal or a blocked write notes one; once approved, a step running in record mode sends that write from that operation and no other.

type CommitStore

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

CommitStore keeps the commit points of each DAG's steps under the browser data directory, apart from the replay cache, which a run may drop.

func NewCommitStore

func NewCommitStore(browserDataDir string) *CommitStore

NewCommitStore returns the commit points kept under the browser data directory.

func (*CommitStore) Approve

func (s *CommitStore) Approve(ctx context.Context, dag, step string, from []CommitPoint, match func(CommitPoint) bool, now time.Time) ([]CommitPoint, error)

Approve marks the points match accepts approved, adding those of from that are not kept yet, and returns them.

func (*CommitStore) Load

func (s *CommitStore) Load(dag, step string) ([]CommitPoint, error)

Load returns the commit points of a step; none when it has no file.

func (*CommitStore) Note

func (s *CommitStore) Note(ctx context.Context, dag, step string, seen ...CommitPoint) error

Note keeps the commit points seen, merged with those kept: a point already kept, by key and signature, keeps its approval and takes the newer example and actions.

func (*CommitStore) Revoke

func (s *CommitStore) Revoke(ctx context.Context, dag, step string, match func(CommitPoint) bool) ([]CommitPoint, error)

Revoke withdraws the approval of the points match accepts and returns them.

func (*CommitStore) Steps

func (s *CommitStore) Steps(dag string) ([]string, error)

Steps returns the steps of a DAG that have commit points, in the file-safe form they are kept under.

type Config

type Config struct {
	// Token is a random value that prefixes the control paths.
	Token string `json:"token"`
	// CDPURL is the DevTools address of the browser, such as
	// http://127.0.0.1:9333.
	CDPURL string `json:"cdp_url"`
	Mode   string `json:"mode"`
	Policy Policy `json:"policy"`
	// Secrets holds the values of the secrets the policy's rules name, by
	// name.
	Secrets map[string]string `json:"secrets,omitempty"`
	// ExtensionDir is where the socket-blocking extension is written.
	ExtensionDir string `json:"extension_dir"`
	// BodyLimit is how much of a write's body the gate keeps; zero means
	// DefaultBodyLimit.
	BodyLimit int `json:"body_limit,omitempty"`
}

Config is what a gate decides, and for which browser.

type Event

type Event struct {
	Seq  int64     `json:"seq"`
	At   time.Time `json:"at"`
	Kind string    `json:"kind"`
	// Method and URL are the request's, with sensitive query values
	// hidden.
	Method       string `json:"method,omitempty"`
	URL          string `json:"url"`
	ResourceType string `json:"resource_type,omitempty"`
	Class        string `json:"class,omitempty"`
	Outcome      string `json:"outcome,omitempty"`
	// Signature identifies the kind of write across runs, apart from the
	// values it carries.
	Signature   string `json:"signature,omitempty"`
	ContentType string `json:"content_type,omitempty"`
	// Fields are the names of the body's fields.
	Fields []string `json:"fields,omitempty"`
	// Body is the start of the body, with sensitive fields hidden.
	Body string `json:"body,omitempty"`
	// BodyBytes is the size of the whole body as the browser reported it.
	BodyBytes int64 `json:"body_bytes,omitempty"`
	// Target is the kind of worker that opened a socket.
	Target string `json:"target,omitempty"`
	// Status is the HTTP status a sent write was answered with, and
	// Response the start of the answer, with sensitive fields hidden.
	// ResponseError is the network error a sent write got in place of an
	// answer.
	Status        int    `json:"status,omitempty"`
	Response      string `json:"response,omitempty"`
	ResponseError string `json:"response_error,omitempty"`
}

Event is a write the gate decided, or a socket it saw open.

func Events

func Events(ctx context.Context, handleURL string, after int64) ([]Event, error)

Events returns the events the gate at handleURL recorded after seq. An error means the gate no longer answers.

func (Event) Consequential

func (e Event) Consequential() bool

Consequential reports whether the event is a write of the work the gate kept from being sent.

func (Event) String

func (e Event) String() string

String describes the event in a few words, such as "POST https://example.com/deals".

type Handle

type Handle struct {
	// URL is the base of the gate's control paths.
	URL string `json:"url"`
	PID int    `json:"pid"`
	// StartedAt is the process's start time in Unix milliseconds, which
	// tells it apart from a later process reusing PID.
	StartedAt int64 `json:"started_at"`
}

Handle locates a running gate process.

func Start

func Start(ctx context.Context, executable string, cfg Config) (*Handle, error)

Start runs executable's browser write-gate command with cfg and returns once the gate decides every request of the browser at cfg.CDPURL. Start it before the browser loads any page. The gate outlives the caller and exits once the browser is gone.

func (Handle) Alive

func (h Handle) Alive() bool

Alive reports whether the gate process still runs.

func (Handle) Kill

func (h Handle) Kill() error

Kill ends the gate process if it is still the one h records. End it only after its browser: a request the gate paused would be sent the moment the gate is gone.

type Policy

type Policy struct {
	// SignIn are the sign-in endpoints whose writes are sent.
	SignIn []Rule `json:"sign_in,omitempty" yaml:"sign_in,omitempty"`
	// Writes declare requests that save although their method only reads,
	// such as a delete link.
	Writes []Rule `json:"writes,omitempty" yaml:"writes,omitempty"`
	// NoiseHosts are hosts whose writes, such as analytics, are blocked
	// without counting as the work's writes.
	NoiseHosts []string `json:"noise_hosts,omitempty" yaml:"noise_hosts,omitempty"`
}

Policy names the writes a gate treats apart from the others.

func (Policy) Secrets

func (p Policy) Secrets() []string

Secrets returns the names of the secrets the policy's rules name.

func (Policy) Validate

func (p Policy) Validate() error

Validate rejects a policy whose rules cannot match.

type Ready

type Ready struct {
	// Port is the port of the control paths.
	Port int `json:"port"`
}

Ready describes a gate that decides the browser's requests.

type Rule

type Rule struct {
	// Host is a host pattern, as the hostpattern package matches it.
	Host string `json:"host" yaml:"host"`
	// Path is the request path; one ending in * matches every path it
	// starts. Empty matches every path.
	Path string `json:"path,omitempty" yaml:"path,omitempty"`
	// Secret names a secret whose value the request must carry, as a
	// sign-in carries its password.
	Secret string `json:"secret,omitempty" yaml:"secret,omitempty"`
}

Rule matches requests by host and path.

Jump to

Keyboard shortcuts

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