Skip to content

feat(egress): keep artifact credentials off a Session's egress policy - #306

Closed
teemow wants to merge 3 commits into
giantswarmfrom
feat/egress-golden-scoped-credentials
Closed

teemow wants to merge 3 commits into
giantswarmfrom
feat/egress-golden-scoped-credentials

Conversation

@teemow

@teemow teemow commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Closes #286.

Problem

A private skill or plugin source binds its git credential (an App or deploy token) as authorization: Basic on the source's host. The revision's one Credentials list compiled both the golden template's default egress policy, which the golden boot needs to fetch the source, and every Session actor's own policy (create, repointTarget and its rollback). So any request a session's sandbox sent to that host with an Authorization header (a shell tool, an MCP client, gh) left with the token, and read what the token can reach rather than what the person of the turn can.

Proposed solution

One carried, upstream-ready commit: a binding names the actors it is for.

  • egress.Credential gains Scope (json:"scope,omitempty"): empty for every actor (model and MCP bindings, unchanged), golden for the golden boot only. CompileCredentials scopes every artifact binding golden; CanonicalCredentials validates the scope and keeps its same-host-and-header rule.
  • ActorTemplateForRevision still compiles the template's default policy from every binding.
  • The three Session sites compile through one helper, sessionEgressPolicy, from egress.ForActors(bindings). The source's host stays allowed from the sandbox, unauthenticated.
  • Substrate gives a new Actor its template's default policy at creation, which now differs from the Session's. Session creation (and a checkpoint fork) passes the template's default to EnsureActorEgressPolicy as the inherited policy: an existing policy equal to it is replaced with the Session's before the Actor first runs, one equal to the Session's is accepted as before (a retried create), any other allowlist is still refused (fix(substrate): replace a new Actor's inherited template allowlist).
  • refactor(session): drop the execute path's dead revision writes: two assignments after the last read that ineffassign reports once the create path reads the revision through the helper.
  • No migration: the JSONB runtime_revision.credentials takes the field. The scope is part of the revision digest, so an agent with a credentialed source renders a new revision once (a golden boot) and its READY sessions are repointed by the superseded-revision mechanism, with their policy replaced; other agents keep their digest. No actorTemplateFormat bump: the golden policy is unchanged.
  • docs/architecture/credential-injection.md describes the scope; FORK.md has the row with the upstream exit (the follow-up a review note on feat(skills): private Git skill sources via egress gateway credentials kagent-dev/kagent#3099 names, once that pull request is merged).

This lands beside #147, which changes translator/credentials.go and egress/credentials.go in other hunks; whichever lands second rebases.

Acceptance criteria

  • Unit tests: TestCompileCredentialsScopesArtifactBindingsToTheGoldenBoot (artifact golden, model and MCP none), TestActorTemplateForRevision (the default policy carries the artifact binding), TestSessionEgressPolicyLeavesOutTheGoldenBootsCredentials (the policy passed to EnsureActorEgressPolicy on create and the one repointTarget builds carry none of it and keep the model and MCP bindings), TestRevisionDigestChangesOnlyWithAnArtifactBinding (a model binding keeps the digest pinned from the base commit; an artifact binding changes it), TestForActors, TestCanonicalCredentials, TestEnsureActorEgressPolicyReplacesTheInheritedDefault.
  • TestGitArtifactCredentialDelivery (every harness) keeps its golden-boot assertion, limited to the fixture's requests recorded before the Agent reported Ready, and gains a turn in which the sandbox's shell tool (driven by the interaction mock) runs git ls-remote against the source twice: once without Authorization, as git does before a challenge, and once with the placeholder header the golden boot sends. Every request recorded from Ready on carries the Secret value in no header and reaches the fixture (none is denied by the gateway): the bare one arrives bare, the placeholder one with the placeholder, not replaced. The placeholder request is the regression check, since the gateway replaces only a header the request carries: before this change it arrived with the Secret.
  • The existing model and MCP credential e2e cases stay green; TestSessionEgressDeniesUnconfiguredDestination unchanged.
  • Released as the line's next release candidate.

A private skill or plugin source binds its git credential as an
Authorization header on the source's host. The revision's one credential
list compiled both the golden template's default policy, which the golden
boot needs to fetch the source, and every Session actor's own policy, so a
request the sandbox sent to that host with an Authorization header left
with the token.

A binding now carries a scope: empty for every actor (model and MCP
bindings), golden for the golden boot only (artifact bindings). The golden
template keeps every binding; the Session's create, repoint and rollback
policies are compiled from egress.ForActors, without the golden ones. The
host stays reachable from the sandbox, unauthenticated.

The scope is part of the revision digest, so an agent with a credentialed
source renders a new revision once and its READY sessions are repointed
onto it; agents without one keep their digest.

Signed-off-by: Timo Derstappen <teemow@gmail.com>
teemow added 2 commits October 9, 2026 13:52
The execute path set revision to the repoint target in two places after its last read; ineffassign reports both once the create path reads the revision through sessionEgressPolicy.

Signed-off-by: Timo Derstappen <teemow@gmail.com>
Substrate gives an Actor its template's default egress policy at creation. A Session's policy now differs from that default when the template carries a golden-only binding, and EnsureActorEgressPolicy refused the existing allowlist, so every Session of such an agent failed to create. The create path now passes the template's default as the inherited policy: an existing policy equal to it is replaced with the Session's before the Actor first runs; any other allowlist is still refused.

Signed-off-by: Timo Derstappen <teemow@gmail.com>
@teemow

teemow commented Oct 10, 2026

Copy link
Copy Markdown
Member Author

(Written by an agent.) Replaced by #366: the base moved 35 commits (the Vertex AI credential authority, the Session workspace volumes, one carried file per change under fork/carried) and this branch conflicted with it; this line rebase-merges, so the change moved to a fresh branch of the base.

@teemow teemow closed this Oct 10, 2026
@teemow
teemow deleted the feat/egress-golden-scoped-credentials branch October 10, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session egress policy compiled without artifact credentials

1 participant