{"record":{"id":"9529bf583fda7a5c","repo":"nanocoai/nanoclaw","slug":"url-query-parameter-key-looks-like-a-credenti","errorCode":null,"errorMessage":"url query parameter \"${key}\" looks like a credential; use OneCLI for authentication","messagePattern":"url query parameter \"(.+?)\" looks like a credential; use OneCLI for authentication","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/container-config.ts","lineNumber":166,"sourceCode":"    if (input.args !== undefined || input.env !== undefined || input.cwd !== undefined) {\n      throw new Error('args, env, and cwd are only valid with command');\n    }\n    let parsed: URL;\n    try {\n      parsed = new URL(url);\n    } catch (err) {\n      throw new Error('url must be a valid HTTP(S) URL', { cause: err });\n    }\n    const loopback = ['localhost', '127.0.0.1', '[::1]', 'host.docker.internal'].includes(parsed.hostname);\n    if (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopback)) {\n      throw new Error('url must use HTTPS (plain HTTP is allowed only for localhost and host.docker.internal)');\n    }\n    if (parsed.username || parsed.password || parsed.hash) {\n      throw new Error('url must not contain credentials or fragments; use OneCLI for authentication');\n    }\n    for (const key of parsed.searchParams.keys()) {\n      if (SECRET_QUERY_KEY_RE.test(key.replace(CAMEL_SPLIT_RE, '$1_$2'))) {\n        throw new Error(`url query parameter \"${key}\" looks like a credential; use OneCLI for authentication`);\n      }\n    }\n    const headers = parseStringRecord(input.headers, 'headers');\n    return {\n      type: 'http',\n      url,\n      ...(headers === undefined ? {} : { headers }),\n      ...(instructions === undefined ? {} : { instructions }),\n    };\n  }\n  if (command === undefined) throw new Error('Provide exactly one of command or url');\n\n  if (input.headers !== undefined) throw new Error('headers is only valid with url');\n  const args = input.args ?? [];\n  if (!Array.isArray(args) || !args.every((arg) => typeof arg === 'string')) {\n    throw new Error('args must be a JSON array of strings');\n  }\n  const env = parseStringRecord(input.env, 'env') ?? {};","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/container-config.ts#L148-L184","documentation":"A query parameter key in the MCP url matches a credential-looking pattern (SECRET_QUERY_KEY_RE after camelCase normalization), e.g. token, apiKey, api_key, secret. Like embedded userinfo, this is rejected to keep secrets out of config files and logs — auth belongs in OneCLI.","triggerScenarios":"urls like https://mcp.example.com/mcp?token=abc123 or ?apiKey=XYZ in an http MCP server entry.","commonSituations":"Vendor docs that put the API key in a query string; copy-pasting a browser-authenticated URL; Sentry/GitHub-style ?token= patterns.","solutions":["Move the credential into OneCLI and let it inject the appropriate header at request time","If the parameter is genuinely not a secret, rename it to something that doesn't match token/key/secret patterns","Ask the MCP server operator for a header-based auth scheme"],"exampleFix":"// before\n{\"url\":\"https://mcp.example.com/mcp?apiKey=abc123\"}\n// after\n{\"url\":\"https://mcp.example.com/mcp\"}  // key stored in OneCLI","handlingStrategy":"validation","validationCode":"const u = new URL(entry.url); for (const k of u.searchParams.keys()) if (/token|key|secret|pass/i.test(k)) throw new UserError(`move ${k} to OneCLI`);","typeGuard":"const urlQueryLooksSafe = (s: string) => { const u = new URL(s); return ![...u.searchParams.keys()].some(k => /token|key|secret|pass/i.test(k)); };","tryCatchPattern":"catch (err) { if (err.message.includes('looks like a credential')) stripQueryAndUseVault(); else throw err; }","preventionTips":["Avoid ?token=/?key= style query auth entirely","Rename genuinely non-secret params away from token/key/secret words"],"tags":["mcp","credentials","security-policy","url-validation"],"backgroundTag":"credentials-in-url-rejected","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}