@vincenzo-bug/credential
v0.3.0
Published
Credential core: agent identity, verifiable credentials, capability tokens, enforcement, audit — local-first.
Maintainers
Readme
@vincenzo-bug/credential
Local-first core for Credential: a Zero-Trust control layer for AI agents and services. Identity, verifiable credentials, capability tokens, enforcement, revocation, and signed audit records — no infrastructure, no chain, no network.
pnpm add @vincenzo-bug/credentialModules
| Area | Key exports |
| --- | --- |
| Identity | createIdentity, identityFromSecretKey, resolveDid, didFromPublicKey |
| Keystore | InMemoryKeystore, FileKeystore, storeIdentity, loadIdentity |
| Credentials | issueCredential, verifyCredential, credentialId |
| Capability tokens | issueCapability, verifyToken, verifyChain, tokenId, capabilityCovers |
| Revocation | InMemoryRevocationRegistry, FileRevocationRegistry |
| Enforcement | Guard (authorize → allow/deny) |
| App gate | CredentialGate (credential verification + capability authorization) |
| Audit | AuditLog, InMemoryAuditSink, FileAuditSink, verifyAuditTrail |
Existing app example
Use CredentialGate at the boundary of an API route, worker job, tool call, or
RPC method. Your application performs the protected action only when the gate
returns allow.
import { CredentialGate, InMemoryRevocationRegistry } from '@vincenzo-bug/credential';
const revocation = new InMemoryRevocationRegistry();
const gate = new CredentialGate({
trustedIssuers: [process.env.COMPANY_ISSUER_DID!],
revocation,
});
app.post('/tools/customer-read', async (req, res) => {
const decision = await gate.authorize({
credential: req.body.credential,
capability: req.body.capability,
request: { resource: `customers/${req.body.customerId}`, action: 'read' },
});
if (decision.outcome !== 'allow') {
return res.status(403).json({ reason: decision.reason });
}
return res.json(await readCustomer(req.body.customerId));
});Authorization, not yet authentication.
CredentialGatechecks that the credential and capability are valid, consistent, and unrevoked, but does not prove the caller controls the agent DID at request time. Until the auth challenge flow lands, treat them as bearer tokens — fine for trusted server-to-server use, but add a freshness / proof-of-possession signature at an untrusted edge.
Primitive example
import {
createIdentity, issueCapability, Guard,
InMemoryRevocationRegistry, AuditLog, verifyAuditTrail,
} from '@vincenzo-bug/credential';
const owner = await createIdentity();
const agent = await createIdentity();
const cap = await issueCapability({
issuer: owner,
audience: agent.did,
capability: { resource: 'db/users/42', actions: ['read'] },
});
const revocations = new InMemoryRevocationRegistry();
const guard = new Guard(revocations);
const audit = new AuditLog(owner);
const decision = await guard.authorize(cap, { resource: 'db/users/42', action: 'read' });
await audit.append({
capId: decision.capId, action: 'read', resource: 'db/users/42',
outcome: decision.outcome, reason: decision.reason,
});
console.log(decision.outcome); // 'allow'
console.log((await verifyAuditTrail(await audit.records())).ok); // trueSee SPEC.md for the on-the-wire token and audit formats.
License
Apache-2.0
