Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b819182
refactor: introduce generic tag and event schema
the-code-learner Aug 25, 2026
6139dbc
refactor: migrate legacy RFID data into generic tables
the-code-learner Aug 25, 2026
a5f917e
feat: add generic tag types
the-code-learner Aug 25, 2026
1fe5f77
feat: add NFC and RAIN security tag catalog
the-code-learner Aug 25, 2026
9c8d075
feat: normalize generic tag identifiers
the-code-learner Aug 25, 2026
b48d96e
refactor: generalize tag service across NFC and RAIN
the-code-learner Aug 25, 2026
6f38327
feat: add unified NFC and RAIN observation pipeline
the-code-learner Aug 25, 2026
4a5692a
feat: add AES-CMAC primitive for self-hosted verification
the-code-learner Aug 25, 2026
4c4bb8f
feat: verify NTAG 424 DNA SDM MACs self-hosted
the-code-learner Aug 25, 2026
8b66399
feat: verify NTAG 22x DNA SUN messages
the-code-learner Aug 25, 2026
a505888
feat: model replay authentication state
the-code-learner Aug 25, 2026
a85b8d3
feat: persist authentication counters for replay detection
the-code-learner Aug 25, 2026
11dfb2a
feat: persist verification counters in observations
the-code-learner Aug 25, 2026
7d36d32
feat: load verification keys from self-hosted secret storage
the-code-learner Aug 25, 2026
f894290
feat: track current and permanent tamper state
the-code-learner Aug 25, 2026
71cd46a
feat: persist permanent tamper state
the-code-learner Aug 25, 2026
e47f6ae
feat: capture current and permanent tamper evidence
the-code-learner Aug 25, 2026
4399c46
feat: add self-hosted tag verification profiles
the-code-learner Aug 25, 2026
fb72083
refactor: harden additive schema upgrades
the-code-learner Aug 25, 2026
75f56d0
refactor: manage generic tags from items service
the-code-learner Aug 25, 2026
0bc4adc
refactor: make inventory sessions tag-technology aware
the-code-learner Aug 25, 2026
6477a8a
refactor: add unified inventory and security reports
the-code-learner Aug 25, 2026
8bb22ac
refactor: expose unified tag observation and security APIs
the-code-learner Aug 25, 2026
45cdcaa
refactor: seed demo data for mixed NFC and RAIN tags
the-code-learner Aug 25, 2026
f25d0c7
test: cover AES-CMAC and NXP truncation
the-code-learner Aug 25, 2026
99aaa9c
test: verify NTAG 424 DNA official SDM vector
the-code-learner Aug 25, 2026
a05f918
test: cover NTAG 22x DNA SUN and tamper decoding
the-code-learner Aug 25, 2026
7bc3784
chore: reset version to 0.0.1 and add test script
the-code-learner Aug 25, 2026
dd856f5
ci: require tests and build before merge
the-code-learner Aug 25, 2026
7ee6524
docs: start changelog at 0.0.1
the-code-learner Aug 25, 2026
41284b0
feat: expose application domain through MCP stdio
the-code-learner Aug 25, 2026
a4e832c
feat: add MCP server command
the-code-learner Aug 25, 2026
cdf813b
refactor: align web client with unified tag APIs
the-code-learner Aug 25, 2026
418a16d
feat: add Web NFC reader adapter
the-code-learner Aug 25, 2026
75dc566
refactor: rebuild operational UI for NFC RAIN and security
the-code-learner Aug 25, 2026
b21b184
refactor: apply dense enterprise operational design system
the-code-learner Aug 25, 2026
a7f81a1
docs: document unified NFC RAIN security architecture
the-code-learner Aug 25, 2026
414bc5f
fix: type dynamic tag query values for node sqlite
the-code-learner Aug 25, 2026
6520e83
test: cover mixed NFC and RAIN inventory session
the-code-learner Aug 25, 2026
f2d3840
fix: type inventory session domain records
the-code-learner Aug 25, 2026
390d360
test: discover nested TypeScript test files portably
the-code-learner Aug 25, 2026
ee211d8
ci: gate high severity production dependency vulnerabilities
the-code-learner Aug 25, 2026
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
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
pull_request:
push:
branches:
- main
- "feat/**"

permissions:
contents: read

jobs:
test-build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Audit production dependencies
run: npm audit --omit=dev --audit-level=high

- name: Test
run: npm test

