Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[test/data/*]
insert_final_newline = unset
trim_trailing_whitespace = unset
44 changes: 38 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,51 @@ on:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
test:
name: Test (Node.js ${{ matrix.node }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [18, 20, 22]
node: [20, 22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
node-version: ${{ matrix.node }}
cache: npm
- run: npm install
- run: npm ci
- run: npm run typecheck
- run: npm run lint
- run: npm test -- --coverage
- run: npm run build
- run: npm run test:package
- run: npm run test:runtime
- run: npm run test:example

runtime-floor:
name: Runtime (Node.js 18)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
# The development tools need Node.js 20+; the package itself supports Node.js 18.
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- run: npm test
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 18
- run: node --version
- run: npm run test:runtime
- run: npm run test:example
29 changes: 0 additions & 29 deletions .github/workflows/publish.yml

This file was deleted.

76 changes: 76 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Release

# Publishes @shieldlabs-ai/node to npm when a maintainer pushes a tag such as v1.0.0.
# The first job checks and packs the package with read-only permissions; the second publishes
# that tarball with provenance (environment "npm", NPM_TOKEN secret). Safe to re-run: a version
# that is already on npm is skipped.
on:
push:
tags: ['v*']

permissions:
contents: read

jobs:
build:
name: Check and pack
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
cache: npm
- run: npm ci
- name: Check that the tag matches package.json
id: version
run: |
version="$(node -p "require('./package.json').version")"
if [ "v${version}" != "${GITHUB_REF_NAME}" ]; then
echo "::error::The tag does not match the package.json version ${version}."
exit 1
fi
echo "version=${version}" >> "$GITHUB_OUTPUT"
- run: npm run typecheck
- run: npm run lint
- run: npm test
- run: npm run build
- run: npm run test:package
- run: npm run test:runtime
- run: npm pack
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: package
path: shieldlabs-ai-node-${{ steps.version.outputs.version }}.tgz
if-no-files-found: error

publish:
name: Publish to npm
needs: build
runs-on: ubuntu-latest
environment: npm
permissions:
contents: read
id-token: write # npm provenance
steps:
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: package
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
registry-url: https://registry.npmjs.org
- name: Publish unless this version is already on npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
VERSION: ${{ needs.build.outputs.version }}
run: |
if [ -n "$(npm view "@shieldlabs-ai/node@${VERSION}" version 2>/dev/null)" ]; then
echo "@shieldlabs-ai/node@${VERSION} is already on npm: nothing to publish."
exit 0
fi
npm publish "shieldlabs-ai-node-${VERSION}.tgz" --provenance --access public
9 changes: 4 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
node_modules/
dist/
build/
*.log
coverage/
*.tgz
*.log
.env
.env.*
.DS_Store
coverage/
.turbo/

.history
.vscode/
.idea/
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dist/
coverage/
node_modules/
test/data/
*.md
package-lock.json
5 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all"
}
71 changes: 69 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,72 @@
# Changelog

## 2026-09-06
All notable changes to this package are documented in this file. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the package uses
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- Minor improvements and bug fixes
## [Unreleased]

## [1.0.0] - 2026-09-30

### Added

- `ShieldLabs` History API client: `identifications.get` with wait-for-verdict polling,
`history.search`, and `history.iterate` with deduplication on `request_id`.
- `identifications.get` waits within one total `timeout` budget (default 10 s): the first poll
runs at once, then after 250 ms, 500 ms, 1 s, 1.5 s and every 2 s, and the last poll runs at
the deadline. A custom `pollInterval` p gives waits of p, 2p, 4p, 6p and then 8p, each capped
at 2 s or at p when p is longer, so 1 s waits 1 s and then every 2 s, and 3 s polls every 3 s.
Each poll is one HTTP attempt without retries; its timeout is the client timeout, shortened to
the time left but at least 1 s. A 429, a 5xx, a connection error or a timeout keeps it
polling. After a 429 the next wait is the longest of the scheduled wait, 1 s and its
`Retry-After` capped at 10 s (a missing `Retry-After`, `0` or a past date counts as 0), cut
short at the deadline; a capped `Retry-After` longer than the time left is thrown at once. At
the deadline the error of the last poll is thrown, or `null` is returned when that poll found
nothing. 400, 401, 403 and 404 are thrown at once.
- Client-side validation of lookup types, UUIDs, IPv4 addresses, `limit` and `offset`: invalid
arguments throw `ValidationError` and send nothing.
- User HID lookups are sent in canonical path form (`@`, `+`, `=`, `:` and similar characters
stay as they are), so such values match. A User HID that contains `/`, or is `.` or `..`,
throws `ValidationError`, because the History API cannot search it.
- `ShieldLabsManagement` client with `getProfile()`, domain normalization and no retries on 429.
- Keys, secrets and domains are checked when a client is created: a value with characters that
cannot be sent in an HTTP header (line breaks, NUL, spaces, non-ASCII) throws
`ValidationError`, and no error message or cause repeats a key or secret. `ConnectionError`
messages name the network cause, for example `connect ECONNREFUSED`.
- `webhooks.verifySignature` and `webhooks.constructEvent`, plus `verifySignatureAsync` and
`constructEventAsync` for every runtime. One secret or a list of secrets for rotation. Typed
`IdentificationScoredEvent`, `WebhookPingEvent` and `UnknownWebhookEvent`.
- One `Identification` model for webhook deliveries and History rows: 19 detection flags, weighted
risk signals and `observed_at` as RFC 3339 UTC with milliseconds.
- `evaluateIdentification`, `riskBand`, `isRateLimited`, `userHid`, `userHidAsync`, `SIGNALS`,
`RISK_BANDS`, `NIL_UUID` and `VERSION`. `evaluateIdentification` with `maxAge: Infinity` skips
the freshness check.
- Base URLs must use https. Plain http is accepted for `localhost`, `127.0.0.1` and `[::1]`, and
for other hosts only with `allowInsecureHttp: true`, so a mistyped URL never sends a key
unencrypted.
- Error hierarchy under `ShieldLabsError`, including `QuotaExceededError` (402); retries with
jittered exponential backoff and `Retry-After`; per-attempt timeouts; `AbortSignal` cancellation.
- Edge build for workers, edge runtimes, Deno and browsers, selected by export conditions. It never
loads `node:crypto`. A client created in a web page prints a one-time warning, because every
visitor could read its key there.
- ESM and CommonJS builds with TypeScript declarations, and the `examples/node-http` app.

### Changed

- History requests go to `https://account.shieldlabs.ai/api/v1/history/...`. The preview doubled
the `/api` prefix (`/api/api/v1/...`) and got 404 responses. A base URL that ends in `/api` is
now normalized.

### Removed

- The preview `ShieldLabsClient` and `verifyWebhook` exports, replaced by `ShieldLabs` and
`webhooks.verifySignature`.

## [0.1.0] - 2026-09-06

### Added

- Preview release: `verifyWebhook`, webhook payload types and a basic History API client.

[Unreleased]: https://github.com/ShieldLabs-ai/shieldlabs-node/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/ShieldLabs-ai/shieldlabs-node/releases/tag/v1.0.0
49 changes: 49 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Contributing

Thank you for helping improve `@shieldlabs-ai/node`.

## Set up

Development needs Node.js 20 or later (the published package supports Node.js 18 and later).

```bash
npm ci
```

## Checks

Every change must pass these before it is merged:

```bash
npm run typecheck
npm run lint
npm test -- --coverage # coverage must stay at or above 90%
npm run build
npm run test:package # export conditions, edge bundles, published files
npm run test:runtime # the built package in the Node.js and edge entries
npm run test:example # examples/node-http end to end
```

## Guidelines

- Keep the package free of runtime dependencies.
- Only `src/index.ts` may import `node:crypto`. Everything shared by both entries (`src/common.ts`
and what it imports) must run on `fetch` and WebCrypto alone; `npm run test:package` checks it.
- `test/data` holds shared test fixtures that every ShieldLabs server SDK passes. Do not edit
them here; add new cases in a separate file.
- Changes to public behavior need tests and an entry under `Unreleased` in `CHANGELOG.md`.
- Commit messages follow Conventional Commits (`feat:`, `fix:`, `docs:`, `test:`, `ci:`, `chore:`).
- Documentation is plain technical English. Say "risk signals" and use the three risk bands:
trusted 0-29, suspicious 30-59, dangerous 60-100.

## Releases

A maintainer bumps the version in `package.json` and `src/version.ts`, updates `CHANGELOG.md`, and
pushes a tag `vX.Y.Z`. The release workflow checks and packs the package with read-only
permissions, then publishes that tarball to npm with provenance (GitHub environment `npm`,
`NPM_TOKEN` secret). Re-running it skips a version that is already on npm.

## Security

Report vulnerabilities privately to [contact@shieldlabs.ai](mailto:contact@shieldlabs.ai) instead
of opening a public issue.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 ShieldLabs
Copyright (c) 2026 ShieldLabs Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
21 changes: 0 additions & 21 deletions PUBLISHING.md

This file was deleted.

Loading
Loading