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
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ release/stable/v*/artifacts/ios/Package.swift text eol=lf
release/stable/v*/artifacts/native/scanly-core.h text eol=lf
release/stable/v*/artifacts/android/*.aar binary
release/stable/v*/artifacts/npm/*.tgz binary
release/stable/v2.1.0/evidence/** text eol=lf
release/stable/v2.1.0/artifacts/provenance/*.json text eol=lf
device-lab/test-targets/sha256.json text eol=lf
fixtures/alpha5/external-open-license/pdf417/ZXING-APACHE-2.0.txt text eol=lf
56 changes: 54 additions & 2 deletions .github/workflows/stable-npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ on:
description: Exact signed Stable SemVer tag to publish (for example v2.0.1)
required: true
type: string
qualify_only:
description: Verify existing OIDC grants without publishing, modifying packages, or creating tags
required: false
type: boolean
default: false

permissions:
contents: read
Expand All @@ -19,13 +24,35 @@ concurrency:
cancel-in-progress: false

jobs:
credential-qualification:
name: Verify existing npm OIDC grants (no publication)
if: github.event_name == 'workflow_dispatch' && inputs.qualify_only
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v7
with:
node-version: 24
- name: Exchange existing trusted identities without publishing
run: node scripts/probe-stable-npm-oidc.mjs
- uses: actions/upload-artifact@v4
with:
name: npm-oidc-qualification
path: release-dist/npm-oidc-qualification.json
if-no-files-found: error
retention-days: 90
publish:
name: Publish npm packages with provenance
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.release.prerelease == false && github.event.release.draft == false)
(github.event_name == 'workflow_dispatch' && !inputs.qualify_only) ||
(github.event_name == 'release' && github.event.release.prerelease == false && github.event.release.draft == false)
runs-on: ubuntu-latest
steps:
- name: Assert publication mode before any release checkout
env:
QUALIFY_ONLY: ${{ inputs.qualify_only }}
run: test "$QUALIFY_ONLY" != "true"
- uses: actions/checkout@v4
with:
fetch-depth: 0
Expand Down Expand Up @@ -132,7 +159,21 @@ jobs:
while IFS=$'\t' read -r package tarball; do
test -n "$package" -a -f "$tarball"
published="$(npm view "${package}@${VERSION}" version --json 2>/dev/null | tr -d '"' || true)"
if [ "$VERSION" = "2.1.0" ] && [ "$package" = "@scanly/url-safety" ] && [ "$published" != "$VERSION" ]; then
node --input-type=commonjs - "$STABLE_RELEASE_ROOT" <<'BOOTSTRAP'
const fs = require('node:fs');
const path = require('node:path');
const credentials = JSON.parse(fs.readFileSync(path.join(process.argv[2], 'publication-credentials.json'), 'utf8'));
const bootstrap = credentials.channels.npmRegistry.evidence.bootstrap;
if (bootstrap?.package !== '@scanly/url-safety' || bootstrap.version !== '2.1.0' || bootstrap.method !== 'INTERACTIVE_NPM_PROVENANCE_FILE' || !bootstrap.interactiveTwoFactorRequired || !bootstrap.signedReleaseTagRequired || !bootstrap.provenanceVerified) throw new Error('No qualified first-publication bootstrap');
fs.writeFileSync('stable-npm-bootstrap-pending.txt', '@scanly/url-safety@2.1.0 requires interactive publication with its qualified CI provenance bundle. No release completion is claimed.\n');
BOOTSTRAP
echo "Deferring the explicitly qualified new package; publishing its dependencies first."
continue
fi
if [ "$published" = "$VERSION" ]; then
expected_integrity="$(node -e 'const fs=require("fs"),c=require("crypto"); process.stdout.write("sha512-"+c.createHash("sha512").update(fs.readFileSync(process.argv[1])).digest("base64"))' "$tarball")"
test "$(npm view "${package}@${VERSION}" dist.integrity --json | tr -d '"')" = "$expected_integrity"
echo "${package}@${VERSION} already exists; verifying and continuing"
elif [ "$VERSION" = "2.0.0" ]; then
# Legacy v2.0.0 recovery only: immutable tarballs predate npm's
Expand Down Expand Up @@ -187,6 +228,7 @@ jobs:
for attempt in {1..60}; do
pending=0
while IFS=$'\t' read -r package tarball; do
if [ -f stable-npm-bootstrap-pending.txt ] && [ "$package" = "@scanly/url-safety" ]; then continue; fi
verified="$(npm view "${package}@${VERSION}" version --json 2>/dev/null | tr -d '"' || true)"
if [ "$verified" != "$VERSION" ]; then
echo "Waiting for Registry propagation: ${package}@${VERSION} (${attempt}/60)"
Expand All @@ -199,6 +241,16 @@ jobs:
test "$pending" -eq 0

while IFS=$'\t' read -r package tarball; do
if [ -f stable-npm-bootstrap-pending.txt ] && [ "$package" = "@scanly/url-safety" ]; then continue; fi
test "$(npm view "$package" dist-tags.latest --json | tr -d '"')" = "$VERSION"
test "$(npm view "${package}@${VERSION}" dist.attestations.provenance.predicateType --json | tr -d '"')" = "https://slsa.dev/provenance/v1"
done < stable-npm-publication-plan.tsv
if [ -f stable-npm-bootstrap-pending.txt ]; then
echo "::notice title=Publication incomplete::Existing packages are verified; the new URL Safety package still requires its approved interactive bootstrap."
fi
- uses: actions/upload-artifact@v4
if: always()
with:
name: stable-npm-bootstrap-status
path: stable-npm-bootstrap-pending.txt
if-no-files-found: ignore
81 changes: 81 additions & 0 deletions .github/workflows/v2.1-artifact-qualification.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: v2.1 Artifact Qualification

on:
pull_request:
branches: [develop]
paths:
- .github/workflows/v2.1-artifact-qualification.yml
- scripts/prepare-v2.1-npm-artifacts.mjs
workflow_dispatch:
inputs:
source_commit:
description: Exact qualified develop product source (40-character commit)
required: true
type: string

permissions:
contents: read
id-token: write

jobs:
clean-build:
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
name: Clean npm build ${{ matrix.build }}
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
build: [A, B]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.sha }}
path: tools
fetch-depth: 0
- uses: actions/checkout@v4
with:
ref: ${{ inputs.source_commit || github.event.pull_request.base.sha }}
path: product
fetch-depth: 0
- uses: actions/setup-node@v7
with:
node-version: 24
- name: Install pinned npm
run: npm install --global npm@11.12.1
- name: Verify source identity and install without dependency cache
working-directory: product
env:
SOURCE_COMMIT: ${{ inputs.source_commit || github.event.pull_request.base.sha }}
run: |
set -euo pipefail
[[ "$SOURCE_COMMIT" =~ ^[a-f0-9]{40}$ ]]
test "$(git rev-parse HEAD)" = "$SOURCE_COMMIT"
test -z "${NODE_AUTH_TOKEN:-}"
test -z "${NPM_TOKEN:-}"
npm ci
- name: Build and verify public packages
working-directory: product
run: |
npm run build:packages
npm run api:snapshot
npm run native:api
npm run native:abi
npm run packages:smoke
npm run packages:tarball
npm run url-safety:security
git diff --exit-code -- packages engines package-lock.json api-snapshots
- name: Pack exact artifacts and sign detached provenance (no publication)
working-directory: product
env:
SOURCE_COMMIT: ${{ inputs.source_commit || github.event.pull_request.base.sha }}
BUILD_LABEL: ${{ matrix.build }}
run: |
export NPM_CLI_PATH="$(readlink -f "$(command -v npm)")"
node ../tools/scripts/prepare-v2.1-npm-artifacts.mjs --root=. --output=../artifacts
- uses: actions/upload-artifact@v4
with:
name: v2.1-npm-${{ matrix.build }}
path: artifacts
if-no-files-found: error
retention-days: 90
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

All notable changes follow semantic versioning.

## Unreleased

## 2.1.0 — Link Intelligence (development; qualification pending)
## 2.1.0 — Link Intelligence

- Added optional `@scanly/url-safety` with local URL signals, canonicalization, redaction, cache, cancellation, confirmed-event integration and deterministic scoring.
- Added a Node-only server entry with pinned public-IP connections, per-hop SSRF validation, bounded inspection, reputation lookup adapters and schema-validated advisory LLM evidence.
- Added explicit privacy modes and a default-disabled demo, with a same-origin rate/size/time-limited backend.
- Existing barcode decoding remains local and independent of network analysis. Published 2.0.0 and 2.0.1 artifacts and historical evidence are unchanged.
- Release status and unresolved qualification requirements are recorded in `docs/releases/v2.1.0.md`; this entry does not assert publication.
- Added source-bound independent npm and Native artifact rebuilds, detached CI provenance for the new package's 2FA-protected first publication, and immutable versioned qualification records. Actual publication is verified separately from qualification.

## 2.0.1 — Scanner lifecycle and runtime hardening

Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Scanly SDK v2.1.0 — Link Intelligence (development)
# Scanly SDK v2.1.0 — Link Intelligence

Privacy-first, local-only barcode scanning SDK for Web, Node.js, React, iOS, and Android.

[![SDK 2.0.1](https://img.shields.io/badge/SDK-2.0.1-green)](https://github.com/Yangjunjie-Lin/Scanly/releases/tag/v2.0.1)
[![Source 2.1.0](https://img.shields.io/badge/SDK-2.1.0-green)](docs/releases/v2.1.0.md)
[![SDK 2.1.0](https://img.shields.io/badge/SDK-2.1.0-green)](docs/releases/v2.1.0.md)
[![npm latest](https://img.shields.io/badge/npm-latest-CB3837)](https://www.npmjs.com/package/@scanly/browser)
[![TypeScript](https://img.shields.io/badge/TypeScript-5-blue)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
Expand All @@ -14,7 +13,7 @@ Privacy-first, local-only barcode scanning SDK for Web, Node.js, React, iOS, and

## Install

This branch develops 2.1.0. The published Stable release remains 2.0.1 until the new release qualification and publication gates pass.
The 2.1.0 release line is software-qualified. Registry publication is finalized separately through the signed release workflow; see the [release notes](docs/releases/v2.1.0.md) and versioned release records below.

Choose the package for your runtime. Browser, Node, and React packages already include their required Scanly workspace dependencies.

Expand Down Expand Up @@ -50,8 +49,8 @@ Advanced engine composition can use `@scanly/core` directly:
npm install @scanly/core
```

- **iOS:** v2.0.1 ships a Swift Package Manager source package under `native/ios`. Because the manifest is not at the repository root, check out tag `v2.0.1` and add `native/ios` as a local package. See the [iOS guide](docs/native/ios-getting-started.md).
- **Android:** download `scanly-sdk-2.0.1.aar` from the [v2.0.1 GitHub Release](https://github.com/Yangjunjie-Lin/Scanly/releases/tag/v2.0.1). Maven Central is not a v2.0.1 distribution channel. See the [Android guide](docs/native/android-getting-started.md).
- **iOS:** v2.1.0 provides a Swift Package Manager source package under `native/ios`. Because the manifest is not at the repository root, check out tag `v2.1.0` and add `native/ios` as a local package. See the [iOS guide](docs/native/ios-getting-started.md).
- **Android:** use `scanly-sdk-2.1.0.aar` from the [v2.1.0 GitHub Release](https://github.com/Yangjunjie-Lin/Scanly/releases/tag/v2.1.0). Maven Central is not a v2.1.0 distribution channel. See the [Android guide](docs/native/android-getting-started.md).

## Quick start

Expand Down Expand Up @@ -149,19 +148,19 @@ Industrial recovery is not an industrial, warehouse, or DPM certification. The `

## Privacy

Scanly decodes locally and remains offline-capable after code and WASM assets are loaded. The SDK does not upload images or payloads and contains no analytics, remote logging, account, or cloud-decoder service. Host applications remain responsible for their own telemetry, storage, and privacy disclosures.
Barcode decoding remains local and offline-capable after code and WASM assets are loaded. No scanning image or camera frame is uploaded. The separate URL intelligence module is disabled by default and shares decoded URLs or sanitized page evidence only after explicit opt-in. There is no built-in analytics or remote logging. Host applications remain responsible for their own telemetry, storage, service configuration and privacy disclosures.

## Platform status

v2.0.1 is the current Stable software line. It hardens cancellation, React concurrency, camera error handling, and the terminal lifecycle of `ScannerSession`. Automated browser, Node, simulator, emulator, API/ABI, security, package, and artifact checks are verified. Automated coverage is not physical-device qualification.
v2.1.0 adds opt-in Link Intelligence on top of the v2.0.1 cancellation, React concurrency, camera error handling and terminal `ScannerSession` lifecycle guarantees. Automated browser, Node, simulator, emulator, API/ABI, security, package and artifact checks are verified. Automated coverage is not physical-device qualification.

Physical Web iOS/Android, Native iOS/Android, 30-minute and 60-minute camera soaks, and the complete hardware matrix remain `POST_RELEASE_VALIDATION_PENDING` under [Issue #13](https://github.com/Yangjunjie-Lin/Scanly/issues/13). No all-device-verified claim is made.

Known limitations include hardware-dependent camera capabilities, difficult or occluded symbols, experimental DPM recovery, unsupported Android `armeabi-v7a`, no Maven Central or CocoaPods distribution for v2.0.1, and the pending physical-device matrix.
Known limitations include hardware-dependent camera capabilities, difficult or occluded symbols, experimental DPM recovery, unsupported Android `armeabi-v7a`, no Maven Central or CocoaPods distribution for v2.1.0, and the pending physical-device matrix. URL risk verdicts are advisory observations, not safety guarantees.

## Branch and release status

- `main` contains the current v2.0.1 Stable line.
- `main` is the Stable release branch; immutable versioned publication records identify completed releases.
- `develop` is the integration branch for post-v2 maintenance and the next patch/minor line.
- Feature and fix branches start from `develop`.
- Immutable RC/Stable tags and versioned evidence preserve qualification ancestry. Only `main` and `develop` are retained as long-lived branches.
Expand All @@ -180,6 +179,10 @@ Detailed Alpha, Beta, and RC development evidence is preserved in [development h

## Release integrity

- [v2.1.0 release notes](docs/releases/v2.1.0.md)
- [v2.1.0 qualification manifest](release/stable/v2.1.0/v2.1.0-manifest.json)
- [v2.1.0 checksums](release/stable/v2.1.0/checksums.sha256)
- [v2.1.0 SBOM](release/stable/v2.1.0/sbom.cdx.json)
- [v2.0.1 release notes](docs/releases/v2.0.1.md)
- [v2.0.1 qualification manifest](release/stable/v2.0.1/v2.0.1-manifest.json)
- [v2.0.1 checksums](release/stable/v2.0.1/checksums.sha256)
Expand Down
6 changes: 4 additions & 2 deletions docs/releases/v2.1.0.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Scanly SDK v2.1.0

Status: development candidate; qualification and publication pending. Published Stable remains v2.0.1. This document is not a publication record.
Status: release-qualified candidate. Registry publication and post-publication verification are tracked separately; this document is not a publication record.

## New: Link Intelligence

Expand Down Expand Up @@ -28,4 +28,6 @@ Existing v2 scanner APIs remain compatible. URL intelligence is an independent,

All software, browser, API/package, security, Native, SBOM/license, artifact/reproducibility, signing and publication gates must pass on the final release identity. Feature-branch test results are development evidence only. See [URL safety architecture](../url-safety.md) and the candidate qualification record when available. Issue #13 remains independent.

The Stable manifest generator refuses new-version generation without `STABLE_QUALIFICATION_RECORD`: an exact-source `scanly-release-qualification-1` record with version, sourceCommit, sourceTree and content-addressed PASS evidence (`path`, `sha256`, `sourceCommit`) for software, apiAbi, security, sbom, licenses, native, npmReproducibility, signing, publicationCredentials and deployment. Fresh public signing metadata, 11-package Trusted Publishing configuration and two independent npm build/hash maps are required. Legacy 2.0.x GO/credential/reproducibility claims cannot be inherited.
The Stable manifest generator refuses new-version generation without `STABLE_QUALIFICATION_RECORD`: an exact-source `scanly-release-qualification-1` record with version, sourceCommit, sourceTree and content-addressed PASS evidence (`path`, `sha256`, `sourceCommit`) for software, apiAbi, security, sbom, licenses, native, npmReproducibility, signing, publicationCredentials and deployment. Fresh public signing metadata and two independent npm build/hash maps are required. Legacy 2.0.x GO/credential/reproducibility claims cannot be inherited.

For the new `@scanly/url-safety` package only, the qualified first-publication path uses npm's official `--provenance-file` support: exact release tarball bytes carry a GitHub-hosted build's verified Sigstore provenance, while an authenticated owner completes npm's interactive 2FA locally. No npm credential is sent to CI, no placeholder version is published, and no provenance or signing requirement is waived. Existing packages continue using their verified OIDC workflow. Dependencies publish before the new package; a deferred bootstrap is reported as incomplete, never as full release success. The new package's Trusted Publisher can be initialized after its first genuine release.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"rc:manifest:verify": "node scripts/verify-release-manifest.mjs",
"stable:manifest:verify": "node scripts/verify-stable-manifest.mjs",
"stable:publication:verify": "node scripts/verify-stable-publication-record.mjs",
"stable:bootstrap": "node scripts/publish-v2.1-bootstrap.mjs",
"stable:manifest:generate": "node scripts/generate-stable-release-manifest.mjs",
"stable:android:record": "node scripts/record-stable-android-artifact.mjs",
"stable:reproducibility:confirm": "node scripts/confirm-stable-metadata-reproducibility.mjs",
Expand Down
Loading
Loading