npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@3maem/ash-node

v2.3.0

Published

ASH SDK for Node.js - Request integrity and anti-replay protection library

Readme

ASH SDK for Node.js

Developed by 3maem Co. | شركة عمائم

ASH SDK provides request integrity and anti-replay protection for web applications. This SDK provides request integrity protection, anti-replay mechanisms, and middleware for Express and Fastify.

Installation

npm install @3maem/ash-node

Requirements: Node.js 18.0.0 or later

Quick Start

Initialize the Library

import { ashInit } from '@3maem/ash-node';

// Call once before using other functions
ashInit();

Canonicalize JSON

import { ashCanonicalizeJson, ashInit } from '@3maem/ash-node';

ashInit();

// Canonicalize JSON to deterministic form
const canonical = ashCanonicalizeJson('{"z":1,"a":2}');
console.log(canonical); // {"a":2,"z":1}

Build a Proof

import { ashInit, ashCanonicalizeJson, ashBuildProof } from '@3maem/ash-node';

ashInit();

// Canonicalize payload
const payload = JSON.stringify({ username: 'test', action: 'login' });
const canonical = ashCanonicalizeJson(payload);

// Build proof
const proof = ashBuildProof(
  'balanced',           // mode
  'POST /api/login',    // binding
  'ctx_abc123',         // contextId
  null,                 // nonce (optional)
  canonical             // canonicalPayload
);

console.log(`Proof: ${proof}`);

Verify a Proof

import { ashInit, ashVerifyProof } from '@3maem/ash-node';

ashInit();

const expectedProof = 'abc123...';
const receivedProof = 'abc123...';

// Use timing-safe comparison to prevent timing attacks
if (ashVerifyProof(expectedProof, receivedProof)) {
  console.log('Proof verified successfully');
} else {
  console.log('Proof verification failed');
}

Express Integration

import express from 'express';
import {
  ashInit,
  ashExpressMiddleware,
  AshMemoryStore,
} from '@3maem/ash-node';

ashInit();

const app = express();
const store = new AshMemoryStore();

app.use(express.json());

// Issue context endpoint
app.post('/ash/context', async (req, res) => {
  const context = await store.create({
    binding: 'POST /api/update',
    ttlMs: 30000,
    mode: 'balanced',
  });

  res.json({
    contextId: context.id,
    expiresAt: context.expiresAt,
    mode: context.mode,
  });
});

// Protected endpoint with middleware
app.post(
  '/api/update',
  ashExpressMiddleware({
    store,
    expectedBinding: 'POST /api/update',
  }),
  (req, res) => {
    // Request verified - safe to process
    res.json({ status: 'success' });
  }
);

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Fastify Integration

import Fastify from 'fastify';
import {
  ashInit,
  ashFastifyPlugin,
  AshMemoryStore,
} from '@3maem/ash-node';

ashInit();

const fastify = Fastify();
const store = new AshMemoryStore();

// Register ASH plugin
fastify.register(ashFastifyPlugin, {
  store,
  protectedPaths: ['/api/*'],
});

// Issue context endpoint
fastify.post('/ash/context', async (request, reply) => {
  const context = await store.create({
    binding: 'POST /api/update',
    ttlMs: 30000,
    mode: 'balanced',
  });

  return {
    contextId: context.id,
    expiresAt: context.expiresAt,
    mode: context.mode,
  };
});

// Protected endpoint
fastify.post('/api/update', async (request, reply) => {
  // Request verified by plugin
  return { status: 'success' };
});

fastify.listen({ port: 3000 });

API Reference

Initialization

ashInit(): void

Initialize the ASH library. Call once before using other functions.

import { ashInit } from '@3maem/ash-node';

ashInit();

Canonicalization

ashCanonicalizeJson(input: string): string

Canonicalizes JSON to deterministic form.

Rules:

  • Object keys sorted lexicographically
  • No whitespace
  • Unicode NFC normalized
