Skip to content
18 changes: 15 additions & 3 deletions packages/components/src/google-utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getCredentialData, getCredentialParam, type ICommonObject, type INodeData } from '.'
import { getCredentialData, getCredentialParam, parseJsonBody, type ICommonObject, type INodeData } from '.'
import type { ChatVertexAIInput, VertexAIInput } from '@langchain/google-vertexai'

type SupportedAuthOptions = ChatVertexAIInput['authOptions'] | VertexAIInput['authOptions']
Expand All @@ -19,8 +19,20 @@ export const buildGoogleCredentials = async (nodeData: INodeData, options: IComm
)

if (googleApplicationCredentialFilePath && !googleApplicationCredential) authOptions.keyFile = googleApplicationCredentialFilePath
else if (!googleApplicationCredentialFilePath && googleApplicationCredential)
authOptions.credentials = JSON.parse(googleApplicationCredential)
else if (!googleApplicationCredentialFilePath && googleApplicationCredential) {
let parsedCredential: unknown
try {
parsedCredential = parseJsonBody(googleApplicationCredential)
} catch (e) {
throw new Error('Invalid Google Application Credential JSON; expected a JSON object.')
}

if (parsedCredential && typeof parsedCredential === 'object' && !Array.isArray(parsedCredential)) {
authOptions.credentials = parsedCredential
} else {
throw new Error('Invalid Google Application Credential JSON; expected a JSON object.')
}
}
Comment on lines +22 to +35
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation parsedCredential ?? googleApplicationCredential can lead to runtime errors. If safeJsonParse fails and returns null, the original (and likely malformed) googleApplicationCredential string is assigned to authOptions.credentials. The credentials property expects an object, not a string. This would replace an immediate crash with a deferred one. To fix this, we should only assign the parsed value if it's a valid object, similar to the validation added in handler.ts.

Suggested change
else if (!googleApplicationCredentialFilePath && googleApplicationCredential) {
const parsedCredential = safeJsonParse(googleApplicationCredential)
authOptions.credentials = parsedCredential ?? googleApplicationCredential
}
else if (!googleApplicationCredentialFilePath && googleApplicationCredential) {
const parsedCredential = safeJsonParse(googleApplicationCredential);
if (parsedCredential && typeof parsedCredential === 'object' && !Array.isArray(parsedCredential)) {
authOptions.credentials = parsedCredential;
}
}


if (projectID) authOptions.projectId = projectID
}
Expand Down
20 changes: 17 additions & 3 deletions packages/components/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { ChainValues } from '@langchain/core/utils/types'
import { AgentAction } from '@langchain/core/agents'
import { LunaryHandler } from '@langchain/community/callbacks/handlers/lunary'

import { getCredentialData, getCredentialParam, getEnvironmentVariable } from './utils'
import { getCredentialData, getCredentialParam, getEnvironmentVariable, parseJsonBody } from './utils'
import { EvaluationRunTracer } from '../evaluation/EvaluationRunTracer'
import { EvaluationRunTracerLlama } from '../evaluation/EvaluationRunTracerLlama'
import { ICommonObject, IDatabaseEntity, INodeData, IServerSideEventStreamer } from './Interface'
Expand Down Expand Up @@ -517,7 +517,14 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
try {
if (!options.analytic) return []

const analytic = JSON.parse(options.analytic)
let analytic: unknown
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how and why would this fail originally? It should be stored as json string straight into database

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question!

In practice, this can fail if the value is empty, malformed, or already an object instead of a JSON string (which can happen with legacy data or inconsistent inputs).

The defensive parsing was introduced to prevent runtime crashes in those cases and keep the system stable.

That said, I agree that ideally this should always be stored as a valid JSON string at the database level, and enforcing that upstream would be the best long-term approach.

try {
analytic = parseJsonBody(options.analytic ?? '')
} catch (e) {
return []
}

if (!analytic || typeof analytic !== 'object' || Array.isArray(analytic)) return []
const callbacks: any = []

for (const provider in analytic) {
Expand Down Expand Up @@ -776,7 +783,14 @@ export class AnalyticHandler {
try {
if (!this.options.analytic) return

const analytic = JSON.parse(this.options.analytic)
let analytic: unknown
try {
analytic = parseJsonBody(this.options.analytic ?? '')
} catch (e) {
return
}

if (!analytic || typeof analytic !== 'object' || Array.isArray(analytic)) return
for (const provider in analytic) {
const providerStatus = analytic[provider].status as boolean
if (providerStatus) {
Expand Down