Skip to content
LogoLogo

Debugging

nautilus redacts what it passes on from the node, the wallet, the verifier and the RPC: error messages, warnings and log events never carry an RPC token, a session id, a signature or a JWT, at any log level. That keeps them safe to log and to show, but sometimes the redacted text is not enough to find out what went wrong. The sensitive debug channel shows the raw values, on a separate channel, only when you turn it on.

When to use it

  • A credential-gated download fails with Presentation failed, and you need the verifier's per-policy results (which policy failed, and why).
  • The node refuses a request, and its full answer would explain more than the one sentence in the error.
  • An RPC call fails, and you need its response body (and, at secrets, which URL it went to).
  • A store refuses an upload, and you need its answer.

Turning it on

In Node, without changing code:

NAUTILUS_DEBUG=sensitive node download.js 2> debug.log

Or in code, also in a browser:

import {  } from '@deltadao/nautilus'
 
.({ : 'sensitive', : ['policy', 'wallet'] }) 
// … reproduce the failure …
.({ : 'off' }) 

setDebug overrides the environment variables. See setDebug for every option.

The two tiers

TierAddsTypical use
off (default)nothingproduction
sensitiveraw node, wallet, verifier and RPC answers; session ids and redirect URIs; the verifier's per-policy results with full errors; request payloads with their signatures and nonces; raw signed transactions; presented VP/VC JWTs; storage pointers without their credentialsaccess, SSI, ordering and indexing problems
secretseverything in sensitive, plus long-lived credentials: the RPC URL's token, S3 secret keys, IPFS (Pinata) JWTs, Authorization headersstore or RPC authentication problems

sensitive still hides long-lived credentials, best effort, by name and shape: an RPC URL reads https://rpc.example.org/v3/<redacted> (a local one without a scheme too, 127.0.0.1:8545/v3/<redacted>, also with a BOM or a zero-width character in its path), and so does any value under a name ending in token, key, secret, password, auth, cookie, jwt or credential(s) (apiToken, pinataGatewayToken, drpc's dkey, S3_SECRET_KEY=, PINATA_JWT=, a Cookie header), in URLs, objects and text, also in JSON quoted inside JSON (up to eight levels of JSON escaping or percent-encoding undone; deeper JSON escaping and percent-encoding, also mixed, are flattened). Session-scoped values stay readable (vp_token, state, sessionId, nonce, signature, id). A credential under a name nautilus does not know, or without a name, still prints: scan the output before you share it. A long id in a URL path (20 or more letters and digits, such as a 64-hex compute job id without 0x, an Arweave id or an S3 object key segment) is masked like an API key in a path, https://…/v3/<key>; the secrets tier shows it. secrets must be named explicitly: no other value of NAUTILUS_DEBUG reaches it.

nautilus never holds the signer's private key or mnemonic (your signer does), so neither is printed at any tier.

Topics

node, wallet, policy, rpc, order, nonce, store and compute. Narrow the output with NAUTILUS_DEBUG_TOPICS=policy,wallet or topics: ['policy', 'wallet']. An unknown topic in NAUTILUS_DEBUG_TOPICS is ignored with a config.env.invalid warning; when none of the topics given is known, no topic prints (never all of them).

nautilus's own log events hand their raw fields to the channel too, but only when the event is printed at the current log level: pair NAUTILUS_DEBUG with NAUTILUS_LOG_LEVEL=debug (or configureLogging({ level: 'debug' })) to see the raw fields of every event.

What you see

Each event is one line on stderr (in a browser, through console.info, which consoles show by default), prefixed with its tier and topic, so the lines are easy to find, split off and delete. A string in an event is cut at 32,768 characters and an event at about 256 KB, after the masking; a longer string is read only up to the last word before the cut, so a credential is never shown in part. A cut string ends in …(cut: N of M characters shown):

[nautilus] WARNING: sensitive debug logging is ON: the output contains session ids, signatures and request data. Don't use it in production, and don't share the output.
[nautilus:debug:sensitive] [policy] 2026-10-10T13:36:58.561Z [access 3f9a2c1b07e4] policy-server session opened (initiate) {…}
[nautilus:debug:sensitive] [wallet] 2026-10-10T13:36:58.759Z [access 3f9a2c1b07e4] answer HTTP 400 to wallet/…/exchange/usePresentationRequest {"status":400,"statusText":"Bad Request","body":"{\"redirectUri\":\"…/error?id=…\",…}"}
[nautilus:debug:sensitive] [policy] 2026-10-10T13:36:58.772Z [access 3f9a2c1b07e4] verifier per-policy results (checkSessionId) {…,"policies":[…,"VerifiablePresentation holder-binding: failed (java.lang.NullPointerException)"]}

The error your code catches is the same with the channel on or off: here a PolicyDeniedError naming holder-binding: java.lang.NullPointerException, read from the verifier's record (checkSessionId) that nautilus asks for after a presentation the verifier refused. Its cause keeps only the wallet's errorMessage; the wallet's redirect URI and the session id are in the debug lines only. Turning the channel on changes nothing in what nautilus sends.

To route the events elsewhere, pass a sink; it receives { time, tier, topic, message, data }, and within an operation its opId and operation. In Node 22.3 or newer, a debug line made during a call shows the call and its opId after the time ([access 3f9a2c1b07e4]), the same opId as the call's log events and as error.opId on the error it throws, so the raw data of one failing call can be picked out of a busy log.

The warnings

Turning a tier on prints a warning on stderr, once per tier, that no log level or log sink suppresses. Under NODE_ENV=production it is stronger. Treat either as a reminder to turn the channel off again.

Sharing debug output

The output is meant for you, on your machine. Before you share any of it, e.g. in a bug report:

  1. Keep only the lines you need (grep '^\[nautilus:debug:' finds them).
  2. Run a secret scan over them, and replace session ids, signatures, nonces and JWTs with <redacted>. A session id lets anyone read the presentation through the node's passthrough until the verifier forgets it. Remove raw signed transactions too: anyone can broadcast one that was never mined. And remove presented VP/VC JWTs: they carry the holder's personal data (name, address, registration numbers, whatever the credential states).
  3. Never share secrets output. If you captured any, rotate every credential it printed: the RPC URL's token, the S3 keys, the IPFS JWT.
  4. Delete the files when you are done.