auth

package
v0.19.0 Latest Latest
Warning

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

Go to latest
Published: May 7, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package auth provides a lightweight, role-based access control layer for kojo's HTTP API. Its purpose is "spoiler prevention" — keeping an agent from incidentally reading other agents' Persona / configuration when it curls the API on its own. It is NOT a security boundary against a malicious agent: an agent runs as the same OS user as kojo itself and can read other agents' files directly. See README for the threat model.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AllowNonOwner

func AllowNonOwner(p Principal, method, path string) bool

AllowNonOwner gates non-Owner principals against a small whitelist of API routes. Unrecognised paths are denied with 403, and recognised paths may further be denied based on Principal/method/target.

This wrapper applies on the *agent-facing* (auth-required) listener only. The public listener uses OwnerOnlyMiddleware and never enters this gate.

The intent is "default deny". Routes are grouped into three buckets:

  1. Public reads — info, directory, agent list/get, avatar.
  2. Self-scoped reads/writes — files, messages, tasks, credentials, memory, slackbot config, notify sources, group memberships, avatar upload, persona / metadata patch, MCP, PreCompact hook. Permitted only when the path's {id} matches the principal.
  3. Privileged-cross-agent — delete / reset / checkin / unarchive / reset-session. Permitted for self by Agent, or for any target by PrivAgent.

Owner-only routes (sessions, git, files browser, oauth, embedding, push, custom-models, group DM mutate-as-owner, fork, /privilege, generate-*) fall through and 403. Note: POST /api/v1/groupdms (group creation) is exposed to Agent / PrivAgent below — the handler then enforces the caller-in-memberIds invariant.

func AuthMiddleware

func AuthMiddleware(resolver *Resolver) func(http.Handler) http.Handler

AuthMiddleware resolves Authorization: Bearer / X-Kojo-Token to a Principal and passes it through ctx. It does NOT enforce per-route policy — that is the handler's responsibility (or a separate gate).

func EnforceMiddleware

func EnforceMiddleware(next http.Handler) http.Handler

EnforceMiddleware gates /api/v1/* requests by Principal/method/path using AllowNonOwner. It runs after AuthMiddleware (which set the Principal in ctx). Static files and non-API paths pass through.

func OwnerOnlyMiddleware

func OwnerOnlyMiddleware(h http.Handler) http.Handler

OwnerOnlyMiddleware unconditionally tags every request as the Owner. Used on the public (Tailscale) listener that the kojo user accesses from their phone — the user's UX is preserved (no token required).

func WithPrincipal

func WithPrincipal(ctx context.Context, p Principal) context.Context

WithPrincipal attaches a Principal to ctx.

Types

type Principal

type Principal struct {
	Role    Role
	AgentID string // populated for RoleAgent / RolePrivAgent
}

Principal identifies the actor behind a request.

func FromContext

func FromContext(ctx context.Context) Principal

FromContext retrieves the Principal stashed in ctx by middleware. Defaults to RoleGuest when no principal is set.

func (Principal) CanDeleteOrReset

func (p Principal) CanDeleteOrReset(targetID string) bool

CanDeleteOrReset returns true for delete/reset/unarchive/checkin/ reset-session ops. Owner: any. PrivAgent: any. Agent: self only.

func (Principal) CanForkOrCreate

func (p Principal) CanForkOrCreate() bool

CanForkOrCreate returns true only for the Owner. Forking copies persona/memory and would leak the source agent's full state, so it is kept Owner-only even for privileged agents. Bare creation is also Owner-only.

func (Principal) CanMutateSelf

func (p Principal) CanMutateSelf(targetID string) bool

CanMutateSelf returns true if the principal may issue self-scoped mutations (PATCH, reset, etc.) against targetID.

func (Principal) CanReadFull

func (p Principal) CanReadFull(targetID string) bool

CanReadFull returns true if the principal can read the full record (Persona, Token-bearing fields, etc.) for the given target agent ID. Owners can read any. Agents can only read their own.

func (Principal) CanSetPrivileged

func (p Principal) CanSetPrivileged() bool

CanSetPrivileged returns true only for the Owner. A privileged-agent must never be able to grant or revoke privilege.

func (Principal) IsAgent

func (p Principal) IsAgent() bool

IsAgent returns true if the principal is bound to a specific agent (regular or privileged).

func (Principal) IsOwner

func (p Principal) IsOwner() bool

IsOwner returns true if the principal is the kojo user.

type Resolver

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

Resolver maps a Bearer token to a Principal.

func NewResolver

func NewResolver(tokens *TokenStore, isPrivileged func(string) bool) *Resolver

NewResolver builds a Resolver from a TokenStore and a privilege predicate (agent.Manager.IsPrivileged).

func (*Resolver) Resolve

func (r *Resolver) Resolve(token string) Principal

Resolve maps a Bearer token to a Principal. Empty/unknown tokens resolve to RoleGuest.

type Role

type Role int

Role identifies the actor behind an HTTP request after middleware resolution.

const (
	// RoleGuest is the default role for unauthenticated requests on the
	// auth-required listener. Guests can only read directory entries.
	RoleGuest Role = iota
	// RoleAgent is a regular agent-bound principal. It can fully read its
	// own data and is limited to DirectoryEntry on other agents.
	RoleAgent
	// RolePrivAgent extends RoleAgent with the ability to delete/reset
	// other agents (but not fork or read their full data).
	RolePrivAgent
	// RoleOwner is the kojo user. It has full access to everything.
	RoleOwner
)

type TokenStore

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

TokenStore persists owner / agent tokens on disk (mode 0600) and keeps an in-memory index for O(1) reverse lookup.

Layout:

<base>/owner.token            — owner secret (single line, hex)
<base>/agent_tokens/<id>      — per-agent secret (single line, hex)

The store does NOT depend on the agent package; agent.Manager is the caller that drives EnsureAgentToken / RemoveAgentToken on agent lifecycle events.

func NewTokenStore

func NewTokenStore(base string, overrideOwner string) (*TokenStore, error)

NewTokenStore initializes a store rooted at base. It creates the directory tree if missing and loads any existing tokens. The owner token is created on first use if absent unless overrideOwner is non-empty (in which case overrideOwner is treated as the canonical owner token and is *not* persisted to disk).

func (*TokenStore) AgentToken

func (s *TokenStore) AgentToken(agentID string) (string, error)

AgentToken returns the token for the given agent ID, generating and persisting one if it does not already exist.

func (*TokenStore) EnsureAgentToken

func (s *TokenStore) EnsureAgentToken(agentID string) error

EnsureAgentToken is a convenience wrapper used at agent-create time.

func (*TokenStore) LookupAgent

func (s *TokenStore) LookupAgent(token string) (string, bool)

LookupAgent returns the agent ID associated with the given token, if any.

func (*TokenStore) OwnerToken

func (s *TokenStore) OwnerToken() string

OwnerToken returns the current owner token (hex, 64 chars).

func (*TokenStore) RemoveAgentToken

func (s *TokenStore) RemoveAgentToken(agentID string)

RemoveAgentToken deletes the on-disk and in-memory token for an agent. Safe to call on an unknown ID. Invalid IDs are ignored.

Jump to

Keyboard shortcuts

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