{"record":{"id":"4aecd8b91c8c84e1","repo":"google-gemini/gemini-cli","slug":"invalid-taskid-taskid","errorCode":null,"errorMessage":"Invalid taskId: ${taskId}","messagePattern":"Invalid taskId: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/a2a-server/src/persistence/gcs.ts","lineNumber":89,"sourceCode":"        logger.info(`Bucket ${this.bucketName} exists.`);\n      }\n    } catch (error) {\n      logger.info(\n        `Error during bucket initialization for ${this.bucketName}: ${error}`,\n      );\n      throw new Error(\n        `Failed to initialize GCS bucket ${this.bucketName}: ${error}`,\n      );\n    }\n  }\n\n  private async ensureBucketInitialized(): Promise<void> {\n    await this.bucketInitialized;\n  }\n\n  private getObjectPath(taskId: string, type: ObjectType): string {\n    if (!isTaskIdValid(taskId)) {\n      throw new Error(`Invalid taskId: ${taskId}`);\n    }\n    return `tasks/${taskId}/${type}.tar.gz`;\n  }\n\n  async save(task: SDKTask): Promise<void> {\n    await this.ensureBucketInitialized();\n    const taskId = task.id;\n    const persistedState = getPersistedState(\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion\n      task.metadata as PersistedTaskMetadata,\n    );\n\n    if (!persistedState) {\n      throw new Error(`Task ${taskId} is missing persisted state in metadata.`);\n    }\n    const workDir = process.cwd();\n\n    const metadataObjectPath = this.getObjectPath(taskId, 'metadata');","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/packages/a2a-server/src/persistence/gcs.ts#L71-L107","documentation":"Thrown by GCSTaskStore.getObjectPath when isTaskIdValid(taskId) fails - the regex /^[a-zA-Z0-9_-]+$/ requires non-empty alphanumeric with only hyphens/underscores. This is a path-traversal guard: taskId is interpolated directly into the GCS object name `tasks/<id>/<type>.tar.gz`, so slashes, dots, or special chars could escape the tasks/ prefix. getObjectPath runs on every save/load.","triggerScenarios":"save(task) or load(taskId) is called with a taskId containing '/', '..', '.', spaces, or any non-[A-Za-z0-9_-] character. The regex rejects it before composing the object key, preventing traversal like `../../other-task/workspace`.","commonSituations":"Client supplies a taskId with a URL path segment or UUID with dots; SDK generates IDs that include ':' (common in some UUID variants); accidental concatenation like 'user:abc-123'; malicious input probing for traversal.","solutions":["Generate taskIds with a known-safe format: UUIDv4 (hyphens OK) or nanoid with the default alphabet.","Sanitize incoming taskIds at the API boundary: strip/replace characters outside [A-Za-z0-9_-] before they reach the store.","Reject the request at the handler with a 400 rather than letting the store throw.","Add a unit test asserting your ID generator's output always matches /^[a-zA-Z0-9_-]+$/."],"exampleFix":"// before\nconst id = `${userScope}:${crypto.randomUUID()}`;\nstore.save({ id, ... });\n// throws: Invalid taskId (contains ':')\n\n// after\nconst id = `${userScope}_${crypto.randomUUID().replaceAll('-', '_')}`;\n// or validate at the boundary:\nif (!/^[a-zA-Z0-9_-]+$/.test(id)) throw new TypeError('Invalid taskId');\nstore.save({ id, ... });","handlingStrategy":"validation","validationCode":"const TASK_ID_RE = /^[a-zA-Z0-9_-]+$/;\nfunction assertTaskIdValid(id: string) {\n  if (!TASK_ID_RE.test(id)) throw new TypeError(`Invalid taskId '${id}'; expected ${TASK_ID_RE}`);\n}\n// call at the API boundary, before store.save/load","typeGuard":"function isValidTaskId(id: string): id is string {\n  return /^[a-zA-Z0-9_-]+$/.test(id);\n}","tryCatchPattern":"try {\n  await store.save(task);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid taskId')) {\n    return { status: 400, error: e.message };\n  }\n  throw e;\n}","preventionTips":["Generate taskIds with UUIDv4 or nanoid - their default alphabets satisfy the regex.","Validate at the API boundary and return 400, never let untrusted IDs reach the store.","Strip/replace ':' or '/' from any upstream ID scheme before composing taskIds.","Add a property test asserting your ID generator's output matches /^[a-zA-Z0-9_-]+$/."],"tags":["gcs","security","path-traversal","validation","task-id"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}