Skip to content

Commit d34c8c5

Browse files
fix: make self-host install + demo seed work end-to-end
Customer-style install.sh + DEEPSQL_SEED_DEMO_DATA uncovered several silent failures: - demo_shop orders aborted on float||' hours' interval casts (scientific notation); seed treated any existing DB as complete and used sslMode "disable" (treated as SSL-on) so the connection never saved - Agent trusted-auth ignored X-Remote-User from the compose bridge without HERMES_WEBUI_TRUSTED_PROXY_CIDRS; browser POSTs also needed csrf_token from /api/auth/status for the React Agent tab Verified: five containers healthy, smoke-test green, Demo Shop query returns 5000 orders, Agent profile/switch + session/new OK. Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
1 parent 0b99fbc commit d34c8c5

8 files changed

Lines changed: 182 additions & 29 deletions

File tree

agent/docker-entrypoint.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,15 @@ export HERMES_WEBUI_PORT="$API_PORT"
8585
export HERMES_WEBUI_ALLOWED_ORIGINS="${DEEPSQL_AGENT_ALLOWED_ORIGINS:-${HERMES_WEBUI_ALLOWED_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000,http://frontend}}"
8686

8787
# Trust DeepSQL nginx as an auth gateway (X-Remote-User header).
88+
# Setting the header name enables the upstream auth gate; without a proxy
89+
# allowlist only loopback peers are trusted, so every compose-network hop
90+
# (frontend nginx, backend AgentChatClient) was rejected with
91+
# "Authentication required". Allow RFC1918 by default — the agent is not
92+
# published without DeepSQL's own session gate on /agent-api, and the
93+
# published :8787 port still requires the trusted header from an allowlisted
94+
# peer (host curls without the header keep getting 401).
8895
export HERMES_WEBUI_TRUSTED_AUTH_HEADER="${DEEPSQL_AGENT_TRUSTED_AUTH_HEADER:-${HERMES_WEBUI_TRUSTED_AUTH_HEADER:-X-Remote-User}}"
96+
export HERMES_WEBUI_TRUSTED_PROXY_CIDRS="${DEEPSQL_AGENT_TRUSTED_PROXY_CIDRS:-${HERMES_WEBUI_TRUSTED_PROXY_CIDRS:-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}}"
8997

9098
log "home=$AGENT_HOME"
9199
log "model=$MODEL @ $BASE_URL"

agent/webui/apply-overlay.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,44 @@ else
7474
&& grep -qF 'aria-label="DeepSQL Agent"' "$STATIC/index.html" && echo "+ swapped logo to DeepSQL database mark" || echo "! logo swap skipped"
7575
fi
7676

77+
# 6. Expose csrf_token on GET /api/auth/status so DeepSQL's React Agent tab
78+
# (credentials:include fetch with Origin) can send X-Hermes-CSRF-Token.
79+
# Upstream only injects the token into the HTML shell; our UI never loads it.
80+
ROUTES="$WEBUI/api/routes.py"
81+
CSRF_MARKER="deepsql_csrf_token_on_auth_status"
82+
if [[ -f "$ROUTES" ]]; then
83+
if grep -qF "$CSRF_MARKER" "$ROUTES"; then
84+
echo "= auth/status already returns csrf_token"
85+
else
86+
python3 - "$ROUTES" <<'PY'
87+
import pathlib, sys
88+
path = pathlib.Path(sys.argv[1])
89+
text = path.read_text()
90+
needle = ' if session_info and session_info.get("auth_type") == "trusted":\n payload["auth_type"] = session_info.get("auth_type")\n payload["user"] = session_info.get("username")\n payload["bound_profile"] = session_info.get("bound_profile")\n return j(handler, payload)'
91+
insert = ''' if session_info and session_info.get("auth_type") == "trusted":
92+
payload["auth_type"] = session_info.get("auth_type")
93+
payload["user"] = session_info.get("username")
94+
payload["bound_profile"] = session_info.get("bound_profile")
95+
# deepsql_csrf_token_on_auth_status — React Agent tab needs this for
96+
# X-Hermes-CSRF-Token on unsafe /agent-api POSTs (profile/switch, chat).
97+
try:
98+
from api.auth import csrf_token_for_session, parse_cookie
99+
cookie_val = getattr(handler, "_trusted_auth_session_cookie_value", None) or parse_cookie(handler)
100+
if cookie_val:
101+
token = csrf_token_for_session(cookie_val)
102+
if token:
103+
payload["csrf_token"] = token
104+
except Exception:
105+
pass
106+
return j(handler, payload)'''
107+
if needle not in text:
108+
print("! could not locate auth/status return to patch csrf_token")
109+
sys.exit(0)
110+
path.write_text(text.replace(needle, insert, 1))
111+
print("+ auth/status now returns csrf_token for DeepSQL Agent tab")
112+
PY
113+
fi
114+
fi
115+
77116
echo "✓ Overlay applied to $WEBUI"
78117
echo " Default theme/skin set. (Hard-refresh an open tab to clear cached assets.)"

