T09 · Insecure Skill Coding Practices
Warning
- Location
- bin/run.mjs:24
- Finding
- API Token Exposed Through Process Arguments and URL Query Parameters<![CDATA[ ## Vulnerability Details **File Location**: `bin/run.mjs:24-31, 77-92, 138-151, 179-191, 216-224`; related usage documentation at `SKILL.md:37-43` **Vulnerability Type**: API credential exposure **Risk Level**: Medium ### Vulnerable Code The operation defines the authentication token as a query parameter: ```js { "defaultValue": null, "description": "User authentication token.", "enumValues": [], "location": "query", "name": "token", "required": true, "schemaType": "string" } ``` The token is accepted as a command-line argument and inserted into the parameter collection: ```js function parseArgs(argv) { const parsed = { operation: null, paramsJson: "{}", token: null }; for (let index = 0; index < argv.length; index += 1) { const flag = argv[index]; const value = argv[index + 1]; if (flag === "--operation") { parsed.operation = value; index += 1; continue; } if (flag === "--params-json") { parsed.paramsJson = value; index += 1; continue; } if (flag === "--token") { parsed.token = value; index += 1; continue; } fail(`Unknown argument "${flag}".`); } return parsed; } ``` ```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; } ``` All query parameters, including the token, are appended to the request 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); } } ``` ```js functio ...[truncated 3343 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Remove command-line token handling** - Read the token directly from `process.env.JUST_ONE_API_TOKEN`. - Do not accept secrets through `--token`, because shell variable expansion still exposes the resulting value in process arguments. - Fail with a non-sensitive error if the environment variable is absent. 2. **Use an authorization header** - If supported by JustOneAPI, send the credential as an HTTP 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}`, }, }; ``` - Remove `token` from the operation's query-parameter definition so it cannot be serialized into the URL. 3. **Apply compensating controls if query authentication is mandatory** - Confirm and document that the upstream service requires query-string authentication. - Use short-lived, narrowly scoped, and readily revocable tokens. - Configure clients, proxies, API gateways, servers, telemetry systems, and error reporting to redact the `token` parameter. - Avoid logging complete request URLs. - Review redirect behavior and prevent credentials from being exposed through unexpected redirects. 4. **Update documentation and generated artifacts** - Change `SKILL.md` to instruct users to set `JUST_ONE_API_TOKEN` only in the execution environment. - Update `generated/operations.json` and `generated/operations.md` so authentication is represented as a security scheme rather than an ordinary query parameter. - Add an explicit warning never to include token values in chat messages, command history, screenshots, logs, or diagnostic output. 5. **Rotate potentially exposed credentials** - Revoke and replace tokens previously used through the documented command-line workflow where process or URL logs may have retained them. ]]>
