T09 · Insecure Skill Coding Practices
- Location
src/upbit/auth.js:11- Finding
Unnecessary Plaintext Exchange Credential Handling
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md:34-46,src/config.js:9-25,src/upbit/auth.js:11-31
Vulnerability Type: Unnecessary plaintext secret storage and access
Risk Level: MediumThe documentation encourages users to place Upbit access and secret keys in a plaintext JSON configuration file:
json { "upbit": { "baseUrl": "https://api.upbit.com", "accessKey": "", "secretKey": "" }, "watchlist": ["KRW-BTC", "KRW-ETH", "KRW-SOL"] }The configuration loader reads the complete file without enforcing restrictive permissions or separating secrets from non-sensitive settings:
js function loadConfig() { if (cachedConfig) return cachedConfig; const configPath = process.env.UPBIT_SKILL_CONFIG ? path.resolve(process.env.UPBIT_SKILL_CONFIG) : DEFAULT_PATH; if (!fs.existsSync(configPath)) { throw new UpbitError(`Config file not found: ${configPath}`); } const raw = fs.readFileSync(configPath, "utf8"); try { cachedConfig = JSON.parse(raw); } catch (err) { throw new UpbitError("Invalid JSON format in config file", { configPath }); } return cachedConfig; }Authentication code then retrieves both credentials and can construct a bearer token containing the access key:
js function createJwt({ queryString = "" } = {}) { const cfg = loadConfig(); const accessKey = cfg?.upbit?.accessKey; const secretKey = cfg?.upbit?.secretKey; if (!accessKey || !secretKey) { throw new UpbitError("Missing upbit.accessKey / upbit.secretKey in config"); } const payload = { access_key: accessKey, nonce: uuidv4() }; if (queryString) { const queryHash = crypto.createHash("sha512").update(queryString, "utf8").digest("hex"); payload.query_hash = queryHash; payload.query_hash_alg = "SHA512"; } return jwt.sign(payload, secretKey, { algorithm: "HS512" }); ...[truncated 2255 chars]- Remediation
View remediation
Remediation Suggestions
- Remove
accessKeyandsecretKeyfrom the documented configuration schema because public market-data endpoints do not require authentication. - Remove
src/upbit/auth.jsand its authentication-only dependencies unless private API functionality is deliberately introduced. - Keep watchlist configuration separate from any future secret-bearing configuration.
- If private API support is added later, obtain secrets from a dedicated secret manager or protected environment variables rather than ordinary JSON.
- Validate restrictive permissions on any unavoidable credential file and document that it must be excluded from source control and backups.
- Require least-privileged, read-only API keys wherever possible.
- Before enabling authenticated requests, restrict the destination to the exact expected HTTPS Upbit origin and prevent credentials from being sent to a configurable arbitrary host.
- Remove
