Skip to content

gVisor Sentry-level event export (seccheck) is production-ready and dynamically attachable — a stronger Part B signal source than static config assumes #894

Description

@doraem-on

Context

This is a writeup from research toward the Agent Sandbox / gVisor Runtime Visibility LFX project
(2026 Term 3), specifically Part B: node-agent's eBPF hooks attach at the kernel level, so they see
nothing happening inside a gVisor-sandboxed actor — the Sentry intercepts syscalls entirely in
userspace. The project description names three candidate signal sources to investigate, including
"Sentry-level seccomp/audit event forwarding." I read gVisor's actual source (pkg/sentry/seccheck)
rather than assume, and it's considerably more mature than "candidate to investigate" implies.

What seccheck actually is

It's a first-class, documented, already-shipped observability API in gVisor:

  • 998 documented trace points with structured protobuf schemas — syscall enter/exit (schematized
    per-syscall, e.g. Open has pathname/flags/fd_path, not just raw args), sentry-internal events,
    container lifecycle (container/start, etc.)
  • A remote sink: the Sentry connects out to a Unix domain socket and streams trace points to
    whatever process is listening. No in-Sentry code changes required — this satisfies the term's own
    constraint directly.
  • A reference Go server implementation already in the gVisor tree
    (pkg/sentry/seccheck/sinks/remote/server/server.go) to build a consumer from, so a node-agent-side
    listener isn't starting from zero.

The deployment question that actually matters

GKE Agent Sandbox controls how sandboxes get created; node-agent doesn't. So the real feasibility
question isn't "does seccheck exist," it's "can something running on the node attach to a sandbox
gVisor didn't ask it to instrument."

It can:

runsc --root <runtime-state-dir> trace create --config session.json <container-id>

runsc trace create attaches a trace session to an already-running container by ID, dynamically,
at runtime — not only via static --pod-init-config at launch. Session config also supports
ignore_setup_error on the sink, so a session doesn't fail sandbox startup if the listening process
isn't reachable yet, which matters for a node-agent that discovers sandboxes after the fact rather than
launching them.

Concretely, this means: node-agent detects a new gVisor-sandboxed actor via whatever container-watching
it already does, shells out (or calls the equivalent client) to runsc trace create against that
container's ID from the host, points the resulting session's remote sink at a socket node-agent itself
listens on (reusing the reference server as a starting point), and feeds the structured protobuf events
into the existing pipeline — processtree's feeder interface looks like the natural attachment point
for lifecycle-shaped points (container/start, sentry/clone, sentry/task_exit), networkstream for
the socket/network-shaped syscall points.

One real limitation, stated honestly

Per the seccheck docs: "there is a current limitation that only a single session can exist in the
system and it must be called Default."
For our use case (one node-agent-managed session per
sandboxed actor) that's not a blocker, but it does mean seccheck can't be layered with some other,
independent trace consumer on the same sandbox without conflict. Worth knowing before this gets
designed further, not something I'd want to gloss over.

Additional Context

I don't have this fully prototyped yet — filing this as a design note, not a finished proof of concept,
because the actual attach/consume/feed-into-pipeline path is real engineering work I'd rather scope
properly than rush. Wanted the finding itself on record: seccheck + dynamic trace create is a
stronger, more concrete answer to "what's a feasible signal source" than the "candidate to investigate"
framing suggests, and it's worth the term prioritizing this over the file-tail (--strace export) or
purely-inferential (host-visible boundary signals alone) options if the single-session limitation and
the per-node runsc trace create privilege requirements check out under real load.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions