Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
bc96421
Implement agent identity profile v01
hammadtq Feb 22, 2026
7ac3d46
Fix X.509 extensions, SAN, and JWKS detection
hammadtq Feb 22, 2026
84a4b35
Fix SAN URI type and document x5u limitations
hammadtq Feb 22, 2026
edcae9c
Harden SAN URI type and add issuance test
hammadtq Feb 22, 2026
ef4c494
Stabilize CA SAN tests
hammadtq Feb 22, 2026
1ffbc5a
Fix JWKS directory detection and doc notes
hammadtq Feb 22, 2026
a93cb3c
Align label parsing and harden cert/JWKS handling
hammadtq Feb 22, 2026
dd5bc35
Fix structured dict key capture and label test
hammadtq Feb 22, 2026
c82adcf
Fix parser cleanup and agent kid cert edge case
hammadtq Feb 22, 2026
01046ce
Fix lockfile and tighten agent cert attachment
hammadtq Feb 22, 2026
4391f8b
Disable public cache for session-derived agent cards
hammadtq Feb 22, 2026
87fe9df
Add cert read APIs and portal certificate lifecycle UI
hammadtq Feb 22, 2026
c6a8fd6
Fix cert revoke idempotency and pagination totals
hammadtq Feb 22, 2026
67f2c2a
Improve cert status validity and portal revoke UX
hammadtq Feb 22, 2026
a986e1d
Polish cert scope docs and revoke dialog behavior
hammadtq Feb 22, 2026
9eb3051
Fix trusted directory check to prevent substring bypass
hammadtq Feb 22, 2026
fb24028
Fix GPT feedback issues for agent identity PR
hammadtq Feb 22, 2026
6f5db9a
Add proof-of-possession and fingerprint validation
hammadtq Feb 22, 2026
0ad69b4
Harden PoP and make cert issuance CLI-only
hammadtq Feb 22, 2026
6e0f4fd
Fix PoP workflow and add private-key-path support
hammadtq Feb 22, 2026
8d09391
Support both JWK JSON and PEM private key formats in CLI
hammadtq Feb 22, 2026
7f4c9f5
Fix portal command filename and tighten JWK validation
hammadtq Feb 23, 2026
0920c8c
Add missing public-status endpoint tests
hammadtq Feb 23, 2026
160cc30
Fix CA key-cert mismatch handling and trusted origin checks
hammadtq Feb 23, 2026
0d83ac3
Fix public-status route ordering to avoid shadowing
hammadtq Feb 23, 2026
85d5531
Add PoP nonce deduplication for replay protection
hammadtq Feb 23, 2026
04b1da6
Fix bot-cli cert command TypeScript typings
hammadtq Feb 23, 2026
7dfd26a
Address GPT feedback round 2 for agent identity PR
hammadtq Feb 23, 2026
891bd1c
Fix PoP nonce migration ROW_COUNT type
hammadtq Feb 23, 2026
4d1554b
Harden x509 chain checks and x5u body limits
hammadtq Feb 23, 2026
82cd8b3
fix: harden cert PoP, issuance limits, and x509 chain checks
hammadtq Feb 23, 2026
24d6b99
fix: make cert issuance caps atomic and portal revoke valid
hammadtq Feb 23, 2026
6fa49e0
feat: sign signature-agent and align directory media types
hammadtq Feb 23, 2026
d5b3894
feat: IETF draft conformance fixes for RFC 9421 signatures
hammadtq Feb 23, 2026
715e21f
test: update bot-cli tests for IETF draft conformance
hammadtq Feb 23, 2026
f7b165e
fix: handle ;key= component parameters in covered headers parsing
hammadtq Feb 23, 2026
da0f922
feat: enforce WBA tag and signature-agent coverage semantics
hammadtq Feb 23, 2026
9e0723f
fix: rollback cert issue tx on early returns and legacy signing
hammadtq Feb 23, 2026
9ecb373
fix: parse parameterized covered components across clients
hammadtq Feb 23, 2026
475c395
fix: add legacy kid compatibility aliases and lookup fallback
hammadtq Feb 23, 2026
b9a6df0
fix: support multi-signature selection and sf-string dictionary seria…
hammadtq Feb 23, 2026
45b0aa7
fix: persist PoP nonce outside cert issuance transaction
hammadtq Feb 23, 2026
f3e9907
fix: restore legacy kid derivation and align PoP nonce TTL
hammadtq Feb 23, 2026
71fe8bd
chore: tighten node engine and parser/CA robustness
hammadtq Feb 23, 2026
998bd7b
Add X.509 EKU clientAuth and SAN URI binding validation
hammadtq Feb 23, 2026
665602e
fix: require not_before for active cert cap checks
hammadtq Feb 24, 2026
dd41064
Fix proxy header parsing for signature-agent;key= format
hammadtq Feb 24, 2026
647a108
chore: enforce node20 in verifier-service and fix crypto key types
hammadtq Feb 24, 2026
82c04b1
fix: align WBA signing/verifier behavior and cert issuance flow
hammadtq Feb 24, 2026
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
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ openbotauth/
│ └─ neon/ ✅ Neon migrations
└─ docs/
├─ ARCHITECTURE.md ✅ System architecture
└─ A2A_CARD.md ✅ A2A discovery documentation
├─ A2A_CARD.md ✅ A2A discovery documentation
└─ BREAKING_CHANGES.md ✅ Behavior changes by PR/release
```

---
Expand Down
4 changes: 2 additions & 2 deletions apps/registry-portal/public/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ const spki = publicKey.export({ type: 'spki', format: 'der' });
if (spki.length !== 44) throw new Error(`Unexpected SPKI length: ${spki.length}`);
const rawPub = spki.subarray(12, 44);
const x = rawPub.toString('base64url');
const thumbprint = JSON.stringify({ kty: 'OKP', crv: 'Ed25519', x });
const thumbprint = JSON.stringify({ crv: 'Ed25519', kty: 'OKP', x });
const hash = crypto.createHash('sha256').update(thumbprint).digest();
const kid = hash.toString('base64url').slice(0, 16);
const kid = hash.toString('base64url');

// Save securely
const dir = path.join(os.homedir(), '.config', 'openbotauth');
Expand Down
48 changes: 48 additions & 0 deletions apps/registry-portal/src/components/AddAgentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const AddAgentModal = ({ open, onOpenChange, onSuccess }: AddAgentModalProps) =>
const [privateKey, setPrivateKey] = useState<any>(null);
const [isGenerating, setIsGenerating] = useState(false);
const [isCreating, setIsCreating] = useState(false);
const [obaAgentId, setObaAgentId] = useState("");
const [obaParentAgentId, setObaParentAgentId] = useState("");
const [obaPrincipal, setObaPrincipal] = useState("");

const generateKeyPair = async () => {
setIsGenerating(true);
Expand Down Expand Up @@ -111,6 +114,9 @@ const AddAgentModal = ({ open, onOpenChange, onSuccess }: AddAgentModalProps) =>
description: description || undefined,
agent_type: agentType,
public_key: publicKey,
oba_agent_id: obaAgentId || undefined,
oba_parent_agent_id: obaParentAgentId || undefined,
oba_principal: obaPrincipal || undefined,
});

// Download private key
Expand All @@ -127,6 +133,9 @@ const AddAgentModal = ({ open, onOpenChange, onSuccess }: AddAgentModalProps) =>
setAgentType("");
setPublicKey(null);
setPrivateKey(null);
setObaAgentId("");
setObaParentAgentId("");
setObaPrincipal("");

onSuccess();
onOpenChange(false);
Expand Down Expand Up @@ -220,6 +229,45 @@ const AddAgentModal = ({ open, onOpenChange, onSuccess }: AddAgentModalProps) =>
</Alert>
)}

<div className="space-y-2 pt-2">
<Label>Agent Identity (Optional)</Label>
<div className="grid grid-cols-1 gap-3">
<div className="space-y-1">
<Label htmlFor="oba-agent-id" className="text-xs text-muted-foreground">
OBA Agent ID
</Label>
<Input
id="oba-agent-id"
placeholder="agent:alice@example.com"
value={obaAgentId}
onChange={(e) => setObaAgentId(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label htmlFor="oba-parent-agent-id" className="text-xs text-muted-foreground">
OBA Parent Agent ID
</Label>
<Input
id="oba-parent-agent-id"
placeholder="agent:parent@example.com"
value={obaParentAgentId}
onChange={(e) => setObaParentAgentId(e.target.value)}
/>
</div>
<div className="space-y-1">
<Label htmlFor="oba-principal" className="text-xs text-muted-foreground">
OBA Principal
</Label>
<Input
id="oba-principal"
placeholder="principal:owner@example.com"
value={obaPrincipal}
onChange={(e) => setObaPrincipal(e.target.value)}
/>
</div>
</div>
</div>

<div className="flex gap-2 pt-4">
<Button
variant="outline"
Expand Down
91 changes: 90 additions & 1 deletion apps/registry-portal/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export interface Agent {
agent_type: string;
status: string;
public_key: Record<string, unknown>;
oba_agent_id: string | null;
oba_parent_agent_id: string | null;
oba_principal: string | null;
created_at: string;
updated_at: string;
}
Expand All @@ -62,6 +65,33 @@ export interface Session {
profile: Profile;
}

export interface AgentCertificate {
id: string;
agent_id: string;
kid: string;
serial: string;
fingerprint_sha256: string;
not_before: string;
not_after: string;
revoked_at: string | null;
revoked_reason: string | null;
created_at: string;
is_active?: boolean;
}

export interface AgentCertificateDetail extends AgentCertificate {
cert_pem: string;
chain_pem: string;
x5c: string[];
}

export interface ListCertsResponse {
items: AgentCertificate[];
total: number;
limit: number;
offset: number;
}

// Radar types
export interface RadarOverview {
window: 'today' | '7d';
Expand Down Expand Up @@ -226,6 +256,9 @@ class RegistryAPI {
description?: string;
agent_type: string;
public_key: Record<string, unknown>;
oba_agent_id?: string | null;
oba_parent_agent_id?: string | null;
oba_principal?: string | null;
}): Promise<Agent> {
const response = await this.fetch('/agents', {
method: 'POST',
Expand Down Expand Up @@ -304,6 +337,63 @@ class RegistryAPI {
}
}

/**
* List issued certificates for the current user (optionally filtered)
*/
async listAgentCerts(params: {
agent_id?: string;
kid?: string;
status?: 'active' | 'revoked' | 'all';
limit?: number;
offset?: number;
} = {}): Promise<ListCertsResponse> {
const searchParams = new URLSearchParams();
if (params.agent_id) searchParams.set('agent_id', params.agent_id);
if (params.kid) searchParams.set('kid', params.kid);
if (params.status) searchParams.set('status', params.status);
if (typeof params.limit === 'number') searchParams.set('limit', String(params.limit));
if (typeof params.offset === 'number') searchParams.set('offset', String(params.offset));

const query = searchParams.toString();
const response = await this.fetch(query ? `/v1/certs?${query}` : '/v1/certs');
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || 'Failed to list certificates');
}
return await response.json();
}

/**
* Get one certificate (metadata + PEM chain + x5c) by serial.
*/
async getCertBySerial(serial: string): Promise<AgentCertificateDetail> {
const response = await this.fetch(`/v1/certs/${encodeURIComponent(serial)}`);
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || 'Failed to fetch certificate');
}
return await response.json();
}

/**
* Revoke one or more certificates by serial or kid.
*/
async revokeCert(data: {
serial?: string;
kid?: string;
reason?: string;
}): Promise<{ success: boolean; revoked: number }> {
const response = await this.fetch('/v1/certs/revoke', {
method: 'POST',
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: response.statusText }));
throw new Error(error.error || 'Failed to revoke certificate');
}
return await response.json();
}

/**
* Get JWKS URL for user
*/
Expand Down Expand Up @@ -499,4 +589,3 @@ class RegistryAPI {
}

export const api = new RegistryAPI();

Loading