T07 · Tool Hijacking and Spoofing
Error
- Location
- skillsign.py:253
- Finding
- Trusted signer identity can be spoofed through unbound fingerprint metadata<![CDATA[ ## Vulnerability Details **File Location**: `skillsign.py`, lines 61–62 and 253–274 **Vulnerability Type**: Authentication metadata substitution **Risk Level**: High ### Vulnerable Code ```python # Lines 61–62: all signature metadata is excluded from the manifest dirs[:] = [d for d in dirs if d != SKILLSIG_DIR] ``` ```python # Lines 253–274 # Verify cryptographic signature pub_key = load_public_key_bytes(signer["public_key"].encode("utf-8")) data = manifest_bytes(stored_manifest) try: pub_key.verify(signature, data) except InvalidSignature: print(f"❌ INVALID SIGNATURE — manifest matches but signature is forged.") sys.exit(1) # Check revocation fp = signer["fingerprint"] revoked, rev_info = is_revoked_at(fp, signer.get("signed_at", "")) if revoked: print(f"🔴 REVOKED — Signer {fp} was revoked.") print(f" Revoked at: {rev_info['revoked_at']}") print(f" Reason: {rev_info['reason']}") print(f" Signed at: {signer.get('signed_at', 'unknown')}") print(f" Signatures after revocation are not trustworthy.") sys.exit(1) # Check trust trusted = is_trusted(fp) trust_label = "TRUSTED" if trusted else "UNTRUSTED" ``` ### Technical Analysis The verifier obtains two security-sensitive identity values from `signer.json`: 1. `public_key`, which is used to verify the Ed25519 signature. 2. `fingerprint`, which is used for trust and revocation decisions. These values are treated independently. After successfully verifying the signature with the embedded public key, the implementation does not calculate the fingerprint of that verified key or compare it with `signer["fingerprint"]`. In addition, the entire `.skillsig` directory is excluded from the signed manifest. Consequently, an attacker can replace `signer.json`, `manifest.json`, and `signature.bin` without those changes being detected as ordinary folder tampering. A valid signature proves only that the supplied private key signed the supplied manifest; it does not ...[truncated 2451 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Derive the fingerprint directly from the public key that successfully verifies the signature: ```python pub_key = load_public_key_bytes(signer["public_key"].encode("utf-8")) verified_fp = pubkey_fingerprint(pub_key) if signer.get("fingerprint") != verified_fp: print("❌ INVALID SIGNER METADATA — fingerprint does not match public key.") sys.exit(1) ``` 2. Use `verified_fp`, rather than the serialized fingerprint field, for every trust and revocation decision. 3. When evaluating trust, load the trusted public-key file and compare its canonical raw key bytes with the verified signer key. Do not rely solely on the existence of a filename. 4. Cryptographically bind security-sensitive signer metadata to the signature. For example, sign a canonical envelope containing: - Manifest or manifest hash - Full signer public key or its full SHA-256 fingerprint - Signing timestamp - Tool and format version - Chain head, if provenance is supported 5. Use a full-length fingerprint internally. A shortened fingerprint may be retained only as a display value. 6. Add regression tests that construct a signature using one key while supplying another trusted fingerprint. Verification must reject the package. 7. Clearly distinguish cryptographic validity from trust status in the command output and exit behavior. ]]>
