confine

package
v0.60.0 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package confine implements per-request root confinement for the /api/files surface. It lets a host app (e.g. work.example.com proxying the embedded filex explorer) lock a caller into a single sub-folder so a multi-tenant deploy can never let one project read or mutate another's files.

The confinement root comes from two trusted sources, combined narrowest-wins:

  1. An API token's `root:<adapter>://<rel>` scope — the HARD ceiling. The browser never holds the token (the host injects it server-side), so a token-confined caller cannot escape its root.
  2. The `X-Filex-Root: <adapter>://<rel>` request header — narrows further within the token root (or, absent a token root, sets the root). Set by the host's trusted proxy per request; a stray client header can only narrow, never widen past the token root.

Enforcement is a single chi middleware that rewrites/validates every path-bearing field of the request (query `?path=` and the JSON body keys BodyPathKeys lists, matched case-insensitively as encoding/json matches them). Anything outside the root is rejected 403; a root/empty path is rewritten to the confined folder so listings open there. It covers an endpoint only through the keys it knows: a guard test holds every handler request body to that list (api/handlers/confine_keys_test.go).

The body is read as JSON whatever its Content-Type, because that is how the handlers read it; only the routes whose body is the bytes of a file (rawBodyRoutes) are passed on unread.

Index

Constants

This section is empty.

Variables

View Source
var ErrBadBody = errors.New("bad json")

ErrBadBody is returned for a body that begins as a JSON object and does not parse as one: what this layer cannot read it cannot hold to the root.

View Source
var ErrBodyTooLarge = errors.New("request body too large")

ErrBodyTooLarge is returned for a body that begins as a JSON object and is larger than maxBody: past the cut its keys would go unseen.

View Source
var ErrOutOfRoot = errors.New("path outside confined root")

ErrOutOfRoot is returned when a requested path escapes the confinement root.

Functions

func BodyPathKeys added in v0.50.0

func BodyPathKeys() []string

BodyPathKeys lists every JSON body key Middleware confines, spelled "key" (a path), "key[]" (a list of paths) and "key[].field" (a list of objects whose field is a path) - what the handlers' guard test holds their request bodies to.

func HoldBody added in v0.52.0

func HoldBody(r Root, body []byte) ([]byte, error)

HoldBody holds a request body a handler reads as JSON to the root exactly as Middleware holds one: the same keys, rewritten or checked the same way, refused with ErrOutOfRoot, and an object that does not parse refused with ErrBadBody. A body Middleware has already held passes through unchanged.

func Middleware

func Middleware(next http.Handler) http.Handler

Middleware enforces the effective root on every /api/files request. Mount it AFTER the auth middleware so the token is on the context.

func Refuse added in v0.52.0

func Refuse(w http.ResponseWriter)

Refuse writes Middleware's own answer to a path outside the root, so a handler that refuses one answers byte for byte what Middleware answers.

func RefuseFor added in v0.53.0

func RefuseFor(w http.ResponseWriter, err error)

RefuseFor writes Middleware's own answer to an error HoldBody returned: 403 for a path outside the root, 400 for a body that does not parse.

Types

type Root

type Root struct {
	Adapter string
	Rel     string
}

Root is a confinement scope: a single storage adapter + a clean relative prefix within it (no leading/trailing slash; "" == the storage root).

func CallerRoot added in v0.52.0

func CallerRoot(ctx context.Context) (Root, bool)

CallerRoot is the confinement root of the call on ctx: the one Middleware stashed (a token's `root:` narrowed by X-Filex-Root) or, where a request did not pass through Middleware (the /api/ai surfaces), the token's own `root:` scope (RootFromToken). ok=false for an unconfined caller.

func FromRequest

func FromRequest(r *http.Request) (Root, bool, error)

FromRequest derives the effective confinement root for r: the token's `root:` scope narrowed by an X-Filex-Root header. Returns ok=false when the request is unrestricted (no token root and no header) — admins / the native panel keep full access.

func ParseRoot

func ParseRoot(raw string) (Root, bool)

ParseRoot exposes parseRoot to the protocol servers, which read a confinement out of an API token's `root:` scope themselves — they authenticate outside the HTTP chain, so the middleware in this package never runs for them. Returns ok=false for an empty or storage-less spec.

func RootFrom

func RootFrom(ctx context.Context) (Root, bool)

RootFrom returns the confinement root stashed by Middleware, if any.

func RootFromToken

func RootFromToken(ctx context.Context) (Root, bool)

RootFromToken derives a confinement Root from the API token on ctx alone (its `root:` scope), without an *http.Request. The AI surface (/api/ai) does not pass through Middleware, so aiOps calls this to honor a token's path ceiling. Returns ok=false for unconfined tokens / cookie sessions.

func (Root) EnforcePath

func (r Root) EnforcePath(p string) (string, error)

EnforcePath is the exported form of enforce: validate/normalize a client path against the root, returning the qualified path or ErrOutOfRoot. The AI surface (aiOps) uses it directly since it bypasses Middleware.

func (Root) String added in v0.52.0

func (r Root) String() string

String is the root spelled `<adapter>://<rel>`, which ParseRoot reads back as the same root.

func (Root) Within

func (r Root) Within(adapter, rel string) bool

Within reports whether a storage-relative path (no adapter prefix) is inside the root for the given adapter name.

Jump to

Keyboard shortcuts

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