@dcp-ai/express — middleware para Express¶
Tradução informativa da versão em inglês, atualizada em 28/10/2026. Em caso de divergência, prevalece a versão em inglês. Identificadores, comandos e blocos de código são mantidos sem tradução.
Middleware de verificação do DCP para Express.js. Verifica automaticamente os Signed Bundles em cada requisição e injeta os dados do agente verificado.
Especificações do DCP suportadas¶
Do DCP-01 ao DCP-09, de forma parcial: a verificação de bundles e a admissão como descritas abaixo, e alguns campos do DCP-05 e do DCP-09 (requireActiveLifecycle, lifecycleState, mandateId); não é uma implementação completa dessas especificações.
Instalação¶
Publicado no npm (26/10/2026). @dcp-ai/express 2.1.0 é latest e depende de @dcp-ai/sdk ^2.2.0. As funcionalidades descritas abaixo estão disponíveis no npm.
Atualize da 2.0.0: essa versão verifica a estrutura V2 sem verificar assinaturas. Use 2.1.0 para o verificador corrigido, a admissão e a supervisão de fontes. Veja SECURITY.md.
Dependência peer: Express 4.18+ ou 5.x
Veja o inventário de publicações npm para todas as versões.
Para desenvolver a partir de uma cópia do repositório, compile e instale o pacote local:
git clone https://github.com/dcp-ai-protocol/dcp-ai.git
cd dcp-ai/integrations/express
npm ci && npm run build
# then, in your project:
npm install /path/to/dcp-ai/integrations/express express
Início rápido¶
import express from "express";
import { dcpVerify, type DCPAgent } from "@dcp-ai/express";
// The middleware sets req.dcpAgent; the package does not augment Express's Request type
type DCPRequest = express.Request & { dcpAgent?: DCPAgent };
const app = express();
// The bundle travels in the JSON body: give the parser a budget sized for your bundles.
app.use(express.json({ limit: "64kb" }));
// Verify DCP on all routes
app.use(dcpVerify());
app.post("/api/action", (req: DCPRequest, res) => {
// The verified agent is available at req.dcpAgent
const agent = req.dcpAgent!;
console.log(`Agent ${agent.agentId} (human: ${agent.humanId})`);
console.log(`Capabilities: ${agent.capabilities}`);
console.log(`Risk tier: ${agent.riskTier}`);
res.json({ ok: true, agent_id: agent.agentId });
});
app.listen(3000);
Enviar um bundle a partir do cliente¶
Envie o bundle em signed_bundle do corpo JSON. É a forma recomendada para os bundles V2 híbridos, e o middleware
também lê o cabeçalho X-DCP-Bundle (primeiro) para os bundles que cabem em um. A primeira requisição abaixo é um
modelo: {...} representa um bundle assinado real.
# Via body (recommended)
curl -X POST http://localhost:3000/api/action \
-H "Content-Type: application/json" \
-d '{"signed_bundle": {...}, "action": "test"}'
# Via header: only for bundles that fit the header limits of the server and every proxy
curl -X POST http://localhost:3000/api/action \
-H "Content-Type: application/json" \
-H "X-DCP-Bundle: $(cat signed_bundle.json)" \
-d '{"action": "test"}'
Um bundle híbrido (Ed25519 + ML-DSA-65) tem cerca de 33 KB: o bundle base dos vetores de admissão tem 32.996 bytes. O
limite padrão do Node para todos os cabeçalhos da requisição é de 16 KiB, então em X-DCP-Bundle ele recebe 431 antes
de o Express ser executado; não aumente o limite para que caiba. No corpo, o limit do analisador é o seu orçamento: um
corpo maior recebe 413 antes do middleware. O orçamento é uma escolha para a sua implantação, não um limite do
protocolo: meça o seu maior bundle (ele cresce com a trilha de auditoria e os checkpoints PQ), deixe margem e mantenha
os proxies na frente pelo menos tão permissivos. examples/strict-v1-body.mjs checa tudo isso com HTTP real (veja
examples/README.md).
Referência da API¶
dcpVerify(options?)¶
Função fábrica que retorna um middleware do Express.
import { dcpVerify } from "@dcp-ai/express";
app.use(dcpVerify({
requireBundle: true,
checkRevocation: false,
cacheTtlSeconds: 300,
headerName: "x-dcp-bundle",
onFailure: (req, res, errors) => {
res.status(403).json({ error: "DCP verification failed", details: errors });
},
}));
Opções (DCPVerifyOptions)¶
Uma opção desconhecida, um campo de verifierPolicy desconhecido ou um valor do tipo errado (por exemplo, allow_v1_bundles: "false") lança uma exceção na criação do middleware: uma opção escrita errado não deve desligar uma checagem.
| Opção | Tipo | Padrão | Descrição |
|---|---|---|---|
requireBundle |
boolean |
true |
Exige um bundle em cada requisição. Se for false, as requisições sem bundle passam sem verificação. |
checkRevocation |
boolean |
false |
Em cada requisição, haja veredito em cache ou não, pergunta ao serviço de revogação se estão revogados o agente (GET /check/<agent_id>) ou, para a V2, alguma chave com a qual o bundle possa ser assinado (GET /v2/check/kid/<kid> para cada chave do passaporte, cada chave de vinculação do RPR e os kids clássico e PQ da assinatura do bundle). Uma resposta JSON com um revoked avaliado como verdadeiro recusa a requisição. |
revocationFailClosed |
boolean |
false |
Com checkRevocation: o que acontece quando uma consulta não dá resposta, ou seja, quando fetch lança uma exceção (serviço inalcançável), a resposta não é 2xx, o corpo não é JSON ou o seu revoked falta ou nem é avaliado como verdadeiro nem é false. false (o padrão) deixa a requisição passar (falha aberta); true a recusa com 403. |
revocationServiceUrl |
string |
"http://localhost:3003" |
URL base do serviço de revogação. |
cacheTtlSeconds |
number |
0 |
Segundos durante os quais os resultados de verificação ficam em cache. 0 desativa o cache. |
cacheMaxEntries |
number |
1000 |
Máximo de bundles verificados mantidos no cache; o usado menos recentemente é removido primeiro. |
headerName |
string |
"x-dcp-bundle" |
Nome do cabeçalho HTTP em que o bundle pode ser enviado; é lido antes de req.body.signed_bundle. Só para bundles dentro dos limites de cabeçalhos do servidor e de todos os proxies (o do Node, por padrão, é de 16 KiB para todos os cabeçalhos): envie os bundles híbridos no corpo. |
onFailure |
function |
undefined |
Tratador próprio para os erros de verificação. Recebe (req, res, errors) e, para uma admissão recusada, o resultado da admissão como quarto argumento. |
verifierPolicy |
object |
undefined |
Campos da política do verificador (VerifierPolicy de @dcp-ai/sdk), combinados sobre o DEFAULT_VERIFIER_POLICY do SDK. Um default_mode sem risk_overrides se aplica a todos os níveis de risco; as entradas de risk_overrides definem os seus níveis por cima dos demais. |
requireHybrid |
boolean |
false |
Todo modo abaixo de hybrid_required passa a ser hybrid_required, e os bundles V1 são recusados. |
requireActiveLifecycle |
boolean |
false |
Recusa um agente cujo status do passaporte é decommissioned. Lê o estado do passaporte, não um certificado de comissionamento nem um registro de ciclo de vida do DCP-05. Só V2. |
trustedPrincipals |
object ou function |
undefined |
Os principais em que você confia e as suas chaves: um conjunto de confiança (schemas/v2/principal_trust_set.schema.json), ou ({ tenant }) => trust set chamada em cada requisição. Com ela, só os bundles V2 admitidos chegam à rota (veja abaixo). Uma função que lança uma exceção, ou que retorna undefined, null ou um conjunto de confiança inválido, recusa a requisição com 503. |
profile |
object |
undefined |
O perfil strict-v1 (schemas/v2/admission_profile.schema.json). Precisa de trustedPrincipals e de revocationSource. |
revocationSource |
function |
undefined |
(subjects, { tenant }) => snapshot, a fonte de revogação do perfil, por exemplo denyListRevocationSource({ url, headers }). |
revocationTimeoutMs |
number |
5000 |
Quanto a fonte de revogação pode demorar antes de a requisição ser recusada. Com supervise.revocation, o prazo supervisionado: um inteiro de 1 a 2147483647. |
sourceSupervisor |
object |
undefined |
Um supervisor de createSourceSupervisor, junto com supervise (veja Supervisionar a fonte de confiança). |
supervise |
object |
undefined |
{ trustTimeoutMs?, revocation? }, pelo menos um: trustTimeoutMs, um inteiro de 1 a 2147483647, o prazo de uma função trustedPrincipals com sourceSupervisor; revocation: true executa sob ele a revocationSource do perfil, com revocationTimeoutMs (veja Supervisionar a fonte de revogação). |
cancelOnDisconnect |
boolean |
false |
Quando o cliente vai embora de verdade durante a admissão, cancela a sua invocação supervisionada da fonte (veja Um cliente que vai embora). |
tenant |
function |
um único inquilino | (req) => string, a partir do seu próprio contexto autenticado, nunca de um cabeçalho que o cliente define. Seleciona o conjunto de confiança e separa os vereditos em cache. |
clock |
function |
Date.now |
Para os testes. |
req.dcpAgent¶
Depois de uma verificação bem-sucedida, req.dcpAgent contém:
interface DCPAgent {
agentId: string; // Agent ID
humanId: string; // ID of the responsible human
publicKey: string; // Ed25519 public key
capabilities: string[]; // Declared capabilities
riskTier: string; // Risk level (low/medium/high)
status: string; // Status (active/suspended/revoked)
dcpVersion: "1.0" | "2.0";
kids?: string[]; // V2: key identifiers of the passport keys
sessionNonce?: string; // V2: session nonce of the bundle
lifecycleState?: string; // DCP-05 lifecycle state
mandateId?: string; // DCP-09 delegation mandate ID
warnings?: string[]; // V2: warnings of the SDK verifier (e.g. classical-only signature)
verificationMode?: string; // V2: the mode the bundle was verified in
securityTier?: string; // V2: security tier of its intent and policy decision (spec §14.2)
admission?: { // with trustedPrincipals
admitted: true;
principalTrust: "trusted";
profile: "strict-v1" | null;
profileCompliance: "compliant" | "not_evaluated";
recordedDecision: string | null; // approve, escalate, block: evidence, not a permission
trustSetVersion: string | null;
actionAuthorized: false; // always: admission does not authorize an action
};
}
Comportamento¶
- Extrai o bundle assinado do cabeçalho
X-DCP-Bundle(JSON) ou dereq.body.signed_bundle - Verifica-o:
- V2 (
bundle.dcp_bundle_version: "2.0"):verifySignedBundleV2()de@dcp-ai/sdk, com um registro Ed25519 / ML-DSA-65 / SLH-DSA-192f e a política acima. Checa os hashes dos payloads dos artefatos e o manifesto; a vinculação de sessão, a expiração e o início de sessão assinado (session_issued_at); a assinatura do bundle sobre o manifesto, feita pelo principal com asbinding_keysdo RPR, e o blocosignaturenão assinado, que deve concordar com ela; a assinatura própria de cada artefato; o modo de verificação, o mais estrito entre o modo da política para orisk_tierdo passaporte e o modo do nível de segurança da intenção e da decisão de política (§14.2 da especificação); os checkpoints PQ; a coerência entre artefatos (estado do passaporte, expiração do RPR, um único agente, principal e intenção nos artefatos e nas entradas de auditoria); a cadeia de hash de auditoria; e a validade das chaves, a derivação dos kids e as chaves em base64 canônico (spec/VERIFICATION.mdde V2-1 a V2-14). Com o@dcp-ai/sdk2.2.0, a cadeia de auditoria e a raiz de Merkle que ele checa são as SHA-256: ele não verifica os campos secundários SHA3-256 do V2-11 (o@dcp-ai/sdk2.2.1 e posteriores verificam os presentes, quando seu lockfile o resolve), então a referência ao VERIFICATION.md não significa que cada um dos seus passos seja checado. Um bundle malformado é recusado com403, nunca é aceito só pela estrutura. - V1 (
bundle.responsible_principal_record.dcp_version: "1.0"):verifySignedBundle(), só onde o VERIFICATION.md V2-10 aceitaria um bundle só clássico, porque um bundle V1 não tem assinatura pós-quântica:allow_v1_bundlesétrue,requireHybridestá desligado, e o mais estrito entre o modo da política para orisk_tierdo passaporte V1 (default_modequando não tem nenhum) e o modo do nível de segurança da intenção e da decisão de política V1 (orisk_scoreV1 vai de 0 a 1, escalado por 1000; se faltar, conta comoelevated) éclassical_onlyouhybrid_preferred. Com a política padrão, uma intenção V1 routine ou standard passa, enquanto um pagamento, dados sensíveis, um impacto estimado alto ou uma pontuação de risco de 0,5 ou mais são recusados. A um bundle V1 só se aplicamallow_v1_bundles, o modo e o nível: a vinculação de audiência, os limites de expiração e de duração da sessão, umaccepted_classical_algssem Ed25519 e os avisos que rejeitam Ed25519 são checagens da V2, e um bundle V1 que passa na checagem de modo é aceito mesmo quando eles estão definidos. Se precisar desses controles, definaallow_v1_bundles: falseourequireHybrid: true, ou usetrustedPrincipalscom o perfil strict-v1. - Um bundle cuja versão não pode ser detectada é recusado com
403. - Se for válido: injeta
req.dcpAgente chamanext() - Se falhar: responde com
403, oserrorsewarningsdo verificador (ou usaonFailure, se configurado) - Se ocorrer um erro interno: responde com
500
O que «verificado» significa¶
Um bundle verificado é internamente coerente e assinado pelas chaves que ele mesmo lista: as binding_keys do RPR para a assinatura do bundle, as chaves do passaporte para os artefatos do agente. Ele não prova quem é o principal: qualquer pessoa pode criar chaves e um RPR que nomeie qualquer human_id. Para isso, defina trustedPrincipals (próxima seção).
Quem tiver um bundle verificado pode reenviá-lo até que a sua sessão expire. Contra o reenvio de bundles V2 (essas checagens não se aplicam a bundles V1, veja Comportamento), defina verifierPolicy.require_audience_binding: true com o seu verifier_id (o manifest.intended_verifier do bundle deve nomear este verificador), e require_session_expiry / max_session_duration_seconds para limitar a sessão, ou use o perfil strict-v1. Nada disso torna um bundle de uso único: mantenha o seu próprio registro de nonces consumidos, se precisar disso.
Principais confiáveis e strict-v1¶
Com trustedPrincipals, uma requisição chega à rota só quando o seu bundle é admitido (docs/ADMISSION.md, spec/profiles/admission): um bundle V2 cujas assinaturas do principal (o RPR e o manifesto do bundle, clássica e pós-quântica) foram feitas com chaves que o seu conjunto de confiança lista para esse human_id, ativas e dentro da sua janela de validade. Com profile, também se aplicam os requisitos do strict-v1: assinaturas híbridas para todos os níveis de risco, um intended_verifier assinado igual ao seu verifier_id, uma sessão assinada de no máximo 300 s e um instantâneo de revogação recente.
import { dcpVerify, denyListRevocationSource } from "@dcp-ai/express";
app.use("/api/agents", dcpVerify({
trustedPrincipals: trustSet,
profile: { profile: "strict-v1", verifier_id: "api.acme.example" },
revocationSource: denyListRevocationSource({ url: "https://revocation.acme.example/.well-known/dcp-revocations.json" }),
tenant: (req) => req.auth.tenant,
cacheTtlSeconds: 60,
}));
Uma requisição recusada recebe 403 com { verified, admitted: false, principal_trust, profile, profile_compliance, codes, errors } e nunca chega à rota. denyListRevocationSource precisa de uma URL https (http simples só para um host de loopback), não segue redirecionamentos e lê no máximo 16 MiB. Uma fonte de revogação que falha, estoura o tempo ou não retorna nada recusa a requisição (REVOCATION_UNAVAILABLE). Com principais confiáveis, requireBundle: false lança AdmissionConfigError na criação do middleware: caso contrário, uma requisição sem bundle chegaria à rota. Com o perfil, verifierPolicy, requireHybrid: false e as opções de checkRevocation também lançam uma exceção. Com principais confiáveis, os bundles V1 são recusados: eles não trazem nenhuma assinatura do principal para ancorar. Executáveis: examples/strict-v1.mjs e, com HTTP real, o bundle no corpo e um orçamento do corpo, examples/strict-v1-body.mjs.
Os testes executam todos os casos de tests/interop/v2/bundle_vectors.json, cada um com os seus verifier_policy_overrides.
Supervisionar a fonte de confiança¶
Sem essas opções, uma função trustedPrincipals não tem prazo, como antes. Com elas, ela tem um, e uma parte de uma capacidade:
import { createSourceSupervisor, dcpVerify } from "@dcp-ai/express";
// One object per process, shared by every middleware (and by @dcp-ai/openclaw) that should share one capacity.
const supervisor = createSourceSupervisor({ maxInFlight: 64, maxInFlightPerTenant: 8 });
app.use("/api/agents", dcpVerify({
trustedPrincipals: ({ tenant, signal }) => loadTrustSet(tenant, { signal }),
sourceSupervisor: supervisor,
supervise: { trustTimeoutMs: 2000 },
tenant: (req) => req.auth.tenant,
}));
- Prazo. A função recebe
{ tenant, signal }. Um conjunto de confiança que chega depois detrustTimeoutMsnão é usado, também quando o loop de eventos estava ocupado demais para executar o temporizador: o prazo é medido em um relógio monotônico quando o resultado chega. A requisição recebe então o mesmo503de uma fonte que falha (TRUST_SOURCE_FAILED), esignalé abortado. - Capacidade.
maxInFlightconta as chamadas que o middleware iniciou e que ainda não se resolveram, em todos os inquilinos;maxInFlightPerTenantlimita um inquilino, e sem ele um único inquilino pode ocupar toda a capacidade. Uma chamada que não encontra vaga livre recebe o mesmo503na hora; não há fila. Uma vaga só é liberada quando a função se resolve de verdade, não quando a requisição estoura o tempo: uma fonte que nunca se resolve mantém a sua vaga, e quando todas as vagas estão ocupadas o middleware recusa todas as requisições até você reiniciar o processo ou criar outro supervisor. - Escopo. A capacidade é por objeto supervisor. Dois supervisores são duas capacidades, então o limite de um processo é a soma dos supervisores que ele cria. Um supervisor criado por
@dcp-ai/openclaw, a partir do seu build ESM ou CJS, funciona aqui também, e vice-versa. supervisor.close({ drainMs })recusa as chamadas novas, responde às pendentes com503, aborta todos ossignale espera no máximodrainMs(padrão 0) pelas chamadas em andamento. Ele se resolve com quantas ainda estão em andamento; as vagas delas continuam ocupadas.supervisor.stats()retorna contadores:open,inFlight,tenants,timeouts,saturated,closed,lateResults,lateErrors,droppedEvents.onSourceEventemcreateSourceSupervisorrecebe{ source, reason, inFlight }, nunca um inquilino. Ele precisa ser rápido, porque é executado no mesmo loop de eventos: uma chamada por vez, e os eventos que chegam enquanto há uma na fila ou com a promessa pendente são descartados e contados. Uma promessa ou um thenable retornados são assimilados como fazPromise.resolve: um thenable resolvido com uma promessa pendente o mantém ocupado, e um resolvedor chamado de novo mais tarde não libera nada. Os seus erros e rejeições são ignorados. Os contadores param emNumber.MAX_SAFE_INTEGER.- O que ele não faz. Não interrompe o trabalho de uma fonte: uma função que ignora
signalé executada até o fim. Não limita o trabalho que uma função inicia por conta própria e não retorna. Não ajuda uma função que bloqueia o loop de eventos, o que atrasa todos os prazos. A verificação de um bundle continua sendo executada antes da fonte de confiança. Semsupervise.revocation, a revogação mantémrevocationTimeoutMscomo antes.
Supervisionar a fonte de revogação¶
Com o perfil strict-v1, supervise: { revocation: true } executa a revocationSource sob o mesmo tipo de supervisor, com revocationTimeoutMs como prazo (então, um inteiro de 1 a 2147483647):
app.use("/api/agents", dcpVerify({
trustedPrincipals: trustSet, // a static trust set, or a function supervised with trustTimeoutMs
profile: { profile: "strict-v1", verifier_id: "verifier:acme-api" },
revocationSource: denyListRevocationSource({ url: "https://revocations.example.com/.well-known/dcp-revocations.json" }),
revocationTimeoutMs: 2000,
sourceSupervisor: supervisor,
supervise: { revocation: true }, // or { trustTimeoutMs: 2000, revocation: true }
}));
- Opcional.
superviseaceitatrustTimeoutMs,revocation: trueou os dois.revocation: trueprecisa de umarevocationSourcee funciona com um conjunto de confiança estático. Sem ela, a revogação mantémrevocationTimeoutMscomo antes. - As mesmas respostas. A fonte recebe
(subjects, { tenant, signal }), e cada resposta dá o mesmo resultado que sem supervisão. Um instantâneo é avaliado como antes.null, uma falha, um tempo estourado, uma chamada sem vaga livre e um supervisor fechado dão403comcodes: ["REVOCATION_UNAVAILABLE"], nunca não revogado. Um erro cujocodeéREVOCATION_INVALIDdáREVOCATION_INVALIDsó quando chega antes do prazo; depois dele, é um tempo estourado. - Uma única capacidade. As chamadas de confiança e de revogação ocupam vagas do mesmo supervisor, cada uma mantida até que a sua fonte se resolva de verdade. Um instantâneo atrasado nunca é usado, nem para a sua requisição nem para nenhuma outra, e o cache de vereditos nunca guarda um.
denyListRevocationSourceaborta o seu fetch, e a leitura do seu corpo, quando osignaldo supervisor é abortado, além de no seu própriotimeoutMs.- Telemetria. Os seus eventos trazem
source: 'revocation'. - Limites. Os mesmos da fonte de confiança. O caminho legado
checkRevocation/revocationServiceUrlnão é supervisionado.
Um cliente que vai embora¶
Com cancelOnDisconnect: true, um cliente que vai embora de verdade enquanto a sua requisição é admitida interrompe o trabalho dessa requisição:
app.use("/api/agents", dcpVerify({
trustedPrincipals: ({ tenant, signal }) => loadTrustSet(tenant, { signal }),
sourceSupervisor: supervisor,
supervise: { trustTimeoutMs: 2000 },
cancelOnDisconnect: true,
}));
- O que conta como ir embora. A resposta se fecha antes de ser enviada, ou a requisição se fecha antes de o seu corpo estar completo. O próprio
closede uma requisição depois de lido o seu corpo, que também acontece em requisições saudáveis, não conta. - O que ele cancela.
- Só a invocação supervisionada da fonte dessa requisição: o seu
signalé abortado e a invocação é contada emstats().cancelled, com o motivo'cancelled'emonSourceEvent. - Nem o supervisor nem qualquer outra requisição.
- A sua vaga continua ocupada até que a fonte se resolva de verdade, como depois de um prazo: uma fonte que ignora
signalcontinua sendo executada. - O que acontece com a requisição. Nada mais:
- nem tratador nem
next(); - nem escrita no cache nem
onFailure; - nenhuma resposta é escrita.
- A verificação do bundle não é interrompida; o middleware checa depois dela.
- Requisitos. Checados na criação do middleware:
- Toda função de fonte deve ser supervisionada:
trustTimeoutMspara uma funçãotrustedPrincipals, erevocation: truepararevocationSource. Não são permitidos nemcheckRevocationnem umcancelOnDisconnectsem uma fonte supervisionada (AdmissionConfigError,CONFIG_PROFILE_CONTRADICTION). - O supervisor deve suportar o cancelamento (caso contrário,
CONFIG_INVALID_VALUE). Os supervisores desta versão de@dcp-ai/expressou@dcp-ai/openclaw, ESM ou CJS, suportam; um de um pacote mais antigo não, e sem a opção ele continua funcionando como antes. - Ler
cancelled.stats().cancelledé opcional no tipo, para que um supervisor de um pacote mais antigo continue passando na checagem de tipos: leia-o comostats().cancelled ?? 0.
Cache¶
Quando cacheTtlSeconds > 0, cada middleware mantém o seu próprio cache de bundles verificados, com a chave formada pelo SHA-256 da forma canônica do bundle assinado (canonicalizeV2), a impressão digital da política do verificador e o inquilino: um veredito em cache só é reutilizado para o mesmo bundle, com a mesma política e para o mesmo inquilino, e um bundle sem forma canônica (um número não inteiro como 1e999, ou um aninhamento profundo demais) é verificado, mas não vai para o cache. Uma entrada expira depois de cacheTtlSeconds, ou antes, quando expiram a sessão do bundle (manifest.session_expires_at), o RPR, uma chave do passaporte ou uma chave de vinculação do RPR; um bundle com um carimbo de data/hora de expiração que não é RFC 3339 não vai para o cache. O cache mantém no máximo cacheMaxEntries bundles, removendo primeiro as entradas expiradas e depois as usadas menos recentemente. Com checkRevocation, a revogação é checada de novo a cada acerto do cache. Com trustedPrincipals, o cache só guarda a verificação criptográfica: a confiança no principal, as checagens do perfil e a revogação são avaliadas a cada requisição, então um principal removido do conjunto de confiança ou uma chave revogada são recusados na requisição seguinte.
Exemplo avançado — rotas protegidas de forma seletiva¶
import express from "express";
import { dcpVerify, type DCPAgent } from "@dcp-ai/express";
type DCPRequest = express.Request & { dcpAgent?: DCPAgent };
const app = express();
app.use(express.json());
// Public routes (no verification)
app.get("/health", (req, res) => res.json({ ok: true }));
// Protected routes (require DCP)
const protectedRoutes = express.Router();
protectedRoutes.use(dcpVerify({ cacheTtlSeconds: 60 }));
protectedRoutes.post("/agent/action", (req: DCPRequest, res) => {
res.json({ agent: req.dcpAgent!.agentId });
});
protectedRoutes.get("/agent/profile", (req: DCPRequest, res) => {
res.json(req.dcpAgent);
});
app.use("/api", protectedRoutes);
app.listen(3000);
Desenvolvimento¶
# Development build uses the published SDK from package-lock.json:
npm install
# Build (ESM + CJS + types)
npm run build
# Tests
npm test
Dependências¶
@dcp-ai/sdk— SDK de verificação do DCPexpress(par) — framework HTTP
Licença¶
Apache-2.0