Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/container-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const defaultConfig: Config = {
PINScreensConfig: {
useNewPINDesign: false,
},
trustedCertificatesUrl: ReactConfig.TRUSTED_CERTIFICATES_URL,
}

export const defaultHistoryEventsLogger: HistoryEventsLoggerConfig = {
Expand Down
26 changes: 15 additions & 11 deletions packages/core/src/hooks/useBifoldAgentSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { TOKENS, useServices } from '../container-api'
import { DispatchAction } from '../contexts/reducers/store'
import { useStore } from '../contexts/store'
import { WalletSecret } from '../types/security'
import { createLinkSecretIfRequired, getAgentModules } from '../utils/agent'
import { createLinkSecretIfRequired, getAgentModulesWithCertificates } from '../utils/agent'
import { migrateToAskar } from '../utils/migration'

export type AgentSetupReturnType = {
Expand All @@ -22,11 +22,12 @@ const useBifoldAgentSetup = (): AgentSetupReturnType => {
const [agent, setAgent] = useState<Agent | null>(null)
const agentInstanceRef = useRef<Agent | null>(null)
const [store, dispatch] = useStore()
const [cacheSchemas, cacheCredDefs, logger, indyLedgers] = useServices([
const [cacheSchemas, cacheCredDefs, logger, indyLedgers, config] = useServices([
TOKENS.CACHE_SCHEMAS,
TOKENS.CACHE_CRED_DEFS,
TOKENS.UTIL_LOGGER,
TOKENS.UTIL_LEDGERS,
TOKENS.CONFIG,
])

const restartExistingAgent = useCallback(
Expand Down Expand Up @@ -62,15 +63,18 @@ const useBifoldAgentSetup = (): AgentSetupReturnType => {
autoUpdateStorageOnStartup: true,
},
dependencies: agentDependencies,
modules: getAgentModules({
indyNetworks: indyLedgers,
mediatorInvitationUrl: mediatorUrl,
txnCache: {
capacity: 1000,
expiryOffsetMs: 1000 * 60 * 60 * 24 * 7,
path: CachesDirectoryPath + '/txn-cache',
modules: await getAgentModulesWithCertificates(
{
indyNetworks: indyLedgers,
mediatorInvitationUrl: mediatorUrl,
txnCache: {
capacity: 1000,
expiryOffsetMs: 1000 * 60 * 60 * 24 * 7,
path: CachesDirectoryPath + '/txn-cache',
},
},
}),
config.trustedCertificatesUrl
),
})
const wsTransport = new WsOutboundTransport()
const httpTransport = new HttpOutboundTransport()
Expand All @@ -80,7 +84,7 @@ const useBifoldAgentSetup = (): AgentSetupReturnType => {

return newAgent
},
[store.preferences.walletName, logger, indyLedgers]
[store.preferences.walletName, logger, indyLedgers, config.trustedCertificatesUrl]
)

const migrateIfRequired = useCallback(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export * from './types/attestation'
export { BifoldError } from './types/error'
export { Screens, Stacks, TabStacks } from './types/navigators'
export * from './types/version-check'
export { createLinkSecretIfRequired, getAgentModules } from './utils/agent'
export { createLinkSecretIfRequired, getAgentModules, getAgentModulesWithCertificates } from './utils/agent'
export { getCredentialIdentifiers, isValidAnonCredsCredential } from './utils/credential'
export {
connectFromScanOrDeepLink,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface Config {
appUpdateConfig?: AppUpdateConfig
preventScreenCapture?: boolean
PINScreensConfig: PINScreensConfig
trustedCertificatesUrl?: string
}

export interface PINScreensConfig {
Expand Down
56 changes: 55 additions & 1 deletion packages/core/src/utils/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
ProofsModule,
V2CredentialProtocol,
V2ProofProtocol,
X509Module,
} from '@credo-ts/core'
import { IndyVdrAnonCredsRegistry, IndyVdrModule, IndyVdrPoolConfig } from '@credo-ts/indy-vdr'
import { OpenId4VcHolderModule } from '@credo-ts/openid4vc'
Expand All @@ -34,18 +35,46 @@ interface GetAgentModulesOptions {
indyNetworks: IndyVdrPoolConfig[]
mediatorInvitationUrl?: string
txnCache?: { capacity: number; expiryOffsetMs: number; path?: string }
trustedCertificates?: string[]
}

export type BifoldAgent = Agent<ReturnType<typeof getAgentModules>>

/**
* Fetches trusted certificates from a remote API
* @param url The API endpoint URL
* @returns Array of certificate strings
*/
async function fetchTrustedCertificates(url: string): Promise<string[]> {
try {
const response = await fetch(url)
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The fetchTrustedCertificates function does not implement any timeout for the fetch request. In a production environment, this could lead to indefinite hangs if the remote server is unresponsive. Other fetch calls in the codebase (e.g., fetchInvitationDataUrl in resolverProof.tsx) use AbortController with a 10-second timeout. Consider implementing a similar timeout mechanism for reliability.

Copilot uses AI. Check for mistakes.
if (!response.ok) {
return []
}
const certificates = await response.json()
if (!Array.isArray(certificates)) {
return []
}
return certificates.filter((cert) => typeof cert === 'string' && cert.trim().length > 0)
Comment on lines +60 to +64
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

There is no validation of the certificate format or content after fetching them. Malformed or malicious certificates could be passed directly to the X509Module. Consider adding basic validation to ensure certificates have the expected format (e.g., PEM format with BEGIN/END CERTIFICATE markers) before accepting them.

Copilot uses AI. Check for mistakes.
} catch (error) {
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The fetchTrustedCertificates function silently swallows all errors and returns an empty array. This makes it difficult to debug issues when the certificate fetch fails. Consider logging the error before returning an empty array to help with debugging and monitoring. The codebase commonly uses logger.error for error logging, as seen in many other files.

Suggested change
} catch (error) {
} catch (error) {
console.error(`Failed to fetch trusted certificates from "${url}". Returning empty list.`, error)

Copilot uses AI. Check for mistakes.
return []
}
}
Comment on lines +54 to +68
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The function fetchTrustedCertificates returns an empty array on failure (lines 58, 62, 66). However, there's no way for the caller to distinguish between "no certificates available" and "fetch failed". This could lead to silent failures where an agent is initialized without certificates when they should be available. Consider returning a result object with success status or throwing an error that can be caught and logged at a higher level.

Copilot uses AI. Check for mistakes.

/**
* Constructs the modules to be used in the agent setup
* @param indyNetworks
* @param mediatorInvitationUrl determine which mediator to use
* @param txnCache optional local cache config for indyvdr
* @param trustedCertificates optional array of trusted certificates for X509 module
* @returns modules to be used in agent setup
*/
export function getAgentModules({ indyNetworks, mediatorInvitationUrl, txnCache }: GetAgentModulesOptions) {
export function getAgentModules({
indyNetworks,
mediatorInvitationUrl,
txnCache,
trustedCertificates = [],
}: GetAgentModulesOptions) {
const indyCredentialFormat = new LegacyIndyCredentialFormatService()
const indyProofFormat = new LegacyIndyProofFormatService()

Expand Down Expand Up @@ -105,9 +134,34 @@ export function getAgentModules({ indyNetworks, mediatorInvitationUrl, txnCache
pushNotificationsFcm: new PushNotificationsFcmModule(),
pushNotificationsApns: new PushNotificationsApnsModule(),
openId4VcHolder: new OpenId4VcHolderModule(),
...(trustedCertificates.length > 0
? {
x509: new X509Module({
trustedCertificates: trustedCertificates as [string, ...string[]],
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The type assertion 'trustedCertificates as [string, ...string[]]' could be unsafe if the array is empty. Although the condition checks 'trustedCertificates.length > 0', TypeScript doesn't narrow the type based on the length check. Consider using a type guard function or explicitly checking if the array has at least one element before the type assertion to make the code more type-safe.

Suggested change
trustedCertificates: trustedCertificates as [string, ...string[]],
trustedCertificates: [trustedCertificates[0], ...trustedCertificates.slice(1)],

Copilot uses AI. Check for mistakes.
}),
}
: {}),
Comment on lines +152 to +158
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The X509Module is conditionally added only when trustedCertificates.length is greater than 0. However, other parts of the codebase (e.g., resolverProof.tsx) use agent.x509 directly, which could cause runtime errors if the module is not registered. The removed code in offerResolve.tsx also accessed agent.x509 directly. Consider either always registering the X509Module with an empty array or ensuring all usages check for the module's existence first.

Copilot uses AI. Check for mistakes.
}
}

/**
* Fetches and prepares agent modules with trusted certificates from remote API
* @param options Agent module options including indyNetworks, mediatorInvitationUrl, txnCache
* @param trustedCertificatesUrl Optional URL to fetch trusted certificates from
* @returns Promise resolving to agent modules
*/
export async function getAgentModulesWithCertificates(
options: Omit<GetAgentModulesOptions, 'trustedCertificates'>,
trustedCertificatesUrl?: string
) {
const trustedCertificates = trustedCertificatesUrl ? await fetchTrustedCertificates(trustedCertificatesUrl) : []

return getAgentModules({
...options,
trustedCertificates,
})
}
Comment on lines 54 to 178
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The new functions fetchTrustedCertificates and getAgentModulesWithCertificates lack test coverage. Given that the repository has comprehensive tests for utility functions (as seen in tests/utils/), these new functions should have tests covering success cases, failure cases (network errors, malformed responses), and edge cases (empty arrays, invalid certificate formats).

Copilot uses AI. Check for mistakes.

interface MyAgentContextInterface {
loading: boolean
agent: BifoldAgent
Expand Down
Loading