T09 · Insecure Skill Coding Practices
- Location
bin/run.mjs:305- Finding
API Token Exposed Through Process Arguments and URL Query Parameters
- Content
View full analysis
Vulnerability Details
File Location:
bin/run.mjs:305-308,bin/run.mjs:337-348,bin/run.mjs:389-402, andSKILL.md:52-60
Vulnerability Type: Credential exposure through command-line arguments and URL query parameters
Risk Level: MediumVulnerable Code
The documented invocation passes the secret as a command-line argument:
bash node {baseDir}/bin/run.mjs --operation "getTaobaoShopItemListV1" --token "$JUST_ONE_API_TOKEN" --params-json '{"userId":"<userId>"}'The argument parser accepts and retains the token:
js if (flag === "--token") { parsed.token = value; index += 1; continue; }The token is inserted into the general request parameter object:
js function injectToken(operation, params, cliToken) { const tokenParam = operation.parameters.find((parameter) => parameter.name === "token"); if (!tokenParam || params.token !== undefined) { return; } if (!cliToken) { fail("--token is required for this operation.", { operationId: operation.operationId, }); } params.token = cliToken; }Because the manifest defines
tokenas a query parameter, the generic query builder places the credential in the URL:js function applyQueryParams(operation, params, url) { for (const parameter of operation.parameters.filter((item) => item.location === "query")) { const value = params[parameter.name]; if (value === undefined) { continue; } appendValue(url.searchParams, parameter.name, value); } } function appendValue(searchParams, name, value) { if (Array.isArray(value)) { for (const item of value) { appendValue(searchParams, name, item); } return; } if (value && typeof value === "object") { searchParams.append(name, JSON.stringify(value)); return; } searchParams.append(name, String(value)); }The resulting URL, including its token query parameter, is then transmitted:
js response = await fetch(url, requestInit ...[truncated 2515 chars]- Remediation
View remediation
Remediation Suggestions
-
Stop accepting secrets through command-line arguments.
- Read
JUST_ONE_API_TOKENdirectly fromprocess.env. - If an explicit alternate input mechanism is required, use protected standard input or a restricted credential file rather than an argument.
- Remove
--tokenfrom the documented command and argument parser.
- Read
-
Prefer header-based authentication.
- If JustOneAPI supports it, send the credential through an authorization header:
js const token = process.env.JUST_ONE_API_TOKEN; if (!token) { fail("JUST_ONE_API_TOKEN is required."); } const requestInit = { method: operation.method, headers: { accept: "application/json", authorization: `Bearer ${token}`, }, };-
Prevent alternate query-string injection.
- Reject a
tokenproperty supplied through--params-json. - Keep authentication data separate from ordinary operation parameters.
- Do not process credentials through the generic
applyQueryParamsfunction.
- Reject a
-
If the service requires query authentication, reduce residual exposure.
- Document that the upstream protocol requires query-based credentials.
- Configure API gateways, proxies, tracing systems, and application logs to redact the
tokenparameter. - Never include the complete request URL in errors or diagnostics.
- Use narrowly scoped, short-lived, and readily revocable tokens.
-
Update documentation.
- Replace the
--token "$JUST_ONE_API_TOKEN"example with environment-based invocation. - Explain token rotation and revocation procedures.
- Ensure documentation does not promise that tokens will never enter logs unless redaction is enforced across the request path.
- Replace the
-
