ledger

package
v0.6.7 Latest Latest
Warning

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

Go to latest
Published: Jun 26, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package ledger is the append-only, hash-chained audit log for brokered operations — Port scope grants and file Trades. It lives host-side at ~/.dejima/ledger.jsonl, outside every container's blast radius, so a compromised island cannot rewrite its own history.

Each entry chains to the previous one via a SHA-256 (or HMAC-SHA-256, if a key is configured) hash over the previous chain value plus the entry's own content. Any in-place edit or deletion of a past entry breaks the chain and is detectable by Verify. This is the substrate the Port spec requires before an island may be granted host-file access (docs/port-island-spec.md §4).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Configure

func Configure(hmacKey []byte)

Configure sets process-wide options for the Default ledger. It must be called before the first Default() use (e.g. at daemon startup, before any append).

A non-empty hmacKey keys the chain with HMAC-SHA-256 instead of plain SHA-256, so the hash chain can only be re-derived by a holder of the key — raising the bar from "tamper is detectable" to "tamper requires the key". The whole file must use one keying, so this is meaningful only on a fresh ledger: turning HMAC on over a file that already holds plain-SHA entries will make Verify report those older entries as broken. Calling after the Default log is already built is a no-op (the keying is fixed for the daemon's life).

func ResetDefault

func ResetDefault()

ResetDefault drops the cached process-wide ledger so the next Default() re-resolves its path from $HOME (and re-reads the Configure'd HMAC key). For tests that redirect HOME between cases; not for production use (the daemon's HOME is fixed for its lifetime).

Types

type Entry

type Entry struct {
	Seq      uint64    `json:"seq"`
	Time     time.Time `json:"time"`
	Type     string    `json:"type"` // port.grant | port.revoke | trade.read | trade.write | trade.deny | api.request | <lifecycle event>
	Island   string    `json:"island"`
	Agent    string    `json:"agent,omitempty"`
	Scope    string    `json:"scope,omitempty"` // scope name
	Path     string    `json:"path,omitempty"`  // host path (scope) or path within scope (trade); request path (api.request)
	Mode     string    `json:"mode,omitempty"`  // ro | rw
	Bytes    int64     `json:"bytes,omitempty"`
	SHA256   string    `json:"sha256,omitempty"`   // content hash of the file (trades)
	Decision string    `json:"decision,omitempty"` // allowed | denied
	Detail   string    `json:"detail,omitempty"`
	// Operational audit fields (api.request + lifecycle records).
	Method string `json:"method,omitempty"` // HTTP method (api.request)
	Status int    `json:"status,omitempty"` // HTTP status code (api.request)
	Actor  string `json:"actor,omitempty"`  // who made the request (identity; filled by the auth layer)
	Role   string `json:"role,omitempty"`   // the actor's role, when known
	Prev   string `json:"prev"`             // chain value of the previous entry ("" for the first)
	Chain  string `json:"chain"`            // chain value of this entry
}

Entry is one append-only record. Trade entries (file crossings) carry Bytes and SHA256; scope entries (grant/revoke) carry Scope/Path/Mode; operational audit entries (api.request + lifecycle) carry Method/Status/Actor/Role. Prev and Chain are filled by Append and must not be set by callers.

Every field beyond the chain bookkeeping is `omitempty`, which is what lets a new build add fields without breaking the verification of entries written by an older build: chainValue re-marshals the parsed Entry, and an absent field marshals identically whether the writer knew about it or not. Do NOT drop omitempty on an existing or new field — it would silently break every chain.

type Log

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

Log is an append-only ledger backed by a single JSONL file.

func Default

func Default() (*Log, error)

Default returns the process-wide ledger at ~/.dejima/ledger.jsonl. A single daemon owns the file, so one shared Log keeps the in-memory chain head consistent across concurrent appends. The path is resolved (from $HOME) on first use and cached; ResetDefault drops the cache. The chain is keyed by the optional HMAC key set via Configure.

func New

func New(path string, hmacKey []byte) *Log

New returns a Log backed by path. If hmacKey is non-nil the chain is an HMAC-SHA-256 keyed by it; otherwise it is a plain SHA-256 chain.

func (*Log) Append

func (l *Log) Append(e Entry) (Entry, error)

Append seals e onto the chain and writes it as one JSONL line. It assigns Seq, Time (if zero), Prev, and Chain, and returns the sealed entry.

func (*Log) Read

func (l *Log) Read() ([]Entry, error)

Read returns every entry in order. Missing file => empty slice.

func (*Log) Verify

func (l *Log) Verify() error

Verify walks the whole chain and reports the first broken link, if any.

Jump to

Keyboard shortcuts

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