Split 4 of 7: read-only sensors entitlement and per-app sockets - #1795
Draft
martien-wdy wants to merge 5 commits into
Draft
Split 4 of 7: read-only sensors entitlement and per-app sockets#1795martien-wdy wants to merge 5 commits into
martien-wdy wants to merge 5 commits into
Conversation
Adds the narrow, read-only "sensors" entitlement and the two per-app Unix domain sockets that carry it: a data socket for an app writing into its own episodes, and a sensor socket for an app subscribing to sensor sources. App identity comes from the private bind mount the socket lives on, never from the request body, matching the existing System API socket. The sensors entitlement carries an optional allowlist that the SensorService consults live, so an app that declares one can neither enumerate nor subscribe to any other source on the device. Part 4 of 7 in the split of the Wendy Data Platform change.
Joannis
reviewed
Aug 25, 2026
Comment on lines
+52
to
+62
| // EntitlementSensors grants read-only subscription to agent-hosted sensor | ||
| // streams over an app-private socket that serves nothing else. It is the | ||
| // first-class model-input path: the app becomes one more subscriber of the | ||
| // same producer episode capture consumes, so the two never fight over a | ||
| // device, and every sample the app receives is recorded into the active | ||
| // episode under the identifier the app was given. It grants no device | ||
| // nodes; raw device access remains the separate "camera" entitlement. An | ||
| // optional allowlist restricts it to named source ids, mirroring what the | ||
| // camera entitlement's allowlist does for device nodes; without one the app | ||
| // may subscribe to any sensor source the device offers. | ||
| EntitlementSensors = "sensors" |
Member
There was a problem hiding this comment.
Instead of sending sensor data, it might be more effective and efficient for the campaign (the agent) to fetch or poll the latest state by calling or tailing a file descriptor created by the app.
Joannis
reviewed
Aug 25, 2026
Comment on lines
+50
to
+62
| // EntitlementData grants only the app-private episode event socket. | ||
| EntitlementData = "data" | ||
| // EntitlementSensors grants read-only subscription to agent-hosted sensor | ||
| // streams over an app-private socket that serves nothing else. It is the | ||
| // first-class model-input path: the app becomes one more subscriber of the | ||
| // same producer episode capture consumes, so the two never fight over a | ||
| // device, and every sample the app receives is recorded into the active | ||
| // episode under the identifier the app was given. It grants no device | ||
| // nodes; raw device access remains the separate "camera" entitlement. An | ||
| // optional allowlist restricts it to named source ids, mirroring what the | ||
| // camera entitlement's allowlist does for device nodes; without one the app | ||
| // may subscribe to any sensor source the device offers. | ||
| EntitlementSensors = "sensors" |
Member
There was a problem hiding this comment.
We should merge these 2 entitlements
Joannis
requested changes
Aug 25, 2026
Joannis
left a comment
Member
There was a problem hiding this comment.
See split 3/7 for the remainder of review
Joannis
marked this pull request as draft
August 27, 2026 18:57
…ps.v1 Follows the proto package move in chunk 3. The per-app socket now registers appspbv1.SensorService, and the defence-in-depth method allowlist on that socket matches the new fully qualified name /wendy.agent.apps.v1.SensorService/ rather than the old control-plane name, which would otherwise have rejected every call.
Contributor
Author
|
On the file descriptor idea; a unix socket already is one, so the difference is really who initiates and whether we keep typing and backpressure. I'd rather keep the app pushing over gRPC, because the connection opening and closing is exactly where we learn the publisher went away, and a tailed file gives us no liveness signal at all. |
…ire an allowlist Problem: the two entitlements the Wendy Data Platform work added, "sensors" and "data", were named for their subject rather than their direction, and a reviewer read them as duplicates and proposed merging them. They are not duplicates. They run in opposite directions and carry very different risk: "sensors" is a read-only subscription to agent-hosted sensor streams (in practice, permission to see the cameras and microphones), while "data" writes the app's own event and prediction records into the device's episode recorder (in practice, permission to write into the recorded dataset, and stronger than it looks, because campaign triggers match on application event names and prediction attributes, so an app holding it can start recordings). Merging them would give anything that wants to log an event the camera, and anything that wants to read a sensor the training corpus. Two further holes sat behind the names. The sensor allowlist was optional, and omitting it granted every subscribable source, so an app declaring the bare entitlement got the cameras today and would silently gain microphones and the Robot Operating System 2 (ROS 2) graph the day those sources became subscribable. And subscribing to a source whose kind has no producer hub failed with "not available to model subscribers", which reads like the source does not exist. Solution: - "sensors" becomes "sensor-read" and "data" becomes "episode-write", so the direction and the object are both visible in the name. The Go constants are EntitlementSensorRead and EntitlementEpisodeWrite. Neither name has shipped, so there is deliberately NO alias for the old spellings: an alias would leave both spellings in the world and defeat the rename. A test pins that the old spellings are rejected. - The sensor-read allowlist is now required and must name at least one source. The JSON schema marks it required with minItems 1, appconfig validation rejects a missing, empty, or blank-entry allowlist with a message naming the fix, and AppSensorSocketManager.Ensure refuses an owner without one so the agent does not depend on the command line interface having validated first. appSensorSocket.permits now fails closed on an owner with no allowlist rather than treating it as a blanket grant. - Subscribing to a source kind that has no producer hub now returns Unimplemented naming the kind and saying it is not subscribable in this release, and a source the device does not have at all returns NotFound saying so. Only camera sources have a producer hub today.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
See chunk 1 for why pull request #1751 is being split. This is chunk 4 of 7, and it is the security sensitive one: it is the reason the whole split exists. It adds a new entitlement and two new Unix domain sockets, and in the unsplit pull request neither could have been reviewed by the Claude Security Review job at all.
Cause
A model running in an app container needs sensor input. Today the only way to get it is the
cameraentitlement, which hands the container a raw/dev/videoNdevice node with the device cgroup rules that go with it, and which is single holder: an app that takes the camera takes it away from episode capture. That is far more authority than "read frames" requires, and it puts the app and the agent in conflict over the device.Solution
Adds a narrow, read-only
sensor-readentitlement and the two per-app sockets that carry the data platform's grants.The trust boundary
nosuidandnoexecinto the container. TheSensorServiceand the data socket handler bind one instance to one app identity, taken from the socket the request arrived on. Nothing in a request body can name a different app. This is the same construction the existing System API socket uses, and the directory rather than the socket inode is mounted so the agent can recreate the socket under a running container.applySensorSocketadds a bind mount, a supplementary group and one environment variable. It adds no device nodes and no device cgroup rules. An app that genuinely wants the raw device still asks forcameraand still gets the single holder semantics that come with it.cameraentitlement's allowlist does for device nodes.SensorService.permitsis consulted on every enumeration and every subscribe, not cached at construction, because a multi service app's owner set changes while the socket keeps serving. An app that declares an allowlist can neither see nor subscribe to any other source on the device. An entitlement with no allowlist is unrestricted, which is the documented meaning.SO_PEERCREDwhen it accepts. It fails open only in the cases where the connection structurally carries no peer identity to read, which are a non Unix socket or a platform with noSO_PEERCRED, and those cases are named explicitly rather than being a silent fallthrough.Deliberately left to later chunks
Nothing wires these sockets into the agent here.
main.gowiring, the camera producer that actually satisfies the sensor provider interface, and the episode capture path all arrive in chunk 5. This chunk is deliberately kept to the grant and its enforcement so the security review has a small surface to look at.Seam note
sensor_service.goandsensor_service_test.gowere moved back from chunk 5 into this chunk.app_sensor_socket.gocannot compile without them, and on reflection they belong here anyway:SensorServiceis where the entitlement allowlist is enforced, so the grant and its enforcement point are now reviewed together.Examples/WendyDataModelApp/wendy.jsonandgo/internal/shared/appconfig/wendy_data_model_app_test.gowere left in chunk 7. That test only guards the shipped example against schema drift and needs three example files as fixtures; the entitlement's own unit coverage isappconfig_sensors_test.go, which is in this chunk.Depends on
Chunk 3 (
split/3-protos), which this pull request is based on.Verification
CC=/usr/bin/clang go build ./...succeeds.CC=/usr/bin/clang go test ./go/internal/agent/services/... ./go/internal/shared/appconfig/... ./go/internal/agent/oci/... ./go/internal/agent/containerd/... ./go/internal/cli/...passes. The command line interface packages are included because this chunk changes embedded documentation assets.gofmt -l go/is empty andgo vetis clean for the touched packages.