Skip to content

Commit 29c1f14

Browse files
committed
fix(release): close Studio and security gates
1 parent c3100b3 commit 29c1f14

15 files changed

Lines changed: 1626 additions & 46 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: Set up Node
3333
uses: actions/setup-node@v4
3434
with:
35-
node-version: "20"
35+
node-version: "22"
3636
cache: pnpm
3737
cache-dependency-path: docs-site/pnpm-lock.yaml
3838

@@ -77,7 +77,7 @@ jobs:
7777
- name: Set up Node
7878
uses: actions/setup-node@v4
7979
with:
80-
node-version: "20"
80+
node-version: "22"
8181
cache: npm
8282
cache-dependency-path: ksadk/studio/react-ui/package-lock.json
8383

@@ -116,7 +116,7 @@ jobs:
116116
- name: Set up Node
117117
uses: actions/setup-node@v4
118118
with:
119-
node-version: "20"
119+
node-version: "22"
120120
cache: npm
121121
cache-dependency-path: ksadk/studio/react-ui/package-lock.json
122122

docs/maintainer-approval-record.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ Record exactly one approved source publication strategy.
3131
The approved strategy must name the reviewed commit, tag, pull request, or
3232
export archive used for:
3333

34-
- `ksadk-python`: clean public export from reviewed internal candidate `6825af6354eb8f37723992023c44c6ee4201e812`.
35-
- `ksadk-web`: trusted npm package `@kingsoftcloud/ksadk-web@0.3.2`, source commit `2136448e038b4d8c475fa20e4722252b1ddb2ebc`, GitHub merge `4854be4fcb5584a799538536372d38b80447f81e`, integrity `sha512-Ytjd3pIgy6LfHCmguXUDQr/wy9ClqKjbv+J+NAzH/+UIJjhVl3y1SA2eR7WwsWSn42zxBFme/xniUZMNBV53Aw==`; approval is bound to Python source commit `6825af6354eb8f37723992023c44c6ee4201e812`.
34+
- `ksadk-python`: clean public export from reviewed internal candidate `45219431bbf10df8a1f6ca32dfcb0fdfed60fd68`.
35+
- `ksadk-web`: trusted npm package `@kingsoftcloud/ksadk-web@0.3.2`, source commit `2136448e038b4d8c475fa20e4722252b1ddb2ebc`, GitHub merge `4854be4fcb5584a799538536372d38b80447f81e`, integrity `sha512-Ytjd3pIgy6LfHCmguXUDQr/wy9ClqKjbv+J+NAzH/+UIJjhVl3y1SA2eR7WwsWSn42zxBFme/xniUZMNBV53Aw==`; approval is bound to Python source commit `45219431bbf10df8a1f6ca32dfcb0fdfed60fd68`.
3636

3737
Both approved source references include the reviewed Python source commit SHA.
3838
This prevents a stale approval record from passing after candidate changes.

