projectconfine

package
v0.0.7 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package projectconfine decides which directories may be a hub project (Task 20418).

A project's directory is not a label. It is what the hub hands an executor: the container driver bind-mounts it read-write at /workspace, the host driver forks the harness in it, and DELETE /api/projects/{idx}?delete_root=true removes it recursively. So "which directory is this project" is the same question as "what may model-authored code read and write on this host", and before this package the hub answered it with a text denylist that let a maintainer register /var/lib on the packaged image — an ancestor of the control plane, its hub.env, its state.db and every tenant's project — and then delete /var/lib/postgresql.

One predicate, Policy.Check, now answers it everywhere a directory becomes or acts as a project: the dashboard's and `cloop remote init`'s creation, the registry writes in pkg/multiui, a project's own state trying to relocate it (pkg/state), and every dispatch, which re-checks the row it is about to bind before any driver sees it. Backstop is the part a driver can apply knowing only the control plane's directory, for a caller that reaches a driver without going through the hub.

The invariant, for a candidate directory P (canonical — see Canonical):

  • P is a plausible project root at all: absolute, not /, not the hub user's home or one of its ancestors, not inside a system tree, not a bare top-level directory, and nothing inside a .cloop directory.
  • P is neither ui.projects_root nor an ancestor of it: the root contains every project created by name.
  • For a caller who is not an administrator, P is strictly inside ui.projects_root, and no component below the root is hidden.
  • P is neither equal to, inside, nor an ancestor of the hub's control plane. The hub's own entry is the exception — it is the operator's directory, not something registered through the API — and so is a directory inside ui.projects_root when the operator put the root inside the control plane, as the packaged image does (HOME=/var/lib/cloop).
  • P is neither equal to, inside, nor an ancestor of any other directory that holds a hub's control plane (.cloop/hub.env, or a running hub's working directory).
  • P neither contains nor sits inside another registered project, and is not one. Features live under their project's .cloop/features and are never registry rows, so they are excepted by construction.

Index

Constants

View Source
const HubEnvFile = ".cloop/hub.env"

HubEnvFile is the file `cloop hub bootstrap` writes a hub's secrets to, relative to the hub's directory. A directory holding it is a hub.

Variables

View Source
var DefaultScan = ScanLimits{MaxDepth: 8, MaxDirs: 100000}

DefaultScan is the search a creation runs: deeper than any hub an operator keeps inside another tree, small enough to answer within a request (about a second on a cold cache at the limit).

Running out of directories is a refusal (RuleUnverifiable); running out of depth is not. A source checkout is routinely more than eight levels deep and nothing about that makes it unsafe, while a tree too broad to search could be hiding anything. A stopped hub kept more than eight levels below a directory somebody registers is the residual case, and the threat model says so.

Functions

func Backstop

func Backstop(controlPlane, workDir string) error

Backstop is the part of the invariant a driver can enforce knowing only the hub's own directory: a work directory may be that directory, but may neither contain it nor sit inside its .cloop, and may not sit inside a directory holding another hub's control plane.

A driver calls it immediately before it binds or forks in workDir. The hub applies the whole invariant before dispatch (pkg/ui/project_confine.go); this is what still holds for a caller that reaches a driver some other way. workDir is a project's directory: a feature's has already been mapped to its project's by the caller. An empty controlPlane checks nothing.

func Canonical

func Canonical(path string) (string, error)

Canonical returns path as the directory the kernel would create for it: absolute and lexically cleaned (a trailing slash, "." and ".." are gone), with the symbolic links of its deepest existing ancestor resolved. The components below that ancestor do not exist yet, so nothing can resolve them; they are re-appended as written.

Lexical cleaning first is deliberate rather than a shortcut. The hub creates and registers the cleaned path, so that is the directory every later rule has to be about; "/srv/link/../x" is "/srv/x" to MkdirAll whatever link points at.

func HubsAbove

func HubsAbove(path string) []string

HubsAbove returns path and those of its ancestors that hold a hub's control plane, nearest first. path should be canonical.

func HubsBelow

func HubsBelow(path string, limits ScanLimits) (found []string, truncated bool)

HubsBelow searches the tree under path, breadth first and without following symbolic links, for directories holding a hub's control plane. path itself is not reported (HubsAbove covers it). truncated says the directory budget ran out before the search finished, so an empty answer is not a "no"; the depth limit ends the search quietly (see DefaultScan).

func UnsafeReason

func UnsafeReason(path, home string) string

UnsafeReason returns why path can never be a project root, or "" when it could be one. path should be canonical; home is the hub user's home directory, "" to skip the home rule.

