Skip to content

Commit 2d89b3f

Browse files
committed
feat(security): add agent security overview and alerts commands
1 parent 2090293 commit 2d89b3f

28 files changed

Lines changed: 952 additions & 11 deletions

File tree

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
66

77
[中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md)
88

9+
## [1.23.0] - 2026-09-09
10+
11+
### Added
12+
13+
- **Agent security commands**`bl security overview` (protection overview for the last 24 hours) and `bl security alerts` (alert list with risk-level, asset-type, status, vendor, pagination and sorting filters). Both call the per-workspace AgentStudio host and honor the shared `text` / `json` / `--quiet` / `--dry-run` contract. The host is derived from `--workspace-id`, or overridden by `--base-url` / `DASHSCOPE_BASE_URL` / `auth login --base-url` pointed at a workspace or pre-release origin (e.g. `https://<workspace-id>.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio`).
14+
15+
### Internal
16+
17+
- Add Agent security E2E coverage (help, missing-workspace usage error, dry-run host derivation and `--base-url` override, query-string filters, enum fast-fail) and generate the `bailian-cli` skill reference for the new `security` group.
18+
919
## [1.22.0] - 2026-09-08
1020

1121
### Changed

CHANGELOG.zh.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66

77
[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)
88

9+
## [1.23.0] - 2026-09-09
10+
11+
### 新增
12+
13+
- **Agent 安全命令** —— `bl security overview`(最近 24 小时的防护总览)与 `bl security alerts`(告警列表,支持风险等级、资产类型、状态、厂商、分页与排序等筛选)。两者均对接按 workspace 区分的 AgentStudio 域名,遵循统一的 `text` / `json` / `--quiet` / `--dry-run` 约定。域名默认由 `--workspace-id` 推导,也可通过 `--base-url` / `DASHSCOPE_BASE_URL` / `auth login --base-url` 指向某个 workspace 或预发源覆盖(例如 `https://<workspace-id>.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio`)。
14+
15+
### 内部
16+
17+
- 补充 Agent 安全 E2E 覆盖(help、缺 workspace 的 usage 错误、dry-run 域名推导与 `--base-url` 覆盖、query string 筛选、枚举快失败),并为新的 `security` 组生成 `bailian-cli` 技能 reference。
18+
919
## [1.22.0] - 2026-09-08
1020

