vc-expiry-watch
Monitor issued Verifiable Credentials for expiry, structural drift,
and revocation, entirely offline.
Verifiable Credentials rarely carry a live pipeline that watches them
after issuance. Wallets, verifiers, and back-office systems accumulate
JSON-LD and JWT-VC credentials whose expirationDate quietly slips into
the past, or whose issuer revokes them via a status list nobody is
polling. vc-expiry-watch is a small, offline-first CLI and library
that ingests local Verifiable Credential files and reports:
- Expiry status — expired, expiring soon (configurable window), or valid
- Structural validity — required VC fields present and well-formed
- Revocation status — checked against a locally-provided revocation or status-list fixture (no live network calls required)
Usage
vc-expiry-watch <path> [options]
node bin/vc-expiry-watch.js examples/credentials --recursive --revocation-list examples/demo-revocation-list.json
Structural validation
Every credential is checked for the required Verifiable Credential
fields: @context, type, issuer, credentialSubject, and
issuanceDate. Missing fields are reported as structural errors,
independent of expiry status.
Revocation checks
Revocation is checked against a locally-provided fixture in one of two formats:
- Simple list —
{ "revoked": ["<credential id>", ...] } - StatusList2021 — a
StatusList2021Credentialwith a base64-encodedencodedListbitstring, referenced by a credential'scredentialStatus.statusListIndex
No live network calls are made when checking revocation; the list is
always read from a local file via --revocation-list <path>.
JWT-VC support
In addition to JSON-LD credentials, .jwt files containing a JWT-VC
(a signed JWT whose payload has a vc claim, or an unwrapped VC-shaped
payload) are auto-detected and decoded. No signature verification is
performed — this tool only inspects structure, expiry (expirationDate
or numeric exp), and revocation status, all against local data.
Exit codes for CI
vc-expiry-watch exits 0 when every scanned credential is valid (or
merely upcoming), and exits 1 if any credential is expired, revoked,
or structurally invalid — making it easy to wire into a CI pipeline as a
credential-health gate.
Fetching a remote status list (optional, untested live)
src/lib/fetchStatusList.js is a thin wrapper around the global fetch
API for pulling a real StatusList2021Credential from a live HTTPS
endpoint. It is deliberately not covered by the automated test suite
or CI, since those only ever touch local fixture files. Wire its result
into checkRevocation yourself if you need live status-list checks.
Config file
Place a .vcexpiryrc.json in your working directory to set defaults:
{
"days": 45,
"revocationList": "./revocation-list.json",
"format": "json",
"recursive": true
}
Any CLI flag you pass explicitly overrides the matching config value.
Troubleshooting
- "Cannot read file" — the path passed doesn't exist or isn't readable.
- "Malformed JSON" — the
.jsoncredential file isn't valid JSON. - "Malformed JWT" — the
.jwtfile isn't a well-formed 3-part JWT. - "Unrecognized revocation list format" — the file passed to
--revocation-listis neither a{ "revoked": [...] }list nor aStatusList2021Credentialwith anencodedList.
Output formats
--format table (default), --format json, and --format csv are all
supported, and can be combined with --output <file> to write the
report to disk instead of stdout.
Examples
vc-expiry-watch ./credentials --recursive --format json
vc-expiry-watch ./credentials --recursive --format csv --output report.csv
vc-expiry-watch ./credentials --revocation-list ./revocation-list.json
Issuer validation
issuer is accepted either as a DID string (any did:<method>:...), an
HTTPS URL, or an object with an id in one of those forms. Anything
else is reported as a structural error.
CLI reference
| Flag | Description |
|---|---|
--days <n> |
Days threshold for "upcoming" expiry (default 30) |
--revocation-list <path> |
Local revocation/status list fixture |
--recursive |
Scan directories recursively |
--format <table|json|csv> |
Output format (default table) |
--output <file> |
Write report to a file instead of stdout |
--verbose |
Print extra diagnostic info |
--help |
Show usage |
--version |
Show version |
Revocation list format comparison
| Format | Shape | Lookup key |
|---|---|---|
simple |
{ "revoked": ["<id>", ...] } |
credential id |
statuslist2021 |
StatusList2021Credential with encodedList |
credentialStatus.statusListIndex |
The format is auto-detected from the file passed to
--revocation-list; no separate flag is needed.
Status priority
When a credential trips more than one check at once, the report shows a
single, prioritized status: revoked > invalid (structural) >
expired > upcoming > no-expiry > valid. A revoked
credential is always reported as revoked, even if it has also expired.
FAQ
Does this verify cryptographic signatures? No. It only checks expiry, structure, and revocation against local data. Pair it with a real VC verification library for signature checks.
Does it call any live network endpoints during tests?
No. All tests run against local fixture files. fetchStatusList.js is
the one optional exception, and it is never exercised by the test suite.
What happens if I point it at a directory with no .json/.jwt files?
The CLI exits with an error and a non-zero status code.
Architecture
bin/vc-expiry-watch.js -> thin executable, delegates to src/cli.js
src/cli.js -> argument parsing, option resolution, output
src/index.js -> programmatic library API
src/lib/parse.js -> JSON-LD / JWT-VC loading and format detection
src/lib/structure.js -> required-field + issuer + type validation
src/lib/expiry.js -> expirationDate / exp-claim status logic
src/lib/revocation.js -> simple-list / StatusList2021 revocation checks
src/lib/scan.js -> file/directory resolution
src/lib/report.js -> combines the above into one prioritized status
src/lib/formatters/* -> table / json / csv rendering
Acknowledgments
Built against the W3C Verifiable Credentials Data Model, the W3C DID Core specification, and the StatusList2021 revocation mechanism. This project is an independent utility and is not affiliated with the W3C.
Tags
verifiable-credentials did digital-identity w3c cli
License
MIT — see LICENSE.