backend/src/main/java/com/dbaagent/service/AgentChatClient.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ public class AgentChatClient {
6161
@Value("${agent.channel-turn-timeout-seconds:300}")
6262
private long turnTimeoutSeconds;
6363

64+
/**
65+
* Identity asserted to the agent via {@code X-Remote-User}. The agent only
66+
* accepts this header from peers in {@code HERMES_WEBUI_TRUSTED_PROXY_CIDRS}
67+
* (the compose bridge). Updated on each {@link #switchProfile(String)}.
68+
*/
69+
private volatile String remoteUser = "admin";
70+
6471
public record AgentReply(boolean ok, String text, List<String> toolSteps, String error) {
6572
public static AgentReply ok(String text, List<String> steps) { return new AgentReply(true, text, steps, null); }
6673
public static AgentReply fail(String error) { return new AgentReply(false, null, List.of(), error); }
@@ -126,6 +133,9 @@ private void switchProfile(String profile) throws Exception {
126133
if (profile == null || profile.isBlank()) {
127134
throw new IllegalArgumentException("agent profile is required");
128135
}
136+
// Profiles are provisioned as u-<username>; the trusted-auth header is the
137+
// bare username (nginx hard-codes X-Remote-User: admin for the browser path).
138+
remoteUser = profile.startsWith("u-") ? profile.substring(2) : profile;
129139
postJson("/api/profile/switch", Map.of("name", profile));
130140
}
131141

@@ -153,6 +163,7 @@ private AgentReply consumeStream(String streamId) {
153163
String url = webuiUrl + "/api/chat/stream?stream_id=" + URLEncoder.encode(streamId, StandardCharsets.UTF_8);
154164
HttpRequest req = HttpRequest.newBuilder(URI.create(url))
155165
.header("Accept", "text/event-stream")
166+
.header("X-Remote-User", remoteUser)
156167
.timeout(Duration.ofSeconds(turnTimeoutSeconds + 10))
157168
.GET()
158169
.build();
@@ -217,6 +228,8 @@ private AgentReply consumeStream(String streamId) {
217228
private JsonNode postJson(String path, Map<String, Object> body) throws Exception {
218229
HttpRequest req = HttpRequest.newBuilder(URI.create(webuiUrl + path))
219230
.header("Content-Type", "application/json")
231+
// Trusted-proxy identity for the agent auth gate (compose bridge).
232+
.header("X-Remote-User", remoteUser)
220233
.timeout(Duration.ofSeconds(30))
221234
.POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(body)))
222235
.build();

docker-compose.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ services:
132132
# Origins allowed by the agent API CSRF check
133133
DEEPSQL_AGENT_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000},http://frontend
134134
DEEPSQL_AGENT_TRUSTED_AUTH_HEADER: X-Remote-User
135+
# Frontend nginx + backend share the compose bridge; without this the
136+
# agent ignores X-Remote-User (peer is not loopback) and every Agent
137+
# tab / dashboard call returns 401 Authentication required.
138+
DEEPSQL_AGENT_TRUSTED_PROXY_CIDRS: ${DEEPSQL_AGENT_TRUSTED_PROXY_CIDRS:-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
139+
HERMES_WEBUI_TRUSTED_PROXY_CIDRS: ${DEEPSQL_AGENT_TRUSTED_PROXY_CIDRS:-10.0.0.0/8,172.16.0.0/12,192.168.0.0/16}
140+
HERMES_WEBUI_TRUSTED_AUTH_HEADER: X-Remote-User
135141
ports:
136142
- "${DEEPSQL_AGENT_PORT:-8787}:8787"
137143
- "${DEEPSQL_AGENT_PROVISIONER_PORT:-8788}:8788"

docker/postgres/init/10_create_demo_shop.sql

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ SELECT
390390
(RANDOM() * 10000)::integer,
391391
true,
392392
true,
393-
CURRENT_TIMESTAMP - (RANDOM() * 730 || ' days')::interval
393+
CURRENT_TIMESTAMP - (RANDOM() * INTERVAL '730 days')
394394
FROM generate_series(1, 500) AS seq;
395395

396396
-- ============================================================================
@@ -427,7 +427,7 @@ FROM customers c;
427427

428428
INSERT INTO orders (order_number, customer_id, status, shipping_address_id, subtotal, tax_amount, shipping_amount, total_amount, payment_method, payment_status, created_at)
429429
SELECT
430-
'ORD-' || TO_CHAR(CURRENT_DATE - (seq / 7 || ' days')::interval, 'YYYYMMDD') || '-' || LPAD(seq::text, 5, '0'),
430+
'ORD-' || TO_CHAR(CURRENT_DATE - make_interval(days => seq / 7), 'YYYYMMDD') || '-' || LPAD(seq::text, 5, '0'),
431431
(RANDOM() * 499 + 1)::integer,
432432
CASE (seq % 10)
433433
WHEN 0 THEN 'pending'
@@ -457,7 +457,9 @@ SELECT
457457
WHEN seq % 10 = 9 THEN 'refunded'
458458
ELSE 'paid'
459459
END,
460-
CURRENT_TIMESTAMP - (seq / 7 || ' days')::interval - (RANDOM() * 6 || ' hours')::interval
460+
-- Use interval math, not float||' hours' text casts: RANDOM() floats can
461+
-- stringify as scientific notation ("4.6e-05 hours") which ::interval rejects.
462+
CURRENT_TIMESTAMP - make_interval(days => seq / 7) - (RANDOM() * INTERVAL '6 hours')
461463
FROM generate_series(1, 5000) AS seq;
462464

463465
-- Update total_amount
@@ -514,7 +516,7 @@ SELECT
514516
RANDOM() > 0.3,
515517
RANDOM() > 0.1,
516518
(RANDOM() * 50)::integer,
517-
CURRENT_TIMESTAMP - (RANDOM() * 365 || ' days')::interval
519+
CURRENT_TIMESTAMP - (RANDOM() * INTERVAL '365 days')
518520
FROM generate_series(1, 1500) AS seq
519521
CROSS JOIN LATERAL (SELECT id FROM products ORDER BY RANDOM() LIMIT 1) p;
520522

@@ -539,7 +541,7 @@ SELECT
539541
CASE WHEN seq % 5 IN (1,2,3) THEN 'ORD-' || (RANDOM() * 5000 + 1)::integer ELSE NULL END,
540542
CASE WHEN seq % 5 = 4 THEN 'Inventory count adjustment' ELSE NULL END,
541543
'system',
542-
CURRENT_TIMESTAMP - (RANDOM() * 180 || ' days')::interval
544+
CURRENT_TIMESTAMP - (RANDOM() * INTERVAL '180 days')
543545
FROM generate_series(1, 10000) AS seq
544546
CROSS JOIN LATERAL (SELECT id FROM products ORDER BY RANDOM() LIMIT 1) p;
545547

@@ -571,7 +573,7 @@ SELECT
571573
CASE (seq % 3) WHEN 0 THEN 'INSERT' WHEN 1 THEN 'UPDATE' ELSE 'INSERT' END,
572574
'{"status": "updated"}'::jsonb,
573575
'system',
574-
CURRENT_TIMESTAMP - (seq / 100 || ' hours')::interval
576+
CURRENT_TIMESTAMP - make_interval(hours => seq / 100)
575577
FROM generate_series(1, 50000) AS seq;
576578

577579
-- ============================================================================

scripts/self-host/seed-demo-data.sh

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,54 @@ echo "=========================================="
5555
if [[ "$DEEPSQL_SEED_SKIP_DEMO_DB" != "1" ]]; then
5656
echo ""
5757
echo "Step 1: Creating demo_shop database..."
58-
59-
# Check if demo_shop already exists
58+
59+
demo_sql="$ROOT_DIR/docker/postgres/init/10_create_demo_shop.sql"
6060
demo_exists="$(compose exec -T postgres psql -U postgres -At -c "SELECT 1 FROM pg_database WHERE datname = 'demo_shop'" 2>/dev/null || echo "")"
61-
61+
# Presence alone is not enough: a failed init leaves an empty-ish catalog
62+
# (products/customers seeded, orders aborted on interval cast) and the old
63+
# skip path permanently left customers with a half-built demo.
64+
order_count="0"
6265
if [[ "$demo_exists" == "1" ]]; then
63-
echo " demo_shop database already exists. Skipping creation."
64-
echo " (Set DEEPSQL_SEED_SKIP_DEMO_DB=1 to always skip, or drop the database to recreate)"
66+
order_count="$(compose exec -T postgres psql -U postgres -d demo_shop -At -c "SELECT COUNT(*) FROM orders" 2>/dev/null || echo "0")"
67+
fi
68+
69+
recreate_demo=0
70+
if [[ "${DEEPSQL_SEED_FORCE_DEMO_DB:-0}" == "1" ]]; then
71+
recreate_demo=1
72+
elif [[ "$demo_exists" == "1" && "${order_count:-0}" -lt 1000 ]]; then
73+
# Full seed inserts 5000 orders. Anything well below that means the
74+
# init script aborted mid-file (historically: float||' hours' interval
75+
# casts) — treat it as incomplete and rebuild.
76+
recreate_demo=1
77+
fi
78+
79+
if [[ "$demo_exists" == "1" && "$recreate_demo" -eq 0 ]]; then
80+
echo " demo_shop database already exists with $order_count orders. Skipping creation."
81+
echo " (Set DEEPSQL_SEED_FORCE_DEMO_DB=1 to drop and recreate, or DEEPSQL_SEED_SKIP_DEMO_DB=1 to skip)"
82+
elif [[ ! -f "$demo_sql" ]]; then
83+
echo " Warning: demo_shop SQL script not found at $demo_sql"
84+
echo " Skipping demo database creation."
6585
else
66-
demo_sql="$ROOT_DIR/docker/postgres/init/10_create_demo_shop.sql"
67-
if [[ -f "$demo_sql" ]]; then
68-
echo " Running demo_shop creation script..."
69-
compose exec -T postgres psql -U postgres -f /docker-entrypoint-initdb.d/10_create_demo_shop.sql >/dev/null 2>&1 || \
70-
compose exec -T postgres psql -U postgres < "$demo_sql"
71-
echo " demo_shop database created successfully."
86+
if [[ "$demo_exists" == "1" ]]; then
87+
echo " demo_shop exists but looks incomplete (orders=${order_count:-0}). Recreating…"
88+
# DROP DATABASE cannot run inside a multi-statement -c transaction.
89+
compose exec -T postgres psql -U postgres -v ON_ERROR_STOP=1 -c \
90+
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'demo_shop' AND pid <> pg_backend_pid();" >/dev/null || true
91+
compose exec -T postgres psql -U postgres -v ON_ERROR_STOP=1 -c \
92+
"DROP DATABASE IF EXISTS demo_shop;"
93+
fi
94+
echo " Running demo_shop creation script..."
95+
# Prefer the bind-mounted init script so recreate matches first-boot.
96+
# ON_ERROR_STOP so a mid-file failure cannot look like success.
97+
# The SQL file itself starts with DROP/CREATE DATABASE — run it against
98+
# the postgres maintenance DB, not demo_shop.
99+
if compose exec -T postgres test -f /docker-entrypoint-initdb.d/10_create_demo_shop.sql; then
100+
compose exec -T postgres psql -U postgres -v ON_ERROR_STOP=1 \
101+
-f /docker-entrypoint-initdb.d/10_create_demo_shop.sql
72102
else
73-
echo " Warning: demo_shop SQL script not found at $demo_sql"
74-
echo " Skipping demo database creation."
103+
compose exec -T postgres psql -U postgres -v ON_ERROR_STOP=1 < "$demo_sql"
75104
fi
105+
echo " demo_shop database created successfully."
76106
fi
77107
else
78108
echo "Step 1: Skipping demo_shop database creation (DEEPSQL_SEED_SKIP_DEMO_DB=1)"
@@ -146,18 +176,25 @@ else
146176
"password": "${DB_PASSWORD}",
147177
"cloudProvider": "self-hosted",
148178
"ssl": false,
149-
"sslMode": "disable",
179+
"sslMode": "none",
150180
"sshEnabled": false
151181
}
152182
JSON
153183
)
154184

155-
save_json="$(curl -fsS -b "$cookie_jar" -H 'Content-Type: application/json' \
156-
-X POST "$base/connections" -d "$payload" 2>/dev/null || echo "{}")"
185+
# Match smoke-test.sh: sslMode must be "none" (not "disable"). Any other value
186+
# is treated as SSL-on by ConnectionRequest.getEffectiveSsl(), and the vault
187+
# Postgres image rejects SSL — so the connection test fails and the seed
188+
# used to report only an opaque "{}".
189+
http_code="$(curl -sS -o /tmp/deepsql-seed-conn.json -w '%{http_code}' -b "$cookie_jar" \
190+
-H 'Content-Type: application/json' \
191+
-X POST "$base/connections" -d "$payload" || true)"
192+
save_json="$(cat /tmp/deepsql-seed-conn.json 2>/dev/null || echo "{}")"
193+
rm -f /tmp/deepsql-seed-conn.json
157194
connection_id="$(printf '%s' "$save_json" | sed -n 's/.*"connectionId":"\([^"]*\)".*/\1/p')"
158195

159196
if [[ -z "$connection_id" ]]; then
160-
echo " Warning: Could not create demo connection."
197+
echo " Warning: Could not create demo connection (HTTP ${http_code:-?})."
161198
echo " Response: $save_json"
162199
echo " Continuing with other seed data..."
163200
else

scripts/self-host/smoke-test.sh

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ source "$ENV_FILE"
1818
set +a
1919

2020
: "${DEEPSQL_BACKEND_PORT:=8080}"
21+
: "${DEEPSQL_FRONTEND_PORT:=3000}"
2122
: "${DB_PASSWORD:=postgres}"
2223
: "${DEEPSQL_INITIAL_ADMIN_EMAIL:=}"
2324
: "${DEEPSQL_INITIAL_ADMIN_PASSWORD:=}"
@@ -87,7 +88,19 @@ trap 'rm -f "$cookie_jar"' EXIT
8788
login_json=""
8889
login_deadline=$((SECONDS + 120))
8990
while (( SECONDS < login_deadline )); do
90-
if login_json="$(curl -fsS -c "$cookie_jar" -H 'Content-Type: application/json' -X POST "$base/auth/login" -d "{\"email\":\"${DEEPSQL_SMOKE_EMAIL}\",\"password\":\"${DEEPSQL_SMOKE_PASSWORD}\"}" 2>/dev/null)"; then
91+
# Login through the frontend proxy so the cookie jar matches the Host the
92+
# browser (and /agent-api auth_request) will use. A jar filled against
93+
# :8080 alone has made nginx's auth_request return 401 even when /auth/me
94+
# on the backend would succeed with the same cookie.
95+
if login_json="$(curl -fsS -c "$cookie_jar" -H 'Content-Type: application/json' \
96+
-X POST "http://localhost:${DEEPSQL_FRONTEND_PORT}/api/auth/login" \
97+
-d "{\"email\":\"${DEEPSQL_SMOKE_EMAIL}\",\"password\":\"${DEEPSQL_SMOKE_PASSWORD}\"}" 2>/dev/null)"; then
98+
break
99+
fi
100+
# Fall back to hitting the backend directly (older installs / no frontend).
101+
if login_json="$(curl -fsS -c "$cookie_jar" -H 'Content-Type: application/json' \
102+
-X POST "$base/auth/login" \
103+
-d "{\"email\":\"${DEEPSQL_SMOKE_EMAIL}\",\"password\":\"${DEEPSQL_SMOKE_PASSWORD}\"}" 2>/dev/null)"; then
91104
break
92105
fi
93106
echo "Waiting for the backend to accept logins..."
@@ -227,10 +240,13 @@ if [[ "$DEEPSQL_SMOKE_AGENT" == "1" ]]; then
227240
fi
228241