1121
### 变更

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bailian-cli",
3-
"version": "1.22.0",
3+
"version": "1.23.0",
44
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
55
"keywords": [
66
"agent",

packages/cli/src/commands.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ import {
7272
speechRecognize,
7373
fileUpload,
7474
consoleCall,
75+
securityOverview,
76+
securityAlerts,
7577
usageFree,
7678
usageFreetier,
7779
usageStats,
@@ -288,6 +290,8 @@ export const commands: Record<string, AnyCommand> = {
288290
"speech recognize": speechRecognize,
289291
"file upload": fileUpload,
290292
"console call": consoleCall,
293+
"security overview": securityOverview,
294+
"security alerts": securityAlerts,
291295
"usage free": usageFree,
292296
"usage freetier": usageFreetier,
293297
"usage stats": usageStats,

packages/commands/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bailian-cli-commands",
3-
"version": "1.22.0",
3+
"version": "1.23.0",
44
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
55
"homepage": "https://bailian.console.aliyun.com/cli",
66
"bugs": {
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import {
2+
defineCommand,
3+
detectOutputFormat,
4+
securityAgentLogsEndpoint,
5+
securityGet,
6+
type FlagsDef,
7+
type SecurityAlertList,
8+
} from "bailian-cli-core";
9+
import { emitResult, emitBare } from "bailian-cli-runtime";
10+
import { WORKSPACE_FLAG, renderAlert, resolveSecurityHost, setSecurityParam } from "./shared.ts";
11+
12+
const ASSET_TYPES = ["agent", "tool", "skill", "knowledge_base", "memory", "channel"] as const;
13+
14+
const ALERTS_FLAGS = {
15+
...WORKSPACE_FLAG,
16+
page: {
17+
type: "number",
18+
valueHint: "<n>",
19+
description: { "en-US": "Page number (default: 1)", "zh-CN": "页码(默认:1)" },
20+
},
21+
pageSize: {
22+
type: "number",
23+
valueHint: "<n>",
24+
description: { "en-US": "Results per page (default: 20)", "zh-CN": "每页结果数(默认:20)" },
25+
},
26+
riskLevel: {
27+
type: "string",
28+
valueHint: "<level>",
29+
choices: ["high", "medium", "low"] as const,
30+
description: {
31+
"en-US": "Filter by risk level: high, medium, low",
32+
"zh-CN": "按风险等级筛选:high、medium、low",
33+
},
34+
},
35+
riskName: {
36+
type: "string",
37+
valueHint: "<text>",
38+
description: { "en-US": "Filter by risk name", "zh-CN": "按风险名称筛选" },
39+
},
40+
status: {
41+
type: "string",
42+
valueHint: "<status>",
43+
description: { "en-US": "Filter by handling status", "zh-CN": "按处理状态筛选" },
44+
},
45+
statusList: {
46+
type: "array",
47+
valueHint: "<status>",
48+
description: {
49+
"en-US": "Filter by multiple statuses (repeatable)",
50+
"zh-CN": "按多个状态筛选(可重复传入)",
51+
},
52+
},
53+
appName: {
54+
type: "string",
55+
valueHint: "<name>",
56+
description: { "en-US": "Filter by application name", "zh-CN": "按应用名称筛选" },
57+
},
58+
assetType: {
59+
type: "string",
60+
valueHint: "<type>",
61+
choices: ASSET_TYPES,
62+
description: {
63+
"en-US": `Filter by asset type: ${ASSET_TYPES.join(", ")}`,
64+
"zh-CN": `按资产类型筛选:${ASSET_TYPES.join("、")}`,
65+
},
66+
},
67+
vendor: {
68+
type: "string",
69+
valueHint: "<vendor>",
70+
description: { "en-US": "Filter by vendor", "zh-CN": "按厂商筛选" },
71+
},
72+
orderBy: {
73+
type: "string",
74+
valueHint: "<field>",
75+
description: {
76+
"en-US": "Sort field (default: check_time)",
77+
"zh-CN": "排序字段(默认:check_time)",
78+
},
79+
},
80+
order: {
81+
type: "string",
82+
valueHint: "<dir>",
83+
choices: ["asc", "desc"] as const,
84+
description: {
85+
"en-US": "Sort direction: asc, desc (default: desc)",
86+
"zh-CN": "排序方向:asc、desc(默认:desc)",
87+
},
88+
},
89+
lang: {
90+
type: "string",
91+
valueHint: "<lang>",
92+
choices: ["zh", "en"] as const,
93+
description: { "en-US": "Response language: zh, en", "zh-CN": "响应语言:zh、en" },
94+
},
95+
} satisfies FlagsDef;
96+
97+
export default defineCommand({
98+
description: {
99+
"en-US": "List Agent security alerts",
100+
"zh-CN": "列出 Agent 安全告警",
101+
},
102+
auth: "apiKey",
103+
usageArgs: "[flags]",
104+
flags: ALERTS_FLAGS,
105+
notes: [
106+
{
107+
"en-US": "Auth: uses DashScope API Key (Bearer token).",
108+
"zh-CN": "鉴权:使用 DashScope API Key(Bearer Token)。",
109+
},
110+
{
111+
"en-US": "`--workspace-id` can be set via BAILIAN_WORKSPACE_ID env or config workspace_id.",
112+
"zh-CN": "`--workspace-id` 可通过 BAILIAN_WORKSPACE_ID 环境变量或配置项 workspace_id 设置。",
113+
},
114+
{
115+
"en-US":
116+
"Filters, pagination and sorting go in the query string; enum flags are validated before any request is sent.",
117+
"zh-CN": "筛选、分页与排序参数走 query string;枚举类 flag 在发起请求前校验。",
118+
},
119+
{
120+
"en-US":
121+
"AgentStudio host: derived from --workspace-id by default; point --base-url / DASHSCOPE_BASE_URL (or `auth login --base-url`) at a workspace or pre-release origin such as https://<workspace-id>.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio to override it, and --workspace-id is then not required.",
122+
"zh-CN":
123+
"AgentStudio 域名:默认由 --workspace-id 推导;将 --base-url / DASHSCOPE_BASE_URL(或 `auth login --base-url`)指向某个 workspace 或预发源(例如 https://<workspace-id>.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio)即可覆盖,此时无需 --workspace-id。",
124+
},
125+
],
126+
exampleArgs: [
127+
{ "en-US": "--workspace-id ws-xxx", "zh-CN": "--workspace-id ws-xxx" },
128+
{
129+
"en-US": "--risk-level high --page-size 50",
130+
"zh-CN": "--risk-level high --page-size 50",
131+
},
132+
{
133+
"en-US": '--asset-type agent --app-name "demo app" --output json',
134+
"zh-CN": '--asset-type agent --app-name "测试应用0" --output json',
135+
},
136+
{
137+
"en-US": "--status-list unhandled --status-list handling",
138+
"zh-CN": "--status-list unhandled --status-list handling",
139+
},
140+
],
141+
async run(ctx) {
142+
const { settings, flags } = ctx;
143+
const format = detectOutputFormat(settings.output);
144+
const host = resolveSecurityHost(ctx);
145+
146+
const params = new URLSearchParams();
147+
setSecurityParam(params, "current_page", flags.page);
148+
setSecurityParam(params, "page_size", flags.pageSize);
149+
setSecurityParam(params, "risk_level", flags.riskLevel);
150+
setSecurityParam(params, "risk_name", flags.riskName);
151+
setSecurityParam(params, "status", flags.status);
152+
setSecurityParam(params, "status_list", flags.statusList);
153+
setSecurityParam(params, "app_name", flags.appName);
154+
setSecurityParam(params, "asset_type", flags.assetType);
155+
setSecurityParam(params, "vendor", flags.vendor);
156+
setSecurityParam(params, "order_by", flags.orderBy);
157+
setSecurityParam(params, "order", flags.order);
158+
setSecurityParam(params, "lang", flags.lang);
159+
160+
const query = params.toString();
161+
const base = securityAgentLogsEndpoint(host);
162+
const endpoint = query ? `${base}?${query}` : base;
163+
164+
if (settings.dryRun) {
165+
emitResult({ endpoint, method: "GET" }, format);
166+
return;
167+
}
168+
169+
const data = await securityGet<SecurityAlertList>(ctx.client, endpoint);
170+
const alerts = data?.data ?? [];
171+
172+
if (format === "json") {
173+
emitResult(data ?? { stats: null, data: [], next_page: null }, format);
174+
return;
175+
}
176+
177+
if (settings.quiet) {
178+
for (const alert of alerts) emitBare(alert.alert_id);
179+
return;
180+
}
181+
182+
const stats = data?.stats;
183+
if (stats) {
184+
emitBare(
185+
`Total: ${stats.total ?? "-"} high: ${stats.high ?? "-"} ` +
186+
`medium: ${stats.medium ?? "-"} low: ${stats.low ?? "-"}\n`,
187+
);
188+
}
189+
190+
if (alerts.length === 0) {
191+
emitBare("No alerts found.");
192+
return;
193+
}
194+
195+
for (const alert of alerts) renderAlert(alert);
196+
197+
if (data?.next_page) {
198+
emitBare(`Next page cursor: ${data.next_page}`);
199+
}
200+
},
201+
});
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {
2+
defineCommand,
3+
detectOutputFormat,
4+
securityGet,
5+
securityOverviewEndpoint,
6+
type FlagsDef,
7+
type SecurityOverview,
8+
type SecurityScanStat,
9+
} from "bailian-cli-core";
10+
import { emitResult, emitBare } from "bailian-cli-runtime";
11+
import {
12+
CAPABILITY_LABELS,
13+
PROTECTION_LABELS,
14+
SCAN_CARDS,
15+
WORKSPACE_FLAG,
16+
renderToggles,
17+
resolveSecurityHost,
18+
} from "./shared.ts";
19+
20+
const OVERVIEW_FLAGS = {
21+
...WORKSPACE_FLAG,
22+
} satisfies FlagsDef;
23+
24+
export default defineCommand({
25+
description: {
26+
"en-US": "Show the Agent security protection overview (last 24 hours)",
27+
"zh-CN": "查看 Agent 安全防护总览(最近 24 小时)",
28+
},
29+
auth: "apiKey",
30+
usageArgs: "[flags]",
31+
flags: OVERVIEW_FLAGS,
32+
notes: [
33+
{
34+
"en-US": "Auth: uses DashScope API Key (Bearer token).",
35+
"zh-CN": "鉴权:使用 DashScope API Key(Bearer Token)。",
36+
},
37+
{
38+
"en-US": "`--workspace-id` can be set via BAILIAN_WORKSPACE_ID env or config workspace_id.",
39+
"zh-CN": "`--workspace-id` 可通过 BAILIAN_WORKSPACE_ID 环境变量或配置项 workspace_id 设置。",
40+
},
41+
{
42+
"en-US":
43+
"Fixed to the last 24 hours; the AgentStudio region is always cn-beijing and is not configurable.",
44+
"zh-CN": "固定统计最近 24 小时;AgentStudio 地域固定为 cn-beijing,不可配置。",
45+
},
46+
{
47+
"en-US":
48+
"AgentStudio host: derived from --workspace-id by default; point --base-url / DASHSCOPE_BASE_URL (or `auth login --base-url`) at a workspace or pre-release origin such as https://<workspace-id>.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio to override it, and --workspace-id is then not required.",
49+
"zh-CN":
50+
"AgentStudio 域名:默认由 --workspace-id 推导;将 --base-url / DASHSCOPE_BASE_URL(或 `auth login --base-url`)指向某个 workspace 或预发源(例如 https://<workspace-id>.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio)即可覆盖,此时无需 --workspace-id。",
51+
},
52+
],
53+
exampleArgs: [
54+
{ "en-US": "--workspace-id ws-xxx", "zh-CN": "--workspace-id ws-xxx" },
55+
{
56+
"en-US": "--workspace-id ws-xxx --output json",
57+
"zh-CN": "--workspace-id ws-xxx --output json",
58+
},
59+
],
60+
async run(ctx) {
61+
const { settings } = ctx;
62+
const format = detectOutputFormat(settings.output);
63+
const endpoint = securityOverviewEndpoint(resolveSecurityHost(ctx));
64+
65+
if (settings.dryRun) {
66+
emitResult({ endpoint, method: "GET" }, format);
67+
return;
68+
}
69+
70+
const data = await securityGet<SecurityOverview>(ctx.client, endpoint);
71+
72+
if (!data) {
73+
if (format === "json") emitResult({}, format);
74+
else emitBare("Overview unavailable.");
75+
return;
76+
}
77+
78+
if (format === "json") {
79+
emitResult(data, format);
80+
return;
81+
}
82+
83+
// Banner totals are client-side sums across the detection cards.
84+
const cards = SCAN_CARDS.map(([key, label]) => ({
85+
label,
86+
stat: data[key] as SecurityScanStat | null | undefined,
87+
}));
88+
const sum = (pick: (stat: SecurityScanStat) => number | null): number =>
89+
cards.reduce((total, card) => total + (card.stat ? (pick(card.stat) ?? 0) : 0), 0);
90+
91+
emitBare(`Scanned: ${sum((stat) => stat.scanned)} Risks: ${sum((stat) => stat.hit)}`);
92+
93+
renderToggles("Capabilities", data.capabilities, CAPABILITY_LABELS);
94+
renderToggles("Protection", data.protection, PROTECTION_LABELS);
95+
96+
emitBare("\nDetections");
97+
for (const { label, stat } of cards) {
98+
if (!stat) {
99+
emitBare(` ${label} (unavailable)`);
100+
} else {
101+
emitBare(` ${label} hit ${stat.hit ?? "-"} / scanned ${stat.scanned ?? "-"}`);
102+
}
103+
}
104+
},
105+
});

0 commit comments

Comments
 (0)