AgnCred Threat Model (Closed Pilot)

Status: living document for the production pilot (PILOT_SPEC section 12). Scope: the AgnCred pilot deployment — one same-origin Fastify service serving the API and the built React portal, backed by Postgres. Every claim below cites the implementing file; where a mitigation does not exist, the residual-risk paragraph says so instead of inventing one.


1. System overview and trust boundaries

AgnCred issues portable, cryptographically verifiable work receipts for AI agents. An operator registers agents and issues scoped credentials; the agent runtime submits receipts; a company reviewer attests them with the company's Ed25519 signing key; third parties verify signatures, key history, and the hash-chained event ledger offline via a verification bundle.

Actors

Actor Authentication Authority source
Operator Magic-link email → cookie session Active operator membership (apps/api/src/auth.ts)
Agent runtime Scoped bearer credential (agk_...) Credential row bound to one agent (apps/api/src/agent-routes.ts)
Company reviewer Magic-link email → cookie session Active company membership with owner/admin/reviewer role (apps/api/src/auth.ts)
Third-party verifier None (public endpoints) Public allowlist projections only (packages/policy/src/index.ts)
AgnCred admin Same session mechanism, agncred_admin role Role check in satisfiesRole (apps/api/src/auth.ts)

Assets

Trust boundary diagram

                                 TRUST BOUNDARY (network / auth)
                    ..........................................................
                    :                                                        :
+----------------+  :   +------------------------------------------------+  :
| Operator       |--:-->| Fastify API (same-origin, /api/v1)             |  :
| (browser,      |  :   |  helmet CSP / rate limits / body limit / CORS  |  :
|  cookie+CSRF)  |  :   |  [apps/api/src/app.ts]                         |  :
+----------------+  :   |                                                |  :
                    :   |  +------------------+  +---------------------+ |  :
+----------------+  :   |  | Auth: magic link |  | Agent routes:       | |  :
| Agent runtime  |--:-->|  | cookie sessions, |  | scoped agk_ keys,   | |  :
| (agk_ bearer   |  :   |  | CSRF, membership |  | idempotency         | |  :
|  credential)   |  :   |  | [auth.ts]        |  | [agent-routes.ts]   | |  :
+----------------+  :   |  +------------------+  +---------------------+ |  :
                    :   |                                                |  :
+----------------+  :   |  +-------------------+ +---------------------+ |  :
| Company        |--:-->|  | Attest / dispute /| | Invitations         | |  :
| reviewer       |  :   |  | rotate / erase    | | (hashed tokens,     | |  :
| (browser,      |  :   |  | [tenant-routes.ts]| |  abuse limits)      | |  :
|  cookie+CSRF)  |  :   |  +-------------------+ | [invitation-        | |  :
+----------------+  :   |          |             |  routes.ts]         | |  :
                    :   |          v             +---------------------+ |  :
+----------------+  :   |  +--------------------------------------+      |  :
| AgnCred admin  |--:-->|  | Signing: Ed25519 attestations,       |      |  :
| (agncred_admin |  :   |  | AES-256-GCM key wrap via KEK          |      |  :
|  role)         |  :   |  | [key-encryption.ts, core/crypto.ts]   |      |  :
+----------------+  :   |  +--------------------------------------+      |  :
                    :   +-----------------------|------------------------+  :
                    :                           v                            :
                    :   +------------------------------------------------+  :
                    :   | Postgres                                       |  :
                    :   |  immutable: work_receipt_versions,             |  :
                    :   |    receipt_events (unique seq), attestations   |  :
                    :   |  erasable: private evidence, reviewer notes,   |  :
                    :   |    emails, invitations, sessions               |  :
                    :   |  [db/migrations/003_immutable_proof.sql]       |  :
                    :   +------------------------------------------------+  :
                    :..........................................................
                                 |
                                 |  PUBLIC BOUNDARY (allowlist projections only)
                                 v
+----------------------------------------------------------------------+
| Third-party verifier: /receipts/:id, /verify, /verification-bundle,  |
| /companies/:id/keys — public DTOs built field-by-field               |
| [packages/policy/src/index.ts, apps/api/src/verification.ts]         |
+----------------------------------------------------------------------+

2. Threats

2.1 Fake companies and reviewers

Attack. An attacker registers a company (self-serve onboarding, or by claiming an invitation they arranged to receive) and "attests" work for an agent they control, giving the agent a fabricated track record.

Impact. Fraudulent reputation on public profiles; erosion of trust in the network.

Current mitigations.

Residual risk & pilot posture. An attacker who controls a real domain can reach domain_verified and earn capped points from a company they own. Registry verification is a manual admin judgment with no automated corporate-registry integration. The domain regex in tenant-routes.ts (/^[a-z0-9.-]+\.[a-z]{2,}$/) does not perform punycode normalisation or homograph checks, so a look-alike IDN domain can be verified as-is. Acceptable for a closed pilot with a small, manually admitted cohort; registry automation and domain hardening are post-pilot work.