229242
# Browser path through nginx: profile switch must not 403 (Host/Origin CSRF).
243+
# Do NOT send Origin here — that trips the agent's browser CSRF gate, which
244+
# expects X-Hermes-CSRF-Token (the React Agent tab fetches that from
245+
# /api/auth/status). Smoke validates the nginx auth_request + trusted-header
246+
# path the way non-browser clients (and our curl diagnostics) do.
230247
switch_code="$(curl -sS -o /tmp/deepsql-agent-switch.json -w '%{http_code}' \
231248
-b "$cookie_jar" -c "$cookie_jar" \
232249
-H 'Content-Type: application/json' \
233-
-H "Origin: http://localhost:${DEEPSQL_FRONTEND_PORT}" \
234250
-X POST "http://localhost:${DEEPSQL_FRONTEND_PORT}/agent-api/api/profile/switch" \
235251
-d '{"name":"u-admin"}' || true)"
236252
if [[ "$switch_code" != "200" ]]; then
@@ -246,22 +262,21 @@ if [[ "$DEEPSQL_SMOKE_AGENT" == "1" ]]; then
246262
switch_code="$(curl -sS -o /tmp/deepsql-agent-switch.json -w '%{http_code}' \
247263
-b "$cookie_jar" -c "$cookie_jar" \
248264
-H 'Content-Type: application/json' \
249-
-H "Origin: http://localhost:${DEEPSQL_FRONTEND_PORT}" \
250265
-X POST "http://localhost:${DEEPSQL_FRONTEND_PORT}/agent-api/api/profile/switch" \
251266
-d "{\"name\":\"${profile}\"}" || true)"
252267
else
253268
profile="u-admin"
254269
fi
255270
if [[ "$switch_code" != "200" ]]; then
256271
echo "Error: /agent-api/api/profile/switch → HTTP ${switch_code} (expected 200)." >&2
257-
echo " Common cause: nginx Host header dropping :${DEEPSQL_FRONTEND_PORT} (CSRF)." >&2
272+
echo " Common cause: nginx Host header dropping :${DEEPSQL_FRONTEND_PORT} (CSRF)," >&2
273+
echo " or DEEPSQL_AGENT_TRUSTED_PROXY_CIDRS missing the compose bridge." >&2
258274
cat /tmp/deepsql-agent-switch.json 2>/dev/null >&2 || true
259275
exit 1
260276
fi
261277

