{"record":{"id":"7456b35f9731b0f7","repo":"iflytek/astron-agent","slug":"invalid-workflow-internal-api-credentials","errorCode":null,"errorMessage":"Invalid workflow internal API credentials","messagePattern":"Invalid workflow internal API credentials","errorType":"http","errorClass":"HTTPException","httpStatus":401,"severity":"error","filePath":"core/agent/infra/workflow_internal_auth.py","lineNumber":54,"sourceCode":"    if not api_key:\n        raise HTTPException(\n            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,\n            detail=\"Workflow internal API authentication is not configured\",\n        )\n    return api_key\n\n\nasync def require_workflow_internal_api_key(\n    supplied_api_key: Annotated[\n        str | None, Security(_workflow_internal_api_key_header)\n    ],\n) -> None:\n    \"\"\"Require the same internal credential shared by Workflow and Agent.\"\"\"\n    expected_api_key = configured_workflow_internal_api_key()\n    if not supplied_api_key or not secrets.compare_digest(\n        supplied_api_key, expected_api_key\n    ):\n        raise HTTPException(\n            status_code=status.HTTP_401_UNAUTHORIZED,\n            detail=\"Invalid workflow internal API credentials\",\n        )\n","sourceCodeStart":36,"sourceCodeEnd":58,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/agent/infra/workflow_internal_auth.py#L36-L58","documentation":"require_workflow_internal_api_key() compares the caller-supplied credential against the configured shared key using secrets.compare_digest. If the header is missing or the value differs in any way, it raises HTTP 401 to reject the request at the Agent's workflow-internal endpoints.","triggerScenarios":"Any Agent->Workflow or Workflow->Agent internal HTTP call that omits the key header or sends a value not byte-equal to the configured shared key (workflow_internal_auth.py:54).","commonSituations":"Caller service still using an old key after rotation; one service updated, the other not; whitespace/newline in the secret when injected from a mounted file; wrong header name; different keys set per environment by mistake.","solutions":["Set the identical WORKFLOW_INTERNAL_API_KEY value on both Agent and Workflow services and restart them","If using file-based secrets, strip trailing newlines (printf '%s' not echo) before injecting the header","Verify the caller actually sends the key header on every internal request","After rotation, redeploy both services together rather than one at a time"],"exampleFix":"// before\nheaders = {\"Content-Type\": \"application/json\"}\n// after\nheaders = {\"Content-Type\": \"application/json\", \"X-Internal-Api-Key\": os.environ[\"WORKFLOW_INTERNAL_API_KEY\"]}","handlingStrategy":"try-catch","validationCode":"key = os.getenv(\"WORKFLOW_INTERNAL_API_KEY\")\nassert key and caller_key == key, \"caller must send the exact shared internal key\"","typeGuard":"def has_valid_internal_key(headers: dict, expected: str) -> bool:\n    supplied = headers.get(\"X-Internal-Api-Key\")\n    return bool(supplied) and secrets.compare_digest(supplied, expected)","tryCatchPattern":"try:\n    resp = await client.post(url, headers={\"X-Internal-Api-Key\": shared_key}, ...)\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 401:\n        logger.error(\"internal key mismatch; check both services share the same WORKFLOW_INTERNAL_API_KEY\")","preventionTips":["Rotate the shared key on both services simultaneously","Use printf '%s' when writing file-based secrets to avoid trailing newlines","Verify the header name matches what the server reads","Run an internal-auth health check after each deployment"],"tags":["authentication","http-401","service-to-service"],"backgroundTag":"authentication-required","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}