Skip to content

attribution: per-session human identity (STS SourceIdentity + k8s impersonation)#30

Draft
stxkxs wants to merge 1 commit into
mainfrom
attribution-source-identity
Draft

attribution: per-session human identity (STS SourceIdentity + k8s impersonation)#30
stxkxs wants to merge 1 commit into
mainfrom
attribution-source-identity

Conversation

@stxkxs

@stxkxs stxkxs commented Jun 12, 2026

Copy link
Copy Markdown
Member

Draft — the per-session human-attribution hook. Closes the "every agent action collapses to one anonymous IRSA role" gap so an evidence engine can bind actions to a named human. Marked draft because it needs companion platform IAM/RBAC (below) stood up before it does anything.

What

FAB_OPERATOR=<human> + FAB_SESSION_ROLE_ARN → the in-pod session, before the agent loop:

  • AWS — assumes the session role with the operator as STS SourceIdentity (via the aws CLI already in the image; zero new deps), exports the temp creds, and drops the pod's IRSA web-identity vars. Every AWS call — Bedrock InvokeModel and any aws the Bash tool runs — is recorded in CloudTrail under SourceIdentity=<operator>.
  • Kubernetes — writes a kubeconfig that authenticates with the SA token but impersonates the operator, so apiserver audit records impersonatedUser=<operator>.

The operator is validated STS-clean up front so the same string binds both streams; creds + kubeconfig are computed before any env mutation (no half-attributed env); fail-closed if setup fails. Inert for every other runtime and when FAB_OPERATOR is unset.

Crossbearing consumes it

CloudTrail SourceIdentityAttrSTSSourceIdentity; K8s impersonatedUserAttrK8sImpersonation. Both genuinely extracted and bound by the engine (verified against its ingesters during review). This is the "after" state of the divergence demo — corroborated agent actions attribute to a named human instead of a faceless role.

Needs (before merge) — platform IAM/RBAC

Documented in docs/attribution.md:

  • A session role whose trust policy lets the tenant IRSA role sts:AssumeRole + sts:SetSourceIdentity, with bedrock:InvokeModel in its permission policy.
  • K8s impersonate RBAC for the session SA, scoped to the operator user(s).

Notes

  • Reviewed adversarially (security/STS mechanics, fab conventions, crossbearing-consumability) — all sound; review fixes folded in (transactional env, IRSA-var cleanup, STS-clean operator validation, role-check-before-STS, doc accuracy on what crossbearing actually joins).
  • Limitations: process-wide operator (a real impl threads the requesting human per workflow onto AgentSandbox.spec), credential-TTL hard cliff (fail-safe), success-only records.
  • 20 tests; full suite 320/320; typecheck + eslint + prettier clean.

🤖 Generated with Claude Code

…ersonation)

By default a fab session acts as the pod's tenant IRSA role, bound to no
named human — so every Bedrock call and every `aws`/`kubectl` the agent
runs traces to a role, not a person. That is exactly the gap an evidence
engine surfaces. This is the opt-in that closes it.

─── src/attribution.ts ───
Set FAB_OPERATOR (a named human) + FAB_SESSION_ROLE_ARN and the in-pod
session, before the agent loop:
  - assumes the session role carrying the operator as STS SourceIdentity
    (via the `aws` CLI already in the image — no new dependency, same
    child-process posture as the claude-cli runtime), exports the temp
    creds, and drops the pod's IRSA web-identity vars so exactly one
    credential mechanism remains. Every AWS call — the Bedrock InvokeModel
    inference call and any `aws` the Bash tool runs — is then recorded in
    CloudTrail under SourceIdentity=<operator>.
  - writes a kubeconfig that authenticates with the SA token but
    impersonates the operator (KUBECONFIG), so apiserver audit records
    impersonatedUser=<operator>.
The operator is validated STS-clean up front so the SAME string binds both
streams. Credential assumption + kubeconfig are computed before any env
mutation (no half-attributed env). Fail-closed: if attribution was
requested but setup fails, the session aborts rather than run unattributed.

─── Wiring ───
role-session.ts applies it once per session pod (after a cheap role check
that avoids a wasted STS call on a typo'd role); sdk-k8s.ts forwards
FAB_OPERATOR / FAB_SESSION_ROLE_ARN / FAB_SESSION_DURATION onto the pod.
Inert for every other runtime and when FAB_OPERATOR is unset.

─── Why SourceIdentity, not Bedrock requestMetadata ───
The Agent SDK doesn't expose the InvokeModel request, so fab can't stamp
requestMetadata from this path. SourceIdentity rides the credentials the
SDK already resolves and is crossbearing's strongest binding — it
attributes the agent's aws/kubectl tool-call records, which crossbearing
corroborates.

docs/attribution.md covers the required platform IAM (session-role trust
policy allowing sts:AssumeRole + sts:SetSourceIdentity; the role needs
bedrock:InvokeModel) and the k8s impersonate RBAC, plus limitations
(process-wide operator, credential-TTL hard cliff, success-only records).
20 tests; full suite green; lint + format clean.

Co-authored-by: stxkxsbot <275011021+stxkxsbot@users.noreply.github.com>
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.

1 participant