- name: Build
run: npm run build
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Changelog

All notable changes to Storage Tags will be documented in this file.

The project follows Semantic Versioning. Release tags are created only after the corresponding pull request has passed tests, been reviewed, and merged.

## [0.0.1] - Unreleased

### Added

- Unified tag domain for NFC and RAIN/UHF identities.
- Product/capability catalog for generic NFC and RAIN tags, NXP NTAG 223/224 DNA, StatusDetect, NTAG 424 DNA/TagTamper, Identiv DNA product families, NXP UCODE DNA/Track, and UCODE Guard.
- Mixed-tag inventory sessions so an item can carry both UHF and NFC identities.
- Immutable tag security/status events in addition to aggregated inventory observations.
- Self-hosted NTAG 22x DNA SUN verification with StatusDetect decoding.
- Self-hosted NTAG 424 DNA SDM verification with replay-counter tracking.
- Current and permanent/once-opened tamper state tracking.
- Security profiles that reference mounted secret keys without storing key material in application data.
- Reader-bridge abstraction for authenticated USB NFC and RAIN readers.
- Automated cryptographic tests and GitHub Actions test/build gate.

### Changed

- Replaced the EPC-first application model with technology-aware tag identifiers.
- Reset the development version baseline to `0.0.1`.
- Kept `/api/rfid/browser-read` only as a compatibility adapter over the generic observation pipeline.

### Security

- AES keys are resolved server-side from configured secret storage and are not exposed by REST/UI/MCP surfaces.
- SUN/SDM counters are tracked to distinguish a valid fresh message from a replayed valid message.
210 changes: 116 additions & 94 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,184 +1,206 @@
# Storage Tags

Storage Tags is a local browser-based UHF RFID inventory system. It is designed for the Scenario B architecture: the RFID reader is connected to the operator device, the browser captures EPC values, and the local backend stores normalized reads in SQLite.

## What It Does

- Registers RFID EPC tags and associates them with inventory items.
- Runs inventory sessions for boxes, shelves, or containers.
- Accepts browser HID keyboard RFID input as the first MVP reader mode.
- Deduplicates repeated reads per inventory session.
- Shows known items, unknown tags, read counts, and confidence labels.
- Lets operators register unknown tags, mark them as external, or ignore them.
- Saves browser station settings for RFID input devices.
- Creates local SQLite backups.
- Loads demo data for testing without a physical RFID reader.
- Exports saved session results as CSV.
Storage Tags is a self-hosted inventory and tag-authentication platform for mixed **RAIN/UHF RFID** and **NFC** workflows. It supports bulk UHF inventory, point NFC verification, tag security/tamper state, local SQLite storage, a browser UI, REST APIs, and an MCP stdio server.

Current development version: **0.0.1**.

## Core capabilities

- Register multiple tag identities against one inventory item, including both UHF EPCs and NFC UIDs.
- Run inventory sessions that aggregate RAIN/UHF reads and NFC point checks in the same audit context.
- Capture UHF readers that behave as keyboard/HID devices.
- Capture NDEF-compatible NFC tags through Web NFC where the browser supports it.
- Store immutable scan, verification, tamper, and sensor events alongside aggregated session observations.
- Perform self-hosted NTAG DNA SUN/SDM verification without a managed verification service.
- Detect replayed SUN/SDM messages by tracking verified counters.
- Track current and permanent/once-opened tamper state where the tag family exposes it.
- Reference AES keys through server-side secret files rather than storing key material in application data.
- Expose the application domain to MCP clients through a stdio MCP server.
- Keep a reader-bridge boundary for low-level NFC operations and authenticated RAIN readers.

## Tag families represented in the catalog

The capability catalog currently includes:

- generic NFC / NDEF;
- generic RAIN / UHF;
- NXP NTAG 223 DNA;
- NXP NTAG 224 DNA;
- NXP NTAG 223 / 224 DNA StatusDetect;
- NXP NTAG 424 DNA and NTAG 424 DNA TagTamper;
- Identiv NTAG 22x DNA portfolio entries;
- Identiv ID-NXP NTAG 424 DNA TT family;
- NXP UCODE DNA;
- NXP UCODE DNA Track;
- NXP UCODE Guard.

Catalog presence describes capabilities and product identity. Full cryptographic operation still depends on the actual tag configuration and, for low-level operations, a compatible reader.

## Architecture