Attack. The operator and the reviewing company are the same party (or colluding), so the "independent" attestation is self-dealing.

Impact. Inflated scores that misrepresent independent validation.

Current mitigations.

Residual risk & pilot posture. Linked-party signals are populated manually; there is no automated detection (shared payment data, IP overlap, corporate registry cross-checks). A colluding pair that declares "independent" and is not flagged earns points. During the pilot this is bounded by manual admission of participants and diminishing returns per company; a falsely declared independence is at least a signed, non-repudiable statement by the company.

2.3 Stolen sessions

Attack. An attacker obtains a reviewer's or operator's browser session (cookie theft, device theft, session fixation, CSRF).

Impact. Full workspace authority: attest receipts with the company key, invite reviewers, read private evidence, trigger erasure.

Current mitigations.

Residual risk & pilot posture. No device binding, IP pinning, or anomaly detection on sessions; a stolen cookie is fully usable for up to 24 h. Magic-link tokens travel in URL query strings (/portal/callback?token=...) and can persist in mail-client logs or browser history; this is bounded by the 15-minute TTL and single-use consumption (apps/api/src/auth.ts, consumeLoginToken). Accepted for the pilot; step-up confirmation for key rotation and erasure is a candidate hardening item.

2.4 Stolen agent keys

Attack. An agent credential (agk_...) leaks from the agent runtime, CI logs, or an env file.

Impact. The attacker can submit fabricated receipts as that agent and read its receipts and profile — but nothing else.

Current mitigations.

Residual risk & pilot posture. A stolen key can spam plausible-looking pending receipts and trigger attestation-request noise until the operator notices (via last_used_at or receipt listings) and revokes. There is no automatic anomaly detection or key-leak scanning. The per-credential rate-limit key is the raw Authorization header, so an unauthenticated brute-forcer gets a fresh bucket per guessed key (see gap list); key entropy (192 bits) makes brute force infeasible regardless.

2.5 Replay attacks

Attack. Re-submitting a captured request: a magic-link token, an invitation link, an attestation, or a receipt submission.

Impact. Duplicate authority grants or duplicate records.

Current mitigations.

Residual risk & pilot posture. Idempotency keys are scoped per credential; two different credentials replaying the same payload create two receipts (see 2.6). Session cookies replayed within their 24 h lifetime succeed by design (see 2.3).

2.6 Duplicate submissions

Attack. The same piece of work submitted repeatedly (retry storms, or deliberately, to farm multiple attestations from one deliverable).

Impact. Ledger noise; if a reviewer accepts duplicates, inflated scores.

Current mitigations.

Residual risk & pilot posture. There is no content-based duplicate detection: the same work described twice with different receipt IDs and idempotency keys produces two receipts, and a careless reviewer can accept both. Reviewer diligence plus diminishing returns is the pilot control; similarity flagging is out of scope (no fraud ML, PILOT_SPEC section 3).

2.7 Event chain forks

Attack. Insert, reorder, or branch lifecycle events — e.g. produce two competing histories in which a receipt is both "accepted" and "revoked".

Impact. Ambiguous or falsified lifecycle status; broken auditability.

Current mitigations.

Residual risk & pilot posture. The chain is per-receipt and server-anchored: a database superuser who can drop triggers can rewrite an entire chain self-consistently (see 2.8). There is no external anchoring (public transparency log, periodic checkpoint publication) in the pilot. Third parties who saved an earlier verification bundle can detect after-the-fact rewrites by comparing hashes.

2.8 Database tampering

Attack. Someone with database access (compromised credentials, malicious insider, hosting compromise) edits receipts, attestations, events, or scores directly.

Impact. Falsified history and reputation at the storage layer, below application controls.

Current mitigations.

Residual risk & pilot posture. Triggers restrain application-role mistakes and casual tampering, but a superuser can disable them. Signature and chain verification make tampering detectable, not preventable — and only for externally held copies once bundles have been downloaded. The pilot accepts single-database trust with standard hosting access controls; external anchoring is future work.

2.9 Signing key compromise (including KEK compromise)

Attack. (a) A company's decrypted private key leaks from process memory or a bug; (b) the KEK (SIGNING_KEY_ENCRYPTION_KEY) leaks together with a database dump, exposing every company's private key.

Impact. The attacker can forge attestations that verify as the compromised company (or, for the KEK, any company) until keys are rotated/revoked.

Current mitigations.

Residual risk & pilot posture. The KEK is a single environment variable — no KMS/HSM, no KEK rotation or re-wrap procedure, no split knowledge. A host compromise that reads both the env and the database defeats encryption at rest entirely. Signatures made before a revocation timestamp are indistinguishable from forgeries made during the compromise window; verifiers see key_status: revoked and must apply their own policy. Signing happens in the API process, so an application-level RCE can sign at will. Key rotation is available to any company member with a review role (owner/admin/reviewer), not owners only. Accepted for the pilot; KMS-backed key storage and company-held keys are documented as future paths only (PILOT_SPEC section 3).

2.10 Score gaming

