// npm package
hbsig
[Documentation](../docs/docs/pages/api/hbsig.mdx)
versions
25
maintainers
1
license
MIT
first publish
2025-07-26
publisher
asteroiddao
tarball
994,877 B
AUTO-PUBLISHED·1 version indexed·latest published 2026-05-27
// publisher campaignby asteroiddao
9 caught packages from this accountThis is not an isolated catch. The same publisher has shipped 8 other packages that our pipeline flagged — the shape of a coordinated campaign, not a one-off. Each link below opens that sibling's analysis.
// offending code· @0.3.3· 3 files flagged
- @0.3.3··AUTO-PUBLISHED·publisher: asteroiddaoheuristic 75/100static flags 2llm skippedmature-packageosv-flagged:MAL-2026-5190base64-decodechild-process-spawn
// offending code· 3 files flaggedpatterns: 2
--- package/src/erl_json.js (excerpt) --- /** * Codec for converting between JS objects with Erlang types and annotated JSON * Only handles types that HyperBEAM supports */ /** * Convert annotated JSON to JS object with Erlang types * @param {Object} json - Annotated JSON object * @returns {*} - JS object with Buffer for binaries, Symbol for atoms */ export function erl_json_from(json) { return convertFromAnnotatedJSON(json) } /** * Convert JS object with Erlang types to annotated JSON * @param {*} jsObj - JS object with Erlang types * @returns {Object} - Annotated JSON object */ export function erl_json_to(jsObj) { return convertToAnnotatedJSON(jsObj) } /** * Normalize JS values to match what comes back from Erlang through erl_str_from * This function is deterministic and matches the behavior of the Erlang round-trip * @param {*} obj - JS object to normalize * @param {boolean} binaryMode - true for binary mode, false for string mode (default) * @returns {*} - Normalized JS object */ export function normalize(obj, binaryMode = false) { if (obj === null) return null if (obj === undefined) return undefined // Handle symbols - convert to their special cases or match erl_str_from behavior if (typeof obj === "symbol") { const key = Symbol.keyFor(obj) const name = key || obj.description || obj.toString().slice(7, -1) // Special symbols that become primitives if (name === "null") return null if (name === "true") return true if (name === "false") return false // --- package/src/erl_str.js (excerpt) --- /** * Erlang term string parser and formatter * Converts between Erlang term strings and JavaScript values */ /** * Parse an Erlang term string into JavaScript values * @param {string} str - Erlang term string * @param {boolean} binaryMode - If true, keep binaries as Buffers; if false, convert to strings * @returns {*} JavaScript value */ export function erl_str_from(str, binaryMode = false) { // Handle null/undefined input if (str === null || str === undefined) { return null } // Handle the new response format if (str.startsWith("#erl_response{")) { const rawMatch = str.match(/#erl_response\{raw=(.*?),formatted=(.*?)\}$/s) if (rawMatch && rawMatch[1] && rawMatch[2]) { const rawStr = rawMatch[1] const formattedStr = rawMatch[2] if (binaryMode) { // In binary mode, just parse formatted const parser = new ErlangParser(formattedStr, true) return parser.parse() } else { // Build a type map from raw, then parse formatted with type info const typeMap = buildTypeMap(rawStr) const parser = new TypeAwareParser(formattedStr, typeMap) return parser.parse() } } } // Fallback for non-response format const parser = new ErlangParser(str, binaryMode) return parser.parse() } /** * Parse body field specially - convert to string only for multipart bodies * @param {*} parsed - Parsed Erlang term object * @returns {*} Object with body field potentially converted --- package/src/id.js (excerpt) --- import { hash, hmac } from "fast-sha256" /** * Parse structured field dictionary format * Handles both complex format: name=(components);params * and simple format: name=:value: */ function parseStructuredFieldDictionary(input) { // Try complex format first const match = input.match(/([^=]+)=\((.*?)\);(.*)$/) if (match) { const name = match[1] const components = match[2].split(" ") const params = {} const paramPairs = match[3].split(";").filter(p => p) paramPairs.forEach(pair => { const [key, value] = pair.split("=") if (key && value) { params[key] = value.replace(/"/g, "") } }) return { name, components, params } } // Try simple format const simpleMatch = input.match(/([^=]+)=:([^:]+):/) if (simpleMatch) { return { name: simpleMatch[1], value: simpleMatch[2] } } return null } /** * Convert base64url string to base64 */ function base64urlToBase64(str) { return str.replace(/-/g, "+").replace(/_/g, "/") } /** * Generate commitment ID for RSA-PSS and ECDSA signatures * The ID is the SHA256 hash of the raw signature bytes * * @param {Object} commitment - The commitment object containing signature * @returns {string} The commitment ID in base64url format */ function rsaid(commitment) { // Extract the base64 signature from structured field format // Format: "signature-name=:BASE64_SIGNATURE:" const match = commitment.signature.match(/^[^=]+=:([^:]+):/) if (!match) { throw ne --- bundled output (OSV-MAL flagged — LLM scope expansion) --- --- src/http-message-signatures/structured-header.js (bundled) --- import { isInnerList, parseDictionary, parseItem, parseList, serializeDictionary, serializeInnerList, serializeItem, serializeList, } from "structured-headers" export class Dictionary { constructor(input) { this.raw = input this.parsed = parseDictionary(input) } toString() { return this.serialize() } serialize() { return serializeDictionary(this.parsed) } has(key) { return this.parsed.has(key) } get(key) { const value = this.parsed.get(key) if (!value) { return value } if (isInnerList(value)) { return serializeInnerList(value) } return serializeItem(value) } } export class List { constructor(input) { this.raw = input this.parsed = parseList(input) } toString() { return this.serialize() } serialize() { return serializeList(this.parsed) } } export class Item { constructor(input) { this.raw = input this.parsed = parseItem(input) } toString() { return this.serialize() } serialize() { return serializeItem(this.parsed) } } export function parseHeader(header) { const classes = [List, Dictionary, Item] for (let i = 0; i < classes.length; i++) { try { return new classes[i](header) } catch (e) { // noop } } throw new Error("Unable to parse header as structured field") } /** * This allows consumers of the library to supply field specifications that aren't * strictly "structured fields". Really a string must start with a `"` but that won't * tend to happen in our configs. * * @param {string} input * @returns {string} */ export function quoteString(input) { // if it's not quoted, attempt to quote if (!input.startsWith('"')) { // try to split the structured field const [name, ...rest] = input.split(";") // no params, just quote the whole thing if (!rest.length) { return `"${name}"` } // quote the first part and put the rest back as it was return --- dist/esm/bin_to_str.js (bundled) --- /** * Convert a Buffer to string if it contains valid UTF-8 text * @param {Buffer} buffer - Buffer to check and potentially convert * @returns {string|Buffer} - String if valid UTF-8, otherwise original Buffer */ export default function bin_to_str(buffer) { if (!Buffer.isBuffer(buffer)) { return buffer } // Empty buffer stays as buffer if (buffer.length === 0) { return buffer } try { const str = buffer.toString("utf8") // Check if it's valid UTF-8 by seeing if it round-trips correctly if (Buffer.from(str, "utf8").equals(buffer)) { // Additional check: ensure all characters are printable or common whitespace // This prevents converting binary data that happens to be valid UTF-8 let isPrintable = true for (let i = 0; i < str.length; i++) { const code = str.charCodeAt(i) // Allow printable ASCII (32-126) and common whitespace (tab, newline, carriage return) if ( !(code >= 32 && code <= 126) && code !== 9 && code !== 10 && code !== 13 ) { isPrintable = false break } } if (isPrintable) { return str } } } catch (e) { // Not valid UTF-8, return original buffer } return buffer } --- dist/esm/collect-body-keys.js (bundled) --- import { hasNonAscii, sha256, hasNewline, isBytes, isPojo, } from "./encode-utils.js" // Helper functions const isEmpty = value => { if (typeof value === "string") return value === "" if (Array.isArray(value)) return value.length === 0 if (isPojo(value)) return Object.keys(value).length === 0 if (isBytes(value)) return value.length === 0 || value.byteLength === 0 return false } const hasOnlyEmptyValues = obj => { if (!isPojo(obj)) return false return Object.values(obj).every( v => (typeof v === "string" && v === "") || (Array.isArray(v) && v.length === 0) || (isPojo(v) && Object.keys(v).length === 0) ) } const isSimpleValue = value => { return ( typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null || value === undefined || typeof value === "symbol" ) } const getValueByPath = (obj, path) => { const parts = path.split("/") let value = obj for (const part of parts) { if (/^\d+$/.test(part)) { value = value[parseInt(part) - 1] } else { value = value[part] } } return value } const canArrayBeInHeader = array => { // Empty arrays can be in headers if (array.length === 0) return true // Arrays with objects must go to body if (array.some(item => isPojo(item))) return false // Arrays with binary data must go to body if (array.some(item => isBytes(item) && item.length > 0)) return false // Arrays with non-ASCII strings must go to body if (array.some(item => typeof item === "string" && hasNonAscii(item))) return false // Arrays with nested arrays must go to body (to match original behavior) if (array.some(item => Array.isArray(item))) return false // Arrays with nested arrays that have objects must go to body if ( array.some( item => Array.isArray(item) && item.some(subItem => isPojo(subItem)) ) ) return false // Simple arrays of primitives can stay in headers --- dist/esm/commit.js (bundled) --- import { id, base, hashpath, rsaid } from "./id.js" import { toAddr } from "./utils.js" import { extractPubKey } from "./signer-utils.js" import { verify } from "./signer-utils.js" import crypto from "crypto" // Helper to compute SHA-256 content-digest in RFC 9530 format const computeContentDigest = (body) => { let bodyBuffer if (Buffer.isBuffer(body)) { bodyBuffer = body } else if (body instanceof Blob) { return null // Can't compute synchronously for Blob } else if (typeof body === "string") { bodyBuffer = Buffer.from(body, "binary") } else { bodyBuffer = Buffer.from(String(body), "binary") } const hash = crypto.createHash("sha256").update(bodyBuffer).digest("base64") return `sha-256=:${hash}:` } // Helper to build ao-types string from an object const buildAoTypes = (obj) => { const types = [] for (const [key, value] of Object.entries(obj)) { if (typeof value === "number") { types.push(`${key}="${Number.isInteger(value) ? "integer" : "float"}"`) } else if (typeof value === "boolean") { types.push(`${key}="atom"`) } else if (value === null) { types.push(`${key}="atom"`) } else if (typeof value === "symbol") { // Symbols are Erlang atoms types.push(`${key}="atom"`) } } return types.length > 0 ? types.join(", ") : null } // todo: handle @ export const commit = async (obj, opts) => { const msg = await opts.signer(obj, opts) const { decodedSignatureInput: { components }, } = await verify(msg) let body = {} // Check for inline-body-key (indicates a field was moved to HTTP body during encoding) const inlineBodyKey = msg.headers["inline-body-key"] || msg.headers["ao-body-key"] // Body field names that HyperBEAM's inline_key() recognizes natively. // For these, normalize_for_encoding() re-derives ao-body-key automatically. // For custom names (e.g., "json"), we must keep ao-body-key in committed list // so HyperBEAM knows which field to inline during veri --- dist/esm/encode-array-item.js (bundled) --- import { toBuffer, formatFloat, isBytes, isPojo } from "./encode-utils.js" // Helper to generate the correct number of backslashes for a given nesting level function getBackslashes(level) { return "\\".repeat(Math.pow(2, level) - 1) } // Helper to encode primitive values at a specific nesting level function encodePrimitiveAtLevel(value, level) { const bs = getBackslashes(level) if (typeof value === "number") { if (Number.isInteger(value)) { ret
