T09 · Insecure Skill Coding Practices
Error
- Location
- .agent/skills/identity-sovereign/scripts/verify_did.ts:17
- Finding
- Untrusted public-key substitution allows forged mandates<![CDATA[ ## Vulnerability Details **File Location**: `.agent/skills/identity-sovereign/scripts/verify_did.ts:17-49` **Vulnerability Type**: Public-key substitution and insufficient identity binding **Risk Level**: High ### Vulnerable Code ```ts const signedMandate = JSON.parse(fs.readFileSync(signedMandatePath, "utf8")); const publicJwk = JSON.parse(fs.readFileSync(publicJwkPath, "utf8")); console.log(`Verifying mandate issued by: ${signedMandate.issuer}`); // Extract JWS const jws = signedMandate.proof.jws; // Import Key const publicKey = await jose.importJWK(publicJwk, "EdDSA"); try { const { payload, protectedHeader } = await jose.compactVerify( jws, publicKey, ); console.log("✅ Verification SUCCESS: JWS signature is valid."); console.log("Protected Header:", protectedHeader); const verifiedPayloadStr = new TextDecoder().decode(payload); const verifiedMandate = JSON.parse(verifiedPayloadStr); // Simple check: issuer matches if (verifiedMandate.issuer === signedMandate.issuer) { console.log("Payload match confirmed."); } ``` ### Technical Analysis The verifier imports the verification key from `public_jwk.json`, which is stored beside the signed mandate and is not cryptographically bound to the claimed `did:key` identity. Possession of any matching key pair is therefore sufficient to pass signature verification. The issuer comparison does not establish trust because both `verifiedMandate.issuer` and the outer `signedMandate.issuer` can be supplied by the same attacker. Furthermore, a mismatch only suppresses a success message; it does not throw an error or reject the mandate. The verifier also does not ensure that: - The supplied JWK corresponds to the public key encoded by the claimed `did:key`. - The protected `kid` identifies a valid verification method belonging to the issuer. - The signed issuer matches a separately configured trusted issuer. - The outer mandate fields are identical to the signed payload. - Req ...[truncated 1218 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse and validate the issuer from the signed payload only. 2. Resolve or decode the Ed25519 public key directly from the claimed `did:key`; do not trust a colocated arbitrary JWK. 3. Compare the resolved key with the verification key using canonical key material. 4. Require the protected `kid` to identify a valid verification method controlled by the signed issuer. 5. Reject issuer mismatches explicitly: ```ts if (verifiedMandate.issuer !== expectedIssuer) { throw new Error("Issuer mismatch"); } ``` 6. Obtain `expectedIssuer` from trusted configuration or caller policy, not from another attacker-controlled field. 7. Validate algorithm, mandate schema, type, subject, audience, expiration, issuance time, authorization scope, and monetary limits. 8. Treat the signed payload as authoritative and either discard unsigned outer fields or require exact canonical equality. 9. Add negative tests for substituted keys, forged issuer values, mismatched `kid`, unsupported algorithms, and altered outer payloads. ]]>
