Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ the task fails before the script runs. This is workdir containment, not a full
mount namespace: scripts can still read or write other host paths allowed to
the agent user.

NetGuard apply leases explicitly marked `durable_result` by the server are
journaled before execution and upload. This scope matches NetGuard's atomic
server-side result/approval/binding transition; heterogeneous legacy tasks keep
their historical one-shot delivery semantics. The outbox uses a private,
server-and-node-specific subdirectory under `LATTICE_LOG_STATE_DIR`; manual runs
without that setting use the current user's cache directory. Override the base
directory with `LATTICE_TASK_OUTBOX_DIR` or `-task-outbox-dir`. If a marked lease
cannot be written, it does not run. After a restart, completed marked results
are retried first and an interrupted marked task is reported as an unknown
outcome rather than executed a second time.

For least-privilege Linux systemd installs, set `LATTICE_AGENT_RUN_USER` before
running `scripts/install.sh`:

Expand Down Expand Up @@ -302,6 +313,10 @@ missing checksum manifest aborts the install before the binary is written.
- `LATTICE_AGENT_ALLOW_EXEC=1` enables bounded task execution.
- `LATTICE_AGENT_ALLOW_ROOT_EXEC=1` permits task execution while the agent runs
as root.
- `LATTICE_TASK_OUTBOX_DIR` overrides the durable NetGuard result-journal base. The
installer creates a private `task-outbox` leaf beneath that base and
preserves it across reconfiguration; otherwise journals share
`LATTICE_LOG_STATE_DIR`.
- `LATTICE_NO_EXEC=1` is the hard kill switch and overrides execution/terminal
enablement.
- `LATTICE_AGENT_RUN_USER` / `LATTICE_AGENT_RUN_GROUP` configure an optional
Expand Down Expand Up @@ -376,6 +391,9 @@ node id.
scripts default to owner-only access.
- Leased tasks carry a server-issued `lease_id`; the agent returns it with the
result and exposes it to the task as `LATTICE_TASK_LEASE_ID` for traceability.
- A lease must be durably journaled before execution. Completed or
unknown-outcome results remain in the outbox until the server acknowledges
them, and are flushed before the agent fetches any new task.
- Leased task payloads contain only execution fields; control-plane actor/token
metadata is not sent to agents.

Expand Down
57 changes: 57 additions & 0 deletions cmd/lattice-agent/guard_reality_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,75 @@
package main

import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"reflect"
"regexp"
"testing"
"time"

"github.com/LatticeNet/lattice-node-agent/internal/guardreality"
"github.com/LatticeNet/lattice-sdk/model"
)

func TestWriteGuardManagedSHAOnlyOutputsCanonicalHashOnSuccess(t *testing.T) {
valid := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
tests := []struct {
name string
collect func(context.Context, guardreality.Source) (string, error)
want string
wantErr bool
}{
{
name: "success",
collect: func(context.Context, guardreality.Source) (string, error) {
return valid, nil
},
want: valid + "\n",
},
{
name: "collection failure",
collect: func(context.Context, guardreality.Source) (string, error) {
return "", errors.New("nft unavailable")
},
wantErr: true,
},
{
name: "invalid collector value",
collect: func(context.Context, guardreality.Source) (string, error) {
return "NOT-A-SHA", nil
},
wantErr: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var out bytes.Buffer
err := writeGuardManagedSHA(context.Background(), &out, tc.collect)
if (err != nil) != tc.wantErr {
t.Fatalf("error = %v, wantErr=%v", err, tc.wantErr)
}
if out.String() != tc.want {
t.Fatalf("stdout = %q, want %q", out.String(), tc.want)
}
if out.Len() > 0 && !regexp.MustCompile(`^[0-9a-f]{64}\n$`).Match(out.Bytes()) {
t.Fatalf("successful stdout is not one lowercase SHA-256: %q", out.String())
}
})
}
}

func TestReportedCapabilitiesAdvertiseGuardManagedSHA(t *testing.T) {
got := reportedCapabilities()
if !reflect.DeepEqual(got, []string{guardManagedSHACapability}) {
t.Fatalf("reported capabilities = %#v", got)
}
}

func TestReportGuardReality(t *testing.T) {
originalClient := httpClient
t.Cleanup(func() { httpClient = originalClient })
Expand Down
Loading