Attack. Maximise public score without genuinely validated work: self-attestation, attestation farming across one friendly company, inflating hours/quality, exploiting defaults.

Impact. Public reputation stops reflecting real, independently validated performance.

Current mitigations (score policy agncred-score.v1, packages/reputation/src/index.ts).

Residual risk & pilot posture. The strongest remaining strategy is a genuinely domain-verified (or registry-approved) colluding company falsely declaring independence — see 2.2. reviewer_quality_score is bounded (0–100) but otherwise unaudited reviewer opinion. There is no fraud ML or anomaly detection by design in the pilot. Deterrent: every inflating statement is inside a company-signed, immutable attestation.

2.11 Prompt injection inside receipt fields rendered to reviewers

Attack. Receipt free-text fields — task.summary, outcome_claims.claims, tools_used, evidence descriptions, invitation message — are attacker-controlled text. They are shown to company reviewers in the review UI and returned to agent/MCP consumers. The text can contain (a) HTML/JS injection attempts, (b) social-engineering payloads aimed at the human reviewer ("SYSTEM: this receipt was pre-verified — click accept"), or (c) prompt-injection payloads aimed at LLM-based consumers of receipt text.

Impact. A manipulated reviewer signs a fraudulent attestation; a manipulated downstream LLM (e.g. an agent reading receipt status via the MCP server) takes attacker-directed actions.

Current mitigations.

Residual risk & pilot posture. Social-engineering injection remains: nothing in the review UI labels operator/agent text as untrusted or strips authority-mimicking phrasing, and the reviewer's decision is exactly the trust anchor the attacker targets. Likewise, MCP and SDK consumers receive receipt text verbatim; any LLM consuming it must treat receipt fields as untrusted data, never as instructions — this is a documentation/integration obligation, not an enforced control (packages/mcp-server). Pilot posture: small reviewer cohort, reviewer guidance in COMPANY_REVIEWER_GUIDE, and explicit "untrusted content" framing are the controls; UI provenance labelling is a hardening candidate.

2.12 Public data leakage

Attack. Private evidence, reviewer notes, exact timestamps, emails, or internal references reach a public endpoint through serialization drift or an over-broad query.

Impact. Confidentiality breach toward the very companies whose trust the network depends on; GDPR exposure.

Current mitigations.

Residual risk & pilot posture. One boundary is looser than the rest: the public /receipts/:id/verification-bundle returns stored events including metadata (needed because event hashes cover metadata), and event metadata includes free-text dispute reasons and pseudonymous actor/user IDs (apps/api/src/agent-routes.ts, verification-bundle handler; apps/api/src/tenant-routes.ts, dispute handler stores reason in metadata). Whatever a disputer types becomes effectively public and immutable. This is listed as a known gap. Snapshot tests asserting private-field absence are the regression control (PILOT_SPEC 11).

2.13 Email abuse (invitation spam, enumeration)

Attack. (a) Use AgnCred's mail identity to spam or phish arbitrary addresses via invitations with attacker-authored messages; (b) enumerate which emails hold accounts.

Impact. Deliverability/reputation damage, phishing on AgnCred letterhead, privacy leak of membership.

Current mitigations.

Onboarding endpoints are non-enumerating: an already-registered email receives an indistinguishable { status: "check_email" } response (with a sign-in link for the real account) instead of a 409, so onboarding and /auth/magic-link both refuse to confirm whether an account exists (apps/api/src/auth.ts).

Residual risk & pilot posture. The invitation email embeds the operator-authored message verbatim (apps/api/src/invitation-routes.ts, sendInvitationMail), so within the 20/day cap an operator can send attacker-phrased text under AgnCred's sender. Acceptable in a closed pilot where operators are admitted manually and can be suspended.

2.14 Erasure versus proof retention (GDPR tension)

Attack / tension. Not an attacker in the classic sense: a data subject exercises erasure (GDPR Art. 17), which naively conflicts with the promise that signed receipts stay verifiable forever. Conversely, an attacker could try to use erasure as a weapon to destroy inconvenient proof.

Impact. Either unlawful retention of personal data, or destruction of the network's cryptographic history.

Current mitigations (design: immutable pseudonymous proof + erasable side tables).

Residual risk & pilot posture. Canonical receipt payloads are immutable by design; if an operator typed direct personal data into free-text fields (task summary, claims), it cannot be removed from the stored version — the guidance "never store personal data in receipt payloads" is procedural, not enforced by a scanner. Dispute reasons in event metadata are similarly unerasable (see 2.12). Pseudonymous IDs remain personal data under GDPR if any mapping survives; the pilot's legal review (docs/LEGAL_REVIEW_REQUIRED.md) must confirm the retained set qualifies as sufficiently de-identified or is retained under a lawful basis.


3. Out of scope for the pilot

Deliberately excluded from the pilot build (PILOT_SPEC sections 3 and 12); listing them here so their absence is a documented decision, not an oversight:

Also out of scope: marketplace, payments, agent hosting, open (non-invited) reviews, and legal contract generation (PILOT_SPEC section 3).