These are the rules that do not depend on anything the hub has registered: the directory is wrong wherever the hub stands.

func Within

func Within(child, parent string) bool

Within reports whether child is strictly inside parent. Both must be clean absolute paths; the comparison is by path component, so /srv/p2 is not inside /srv/p.

Types

type Policy

type Policy struct {
	// ControlPlane is the hub's own directory.
	ControlPlane string
	// ProjectsRoot is ui.projects_root, or its default.
	ProjectsRoot string
	// ConfineToRoot is set for a caller who is not an administrator: the
	// directory must then be strictly inside ProjectsRoot.
	ConfineToRoot bool
	// Projects are the registered projects. The candidate may be among them
	// when an existing row is re-checked (Existing).
	Projects []string
	// HubDirs are the working directories of running hubs: control planes the
	// search may not find, because one need not keep a hub.env. A candidate
	// may not be one or contain one.
	HubDirs []string
	// Home is the hub user's home directory.
	Home string
	// Existing marks the re-check of a directory that is already a project:
	// at dispatch, in `cloop hub doctor`, on the Projects tab. Its own row is
	// not a collision, and the hub's own directory is allowed.
	Existing bool
	// Scan is the search below the candidate for another hub. Zero searches
	// nothing.
	Scan ScanLimits
}

Policy is everything the invariant is checked against. Every path in it must be canonical (Canonical); empty means "none".

func (Policy) Check

func (p Policy) Check(path string) (string, error)

Check applies the invariant to path. It returns the canonical path — the one to create, register and bind — and a *Violation when the path is refused.

type Relation

type Relation string

Relation is how a directory sits relative to another.

const (
	// Same: the two are one directory.
	Same Relation = "same"
	// Inside: the directory is strictly inside the other.
	Inside Relation = "inside"
	// Contains: the directory is a strict ancestor of the other.
	Contains Relation = "contains"
)

func Relate

func Relate(path, other string) Relation

Relate says how path sits relative to other, or "" when the two are unrelated. Both must be clean absolute paths.

type Rule

type Rule string

Rule names the part of the invariant a directory breaks. The values are stable: the dashboard flags a project by its rule and `cloop hub doctor` reports it.

const (
	// RuleUnresolvable: the path could not be resolved (a dangling or looping
	// symlink), so nothing about where it points can be checked.
	RuleUnresolvable Rule = "unresolvable"
	// RuleUnsafe: never a project root wherever the hub stands.
	RuleUnsafe Rule = "unsafe"
	// RuleProjectsRoot: ui.projects_root itself, or a directory containing it.
	RuleProjectsRoot Rule = "projects_root"
	// RuleOutsideRoot: a caller who is not an administrator named a directory
	// outside ui.projects_root.
	RuleOutsideRoot Rule = "outside_projects_root"
	// RuleControlPlane: overlaps this hub's own directory.
	RuleControlPlane Rule = "control_plane"
	// RuleHub: overlaps a directory holding another hub's control plane.
	RuleHub Rule = "hub"
	// RuleProject: overlaps another registered project, or is one.
	RuleProject Rule = "project"
	// RuleUnverifiable: the search below the directory for a hub's control
	// plane ran out of budget before it finished.
	RuleUnverifiable Rule = "unverifiable"
	// RuleRelocated: the project's .cloop is a link out of its directory, so
	// the hub's reads and writes of its state would land somewhere else.
	RuleRelocated Rule = "relocated"
)

type ScanLimits

type ScanLimits struct {
	// MaxDepth is how many levels below the directory are searched.
	MaxDepth int
	// MaxDirs is how many directories are visited before giving up.
	MaxDirs int
}

ScanLimits bounds the search below a directory for a hub's control plane.

type Violation

type Violation struct {
	// Path is the candidate, canonical when it could be resolved.
	Path string
	Rule Rule
	// Other is the directory Path collides with: the projects root, the
	// control plane, a hub's directory, another project. Empty for
	// RuleUnsafe, RuleUnresolvable and RuleUnverifiable.
	Other string
	// Relation is how Path sits relative to Other.
	Relation Relation
	// Reason is the specific rule's wording, for the rules that have no Other.
	Reason string
}

Violation is a refusal, naming the rule and the directory it collides with.

Its Error names every path involved, which is right for an administrator and for the hub's own log. A handler answering someone who may not see the other directory must not hand them this text: see the identical answers in pkg/ui/project_confine.go.

func IsViolation

func IsViolation(err error) (*Violation, bool)

IsViolation reports whether err is a refusal by this package, and returns it.

func (*Violation) Error

func (v *Violation) Error() string

Jump to

Keyboard shortcuts

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