
Security News
The Code You Didn't Write Is Still Yours to Defend
AI agents are pulling packages into environments no scanner is watching, creating exposure before security teams can see it.
@tabularium/manifest
Advanced tools
Pure validator + integrity primitives for the Tabularium plugin manifest (`.tabularium`, JSON).
@tabularium/manifestPure validator + integrity primitives for the Tabularium plugin registry. No Bun-specific APIs, no DB, no network — runs in Bun, Node ≥ 19, Deno, and the browser. Used by the registry server, the @tabularium/cli tool, and any consumer that wants to verify a release locally.
bun add @tabularium/manifest
# or
npm i @tabularium/manifest
import { parseManifest, validateManifest, buildSchema, fetchSchema, type ValidationError } from '@tabularium/manifest'
parseManifest(text) — JSON → object. Throws ParseError. Tabularium is JSON-only.validateManifest(parsed, schema, { lenient? }) — ajv-backed JSON Schema 2020-12 validator. Returns { ok: true, normalized, errors: [] } or { ok: false, normalized: null, errors }. lenient: true strips unknown fields via removeAdditional: 'all' instead of erroring.buildSchema(opts) — merges the core ManifestSchema with operator-defined extension deltas (global + per-kind) into a single JSON Schema.fetchSchema(url) — GET <url>/manifest.schema.json helper. Includes the response body in HTTP error messages.ValidationError shape:
type ValidationError = {
path: string // JSON Pointer, e.g. "/screenshots/0/url"
code: string // "type", "pattern", "required", "additionalProperties", ...
message: string // human-readable
expected?: unknown // schema constraint, e.g. "https?://.+"
actual?: unknown // truncated to ~200 chars
}
import { canonicalize, verifyAssetHash, verifyRegistrySignature } from '@tabularium/manifest'
canonicalize(value) — RFC 8785 JSON Canonicalisation Scheme. Lexicographic key order, no whitespace, RFC-compliant number serialisation, rejects NaN/Infinity/undefined. Used to compute deterministic bytes for signing/verification.verifyAssetHash(stream, expectedHex) — reads a ReadableStream<Uint8Array>, computes SHA-256, compares constant-time. Returns { ok, sha256, size }.verifyRegistrySignature({ payloadBytes, signature, publicKeyJwk }) — Web Crypto Ed25519 verify. Returns boolean.The package intentionally does not parse JWS or fetch JWKS — use a JOSE library (jose) for that and feed the raw bytes here. This keeps the dependency surface minimal.
import { parseManifest, validateManifest, fetchSchema } from '@tabularium/manifest'
const text = await Bun.file('./tabularium.yaml').text()
const schema = await fetchSchema('https://registry.example.com')
const parsed = parseManifest(text, 'tabularium.yaml')
const result = validateManifest(parsed, schema)
if (!result.ok) {
for (const e of result.errors) {
console.error(`${e.path} ${e.code} ${e.message}`)
}
process.exit(1)
}
import { verifyAssetHash } from '@tabularium/manifest'
const releaseRes = await fetch('https://registry.example.com/api/plugins/my-plugin')
const plugin = await releaseRes.json() as { releases: Array<{ version: string, integrity: { jws: string, assets: Array<{ name: string, sha256: string, size: number }> } | null }> }
const release = plugin.releases[0]
const expected = release.integrity!.assets.find(a => a.name === 'my-plugin.zip')!
const assetRes = await fetch('https://github.com/me/my-plugin/releases/download/.../my-plugin.zip')
const { ok, sha256, size } = await verifyAssetHash(assetRes.body!, expected.sha256)
if (!ok) throw new Error(`hash mismatch: got ${sha256}, expected ${expected.sha256}`)
if (size !== expected.size) throw new Error(`size mismatch`)
import { canonicalize, verifyRegistrySignature } from '@tabularium/manifest'
import { compactVerify, importJWK } from 'jose'
// 1. Pull the JWKS once and cache it; rotate if `kid` no longer matches.
const jwks = await (await fetch('https://registry.example.com/.well-known/registry-key.json')).json() as { keys: Array<{ kid: string, kty: string, crv: string, x: string, alg: string }> }
// 2. Pick the key matching the JWS protected-header `kid` (decode without verifying first).
const { jws } = release.integrity!
const headerB64 = jws.split('.')[0]
const { kid } = JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0))))
const jwk = jwks.keys.find(k => k.kid === kid)
if (!jwk) throw new Error(`unknown kid ${kid} — JWKS may be stale`)
// 3. Verify via jose.
const key = await importJWK(jwk, 'EdDSA')
const { payload, protectedHeader } = await compactVerify(jws, key)
const body = JSON.parse(new TextDecoder().decode(payload)) as { plugin_slug: string, release_version: string, assets: Array<{ name: string, sha256: string, size: number }> }
// 4. Cross-check: the JWS-signed assets[] must include the asset you're about to install with the same sha256.
const signedAsset = body.assets.find(a => a.name === 'my-plugin.zip')
if (!signedAsset || signedAsset.sha256 !== expected.sha256) throw new Error('signed payload does not match release JSON')
You now have three layers of trust:
attestation_bundle, verify it with a sigstore library to prove the GitHub Actions run that built it.This package follows the registry's release cycle. The ManifestSchema shape is versioned via the $schema URL and stays additive within a major version. Breaking changes to validation behaviour (lenient defaults, error codes) are called out in the changelog.
FAQs
Pure validator + integrity primitives for the Tabularium plugin manifest (`.tabularium`, JSON).
We found that @tabularium/manifest demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
AI agents are pulling packages into environments no scanner is watching, creating exposure before security teams can see it.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.