Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
17a8685
refactor(api): move a seat's code tier to spec.code.access
clwluvw Oct 26, 2026
256794a
fix(api): raise the data volume floor to the engine's eight logs
clwluvw Oct 26, 2026
4511dbf
fix(controller): shorten the boot windows to the engine's new bounds
clwluvw Oct 26, 2026
0370dbc
test(render): read again the boot bounds the engine moved
clwluvw Oct 26, 2026
de97625
fix(render): hold a pod back on a public address no sandbox can dial
clwluvw Oct 26, 2026
03925ef
docs: follow the engine image's own git and the role it adds
clwluvw Oct 26, 2026
b4cc2c1
fix(api): refuse code in engineConfig until the code host is offered
clwluvw Oct 26, 2026
f47c830
fix(controller): count the estate service's two lanes in the boot window
clwluvw Oct 26, 2026
1306e5c
test(render): read serveEstate again after the git lane
clwluvw Oct 26, 2026
002837b
fix(controller): count a state log's boot reads at their new ceiling
clwluvw Oct 26, 2026
35ed36e
test(render): read the leader read and its bound again after the hedge
clwluvw Oct 26, 2026
ec50b17
test(render): read the positions listing again after the clock measure
clwluvw Oct 27, 2026
ab3fcc8
test(render): read the fleet store's key reads and create to the bounds
clwluvw Oct 27, 2026
62ade51
feat(render): render the inbox horizon at the company's top level
clwluvw Oct 27, 2026
a7d9b93
test(render): re-digest the engine's inbox relay registration
clwluvw Oct 27, 2026
64656f5
test(render): read the run_sandbox launch again after its code holds
clwluvw Oct 29, 2026
4df7827
test(render): read the agent-mode launch again after the bridge's model
clwluvw Oct 29, 2026
11b5a26
fix(api): refuse the variables Crewlet sets in a coding run itself
clwluvw Oct 30, 2026
2b91f16
test(render): read the box address reads again after the code host's
clwluvw Oct 30, 2026
2b1e20e
test(render): read the run_sandbox launch again after its repositories
clwluvw Oct 30, 2026
bac2e31
docs(api): say a stop also refuses a detached run's writes
clwluvw Oct 31, 2026
8560750
test(render): read the agent-mode launch again after the bridge's stop
clwluvw Oct 31, 2026
cebce40
fix(controller): say a stopped seat's runs' writes are refused
clwluvw Oct 31, 2026
9341b49
docs(engine): say a seat's stop also refuses its runs' writes
clwluvw Oct 31, 2026
175b451
test(render): read the broker's connect and leaf wait after its keys
clwluvw Oct 31, 2026
61b2c77
docs: say the leaf listener admits only the company's keys
clwluvw Oct 31, 2026
137cb3c
docs(docs): say which keyring edit has the data pods refuse seat pods
clwluvw Oct 31, 2026
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
68 changes: 63 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,16 @@ start on at all (`checkExternalURL`, `checkPort`, `checkBrowserHost`,
Tier A string in `internal/config/normalize.go`, which
`TestContractAddressShapes` holds the render to and test/crd's
`TestAddressShapesFollowTheEngine` the API's rules on a Company's and a
class's addresses, at the shapes they try) — with
class's addresses, at the shapes they try; and, for the address a pod hands
the engine's sandboxes, `ParseBoxURL` and its helpers in
`internal/sandbox/boxurl.go`, through which the boot reads each such
variable (`ReadBoxURLs` in `internal/engine/boxurl.go`), and which
`TestContractBoxURLShapes` holds the render to by asking `crewlet llm doctor`) — with
every operator file that copies it and a digest of the function as it was
read. Those files are the renderer's; for the addresses, also the Company
and EngineClass API types whose rules refuse them, the controller's message
on a Company held back for one, and the [exposure](docs/guides/exposure.md)
on a Company held back for one and its choice of the addresses a pod hands
sandboxes, and the [exposure](docs/guides/exposure.md)
and [troubleshooting](docs/guides/troubleshooting.md) guides that state
them; and, for how an agent-mode run's CLI signs in inside the remote
sandbox, also the LLMProvider API's rule on a CLI of mode `Agent`, the
Expand Down Expand Up @@ -232,9 +237,62 @@ a seat pod's boot, which the controller's startup windows add up
EngineClass API floors `data.startupSeconds` at, and the runtime and
troubleshooting pages quote, and one step the windows leave out, past the
recovery boundary: the boot's read of the company's secrets (`bootSecrets`),
which waits out a store that does not answer. Which steps a boot runs, and in
what order, is `engine.New`'s, which is not read, since it changes with most
of the engine's work. The digest (`enginetest.Fingerprint`) is taken over the
which waits out a store that does not answer. It names, too, the one wait
the engine bounds by no time, which the windows count as a boot meets it:
the agreement of the object store's fleet record, bounded by a number of
attempts (`casRetry`), at the one attempt a boot meets. How long a read may
wait is read down to the function that bounds it, for every read the engine
asks again while nobody answers it (`hedge` and `ReadBoundedWith` in
`internal/jsprovision/hedge.go`): a leader read, the pages of a pass's key
index and the broker's limit read are held to the client's own five seconds
where their caller set no deadline (`(*Leader).get`, `NewLeader`, `ask`,
`askTimed`, `page`, `sorted` and `(*Leader).Subjects` in `internal/jsapi`,
`keysUnder` in `internal/coord/kv/walk.go`, `(*Queue).budget` in
`internal/queue/jetstream/budget.go`), and a read of a state log's state or
record to the thirty seconds of its timing's `Lookup` (`ReadWith`, with the
queue's timing, `(*Queue).provisioning` and `Clustered.Timing`), which a
boot's read waits out, its context having no deadline. Each read of one key
the windows count is one such leader read, of the key's newest message
(`(*leaderReader).last` in `internal/coord/kv/walk.go` and `(*Leader).Last`
in `internal/jsapi`), whether it reads the key's live value
(`(*FleetStore).get` in `internal/coord/kv/fleet.go`, `getLatest` and
`(*leaderReader).live`), is a round of a create (`(*FleetStore).create`, and
`createKey` in `internal/coord/kv/markers.go`) or is a listing's read back.
The write each round of that create makes before its read is read down to
its bound too: it is a settled update, which waits out the leader's answer
that another write to the key is still in flight, no longer than the
client's own five seconds where its caller set no deadline (`settled`,
`settledBucket.Update` and `settledBucket.settle` in
`internal/coord/kv/settle.go`, and `Decide` in
`internal/jsinflight/jsinflight.go`). That the bucket it writes is settled
is not read: Crewlet's own
`TestAConditionalWriteIsAnsweredByWhatTheLeaderDecided` requires it.
How many registrations a step makes is read down to the queue, for every
step that registers what a node answers: the steer answerer and the fleet
broker register in their step's own code (`armSteer`, `serveFleetBroker`);
the history answerer, the inbox relay and the presence answerer each
register one subject in the function their step calls (`Serve` in
`internal/eventfan/fanwire.go`, `(*inboxRelay).serve` in
`internal/engine/inboxrelay.go` and `(*Hub).Start` in
`internal/presence/hub.go`); the estate service registers each of its lanes
in turn (`Serve` and `lanes` in `internal/estate`), two on a data pod; and
every registration is one flush (`(*Queue).Serve` and `flush` in
`internal/queue/jetstream/scatter.go`). So a registration added below a
function whose own code does not change still fails the test.
The listing the windows count, the join's of the fleet's positions, is read
down the same way, to the certified listing it is and to the functions that
bound that listing's waits: the positions register's walk of one class
(`(*FleetStore).eachPositionKey` in `internal/coord/kv/positions.go`), a
fleet bucket's filtered walk (`(*FleetStore).eachUnder` in
`internal/coord/kv/fleet.go`, `eachEntryUnder` and `newLeaderReader` in
`internal/coord/kv/walk.go`), and the pass a certified listing is, with its
read back of a key the pass did not deliver live (`listUnder`, `beginPass`,
`(*pass).walk`, `passOver`, `passIdle`, `(*pass).stop`, `(*pass).certify`,
`certify`, `(*pass).close` and `(*leaderReader).last` in the same file, and
`(*Leader).Last` in `internal/jsapi`). So a request added below that listing
fails it too.
Which steps a boot runs, and in what order, is `engine.New`'s, which is not
read, since it changes with most of the engine's work. The digest (`enginetest.Fingerprint`) is taken over the
function, or over the one spec that declares a constant, as `go/printer`
lays it out without its comments or the source's line breaks, so a comment
or a rewrapped line leaves it alone and a changed statement, value, spread
Expand Down
22 changes: 14 additions & 8 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,21 @@ breaks one of them is a vulnerability and should be reported as above.
reach them, unless the EngineClass routes the whole API (`exposure.api`)
through a Gateway or an Ingress of your own, which should be internal.
Your CNI must enforce NetworkPolicy for that layer to exist.
- **The data pods' leaf listener is unauthenticated.** While a company may
run seat pools, its data pods listen on port 7422 for the seat pods to
join the event stream, with no authentication and no TLS; whoever connects
reads and writes the company's whole event stream. The NetworkPolicy
- **The data pods' leaf listener admits only the company's keys, and
encrypts nothing.** While a company may run seat pools, its data pods
listen on port 7422 for the seat pods to join the event stream. Crewlet's
broker admits a link there only when it presents a key derived from the
company's keyring, so whoever can read a Secret that carries it can join:
`<company>-keyring`, `<company>-bootstrap` or any
`<company>-bootstrap-<pool>-<hash>`. Whoever joins reads and writes the
company's whole event stream; the link carries no TLS, so whoever can watch the pod network
between the pods reads what crosses it. The NetworkPolicy
`<company>-broker` admits the port only from pods carrying the company's
seat-pod labels, so the listener is closed to other namespaces only where
the CNI enforces NetworkPolicy, and within a namespace only where nobody
but the operator creates pods: one company per namespace, or a namespace
no tenant can create pods in
seat-pod labels, so other pods cannot reach the listener at all, but only
where the CNI enforces NetworkPolicy. Within a namespace neither holds
against whoever can create pods there, since such a pod can carry the
labels and mount any of those Secrets: one company per namespace, or a
namespace no tenant can create pods in
([Network policy](docs/concepts/runtime.md#network-policy)).
- **The manager's metrics endpoint requires authentication.** It is served
over HTTPS and every request is authorised against the Kubernetes API,
Expand Down
16 changes: 11 additions & 5 deletions api/v1alpha1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,19 @@ type SeatCredentialSource struct {
}

// SeatEnvVar is a named credential of a seat delivered as an environment
// variable: from a Secret, or provisioned by the engine.
// variable of its sandbox runs (a Seat's spec.sandbox.env): from a Secret, or
// provisioned by the engine.
type SeatEnvVar struct {
// name is the variable's name.
// name is the variable's name. It may not be one Crewlet sets in a coding
// run itself, which Crewlet refuses in every company document:
// CREWLET_GIT_TOKEN, CREWLET_GIT_URL, CREWLET_GIT_TRAILER_SEAT,
// CREWLET_GIT_TRAILER_RUN or CREWLET_GIT_TRAILER_WORK_ITEM (Crewlet's
// ReservedSandboxEnv).
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=128
// +kubebuilder:validation:Pattern=`^[A-Za-z_][A-Za-z0-9_]*$`
// +kubebuilder:validation:XValidation:rule="!(self in ['CREWLET_GIT_TOKEN', 'CREWLET_GIT_URL', 'CREWLET_GIT_TRAILER_SEAT', 'CREWLET_GIT_TRAILER_RUN', 'CREWLET_GIT_TRAILER_WORK_ITEM'])",message="CREWLET_GIT_TOKEN, CREWLET_GIT_URL, CREWLET_GIT_TRAILER_SEAT, CREWLET_GIT_TRAILER_RUN and CREWLET_GIT_TRAILER_WORK_ITEM are the engine's: Crewlet sets them in a coding run itself, as the run's credential on the company's own code host, the base its clones and pushes reach and the values its commit hook writes into every commit's trailers, and refuses a company document that sets one, since a value here would break the run's clones and pushes or make a commit's trailers disagree with its push's record; remove it"
Name string `json:"name,omitempty"`

// valueFrom is where the value comes from.
Expand All @@ -167,9 +173,9 @@ type SeatEnvVar struct {
}

// SecretEnvVar is a named credential delivered as an environment variable,
// such as a sandbox setup step's token or a coding CLI's model key. Crewlet
// treats every value in the positions that use this type as a credential, so
// a literal value is not accepted: the value always comes from a Secret.
// such as a coding CLI's model key. Crewlet treats every value in the
// positions that use this type as a credential, so a literal value is not
// accepted: the value always comes from a Secret.
type SecretEnvVar struct {
// name is the variable's name.
// +required
Expand Down
Loading