Skip to content

Commit 688404a

Browse files
committed
Merge remote-tracking branch 'origin/main' into iplay88keys/db-upgrade-compatibility-tests
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
2 parents ca0398c + 20b19c7 commit 688404a

167 files changed

Lines changed: 6384 additions & 772 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/kagent-dev/references/database-migrations.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ CREATE TABLE myschema.eval_set (...);
105105
CREATE TABLE IF NOT EXISTS eval_set (...);
106106
```
107107

108-
Schema is a deploy-time choice, fixed by the connection rather than the migration file. A hard-coded schema breaks any deployment that runs the track in a different schema (e.g. a connection that sets `?search_path=<schema>`). The core and vector migrations comply today (verified by inspection until the lint lands).
108+
Schema is a deploy-time choice, fixed by the connection rather than the migration file. A hard-coded schema breaks any deployment that runs the track in a different schema (e.g. a connection that sets `?search_path=<schema>`). The core and vector migrations comply, enforced by `TestSchemaAgnosticSQL`.
109109

110-
> **Enforcement.** A static lint test rejecting the forbidden patterns across all migration files (*Target — not yet enforced*; see [Static Analysis Enforcement](#static-analysis-enforcement)).
110+
> **Enforcement.** `TestSchemaAgnosticSQL` rejects the forbidden patterns across all migration files (see [Static Analysis Enforcement](#static-analysis-enforcement)).
111111
112112
### Idempotency and cross-track safety
113113

@@ -197,7 +197,7 @@ The policies above are enforced by static analysis tests in `go/core/pkg/migrati
197197
| `TestNoCrossTrackDDL` | No track may `ALTER TABLE` or `CREATE INDEX ON` a table owned by another track |
198198
| `TestMigrationGuards` | Up migrations must use `IF NOT EXISTS` on all `CREATE`/`ADD COLUMN`; down migrations must use `IF EXISTS` on all `DROP` statements |
199199
| Contraction guard *(target)* | Blocks undeclared destructive DDL — `DROP`/`RENAME` of shipped objects, type narrowing, new constraints on shipped tables (see [Backward compatibility and contraction](#backward-compatibility-and-contraction)) |
200-
| Schema-agnostic lint *(target)* | Rejects `CREATE SCHEMA`, schema-qualified DDL, `SET search_path`, and `ALTER ... SET SCHEMA` (see [Schema-agnostic SQL](#schema-agnostic-sql)) |
200+
| `TestSchemaAgnosticSQL` | Rejects `CREATE SCHEMA`, schema-qualified DDL, `SET search_path`, and `ALTER ... SET SCHEMA` (see [Schema-agnostic SQL](#schema-agnostic-sql)) |
201201

202202
**Adding a new track**: add the track directory name to the `tracks` slice in each test so the new track is covered by the same checks.
203203

.github/workflows/ci.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ name: CI Build
22

33
on:
44
push:
5-
branches: [main, release/v0.7.x]
5+
branches: [main, "release/**"]
66
paths-ignore:
77
- "**/*.md"
88
pull_request:
9-
branches: [main, release/v0.7.x]
9+
branches: [main, "release/**"]
1010
paths-ignore:
1111
- "**/*.md"
1212
workflow_dispatch:
@@ -63,7 +63,7 @@ jobs:
6363
driver-opts: network=host
6464

6565
- name: Set up Helm
66-
uses: azure/setup-helm@v5.0.0
66+
uses: azure/setup-helm@v5.0.1
6767
with:
6868
version: v3.18.0
6969

@@ -292,7 +292,7 @@ jobs:
292292
uses: actions/checkout@v6
293293

294294
- name: Set up Helm
295-
uses: azure/setup-helm@v5.0.0
295+
uses: azure/setup-helm@v5.0.1
296296
with:
297297
version: v3.18.0
298298
# Install unittest plugin

.github/workflows/migration-immutability.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Migration Immutability
22

33
on:
44
pull_request:
5-
branches: [main]
5+
branches: [main, "release/**"]
66
paths:
77
- "go/core/pkg/migrations/**"
88

.github/workflows/sqlc-generate-check.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: sqlc Generate Check
22

33
on:
44
pull_request:
5-
branches: [main]
5+
branches: [main, "release/**"]
66
paths:
77
- "go/core/internal/database/queries/**"
88
- "go/core/internal/database/sqlc.yaml"

.github/workflows/ui-chromatic.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ name: UI Storybook & Chromatic
55

66
on:
77
push:
8-
branches: [main, release/v0.7.x]
8+
branches: [main, "release/**"]
99
paths:
1010
- "ui/**"
1111
pull_request:
12-
branches: [main, release/v0.7.x]
12+
branches: [main, "release/**"]
1313
paths:
1414
- "ui/**"
1515
workflow_dispatch:

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* @EItanya @peterj @ilackarms @yuval-k
22
python/ @EItanya @peterj @yuval-k @supreme-gg-gg @iplay88keys @jmhbh
33
go/ @EItanya @ilackarms @yuval-k
4-
ui/ @peterj @Charlesthebird
4+
ui/ @peterj @Charlesthebird
55
helm/ @EItanya @ilackarms @yuval-k @supreme-gg-gg @iplay88keys @jmhbh
66
go/adk/ @supreme-gg-gg
77
go/core/ @supreme-gg-gg @iplay88keys @jmhbh

Makefile

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,19 @@ ACP_SANDBOX_OPENCLAW_IMAGE_NAME ?= acp-sandbox-openclaw
6363
CONTROLLER_IMAGE_TAG ?= $(VERSION)
6464
UI_IMAGE_TAG ?= $(VERSION)
6565
APP_IMAGE_TAG ?= $(VERSION)
66+
APP_FULL_IMAGE_TAG ?= $(VERSION)-full
6667
KAGENT_ADK_IMAGE_TAG ?= $(VERSION)
68+
KAGENT_ADK_FULL_IMAGE_TAG ?= $(VERSION)-full
6769
GOLANG_ADK_IMAGE_TAG ?= $(VERSION)
6870
GOLANG_ADK_FULL_IMAGE_TAG ?= $(VERSION)-full
6971
SKILLS_INIT_IMAGE_TAG ?= $(VERSION)
7072
ACP_SANDBOX_IMAGE_TAG ?= $(VERSION)
7173
CONTROLLER_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(CONTROLLER_IMAGE_NAME):$(CONTROLLER_IMAGE_TAG)
7274
UI_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(UI_IMAGE_NAME):$(UI_IMAGE_TAG)
7375
APP_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(APP_IMAGE_NAME):$(APP_IMAGE_TAG)
76+
APP_FULL_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(APP_IMAGE_NAME):$(APP_FULL_IMAGE_TAG)
7477
KAGENT_ADK_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(KAGENT_ADK_IMAGE_NAME):$(KAGENT_ADK_IMAGE_TAG)
78+
KAGENT_ADK_FULL_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(KAGENT_ADK_IMAGE_NAME):$(KAGENT_ADK_FULL_IMAGE_TAG)
7579
GOLANG_ADK_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(GOLANG_ADK_IMAGE_NAME):$(GOLANG_ADK_IMAGE_TAG)
7680
GOLANG_ADK_FULL_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(GOLANG_ADK_IMAGE_NAME):$(GOLANG_ADK_FULL_IMAGE_TAG)
7781
SKILLS_INIT_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(SKILLS_INIT_IMAGE_NAME):$(SKILLS_INIT_IMAGE_TAG)
@@ -208,12 +212,14 @@ build-all: buildx-create
208212

209213
.PHONY: build
210214
build: ## Build and push all component images
211-
build: buildx-create build-ui build-skills-init build-golang-adk build-golang-adk-full build-app build-controller
215+
build: buildx-create build-ui build-skills-init build-golang-adk build-golang-adk-full build-app build-app-full build-controller
212216
@echo "Build completed successfully."
213217
@echo "Controller Image: $(CONTROLLER_IMG)"
214218
@echo "UI Image: $(UI_IMG)"
215219
@echo "App Image: $(APP_IMG)"
220+
@echo "App Full Image: $(APP_FULL_IMG)"
216221
@echo "Kagent ADK Image: $(KAGENT_ADK_IMG)"
222+
@echo "Kagent ADK Full Image: $(KAGENT_ADK_FULL_IMG)"
217223
@echo "Golang ADK Image: $(GOLANG_ADK_IMG)"
218224
@echo "Golang ADK Full Image: $(GOLANG_ADK_FULL_IMG)"
219225
@echo "Skills Init Image: $(SKILLS_INIT_IMG)"
@@ -241,7 +247,9 @@ build-img-versions: ## Print the fully-qualified image tags for all components
241247
@echo controller=$(CONTROLLER_IMG)
242248
@echo ui=$(UI_IMG)
243249
@echo app=$(APP_IMG)
250+
@echo app-full=$(APP_FULL_IMG)
244251
@echo kagent-adk=$(KAGENT_ADK_IMG)
252+
@echo kagent-adk-full=$(KAGENT_ADK_FULL_IMG)
245253
@echo golang-adk=$(GOLANG_ADK_IMG)
246254
@echo golang-adk-full=$(GOLANG_ADK_FULL_IMG)
247255
@echo skills-init=$(SKILLS_INIT_IMG)
@@ -256,10 +264,11 @@ controller-manifests: ## Regenerate CRD manifests and copy them into the Helm ch
256264

257265
.PHONY: build-controller
258266
build-controller: ## Build and push the controller image (embeds agent runtime + acp-sandbox digests via scripts/controller-digest-ldflags.sh)
259-
build-controller: buildx-create controller-manifests build-app build-golang-adk build-golang-adk-full build-acp-sandbox-openclaw build-acp-sandbox-hermes
267+
build-controller: buildx-create controller-manifests build-app build-app-full build-golang-adk build-golang-adk-full build-acp-sandbox-openclaw build-acp-sandbox-hermes
260268
@set -e; \
261269
DIGEST_LDFLAGS=$$(CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) \
262270
APP_IMG=$(APP_IMG) \
271+
APP_FULL_IMG=$(APP_FULL_IMG) \
263272
GOLANG_ADK_IMG=$(GOLANG_ADK_IMG) \
264273
GOLANG_ADK_FULL_IMG=$(GOLANG_ADK_FULL_IMG) \
265274
ACP_SANDBOX_OPENCLAW_IMG=$(ACP_SANDBOX_OPENCLAW_IMG) \
@@ -284,11 +293,23 @@ build-kagent-adk: buildx-create
284293
$(DOCKER_PUSH) $(KAGENT_ADK_IMG)
285294

286295
.PHONY: build-app
287-
build-app: ## Build and push the app image (depends on kagent-adk)
296+
build-app: ## Build and push the app image (distroless slim; depends on kagent-adk)
288297
build-app: buildx-create build-kagent-adk
289298
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg KAGENT_ADK_VERSION=$(KAGENT_ADK_IMAGE_TAG) --build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY) -t $(APP_IMG) -f python/Dockerfile.app ./python
290299
$(DOCKER_PUSH) $(APP_IMG)
291300

301+
.PHONY: build-kagent-adk-full
302+
build-kagent-adk-full: ## Build and push the full Python kagent ADK image (includes sandbox runtime)
303+
build-kagent-adk-full: buildx-create
304+
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(KAGENT_ADK_FULL_IMG) -f python/Dockerfile.full ./python
305+
$(DOCKER_PUSH) $(KAGENT_ADK_FULL_IMG)
306+
307+
.PHONY: build-app-full
308+
build-app-full: ## Build and push the full app image (sandbox runtime; depends on kagent-adk-full)
309+
build-app-full: buildx-create build-kagent-adk-full
310+
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg KAGENT_ADK_VERSION=$(KAGENT_ADK_FULL_IMAGE_TAG) --build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY) -t $(APP_FULL_IMG) -f python/Dockerfile.app ./python
311+
$(DOCKER_PUSH) $(APP_FULL_IMG)
312+
292313
.PHONY: build-golang-adk
293314
build-golang-adk: ## Build and push the Go ADK image
294315
build-golang-adk: buildx-create
@@ -342,8 +363,8 @@ lint: ## Run linters for Go and Python
342363
make -C python lint
343364

344365
.PHONY: push-test-agent
345-
push-test-agent: buildx-create build-kagent-adk ## Build and push E2E test agent images to the local registry
346-
echo "Building FROM DOCKER_REGISTRY=$(DOCKER_REGISTRY)/$(DOCKER_REPO)/kagent-adk:$(VERSION)"
366+
push-test-agent: buildx-create build-kagent-adk build-kagent-adk-full ## Build and push E2E test agent images to the local registry
367+
echo "Building FROM DOCKER_REGISTRY=$(DOCKER_REGISTRY)/$(DOCKER_REPO)/kagent-adk:$(VERSION)-full"
347368
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(DOCKER_REGISTRY)/kebab:latest -f go/core/test/e2e/agents/kebab/Dockerfile ./go/core/test/e2e/agents/kebab
348369
$(DOCKER_PUSH) $(DOCKER_REGISTRY)/kebab:latest
349370
kubectl apply --namespace kagent --context kind-$(KIND_CLUSTER_NAME) -f go/core/test/e2e/agents/kebab/agent.yaml

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Do not send a report for vulnerabilities that are not part of the kagent project
2727
- You want help configuring kagent components for security purposes.
2828
- You want help applying security related updates to your kagent configuration or environment.
2929
- Your issue is not related to security vulnerabilities.
30-
- Your issue is related to base image dependencies, such as AutoGen.
30+
- Your issue is related to base image dependencies, such as Google ADK or other third-party runtimes.
3131

3232
### Evaluation
3333

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# EP-2045: SSO session-expiry re-authentication redirect
2+
3+
* Issue: [#2045](https://github.com/kagent-dev/kagent/issues/2045)
4+
5+
## Background
6+
7+
kagent is frequently deployed behind [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy)
8+
acting as an OIDC relying party. oauth2-proxy authenticates the user, maintains
9+
its own session cookie, and forwards the user's `id_token` to the kagent UI as a
10+
`Authorization: Bearer <jwt>` header. The UI decodes the JWT to derive the current
11+
user (`getAuthResult`).
12+
13+
Two distinct failure modes were conflated in the previous implementation, which
14+
decoded the token and returned `CurrentUser | null`:
15+
16+
1. **No proxy in front of the UI** — there is no `Authorization` header at all.
17+
This is a valid "unsecured" deployment (local dev, no SSO).
18+
2. **Proxy session valid but forwarded `id_token` expired** — oauth2-proxy still
19+
holds a valid session cookie, but the `id_token` it forwards has expired (its
20+
lifetime is typically shorter than the cookie session). The JWT decodes to an
21+
expired token.
22+
23+
Both previously collapsed to `null`, so the UI silently rendered a logged-out
24+
experience. In case (2) the correct behavior is to re-run the OIDC flow against
25+
oauth2-proxy (`/oauth2/start`) so a fresh `id_token` is minted, transparently
26+
restoring the session. In case (1) the UI must **not** redirect — there is no
27+
`/oauth2` endpoint, so a redirect would loop forever.
28+
29+
## Motivation
30+
31+
Users behind SSO are unexpectedly "logged out" mid-session when the forwarded
32+
`id_token` expires, even though their oauth2-proxy session is still valid. They
33+
must manually reload to recover. This is confusing and looks like a kagent bug.
34+
35+
### Goals
36+
37+
- Distinguish three auth states in the UI: `authenticated`, `expired`, `unsecured`.
38+
- On `expired`, transparently re-run the OIDC flow (redirect to the oauth2-proxy
39+
re-auth endpoint) so a fresh token is obtained without user intervention.
40+
- Never redirect in `unsecured` mode (no proxy), to avoid an infinite loop.
41+
- Guard against redirect loops if re-auth keeps returning a stale token.
42+
43+
### Non-Goals
44+
45+
- Changing the server-side token validation or the controller/HTTP server auth.
46+
- Implementing a kagent-native OIDC client (oauth2-proxy remains the RP).
47+
- Refresh-token handling inside the UI (delegated to oauth2-proxy).
48+
49+
## Implementation Details
50+
51+
### Auth status model (`ui/src/app/actions/auth.ts`)
52+
53+
`getAuthResult()` now returns an `AuthResult` instead of `CurrentUser | null`:
54+
55+
```ts
56+
export type AuthStatus = "authenticated" | "expired" | "unsecured";
57+
58+
export interface AuthResult {
59+
status: AuthStatus;
60+
user: CurrentUser | null;
61+
}
62+
```
63+
64+
- No `Authorization: Bearer` header → `{ status: "unsecured", user: null }`.
65+
- Header present but token missing/expired (`isTokenExpired`) → `{ status: "expired", user: null }`.
66+
- Valid token → `{ status: "authenticated", user: claims }`.
67+
68+
### Re-auth redirect (`ui/src/contexts/AuthContext.tsx`)
69+
70+
`AuthProvider` exposes `status` alongside `user`. When `status === "expired"`
71+
(and only then) it redirects the browser to the oauth2-proxy re-auth endpoint,
72+
preserving the current location for return:
73+
74+
```ts
75+
const SSO_REAUTH_PATH = process.env.NEXT_PUBLIC_SSO_REAUTH_PATH || "/oauth2/start";
76+
const REAUTH_GUARD_KEY = "kagent_reauth_attempt";
77+
const REAUTH_GUARD_WINDOW_MS = 10_000;
78+
// ...redirect to `${SSO_REAUTH_PATH}?rd=${encodeURIComponent(path + search)}`
79+
```
80+
81+
A `sessionStorage` guard (`REAUTH_GUARD_WINDOW_MS`) prevents a redirect loop: if a
82+
re-auth attempt happened within the window and the token is still expired, the UI
83+
surfaces an error instead of redirecting again. The guard is cleared on a
84+
successful `authenticated` result.
85+
86+
### UI surface
87+
88+
- `ui/src/components/UserMenu.tsx` — reflects the new status (e.g. distinguishes a
89+
logged-out/unsecured menu from an authenticated one).
90+
- `ui/src/app/login/page.tsx` — the branded login page (a server component) reads
91+
the server-side `SSO_REDIRECT_PATH`. `AuthContext` (a client component) reads the
92+
client-exposed `NEXT_PUBLIC_SSO_REDIRECT_PATH`; both default to `/oauth2/start`
93+
and the Helm chart injects both from `ui.auth.ssoRedirectPath`.
94+
- `docs/OIDC_PROXY_AUTH_ARCHITECTURE.md` — documents the three states and the
95+
re-auth flow.
96+
97+
### Configuration
98+
99+
| Env var | Default | Purpose |
100+
|---------|---------|---------|
101+
| `NEXT_PUBLIC_SSO_REAUTH_PATH` | `/oauth2/start` | oauth2-proxy endpoint that restarts the OIDC flow. |
102+
103+
## Test Plan
104+
105+
- **Unit (UI):** `getAuthResult` returns the correct `AuthStatus` for: no header,
106+
expired token, valid token. `AuthProvider` redirects only on `expired`, never on
107+
`unsecured`, and respects the loop guard.
108+
- **Manual / e2e:** Deploy behind oauth2-proxy with a short `id_token` lifetime;
109+
confirm that on token expiry the UI redirects to `/oauth2/start?rd=...` and
110+
returns to the same page authenticated, without a logout flash. Confirm that a
111+
no-proxy deployment never redirects.
112+
113+
## Alternatives
114+
115+
- **Silent background refresh via fetch to `/oauth2/auth`** — more complex, and
116+
oauth2-proxy already mints a fresh token on a full `/oauth2/start` round-trip.
117+
- **Server-side redirect (middleware)** — harder to distinguish unsecured vs
118+
expired without the decoded claims available client-side, and risks loops.
119+
120+
## Open Questions
121+
122+
- Should the re-auth guard window be configurable per deployment?
123+
- Should `expired` optionally render a non-blocking toast ("re-authenticating…")
124+
before the redirect for slow networks?

docker/acp-sandbox/Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,3 @@ COPY --chmod=755 \
132132
USER agent
133133
ENTRYPOINT ["/usr/local/bin/openclaw-acp-entrypoint.sh"]
134134
CMD []
135-

0 commit comments

Comments
 (0)