huaweicloud-devkit 1.1.4-next.3 → 1.1.4-next.4

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,7 +1,7 @@
1
1
  {
2
2
  "name": "huaweicloud-devkit",
3
3
  "mcpName": "io.github.huaweicloud/huaweicloud-devkit",
4
- "version": "1.1.4-next.3",
4
+ "version": "1.1.4-next.4",
5
5
  "kooCliVersion": "7.2.12",
6
6
  "description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
7
7
  "type": "module",
@@ -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.4-next.3",
20
+ "version": "1.1.4-next.4",
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.4-next.3",
23
+ "version": "1.1.4-next.4",
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.4-next.3",
20
+ "version": "1.1.4-next.4",
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.4-next.3",
3
+ "version": "1.1.4-next.4",
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.4-next.3",
3
+ "version": "1.1.4-next.4",
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.4-next.3",
5
+ "version": "1.1.4-next.4",
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.",
@@ -58,6 +58,33 @@ export function readGlobalCredentials() {
58
58
  }
59
59
  }
60
60
 
61
+ // R11: Placeholder / masked credential values are treated as "not configured".
62
+ // A vendor/IDE marketplace that pre-fills mcp_settings env with template text
63
+ // (e.g. "<HW_ACCESS_KEY>" or "${SECRET_KEY}") must not shadow the user's real
64
+ // S1 vault. Real AK/SK are >=20 char alphanumeric strings, so none of the
65
+ // template/masked patterns below can match a genuine credential.
66
+ export function isPlaceholder(value) {
67
+ if (typeof value !== 'string' || value.length === 0) return false;
68
+ // <HW_ACCESS_KEY> / <your-ak> / <...>
69
+ if (/^<[^>]*>$/.test(value)) return true;
70
+ // ${HW_ACCESS_KEY}
71
+ if (/^\$\{[^}]*\}$/.test(value)) return true;
72
+ // YOUR_AK / ACCESS_KEY / SECRET_KEY / SECURITY_TOKEN / REGION (bare keyword)
73
+ if (/^(YOUR_)?(AK|SK|ACCESS[_-]?KEY|SECRET[_-]?KEY|TOKEN|SECURITY[_-]?TOKEN|REGION)$/i.test(value)) return true;
74
+ // placeholder / replace_me / change.me
75
+ if (/\b(placeholder|replace[._-]?me|change[._-]?me)\b/i.test(value)) return true;
76
+ // masked value: short alnum prefix + asterisk run (abc**** / token****) — the
77
+ // S4 mirror writes the SK slot this way so the secret never lands in plaintext.
78
+ if (/^[A-Za-z0-9]{3,8}\*+$/.test(value)) return true;
79
+ // bare asterisks
80
+ if (/^\*+$/.test(value)) return true;
81
+ return false;
82
+ }
83
+
84
+ function present(v) {
85
+ return typeof v === 'string' && v.length > 0 && !isPlaceholder(v);
86
+ }
87
+
61
88
  export function writeGlobalCredentials(credentials = {}) {
62
89
  const path = globalCredentialsPath();
63
90
  mkdirSync(dirname(path), { recursive: true });
@@ -99,9 +126,10 @@ export function writeObsConfig(credentials = {}) {
99
126
  }
100
127
 
101
128
  export function resolveCredentials(options = {}) {
102
- let ak = process.env.HW_ACCESS_KEY;
103
- let sk = process.env.HW_SECRET_KEY;
104
- let securityToken = process.env.HW_SECURITY_TOKEN;
129
+ // R11: placeholder/masked env values are "not configured", never credentials.
130
+ let ak = present(process.env.HW_ACCESS_KEY) ? process.env.HW_ACCESS_KEY : '';
131
+ let sk = present(process.env.HW_SECRET_KEY) ? process.env.HW_SECRET_KEY : '';
132
+ let securityToken = present(process.env.HW_SECURITY_TOKEN) ? process.env.HW_SECURITY_TOKEN : '';
105
133
  let region = process.env.HW_REGION || process.env.HUAWEICLOUD_REGION || '';
106
134
 
107
135
  const codeartsCreds = isCodeArtsContext() ? readCodeArtsCredentials() : null;
@@ -113,36 +141,33 @@ export function resolveCredentials(options = {}) {
113
141
 
114
142
  const stored = readGlobalCredentials();
115
143
  if (stored) {
116
- if (!ak && stored.ak) ak = stored.ak;
117
- if (!sk && stored.sk) sk = stored.sk;
118
- if (!securityToken && stored.securityToken) securityToken = stored.securityToken;
144
+ // R11: skip placeholder/masked values from the vault too (templates must
145
+ // not win the truthiness check and shadow another store).
146
+ if (!ak && present(stored.ak)) ak = stored.ak;
147
+ if (!sk && present(stored.sk)) sk = stored.sk;
148
+ if (!securityToken && present(stored.securityToken)) securityToken = stored.securityToken;
119
149
  if (!region && stored.region) region = stored.region;
120
150
  }
121
151
 
122
152
  // R9: S1 written by `auth_switch persist` (configuredBySession) is the session's
123
153
  // source of truth and wins over env-injected defaults. Plain S1 (`auth init`)
124
154
  // still yields to env so devspace-style env injection keeps working.
125
- if (stored && stored.configuredBySession === true && stored.ak && stored.sk) {
155
+ if (stored && stored.configuredBySession === true && present(stored.ak) && present(stored.sk)) {
126
156
  ak = stored.ak;
127
157
  sk = stored.sk;
128
- if (!securityToken) securityToken = stored.securityToken || '';
158
+ if (!securityToken) securityToken = present(stored.securityToken) ? stored.securityToken : '';
129
159
  if (!region) region = stored.region || '';
130
160
  }
131
161
 
132
162
  // Sandbox/platform-injected temporary STS credentials (env vars carrying a
133
163
  // security token) must not shadow the user's explicit permanent credentials
134
164
  // from `auth init`. Prefer the stored file when both exist.
135
- if (
136
- process.env.HW_ACCESS_KEY &&
137
- process.env.HW_SECRET_KEY &&
138
- process.env.HW_SECURITY_TOKEN &&
139
- stored &&
140
- stored.ak &&
141
- stored.sk
142
- ) {
165
+ const envHasFullTriplet =
166
+ present(process.env.HW_ACCESS_KEY) && present(process.env.HW_SECRET_KEY) && present(process.env.HW_SECURITY_TOKEN);
167
+ if (envHasFullTriplet && stored && present(stored.ak) && present(stored.sk)) {
143
168
  ak = stored.ak;
144
169
  sk = stored.sk;
145
- securityToken = stored.securityToken || '';
170
+ securityToken = present(stored.securityToken) ? stored.securityToken : '';
146
171
  region = stored.region || region;
147
172
  }
148
173
 
@@ -189,16 +214,16 @@ export function readCodeArtsCredentials() {
189
214
  try {
190
215
  if (!existsSync(path)) continue;
191
216
  const config = JSON.parse(readFileSync(path, 'utf8'));
192
- const server = config?.mcpServers?.['huaweicloud-devkit'];
217
+ const server = config?.mcpServers?.['huaweicloud-devkit'] || config?.mcpServers?.['HuaweiCloud DevKit'];
193
218
  if (!server?.env) continue;
194
219
 
195
220
  const ak = server.env.HW_ACCESS_KEY;
196
221
  const sk = server.env.HW_SECRET_KEY;
197
- if (ak && sk) {
222
+ if (present(ak) && present(sk)) {
198
223
  return {
199
224
  ak,
200
225
  sk,
201
- securityToken: server.env.HW_SECURITY_TOKEN || '',
226
+ securityToken: present(server.env.HW_SECURITY_TOKEN) ? server.env.HW_SECURITY_TOKEN : '',
202
227
  region: server.env.HW_REGION || server.env.HUAWEICLOUD_REGION || '',
203
228
  };
204
229
  }
@@ -213,15 +238,15 @@ export function readCodeArtsCredentials() {
213
238
  try {
214
239
  if (existsSync(path)) {
215
240
  const config = JSON.parse(readFileSync(path, 'utf8'));
216
- const server = config?.mcp?.['huaweicloud-devkit'];
241
+ const server = config?.mcp?.['huaweicloud-devkit'] || config?.mcp?.['HuaweiCloud DevKit'];
217
242
  if (server?.environment) {
218
243
  const ak = server.environment.HW_ACCESS_KEY;
219
244
  const sk = server.environment.HW_SECRET_KEY;
220
- if (ak && sk) {
245
+ if (present(ak) && present(sk)) {
221
246
  return {
222
247
  ak,
223
248
  sk,
224
- securityToken: server.environment.HW_SECURITY_TOKEN || '',
249
+ securityToken: present(server.environment.HW_SECURITY_TOKEN) ? server.environment.HW_SECURITY_TOKEN : '',
225
250
  region: server.environment.HW_REGION || server.environment.HUAWEICLOUD_REGION || '',
226
251
  };
227
252
  }
@@ -6,6 +6,7 @@ import { spawnSync } from 'node:child_process';
6
6
 
7
7
  import {
8
8
  hasRuntimeCredentials,
9
+ isPlaceholder,
9
10
  obsConfigPath,
10
11
  readGlobalCredentials,
11
12
  readLastSync,
@@ -104,10 +105,13 @@ function s2CurrentMatchesLastDevkitSync(kooCli, s1, s1Fingerprint) {
104
105
 
105
106
  export function scanState() {
106
107
  const s1 = readGlobalCredentials() || {};
107
- const envAk = process.env.HW_ACCESS_KEY || '';
108
- const envSk = process.env.HW_SECRET_KEY || '';
108
+ const envAk = isPlaceholder(process.env.HW_ACCESS_KEY) ? '' : process.env.HW_ACCESS_KEY || '';
109
+ const envSk = isPlaceholder(process.env.HW_SECRET_KEY) ? '' : process.env.HW_SECRET_KEY || '';
109
110
  const kooCli = readKooCliProfiles();
110
111
  const inconsistencies = [];
112
+ const envIsPlaceholder =
113
+ Boolean(process.env.HW_ACCESS_KEY && isPlaceholder(process.env.HW_ACCESS_KEY)) ||
114
+ Boolean(process.env.HW_SECRET_KEY && isPlaceholder(process.env.HW_SECRET_KEY));
111
115
 
112
116
  const s1Fingerprint = fingerprint(s1.ak, s1.sk);
113
117
  const currentFp = currentFingerprintFromHcloud(kooCli);
@@ -153,6 +157,9 @@ export function scanState() {
153
157
  runtimeFingerprint,
154
158
  },
155
159
  kooCliCurrent: kooCli.error ? null : kooCli.current,
160
+ // R11: env carries placeholder/masked values → surface that explicitly so
161
+ // diagnostics don't show a bogus fingerprint derived from template text.
162
+ envIsPlaceholder,
156
163
  // S2 is encrypted storage (authEncrypt) → its fingerprint is unavailable,
157
164
  // so the S2-current drift check is intentionally skipped (#533).
158
165
  s2Encrypted: Boolean(!kooCli.error && kooCli.authEncrypt),