The data path is:

```text
RFID Reader -> Browser -> Fastify API -> SQLite
RAIN HID reader ───────┐
├─> Browser UI ──> Fastify REST API ──> Domain services ──> SQLite
Android Web NFC ───────┘ │
├─> self-hosted SUN / SDM verification
Authenticated reader bridge ────────────────────┘

MCP client ──stdio──> Storage Tags MCP server ──> same domain services ──> SQLite
```

The backend does not need direct USB access to the reader. A low-cost USB-C, USB-A, OTG, or Bluetooth HID RFID reader can type EPC codes into the browser, and the frontend forwards those codes to the API.
The data model is technology-aware rather than EPC-only. A tag is keyed by `(technology, identifier)` and can carry transport-specific fields such as EPC, TID, or UID.

## Web NFC and reader bridge

Web NFC is used only for browser-accessible NDEF flows. It is suitable for reading dynamic SUN/SDM URLs on supported Android browsers, but it is not treated as a replacement for low-level NFC commands, protected-memory access, or mutual authentication.

Those operations, plus UCODE DNA authenticated challenge-response, belong behind the `reader-bridge` adapter. No USB NFC or authenticated RAIN reader model is hard-coded yet.

## Requirements

- Node.js 24 or newer
- npm
- Docker and Docker Compose, optional for containerized usage
- Linux is the primary server target
- Docker / Docker Compose optional

This project currently uses Node's built-in SQLite API, so no native npm SQLite package or Visual Studio C++ build tools are required for local development.
The intended deployment targets include conventional Linux hosts, Proxmox VMs/LXCs, and Docker.

## Install

```bash
npm install
```

## Run In Development
## Development

```bash
npm run dev
```

Development starts two services:
Development starts:

- API: `http://localhost:3000`
- Frontend: `http://localhost:5173`
- frontend: `http://localhost:5173`

The Vite frontend proxies `/api` requests to the Fastify backend.

## Build
## Test and build

```bash
npm test
npm run build
```

The frontend is built into `public/`, and the TypeScript backend is built into `dist/`.
Pull requests are expected to pass both commands in GitHub Actions before merge.

## Run The Production Build
## Production

```bash
npm run build
npm start
```

Then open:
Then open `http://localhost:3000`.

```text
http://localhost:3000
```

## Run With Docker
With Docker:

```bash
docker compose up --build
```

The app is exposed at:

```text
http://localhost:3000
```

SQLite data is stored in the local `data/` directory through the Docker volume mapping.

## Environment Variables
## Environment variables

| Variable | Default | Purpose |
| --- | --- | --- |
| `HOST` | `0.0.0.0` | Fastify bind host |
| `PORT` | `3000` | Fastify port |
| `DATABASE_URL` | `data/inventory.sqlite` | SQLite database file |
| `NODE_ENV` | `development` | Enables static frontend serving when set to `production` |
| `RFID_INPUT_MODE` | `browser` in Docker Compose | Documents the intended reader mode |
| `APP_BASE_URL` | `http://localhost:3000` in Docker Compose | Public base URL hint |

## Main Pages
| `NODE_ENV` | `development` | Runtime mode |
| `STORAGE_TAGS_KEYS_DIR` | `/run/secrets/storage-tags` | Base directory for server-side tag verification keys |

- `Dashboard`: review inventory activity, open sessions, and unknown tags.
- `Inventory`: start and stop container inventory sessions.
- `Register Tag`: scan an EPC and associate it with a new item.
- `Items`: create, edit, and archive inventory items.
- `Sessions`: reopen saved inventory sessions.
- `Reader`: test HID keyboard input, inspect raw input, and save station settings.
- `Reports`: review unknown tags and item last-seen data.
Verification profiles store a **key reference** such as `production/oil-sdm-file-read.key`. They do not store the AES key itself. A key file may contain either 16 raw bytes or 32 hexadecimal characters.

## RFID Reader Usage
## Main UI surfaces

