filedoor

package
v0.4.1-rc.1 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package filedoor is the loopback door: a 127.0.0.1 HTTP listener, owned by one chat surface, that turns files on the FAR machine into things this machine's own programs can open — a browser tab, cmd+click on an OSC 8 link, the platform opener.

── CAPABILITIES, NOT PATHS ─────────────────────────────────────────────────

A byte URL is /f/<id> and an interactive-open URL is /o/<id>; their shared id maps to a path in a table nobody else can write. The door cannot be asked for an arbitrary path: a local process that guesses URLs can reach only what the surface itself chose to link, and each id dies with the door. The browse page's token is the same idea for the listing side: /browse/ and /api/... require the one token minted at Open, so another local user's curl gets 404, never a listing.

A WRONG TOKEN AND AN UNKNOWN ID BOTH ANSWER 404, ALWAYS THE SAME 404. A distinct status for "the id is real but your token is wrong" would confirm existence to somebody who is guessing, and a door whose refusals are informative is a door with a side channel in it.

── THE TOKEN IS NEVER IN AN ARGV ───────────────────────────────────────────

Door.BrowseURL is handed to the platform opener, and the platform opener is exec: whatever is in that address is in /proc/<pid>/cmdline, which under the default hidepid every other account on this machine can read, and after that it is in the browser's history and in a Referer header. So the address the door hands out CARRIES NO TOKEN AT ALL. It is /enter/<nonce>: a single-use name, good once and briefly, that is spent the moment a browser walks it. In exchange the door sets the real token as an HttpOnly SameSite=Strict cookie and redirects to /browse/, with nothing secret left in the address bar. After that the secret lives in exactly two places — this process's memory and one browser's cookie jar — and no other account on the machine can read either.

The path lane (/browse/<token>, /api/<token>/ls) still authorises, because a person holding the token and curl is the oldest way through this door and the simplest way to test it. Where both are offered the cookie wins.

── THE DOOR SERVES, THE SOURCE DECIDES ─────────────────────────────────────

Every byte and every listing comes through Source, which is the engine's law speaking (internal/remote's handOver two-roots rule). The door adds no judgement of its own about what may be shown: a path the source refuses is a sentence passed through verbatim, exactly as the surface passes the engine's refusals through today. That is why a refusal leaves here as 502 with the sentence as its whole body — the door has nothing to add to it, and rewriting somebody else's refusal is how a message stops being true.

── AND EVERY BYTE LEAVES SANDBOXED ─────────────────────────────────────────

THE FILES ARE NOT OURS. They came off somebody else's disk, quite possibly written by a model, and they are served on the same 127.0.0.1 port that holds the capability to list that disk and to write to it. A far machine's .html or .svg rendered on this origin would be a document executing next to the keys: it could read the token out of a referrer, call /api/ls, GET every file it found and POST the lot anywhere. So /f/<id> answers under `Content-Security-Policy: sandbox`, which drops the response into an opaque origin where it is same-origin with nothing, and the kinds a browser actually executes are additionally sent as an attachment rather than rendered. What a person came to look at — a picture, a log, a PDF — stays inline, because viewing is the product.

── UPLOADS LAND IN attachments/ AND NOWHERE ELSE ───────────────────────────

Dragging a file onto the browse page sends it up the same lane /attach uses, and it lands where an attachment lands: the far session's attachments/ folder. The door never writes an arbitrary remote path — moving a file INTO the workspace proper is the conversation's job ("put attachments/x.csv next to the others"), because that is a write on somebody's machine and writes belong to the lane that already owns consent for them.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Door

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

Door is one running listener. Zero value is not usable; Open makes one.

func Open

func Open(source Source) (*Door, error)

Open starts the door on an OS-chosen 127.0.0.1 port and mints its token.

func (*Door) BrowseURL

func (d *Door) BrowseURL() string

BrowseURL is the address to hand a browser, and THERE IS NO TOKEN IN IT. It is /enter/<nonce>, spent once by whichever browser walks it first; see the package header for why a token that reaches an argv is a token another account on this machine already has.

func (*Door) Close

func (d *Door) Close() error

Close stops the listener and forgets every id, every unspent nonce, and the token.

func (*Door) FileURL

func (d *Door) FileURL(path string) (string, error)

FileURL mints (or reuses) the capability URL for one remote path, for OSC 8 links and for the open flow. Idempotent per path for the door's lifetime.

Idempotence is not tidiness: the surface calls this every time it redraws a line that names a file, so a fresh id per call would grow the table by one entry per repaint and give the same file a different URL in every scrollback line — which is exactly the sort of thing that makes a person believe a link they clicked once has stopped working.

func (*Door) OpenURL

func (d *Door) OpenURL(path string) (string, error)

OpenURL mints the capability used by an interactive click. It is separate from FileURL because callers which asked for bytes must keep receiving bytes, while a terminal click asks the surface OS to open its cached named copy.

type Entry

type Entry struct {
	Name    string
	Dir     bool
	Size    int64
	ModTime time.Time
	MIME    string
}

Entry is one row of a remote listing, the door's own shape so the package depends on internal/remote only through Source's implementor.

type File

type File struct {
	Name  string
	MIME  string
	Bytes []byte
}

File is one fetched file: its bytes and what to call them on the wire out.

type Source

type Source interface {
	// Host is the far machine's name as the person typed it — the browse
	// page's title, so a person with three doors open knows whose disk this is.
	Host() string
	// List returns one directory under the source's own law.
	List(path string) (resolved string, entries []Entry, truncated bool, err error)
	// Fetch returns one file under the same law.
	Fetch(path string) (File, error)
	// Deposit sends bytes to the far session's attachments folder and returns
	// the path they landed at, on the engine's disk.
	Deposit(name string, data []byte) (landed string, err error)
}

Source is where every listing and every byte comes from. The chat surface implements it over its remote client; a test implements it over a map.

Jump to

Keyboard shortcuts

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