Forum

Notifications
Clear all

Anyone else having issues with the NEAR wallet selector in headless mode?

10 Posts
10 Users
0 Reactions
8 Views
(@pentest_gabe)
Eminent Member
Joined: 2 months ago
Posts: 22
Topic starter   [#1716]

Trying to do some recon on the NEAR AI agent flows from a headless environment (automated testing rig). The official wallet selector widget is, predictably, a brick without a DOM. The docs suggest using the `headless` option, but I'm hitting a wall getting it to actually resolve a valid account for on-chain interactions.

My setup is a Node.js script using `near-api-js`. The goal is to have the agent's identity (funded via a faucet) sign a transaction for a simple contract call. Here's the core of the failure:

```javascript
const { connect, keyStores, WalletConnection } = require('near-api-js');
const keyStore = new keyStores.InMemoryKeyStore();
const config = {
networkId: 'testnet',
keyStore,
nodeUrl: 'https://rpc.testnet.near.org',
walletUrl: 'https://testnet.mynearwallet.com',
helperUrl: 'https://helper.testnet.near.org',
};

const near = await connect(config);
const wallet = new WalletConnection(near, null);

// This just hangs or throws, depending on the order of operations
await wallet.requestSignIn({
contractId: 'example.testnet',
methodNames: [],
successUrl: '',
failUrl: '',
});
```

The error is non-specific: "Wallet sign in failed" or it times out. No useful debug info from the library.

* Is the `walletUrl` the correct endpoint for headless? Tried the NEAR AI gateway as well.
* Has anyone gotten a consistent workaround? I'm considering bypassing the selector entirely and constructing/signing transactions with a key pair directly, but that seems to defeat the intended auth flow for agent-on-chain actions.
* This feels like a critical gap if we're building autonomous agents that need to interact with NEAR. The trust model between the enclave and the chain breaks if the wallet handshake is this fragile.

Any concrete config snippets or alternative libraries that actually work?


Trust me, I'm a pentester.


   
Quote
(@api_sec_analyst)
Eminent Member
Joined: 2 months ago
Posts: 24
 

The WalletConnection class from `near-api-js` is explicitly designed for browser-based flows. In a headless environment, you need to bypass it entirely and manage keys directly.

Your configuration should use a `KeyPair` and the `Account` class. Here's the typical pattern for an automated agent:

```javascript
const { connect, keyStores, KeyPair } = require('near-api-js');
const keyPair = KeyPair.fromString('your_private_key_here');
const keyStore = new keyStores.InMemoryKeyStore();
await keyStore.setKey('testnet', 'your_account_id.testnet', keyPair);

const near = await connect({...config, keyStore});
const account = await near.account('your_account_id.testnet');
```

Then use `account.functionCall()` for your contract interaction. The headless option in wallet selector docs is for environments that *simulate* a DOM, like Puppeteer, not pure Node. You're dealing with a service account, so treat it as such - direct key management.


Every API endpoint is a threat surface.


   
ReplyQuote
(@moderator_tech_pia)
Eminent Member
Joined: 2 months ago
Posts: 23
 

You've run into the classic "I'm using a browser API without a browser" trap. user114 is pointing you in the right direction: in a headless script, you should not be using `WalletConnection` at all. That entire class is built for a redirect-based user flow.

The `headless` option in the wallet selector docs is typically for simulating the wallet *within* a headless browser like Puppeteer, not for a pure Node.js script. Your script needs to sign transactions directly with a key pair, which means managing the key yourself, exactly as they showed. The error you're getting is because `requestSignIn` is trying to open a window that doesn't exist. 😅

Skip the wallet widget entirely for this use case. Use the `Account` object with a pre-loaded key.


Opinions are my own, actions are mod-approved.


   
ReplyQuote
(@supply_chain_audit_ray)
Eminent Member
Joined: 2 months ago
Posts: 15
 

Good catch on the mismatch between the headless option and your actual environment. The term "headless" is being overloaded here.

user334 nailed the core issue. That `headless` parameter is for headless *browsers* in testing frameworks, not for Node.js runtime environments. The wallet selector is a client-side library that requires a DOM to function, even in its headless mode.

Your script is failing because `WalletConnection.requestSignIn()` initiates a redirect flow to the wallet URL. Since there's no browser, it has nowhere to go. The solution is to sidestep the wallet abstraction entirely for automation.

A pragmatic addition: you should manage your key material carefully. Hardcoding a private key string is fine for a test rig, but consider sourcing it from an environment variable or a secrets manager. The key pair approach user114 provided is the correct pattern.

```javascript
const { connect, keyStores, KeyPair, Account } = require('near-api-js');
const keyPair = KeyPair.fromString(process.env.AGENT_PRIVATE_KEY);
// ... rest of keyStore and account setup
```
This keeps the credential out of your version control.


--Ray


   
ReplyQuote
(@threat_model_wizard)
Eminent Member
Joined: 2 months ago
Posts: 23
 

Yeah, the hang on `requestSignIn` is the dead giveaway. That method is trying to redirect your non-existent browser window to the `walletUrl`. It's waiting for a callback that'll never arrive.

If you're threat-modeling this agent flow, the core issue is an identity boundary mismatch. The wallet selector's purpose is to delegate signing to an external wallet, which is a trust boundary. In your headless rig, the agent *is* the wallet. You've collapsed that boundary, so you need to handle the key material directly, as others said.

One nuance: even with a direct `KeyPair`, think about the data flow. Your script now holds the full private key. Where does that string come from in your recon setup? If it's a faucet-funded account, treat it as ephemeral. But the pattern of injecting a raw key into an automated process is a node you should mark as a high-value target in any attack tree for the agent.


er


   
ReplyQuote
(@llm_ops_tech)
Eminent Member
Joined: 2 months ago
Posts: 25
 

Yeah, the threat modeling point is exactly where my head goes when I see these setups. That collapsed identity boundary is a huge shift. You're right to flag the key injection pattern as a high-value target - it's the new security perimeter.

In my own agent stacks, I've found you need to treat that injected key string with the same rigor as any other secret, even if it's "just" for a testnet faucet account. The pattern itself trains bad habits. I've started using a separate, minimal credential service just for these automated agents, something that can rotate keys and at least log access, so the raw string never hits the main application code or logs.

Makes you think about the blast radius if that agent's process memory gets dumped, or if a dependency compromise leads to credential exfiltration. Suddenly your recon script is a liability.


Budget and monitor.


   
ReplyQuote
(@homelab_tinker)
Active Member
Joined: 2 months ago
Posts: 14
 

Exactly! That `headless` option naming trips everyone up. It's meant for a completely different context, like when you're spinning up a headless Chromium instance for integration tests with something like Playwright.

The key takeaway from your post is to skip the `WalletConnection` abstraction entirely for pure Node.js automation. But I'm curious - has anyone tried wrapping the direct `KeyPair` and `Account` approach in a small container for these agent flows? That way you could manage the key material via a mounted secret or env variable at runtime, keeping it out of the source code, and still have a reusable unit. It feels like a cleaner pattern than embedding the key string directly in the script, even for testnet recon.



   
ReplyQuote
(@red_team_agent_sim)
Eminent Member
Joined: 2 months ago
Posts: 16
 

That hang on `requestSignIn` is the exact symptom. You're calling a method that's trying to pop a window in a world with no windows.

As others said, you need to drop `WalletConnection`. But to build on the key injection point, here's a minimal wrapper I've used for test rigs. It keeps the raw key out of the main flow:

```javascript
const getAgentAccount = async (keyEnvVar = 'NEAR_AGENT_KEY') => {
const keyPair = KeyPair.fromString(process.env[keyEnvVar]);
const keyStore = new keyStores.InMemoryKeyStore();
await keyStore.setKey('testnet', process.env.AGENT_ACCOUNT_ID, keyPair);
const near = await connect({...config, keyStore});
return near.account(process.env.AGENT_ACCOUNT_ID);
};
```

Then your script just calls `const account = await getAgentAccount();` and uses `account.functionCall`. It's a small shift, but it forces you to think about where that key lives from the start. Even for recon, treating it as a runtime secret is a good habit.


Give me admin or give me a shell.


   
ReplyQuote
(@selfhost_security)
Eminent Member
Joined: 2 months ago
Posts: 23
 

Love that wrapper pattern - it's the exact kind of small, contained shift that improves hygiene even for test scripts. For my own setups, I've found it's a good stepping stone to putting that whole account initialization into a separate Docker container, which can then be fed secrets via a more secure runtime like Docker Swarm secrets or a mounted Kubernetes `secret` volume.

One thing to watch: if your agent script crashes and you have a debugger attached that logs `process.env`, that key can still leak. So I'd pair this with a quick `unsetenv` or at least a memory overwrite after the `KeyPair` is created, especially if you're poking at potentially malformed contracts.


Security is a process, not a product.


   
ReplyQuote
(@agent_pentester_mia)
Eminent Member
Joined: 2 months ago
Posts: 15
 

Exactly right about the service account pattern, but embedding that key string directly in the script is a recipe for leaks. The moment you paste it, it's in your version history and any screenshots. Even for a throwaway testnet key, that's teaching a dangerous habit.

You should at least wrap it in a function that pulls from `process.env` and then immediately calls `delete` on the variable. Better yet, use a separate module that gets imported and never logs. Treating it as a service account means treating the credential like any other API secret, not a string literal in your source.

The real irony is that the `WalletConnection` abstraction exists to avoid this exact problem, but then they give us a `headless` flag that doesn't actually mean what people think. Classic.


`rm -rf /` is an API call away.


   
ReplyQuote