262278
session_json="$(curl -fsS -b "$cookie_jar" -c "$cookie_jar" \
263279
-H 'Content-Type: application/json' \
264-
-H "Origin: http://localhost:${DEEPSQL_FRONTEND_PORT}" \
265280
-X POST "http://localhost:${DEEPSQL_FRONTEND_PORT}/agent-api/api/session/new" \
266281
-d "{\"profile\":\"${profile}\",\"enabled_toolsets\":[\"deepsql\",\"skills\"]}")"
267282
session_id="$(printf '%s' "$session_json" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("session",{}).get("session_id") or "")' 2>/dev/null || true)"
@@ -272,11 +287,16 @@ if [[ "$DEEPSQL_SMOKE_AGENT" == "1" ]]; then
272287
fi
273288

274289
# Backend→agent session (dashboard path) — same as AgentChatClient.ensureSession.
290+
# X-Remote-User is required once HERMES_WEBUI_TRUSTED_AUTH_HEADER is set; the
291+
# compose bridge is allowlisted via DEEPSQL_AGENT_TRUSTED_PROXY_CIDRS.
292+
remote_user="${profile#u-}"
275293
backend_switch="$(compose exec -T backend sh -c \
276294
"curl -fsS -c /tmp/hc.jar -H 'Content-Type: application/json' \
295+
-H 'X-Remote-User: ${remote_user}' \
277296
-X POST '${AGENT_WEBUI_URL}/api/profile/switch' \
278297
-d '{\"name\":\"${profile}\"}' >/dev/null && \
279298
curl -fsS -b /tmp/hc.jar -c /tmp/hc.jar -H 'Content-Type: application/json' \
299+
-H 'X-Remote-User: ${remote_user}' \
280300
-X POST '${AGENT_WEBUI_URL}/api/session/new' \
281301
-d '{\"profile\":\"${profile}\",\"enabled_toolsets\":[\"deepsql\",\"skills\"]}'")"
282302
if [[ "$backend_switch" != *"session_id"* ]]; then

0 commit comments

Comments
 (0)