@exodus/errors
TypeScript icon, indicating that this package has built-in type declarations

3.0.1 • Public • Published

@exodus/errors

Usage

import { SafeError } from '@exodus/errors'

try {
  throw new Error('Something went wrong')
} catch (e) {
  const safeError = SafeError.from(e)

  // It's now safe to report or log the error, even to a remote server.
  console.error({
    name: safeError.name, // Sanitized error name.
    code: safeError.code, // Optional error code (if present).
    hint: safeError.hint, // Optional error hint (if present).
    stack: safeError.stack, // Sanitized stack trace.
    timestamp: safeError.timestamp, // When the error occurred.
  })
}

FAQ

Why using SafeError instead of built-in Errors?

In large codebases, errors can be thrown from anywhere, making it impossible to audit every error message for sensitive information. A single error containing sensitive data could potentially expose user information. Centralizing error handling with SafeError makes it possible to enforce security and consistency across the board, by ensuring:

  1. Controlled Error Flow: All errors go through a single, well-tested sanitization layer before they hit error tracking systems.
  2. Enforceable Security: Error handling can be owned through codeowners and covered by tests, so nothing slips through unnoticed.

In addition to enforcing these practices, SafeError includes a few key design decisions that make it safer and more reliable than native Error objects:

  1. Message Sanitization: The message property from built-in Errors is intentionally omitted as it often contains sensitive information. Instead, a hint property is used that contains only sanitized, non-sensitive information.
  2. Native Stack Parsing: The library uses the Error.prepareStackTrace API to parse stack traces, providing consistent and reliable stack trace information across different JavaScript environments.
  3. Immutability: Once created, a SafeError instance cannot be modified, preventing tampering with error data.
  4. Safe Serialization: The toJSON method ensures safe serialization for logging or sending to error tracking services.

Why not redacting Error messages?

Parsing/sanitization of error messages is unreliable and the cost of failure is potential loss of user funds and permanent reputation damage.

Why not use the built-in .stack Error property?

Unfortunately, the built-in .stack property is mutable and outside of our control. Instead, we use the Error.prepareStackTrace API, which enables us to make sure we access the actual call stack and not a cached err.stack value that may have already been consumed and modified. We then parse it into a structured format that we can safely sanitize and control. This approach provides consistent, reliable stack traces across different environments (currently supporting both V8 and Hermes).

Troubleshooting

A SafeError instance returns undefined stack trace?

That likely means that something accesses error.stack before the Safe Error constructor had a chance to apply the custom Error.prepareStackTrace handler. This could be React, a high-level error handler, or any other framework. error.stack is computed only on the first access, so the custom handler won’t be called on subsequent attempts (see the stack.test.js for a quick demo).

If you can identify the exact place where .stack is accessed, consider capturing the stack trace explicitly like this:

import { captureStackTrace, SafeError } from '@exodus/errors'

try {
  // the devil's work
} catch (e) {
  captureStackTrace(e)
  void e.stack // Intentionally access the property to "break" it here.
  SafeError.from(e).stack // A non-empty string!
  // 🎉 Congrats — you just (hopefully) saved hours of debugging! The custom stack trace parsing logic now works because the stack was captured explicitly above.
}

Readme

Keywords

none

Package Sidebar

Install

npm i @exodus/errors

Weekly Downloads

1,717

Version

3.0.1

License

MIT

Unpacked Size

23.2 kB

Total Files

18

Last publish

Collaborators

  • joshuabot