1. Connect the RFID reader to the operator device.
2. Open the app in a browser.
3. Go to `Reader`, `Register Tag`, or `Inventory`.
4. Make sure the browser page is active.
5. Scan a tag.
6. The reader should type the EPC and usually submit it with Enter or Tab.
- **Dashboard** — operational metrics, security exceptions, reader readiness, recent immutable events.
- **Inventory** — mixed RAIN/NFC session capture and CSV export.
- **Tags** — generic tag registry, product family, item assignment, security-profile assignment.
- **Items** — inventory item lifecycle and linked-tag counts.
- **Security** — SUN/SDM/reader-bridge profiles, replay/tamper alerts, point verification.
- **Readers** — HID, Web NFC and reader-bridge station configuration.
- **Sessions** — session history with NFC/UHF/security metrics.
- **Reports** — unknown identifiers, last-seen data and immutable security event history.

For Android devices, the reader must be supported as USB OTG, Bluetooth HID, or another keyboard-like input device.
## REST API overview

## API Overview
### Items

- `GET /api/health`
- `GET /api/items`
- `POST /api/items`
- `GET /api/items/:id`
- `POST /api/items`
- `PUT /api/items/:id`
- `DELETE /api/items/:id`

### Tags and observations

- `GET /api/tag-catalog`
- `GET /api/tags`
- `GET /api/tags/:epc`
- `POST /api/tags/register`
- `PUT /api/tags/:epc/status`
- `GET /api/tags/:technology/:identifier`
- `POST /api/tags`
- `PUT /api/tags/:technology/:identifier/status`
- `POST /api/tags/mark-unknown`
- `POST /api/rfid/browser-read`
- `POST /api/rfid/batch-browser-read`
- `POST /api/observations`
- `POST /api/observations/batch`
- `GET /api/events`

`POST /api/rfid/browser-read` remains as a compatibility adapter for browser-HID UHF reads.

### Security

- `GET /api/security/profiles`
- `PUT /api/security/profiles/:profileKey`
- `POST /api/security/verify`

### Inventory and stations

- `GET /api/inventory-sessions`
- `POST /api/inventory-sessions`
- `GET /api/inventory-sessions/:sessionKey`
- `POST /api/inventory-sessions/:sessionKey/close`
- `GET /api/stations`
- `POST /api/stations`
- `GET /api/stations/:stationKey`
- `POST /api/stations`
- `PUT /api/stations/:stationKey`

### Reports and administration

- `GET /api/reports/dashboard`
- `GET /api/reports/unknown-tags`
- `GET /api/reports/items-last-seen`
- `GET /api/reports/security-alerts`
- `GET /api/reports/session/:sessionKey/csv`
- `POST /api/admin/backup`
- `POST /api/admin/seed-demo`

## Testing Without A Reader

1. Start the app.
2. Click `Load Demo Data`.
3. Open `Inventory`.
4. Start a session.
5. Use the demo scan buttons to simulate known and unknown RFID reads.
6. Open `Reports` to review unknown tags and last-seen item data.
## MCP server

The demo EPC values are:
After building the project:

```text
3034257BF7194E4000001A85
3034257BF7194E4000001A86
E2806894000040178F2A91B5
```bash
npm run mcp
```

## Backups
The MCP server communicates over stdio and exposes the same domain operations used by the REST/UI layers: dashboard summary, items, tags, observations, immutable events, security profiles and verification, sessions, stations, reports, backup, and demo seeding.

Click `Create Backup` in the sidebar or call:
Key material is never returned as an MCP result. Security tools expose only profile metadata and key references.

```bash
curl -X POST http://localhost:3000/api/admin/backup \
-H "Content-Type: application/json" \
-d "{}"
```
## Demo data

Backups are written under:
The demo seed contains non-secret inventory records with mixed UHF/NFC identities. It intentionally does not configure verification keys.

```text
data/backups/
```
## Development workflow

Feature work is developed on branches and submitted through pull requests. `CHANGELOG.md` is maintained from version `0.0.1`; release tags are created only after the corresponding pull request passes tests, is reviewed, and is merged.

## License

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
{
"name": "storage-tags",
"version": "0.1.0",
"version": "0.0.1",
"private": true,
"license": "SEE LICENSE IN LICENSE",
"type": "module",
"scripts": {
"dev": "concurrently \"npm:dev:api\" \"npm:dev:web\"",
"dev:api": "tsx watch src/server.ts",
"dev:web": "vite --host 0.0.0.0",
"test": "tsx --test \"src/**/*.test.ts\"",
"build": "tsc && vite build",
"start": "node dist/server.js"
"start": "node dist/server.js",
"mcp": "node dist/mcp-server.js"
},
"dependencies": {
"@fastify/cors": "^11.0.1",
Expand Down
Loading
Loading