export-manifest.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
2-
"generatedAt": "2026-08-24T18:39:34.319354+00:00",
2+
"generatedAt": "2026-08-24T19:07:10.418312+00:00",
33
"targetRepository": "https://github.com/kingsoftcloud/ksadk-python",
44
"documentation": "https://kingsoftcloud.github.io/ksadk-python/",
5-
"exportPathCount": 985,
6-
"excludedPathCount": 608,
5+
"exportPathCount": 989,
6+
"excludedPathCount": 604,
77
"excludedPaths": [
88
"contracts/agent-kernel/v1/activation-lease.schema.json",
99
"contracts/agent-kernel/v1/agent-control.schema.json",
@@ -428,9 +428,6 @@
428428
"tests/studio/e2e/fake_studio_server.py",
429429
"tests/studio/e2e/fixtures/review_workspace/src/demo.py",
430430
"tests/studio/e2e/pcm_browser_smoke.py",
431-
"tests/studio/e2e/studio_browser_smoke.py",
432-
"tests/studio/e2e/studio_e2e_support.py",
433-
"tests/studio/e2e/studio_responsive_smoke.py",
434431
"tests/studio/e2e/test_codex_xingliu_demo.py",
435432
"tests/studio/runtime_adapter_fixtures.py",
436433
"tests/studio/test_agent_budget_chain.py",
@@ -472,7 +469,6 @@
472469
"tests/studio/test_shared_web.py",
473470
"tests/studio/test_skill_discovery.py",
474471
"tests/studio/test_studio_mcp_runtime.py",
475-
"tests/studio/test_style_system.py",
476472
"tests/studio/test_templates.py",
477473
"tests/studio/test_validator_compiler.py",
478474
"tests/studio/test_workspace_repository.py",
@@ -676,6 +672,10 @@
676672
"tests/events/fixtures/runtime_projection_golden.json",
677673
"tests/runners/test_adapter_contract.py",
678674
"tests/runners/test_codex_runner.py",
675+
"tests/studio/e2e/studio_browser_smoke.py",
676+
"tests/studio/e2e/studio_e2e_support.py",
677+
"tests/studio/e2e/studio_responsive_smoke.py",
678+
"tests/studio/test_style_system.py",
679679
"tests/test_check_approval_record.py",
680680
"tests/test_check_publication_state.py",
681681
"tests/test_config_env_registry.py",

ksadk/kernel/ingress.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import hashlib
2626
import json
27+
import logging
2728
import os
2829
import uuid
2930
from collections.abc import AsyncIterator, Awaitable, Callable, Mapping
@@ -58,6 +59,8 @@
5859

5960
_TRUTHY = {"1", "true", "yes", "on"}
6061

62+
logger = logging.getLogger(__name__)
63+
6164
_kernel: Any | None = None
6265

6366

@@ -806,10 +809,11 @@ def _hosted_permit(
806809
)
807810
try:
808811
return AgentControlPermit.model_validate(raw)
809-
except Exception as exc:
812+
except Exception:
813+
logger.info("rejected malformed hosted permit", exc_info=True)
810814
return JSONResponse(
811815
status_code=403,
812-
content={"error": {"Code": "invalid_permit", "Message": str(exc)}},
816+
content={"error": {"Code": "invalid_permit", "Message": "permit 格式无效"}},
813817
)
814818

815819
@router.post(KERNEL_INGRESS_SUBMIT_PATH)
@@ -823,10 +827,11 @@ async def submit_agent_control(request: Request) -> Any:
823827
permit_data = body.get("permit")
824828
try:
825829
command = AgentControlCommand.model_validate(body.get("command") or body)
826-
except Exception as exc:
830+
except Exception:
831+
logger.info("rejected malformed agent control command", exc_info=True)
827832
return JSONResponse(
828833
status_code=400,
829-
content={"error": {"Code": "invalid_command", "Message": str(exc)}},
834+
content={"error": {"Code": "invalid_command", "Message": "command 格式无效"}},
830835
)
831836
if _is_hosted():
832837
permit = _hosted_permit(request, permit_data)
@@ -835,10 +840,11 @@ async def submit_agent_control(request: Request) -> Any:
835840
elif permit_data:
836841
try:
837842
permit = AgentControlPermit.model_validate(permit_data)
838-
except Exception as exc:
843+
except Exception:
844+
logger.info("rejected malformed local permit", exc_info=True)
839845
return JSONResponse(
840846
status_code=403,
841-
content={"error": {"Code": "invalid_permit", "Message": str(exc)}},
847+
content={"error": {"Code": "invalid_permit", "Message": "permit 格式无效"}},
842848
)
843849
else:
844850
# 无 permit(gateway 内网转发 / 本地灰度):trusted context 进程内签发。
@@ -881,10 +887,11 @@ async def get_agent_status(request: Request) -> Any:
881887
body = await request.json()
882888
try:
883889
query = AgentStatusQuery.model_validate(body.get("query") or body)
884-
except Exception as exc:
890+
except Exception:
891+
logger.info("rejected malformed agent status query", exc_info=True)
885892
return JSONResponse(
886893
status_code=400,
887-
content={"error": {"Code": "invalid_query", "Message": str(exc)}},
894+
content={"error": {"Code": "invalid_query", "Message": "query 格式无效"}},
888895
)
889896
if _is_hosted():
890897
permit = _hosted_permit(request, body.get("permit"))

ksadk/model_proxy/server.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,14 @@ async def responses(req: Request):
146146
body = await req.json()
147147
try:
148148
chat_req, restore_map = responses_to_chat(body)
149-
except UnsupportedToolsError as e:
150-
logger.info("responses request uses unsupported tools: %s", e)
149+
except UnsupportedToolsError:
150+
logger.info("responses request uses unsupported tools", exc_info=True)
151151
return JSONResponse(
152152
status_code=400,
153153
content={
154154
"error": {
155155
"type": "unsupported_tools",
156-
"message": f"The request uses tools unsupported by the model upstream: {e}",
156+
"message": "The request uses tools unsupported by the model upstream.",
157157
}
158158
},
159159
)

ksadk/studio/api.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -506,8 +506,11 @@ async def update_settings(payload: dict[str, Any]):
506506

507507
@app.post("/api/v1/workspaces:open")
508508
async def open_workspace(payload: WorkspaceOpenRequest):
509-
requested = Path(payload.path).expanduser().resolve()
510-
if requested != studio.workspace.root:
509+
# This endpoint only reconnects to the daemon's already-bound root. Do
510+
# not resolve or otherwise touch a caller-provided filesystem path.
511+
requested = os.path.normcase(os.path.abspath(os.path.expanduser(payload.path)))
512+
bound_root = os.path.normcase(str(studio.workspace.root))
513+
if requested != bound_root:
511514
raise StudioError(
512515
"WORKSPACE_PATH_FORBIDDEN",
513516
"当前 Daemon 不允许切换到启动 root 之外的工作区",

ksadk/studio/react-ui/package-lock.json

Lines changed: 83 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ksadk/studio/react-ui/src/pages/DeploymentsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ export function DeploymentsPage({ onCreate, onOpenChat, onSelectBuild }: {
762762
signal,
763763
);
764764
if (!deploymentResponse.ok) throw new Error(`刷新云端 Agent 状态失败(${deploymentResponse.status})`);
765-
const refreshed = {
765+
const refreshed: Deployment = {
766766
...deployment,
767767
...(await deploymentResponse.json() as Deployment),
768768
source: "receipt" as const,

ksadk/studio/react-ui/src/pages/ObservabilityPage.test.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,11 @@ describe("ObservabilityPage trajectory integration", () => {
400400
expect(showSaveFilePicker).toHaveBeenCalledWith(expect.objectContaining({
401401
suggestedName: expect.stringMatching(/^session-1-run-1-.*\.jsonl$/),
402402
}));
403-
expect(write).toHaveBeenCalledWith(expect.any(Blob));
403+
expect(write).toHaveBeenCalledTimes(1);
404+
expect(write.mock.calls[0]?.[0]).toMatchObject({
405+
size: 19,
406+
type: "application/x-ndjson",
407+
});
404408
expect(close).toHaveBeenCalledOnce();
405409
expect(showToast).toHaveBeenCalledWith(
406410
"Session Log 已导出",

ksadk/studio/workspace.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,32 @@ def initialize(self) -> None:
5858

5959
def resolve(self, relative: Path | str, *, must_exist: bool = False) -> Path:
6060
raw = Path(relative)
61+
# Reject lexical traversal before asking the filesystem to resolve any
62+
# symlink. The post-resolution relative_to check below is still needed
63+
# because a path that is lexically inside the workspace may contain a
64+
# symlink that escapes it.
65+
if ".." in raw.parts:
66+
raise StudioError(
67+
"WORKSPACE_PATH_FORBIDDEN",
68+
"路径不在当前工作区内",
69+
status_code=403,
70+
details={"path": str(relative)},
71+
)
6172
if raw.is_absolute():
62-
candidate = raw.resolve(strict=must_exist)
73+
try:
74+
raw.relative_to(self.root)
75+
except ValueError as exc:
76+
raise StudioError(
77+
"WORKSPACE_PATH_FORBIDDEN",
78+
"路径不在当前工作区内",
79+
status_code=403,
80+
details={"path": str(relative)},
81+
) from exc
82+
candidate = raw.resolve(strict=must_exist) # lgtm[py/path-injection]
6383
else:
64-
candidate = (self.root / raw).resolve(strict=must_exist)
84+
candidate = (self.root / raw).resolve( # lgtm[py/path-injection]
85+
strict=must_exist
86+
)
6587
try:
6688
candidate.relative_to(self.root)
6789
except ValueError as exc:

0 commit comments

Comments
 (0)