const canonical = ashCanonicalizeJson('{"z":1,"a":2}');
// Result: '{"a":2,"z":1}'

ashCanonicalizeUrlencoded(input: string): string

Canonicalizes URL-encoded form data.

const canonical = ashCanonicalizeUrlencoded('z=1&a=2');
// Result: 'a=2&z=1'

Proof Generation

ashBuildProof(mode, binding, contextId, nonce, canonicalPayload): string

Builds a cryptographic proof for request integrity.

const proof = ashBuildProof(
  'balanced',           // mode: 'minimal' | 'balanced' | 'strict'
  'POST /api/update',   // binding
  'ctx_abc123',         // contextId
  null,                 // nonce (optional)
  '{"name":"John"}'     // canonicalPayload
);

ashVerifyProof(expected: string, actual: string): boolean

Verifies two proofs match using constant-time comparison.

const isValid = ashVerifyProof(expectedProof, receivedProof);

Binding Normalization

ashNormalizeBinding(method: string, path: string): string

Normalizes a binding string to canonical form.

Rules:

  • Method uppercased
  • Path starts with /
  • Query string excluded
  • Duplicate slashes collapsed
  • Trailing slash removed (except for root)
const binding = ashNormalizeBinding('post', '/api//test/');
// Result: 'POST /api/test'

Secure Comparison

ashTimingSafeEqual(a: string, b: string): boolean

Constant-time string comparison to prevent timing attacks.

const isEqual = ashTimingSafeEqual('secret1', 'secret2');

Version Information

ashVersion(): string

Returns the ASH protocol version (e.g., "ASHv1").

ashLibraryVersion(): string

Returns the library semantic version.

Types

AshMode

type AshMode = 'minimal' | 'balanced' | 'strict';

| Mode | Description | |------|-------------| | minimal | Basic integrity checking | | balanced | Recommended for most applications | | strict | Maximum security with nonce requirement |

AshContext

interface AshContext {
  id: string;                          // Unique context identifier
  binding: string;                     // Endpoint binding
  expiresAt: number;                   // Expiration timestamp (Unix ms)
  mode: AshMode;                       // Security mode
  used: boolean;                       // Whether context has been used
  nonce?: string;                      // Optional server-generated nonce
  metadata?: Record<string, unknown>;  // Optional metadata
}

AshVerifyResult

interface AshVerifyResult {
  valid: boolean;                      // Whether verification succeeded
  errorCode?: string;                  // Error code if failed
  errorMessage?: string;               // Error message if failed
  metadata?: Record<string, unknown>;  // Context metadata (on success)
}

AshContextStore

interface AshContextStore {
  create(options: AshContextOptions): Promise<AshContext>;
  get(id: string): Promise<AshContext | null>;
  consume(id: string): Promise<boolean>;
  cleanup(): Promise<number>;
}

Context Stores

AshMemoryStore

In-memory store for development and testing.

import { AshMemoryStore } from '@3maem/ash-node';

const store = new AshMemoryStore();

AshRedisStore

Production-ready store with atomic operations.

import { AshRedisStore } from '@3maem/ash-node';
import Redis from 'ioredis';

const redis = new Redis('redis://localhost:6379');
const store = new AshRedisStore(redis);

AshSqlStore

SQL-based store for relational databases.

import { AshSqlStore } from '@3maem/ash-node';

const store = new AshSqlStore(databaseConnection);

Express Middleware

ashExpressMiddleware(options: AshExpressOptions): RequestHandler

Creates ASH verification middleware for Express.

interface AshExpressOptions {
  store: AshContextStore;              // Context store instance
  expectedBinding?: string;            // Expected endpoint binding
  mode?: AshMode;                      // Security mode (default: balanced)
  onError?: (error, req, res, next) => void;  // Custom error handler
  skip?: (req) => boolean;             // Skip verification condition
}

Usage

