huaweicloud-devkit 1.1.1-next.2 → 1.1.1-next.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.1-next.2",
3
+ "version": "1.1.1-next.3",
4
4
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
5
5
  "type": "module",
6
6
  "files": [
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.1-next.2",
20
+ "version": "1.1.1-next.3",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -20,7 +20,7 @@
20
20
  "mcpServers": "./.mcp.json",
21
21
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
22
22
  "skills": "./skills/",
23
- "version": "1.1.1-next.2",
23
+ "version": "1.1.1-next.3",
24
24
  "author": {
25
25
  "name": "HuaweiCloud Mate",
26
26
  "url": "https://github.com/huaweicloud"
@@ -17,7 +17,7 @@
17
17
  "mcpServers": "./.mcp.json",
18
18
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
19
19
  "skills": "./skills/",
20
- "version": "1.1.1-next.2",
20
+ "version": "1.1.1-next.3",
21
21
  "author": {
22
22
  "name": "HuaweiCloud Mate",
23
23
  "url": "https://github.com/huaweicloud"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.1-next.2",
3
+ "version": "1.1.1-next.3",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
- "version": "1.1.1-next.2",
3
+ "version": "1.1.1-next.3",
4
4
  "description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
5
5
  "author": {
6
6
  "name": "HuaweiCloud Mate",
@@ -2,7 +2,7 @@
2
2
  "name": "huaweicloud-devkit",
3
3
  "id": "huaweicloud-devkit",
4
4
  "displayName": "HuaweiCloud DevKit",
5
- "version": "1.1.1-next.2",
5
+ "version": "1.1.1-next.3",
6
6
  "family": "bundle-plugin",
7
7
  "bundleFormat": "codex",
8
8
  "description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
@@ -82,7 +82,7 @@ function codeartsRegistered() {
82
82
  function codeartsWorkRegistered() {
83
83
  const path = join(baseHome(), '.codeartswork', 'mcp', 'mcp_settings.json');
84
84
  const cfg = readJsonSafe(path);
85
- return Boolean(cfg?.mcpServers?.['huaweicloud-devkit']);
85
+ return Boolean(cfg?.mcp?.['huaweicloud-devkit']);
86
86
  }
87
87
 
88
88
  function workbuddyRegistered() {
@@ -164,16 +164,16 @@ function readCodeArtsCredentials() {
164
164
  try {
165
165
  if (existsSync(path)) {
166
166
  const config = JSON.parse(readFileSync(path, 'utf8'));
167
- const server = config?.mcpServers?.['huaweicloud-devkit'];
168
- if (server?.env) {
169
- const ak = server.env.HW_ACCESS_KEY;
170
- const sk = server.env.HW_SECRET_KEY;
167
+ const server = config?.mcp?.['huaweicloud-devkit'];
168
+ if (server?.environment) {
169
+ const ak = server.environment.HW_ACCESS_KEY;
170
+ const sk = server.environment.HW_SECRET_KEY;
171
171
  if (ak && sk) {
172
172
  return {
173
173
  ak,
174
174
  sk,
175
- securityToken: server.env.HW_SECURITY_TOKEN || '',
176
- region: server.env.HW_REGION || server.env.HUAWEICLOUD_REGION || '',
175
+ securityToken: server.environment.HW_SECURITY_TOKEN || '',
176
+ region: server.environment.HW_REGION || server.environment.HUAWEICLOUD_REGION || '',
177
177
  };
178
178
  }
179
179
  }
@@ -1,4 +1,4 @@
1
- import { spawn } from 'node:child_process';
1
+ import { spawn, spawnSync } from 'node:child_process';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
@@ -51,11 +51,92 @@ function saveLargeOutput(rawStdout) {
51
51
  }
52
52
  }
53
53
 
54
+ function preflightSecurityGroupCheck(normalizedArgs) {
55
+ const service = normalizedArgs[0];
56
+ const operation = normalizedArgs[1];
57
+ if (service !== 'ECS' || !/CreateServers|RunInstances/i.test(operation)) return [];
58
+
59
+ const sgIds = [];
60
+ for (const arg of normalizedArgs) {
61
+ const m = arg.match(/^--security_group_id(\.\d+)?=(.+)/);
62
+ if (m) sgIds.push(m[2]);
63
+ }
64
+ if (sgIds.length === 0) return [];
65
+
66
+ const findings = [];
67
+ for (const sgId of sgIds) {
68
+ try {
69
+ const hcloudBin = process.env.HCLOUD_BIN || 'hcloud';
70
+ const r = spawnSync(hcloudBin, ['VPC', 'ListSecurityGroupRules', `--security_group_id=${sgId}`], {
71
+ shell: false,
72
+ windowsHide: true,
73
+ stdio: 'pipe',
74
+ timeout: 20000,
75
+ });
76
+ const stdout = (r.stdout || '').toString();
77
+ if (r.status !== 0 || !stdout.includes('security_group_rules')) continue;
78
+
79
+ const bracketIdx = stdout.indexOf('{');
80
+ const jsonText = bracketIdx >= 0 ? stdout.slice(bracketIdx) : stdout;
81
+ const data = JSON.parse(jsonText);
82
+ const rules = data?.security_group_rules || [];
83
+
84
+ for (const rule of rules) {
85
+ const direction = rule.direction || '';
86
+ const remoteIp = rule.remote_ip_prefix || rule.remote_address_group_id || '';
87
+ const portMin = String(rule.port_range_min || rule.multiport || '');
88
+ const portMax = String(rule.port_range_max || rule.multiport || '');
89
+ const protocol = (rule.protocol || '').toLowerCase();
90
+ if (direction !== 'ingress') continue;
91
+ if (remoteIp !== '0.0.0.0/0' && remoteIp !== '::/0') continue;
92
+ if (protocol === 'icmp' || protocol === 'icmpv6') {
93
+ findings.push({
94
+ severity: 'warn',
95
+ title: `Security group ${sgId}: ICMP open to public`,
96
+ message: `安全组 ${sgId} 对公网开放了 ICMP (ping),可能被用于探测。是否继续?`,
97
+ });
98
+ continue;
99
+ }
100
+ const sensitivePorts = ['22', '3389', '3306', '5432', '6379', '9200', '27017', '8080', '8443'];
101
+ const port = portMin || portMax;
102
+ if (port && sensitivePorts.includes(port)) {
103
+ findings.push({
104
+ severity: 'deny',
105
+ title: `Security group ${sgId}: port ${port} open to public`,
106
+ message: `安全组 ${sgId} 已对公网开放 ${port} 端口(${protocol || 'TCP'})。确认继续创建 ECS?`,
107
+ });
108
+ }
109
+ }
110
+ } catch (error) {
111
+ // Preflight failed silently; don't block the plan
112
+ }
113
+ }
114
+ return findings;
115
+ }
116
+
117
+ function applyPreflightFindings(classification, sgFindings) {
118
+ if (!sgFindings || sgFindings.length === 0) return classification;
119
+ const hasDeny = sgFindings.some((f) => f.severity === 'deny');
120
+ if (hasDeny) {
121
+ return {
122
+ ...classification,
123
+ decision: 'deny',
124
+ risk: 'public_exposure',
125
+ reason: sgFindings[0].message,
126
+ };
127
+ }
128
+ return classification;
129
+ }
130
+
54
131
  export function planHcloudCommand(args, options = {}) {
55
132
  const normalizedArgs = Array.isArray(args) ? args.map(String) : [];
56
133
  const classification = classifyHcloudArgs(normalizedArgs, options);
57
134
  const command = ['hcloud', ...normalizedArgs].map((arg) => quoteShellArg(arg)).join(' ');
58
135
  const warnings = planningWarnings(normalizedArgs);
136
+ const sgFindings = preflightSecurityGroupCheck(normalizedArgs);
137
+ if (sgFindings.length > 0) {
138
+ for (const f of sgFindings) warnings.push(f);
139
+ }
59
140
  const paramValidation = validateRequiredParams(normalizedArgs);
60
141
  if (paramValidation.missing.length > 0) {
61
142
  warnings.push('Missing required parameters: ' + paramValidation.missing.join(', '));
@@ -69,7 +150,8 @@ export function planHcloudCommand(args, options = {}) {
69
150
  command: redactOutput(command),
70
151
  executableBlock: redactOutput(command),
71
152
  warnings,
72
- classification,
153
+ classification: applyPreflightFindings(classification, sgFindings),
154
+ sgFindings,
73
155
  approvalToken: createApprovalToken(normalizedArgs),
74
156
  safeToRun: classification.decision === 'allow',
75
157
  };
@@ -1272,36 +1272,37 @@ function codeartsStatus() {
1272
1272
  function registerCodeartsWorkMcp() {
1273
1273
  const configPath = codeartsWorkMcpSettingsFile();
1274
1274
  const mcpPath = join(codeartsWorkPluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
1275
- const env = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
1275
+ const environment = { HUAWEICLOUD_AGENT_TOOLKIT_MODE: 'local' };
1276
1276
  const hcloudBin = findHcloudBin();
1277
- if (hcloudBin) env.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
1277
+ if (hcloudBin) environment.HCLOUD_BIN = hcloudBin.replace(/\\/g, '/');
1278
1278
  let config = {};
1279
1279
  if (existsSync(configPath)) {
1280
1280
  try {
1281
1281
  config = JSON.parse(readFileSync(configPath, 'utf8'));
1282
1282
  } catch {
1283
1283
  console.log(
1284
- ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcpServers.huaweicloud-devkit" points to ${mcpPath}.`,
1284
+ ` \x1b[33m[WARN]\x1b[0m Could not parse ${configPath}. Skipping MCP config write; ensure "mcp.huaweicloud-devkit" points to ${mcpPath}.`,
1285
1285
  );
1286
1286
  return;
1287
1287
  }
1288
- const existing = config.mcpServers?.['huaweicloud-devkit'];
1288
+ const existing = config.mcp?.['huaweicloud-devkit'];
1289
1289
  if (
1290
1290
  existing &&
1291
- existing.command === 'node' &&
1292
- Array.isArray(existing.args) &&
1293
- existing.args[0] === mcpPath &&
1291
+ existing.type === 'local' &&
1292
+ Array.isArray(existing.command) &&
1293
+ existing.command[0] === 'node' &&
1294
+ existing.command[1] === mcpPath &&
1294
1295
  existing.timeout === 300000
1295
1296
  ) {
1296
1297
  console.log(` MCP config unchanged: ${configPath}`);
1297
1298
  return;
1298
1299
  }
1299
1300
  }
1300
- config.mcpServers = config.mcpServers || {};
1301
- config.mcpServers['huaweicloud-devkit'] = {
1302
- command: 'node',
1303
- args: [mcpPath],
1304
- env,
1301
+ config.mcp = config.mcp || {};
1302
+ config.mcp['huaweicloud-devkit'] = {
1303
+ type: 'local',
1304
+ command: ['node', mcpPath],
1305
+ environment,
1305
1306
  enabled: true,
1306
1307
  timeout: 300000,
1307
1308
  };
@@ -1369,9 +1370,9 @@ function uninstallCodeArtsWork() {
1369
1370
  try {
1370
1371
  config = JSON.parse(readFileSync(configPath, 'utf8'));
1371
1372
  } catch {}
1372
- if (config.mcpServers?.['huaweicloud-devkit']) {
1373
- delete config.mcpServers['huaweicloud-devkit'];
1374
- if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
1373
+ if (config.mcp?.['huaweicloud-devkit']) {
1374
+ delete config.mcp['huaweicloud-devkit'];
1375
+ if (Object.keys(config.mcp).length === 0) delete config.mcp;
1375
1376
  writeFileSync(configPath, JSON.stringify(config, null, 2));
1376
1377
  console.log(` Config cleaned: ${configPath}`);
1377
1378
  }
@@ -1399,7 +1400,7 @@ function codeartsWorkStatus() {
1399
1400
  try {
1400
1401
  const config = JSON.parse(readFileSync(codeartsWorkMcpSettingsFile(), 'utf8'));
1401
1402
  console.log(
1402
- ` MCP config: ${config.mcpServers?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1403
+ ` MCP config: ${config.mcp?.['huaweicloud-devkit'] ? '\x1b[32mConfigured\x1b[0m' : '\x1b[31mNot configured\x1b[0m'}`,
1403
1404
  );
1404
1405
  } catch {
1405
1406
  console.log(` MCP config: \x1b[31mInvalid\x1b[0m`);
@@ -3146,7 +3147,7 @@ async function cmdDoctor() {
3146
3147
  if (!mcpConfigured && existsSync(codeartsWorkCfg)) {
3147
3148
  try {
3148
3149
  const cfg = JSON.parse(readFileSync(codeartsWorkCfg, 'utf8'));
3149
- if (cfg.mcpServers && cfg.mcpServers['huaweicloud-devkit']) {
3150
+ if (cfg.mcp && cfg.mcp['huaweicloud-devkit']) {
3150
3151
  mcpConfigured = true;
3151
3152
  mcpCfgTarget = 'CodeArts Work';
3152
3153
  }