import express from 'express';
import { ashExpressMiddleware, AshMemoryStore } from '@3maem/ash-node';

const app = express();
const store = new AshMemoryStore();

// Apply to specific route
app.post(
  '/api/update',
  ashExpressMiddleware({
    store,
    expectedBinding: 'POST /api/update',
  }),
  handler
);

// Custom error handling
app.post(
  '/api/sensitive',
  ashExpressMiddleware({
    store,
    onError: (error, req, res, next) => {
      console.error('ASH verification failed:', error);
      res.status(403).json({ error: error.code });
    },
  }),
  handler
);

// Skip verification conditionally
app.post(
  '/api/data',
  ashExpressMiddleware({
    store,
    skip: (req) => req.headers['x-internal'] === 'true',
  }),
  handler
);

Client Usage

For Node.js clients making requests to ASH-protected endpoints:

import { ashInit, ashCanonicalizeJson, ashBuildProof } from '@3maem/ash-node';
import axios from 'axios';

ashInit();

async function makeProtectedRequest() {
  // 1. Get context from server
  const { data: context } = await axios.post('https://api.example.com/ash/context');

  // 2. Prepare payload
  const payload = { name: 'John', action: 'update' };
  const canonical = ashCanonicalizeJson(JSON.stringify(payload));

  // 3. Build proof
  const proof = ashBuildProof(
    context.mode,
    'POST /api/update',
    context.contextId,
    context.nonce ?? null,
    canonical
  );

  // 4. Make protected request
  const response = await axios.post(
    'https://api.example.com/api/update',
    payload,
    {
      headers: {
        'X-ASH-Context-ID': context.contextId,
        'X-ASH-Proof': proof,
        'Content-Type': 'application/json',
      },
    }
  );

  return response.data;
}

Complete Server Example

import express from 'express';
import {
  ashInit,
  ashExpressMiddleware,
  ashNormalizeBinding,
  AshMemoryStore,
} from '@3maem/ash-node';

ashInit();

const app = express();
const store = new AshMemoryStore();

app.use(express.json());

// Issue context endpoint
app.post('/ash/context', async (req, res) => {
  const { binding = 'POST /api/update', ttlMs = 30000 } = req.body;

  const context = await store.create({
    binding,
    ttlMs,
    mode: 'balanced',
    metadata: { userId: req.headers['x-user-id'] },
  });

  res.json({
    contextId: context.id,
    expiresAt: context.expiresAt,
    mode: context.mode,
  });
});

// Protected endpoints
app.post(
  '/api/update',
  ashExpressMiddleware({
    store,
    expectedBinding: 'POST /api/update',
  }),
  (req, res) => {
    // Access context metadata
    const ashContext = (req as any).ashContext;
    console.log('User ID:', ashContext.metadata?.userId);

    res.json({ status: 'success' });
  }
);

// Global protection for /api/* routes
app.use(
  '/api',
  ashExpressMiddleware({
    store,
    // Derive binding from request
    expectedBinding: undefined,
  })
);

// Cleanup expired contexts periodically
setInterval(async () => {
  const cleaned = await store.cleanup();
  console.log(`Cleaned up ${cleaned} expired contexts`);
}, 60000);

app.listen(3000, () => {
  console.log('ASH-protected server running on port 3000');
});

Error Codes

| Code | Description | |------|-------------| | MISSING_CONTEXT_ID | Missing X-ASH-Context-ID header | | MISSING_PROOF | Missing X-ASH-Proof header | | INVALID_CONTEXT | Invalid or expired context | | CONTEXT_EXPIRED | Context has expired | | CONTEXT_USED | Context already used (replay detected) | | BINDING_MISMATCH | Endpoint binding mismatch | | PROOF_MISMATCH | Proof verification failed | | CANONICALIZATION_FAILED | Failed to canonicalize payload |

License

ASH Source-Available License (ASAL-1.0)

See the LICENSE for full terms.

Links