Back to skill

Security audit

Agent Attestation

Security checks for vulnerabilities and agentic risk

Overview

This skill is a coherent local agent reputation prototype, but it has real review-worthy risks around local file writes, private key storage, and trust claims that are not consistently enforced.

Review before installing or using this for any real trust decision. Treat it as a prototype unless the legacy v1/v2 modules are removed or clearly disabled, agent names are constrained to safe identifiers, key and KV storage are moved to protected directories with restrictive permissions, and private keys or sensitive identity data are protected at rest.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
attestation_system_v3.py:54
Finding

Agent Name Allows Key-File Path Traversal and Arbitrary File Overwrite

Content
View full analysis
Optional[ed25519.Ed25519PrivateKey]: """Load private key from file.""" private_path = self.keys_dir / f"{agent_name}.key" if not private_path.exists(): return None with open(private_path, 'rb') as f: private_pem = f.read() return serialization.load_pem_private_key( private_pem, password=None, backend=default_backend() ) def load_public_key(self, agent_name: str) -> Optional[ed25519.Ed25519PublicKey]: """Load public key from file.""" public_path = self.keys_dir / f"{agent_name}.pub" if not public_path.exists(): return None with open(public_path, 'rb') as f: public_pem = f.read() return serialization.load_pem_public_key( public_pem, backend=default_backend() ) ``` ### Technical Analysis `agent_name` is incorporated directly into private- and public-key paths without validation. A value containing `../` can escape `keys_dir`, while an absolute path causes `pathlib` to disregard the configured base directory. The code also follows filesystem symbolic links. When the computed key pair does not already exist, object initialization generates a new pair and writes both files using `wb`, which truncates existing writable files. If both attacker-selected files already exist and contain valid PEM keys, the loading path can instead cause the application to consume keys outside the intended ke ...[truncated 1607 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
attestation_system_v3.py:35
Finding

Ed25519 Private Keys Are Stored Unencrypted Without Enforced Access Permissions

Content
View full analysis
Tuple[str, str]: """Generate a new Ed25519 keypair for an agent.""" private_key = ed25519.Ed25519PrivateKey.generate() public_key = private_key.public_key() # Serialize keys private_pem = private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption() ) public_pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) # Save to files private_path = self.keys_dir / f"{agent_name}.key" public_path = self.keys_dir / f"{agent_name}.pub" with open(private_path, 'wb') as f: f.write(private_pem) with open(public_path, 'wb') as f: f.write(public_pem) ``` ### Technical Analysis Private signing keys are serialized using `serialization.NoEncryption()` and written through ordinary `open()` calls. The code does not explicitly create the key directory with mode `0700` or the private-key file with mode `0600`. Actual accessibility therefore depends on the process umask, inherited directory permissions, and the caller-selected location. The comments recommend a secure location, but comments do not enforce confidentiality. If the key directory is shared, backed up insecurely, or created under a permissive umask, another local principal may obtain the private key. ### Attack Path 1. The application initializes `AttestationSystemV3` in a directory accessible to another local user, workspace participant, container process, or ...[truncated 728 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
attestation_system_v2.py:73
Finding

Legacy Attestations Use Forgeable Hashes and Are Scored Without Authentication

Content
View full analysis
dict: """Verify an agent's reputation""" filepath = Path(filepath) if filepath else self.attestations_file if not filepath.exists(): return {"found": False, "message": "No attestations found"} with open(filepath, 'r', encoding='utf-8') as f: data = json.load(f) attestations = [a for a in data.get("attestations", []) if a.get("subject") == agent_name] return { "found": len(attestations) > 0, "count": len(attestations), "attestations": attestations } ``` The v2 implementation repeats the unkeyed hash construction: ```python # Create hash signature canonical = json.dumps(attestation, sort_keys=True, ensure_ascii=False) attestation["signature"] = hashlib.sha256(canonical.encode()).hexdigest()[:16] ``` Its scoring path also accepts records without signature verification: ```python for att in attestations: if att.get("subject") == agent_name: if self.is_expired(att.get("timestamp", "")): expired_attestations.append(att) else: valid_attestations.append(att) total_score = 0.0 vouch_count = 0 for att in valid_attestations: task_value = att.get("task_value", "medium") weight = TASK_VALUE_WEIGHTS.get(task_value, 1.0) # Base score score = ...[truncated 1747 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
handoff_kv.py:31
Finding

Persistent Identity and Reputation Records Lack Confidentiality and Integrity Protection

Content
View full analysis
Dict: """Load the manifest of all keys.""" if self.manifest_file.exists(): with open(self.manifest_file, 'r', encoding='utf-8') as f: return json.load(f) return {"keys": {}, "last_updated": None} def _save_manifest(self) -> None: """Save the manifest.""" self.manifest["last_updated"] = datetime.now(timezone.utc).isoformat() with open(self.manifest_file, 'w', encoding='utf-8') as f: json.dump(self.manifest, f, indent=2, ensure_ascii=False) ``` Values are likewise written directly as plaintext JSON: ```python with open(value_file, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) ``` Identity and reputation are restored without an integrity check: ```python def load_identity(self) -> Optional[Dict]: """Load agent identity from KV.""" data = self.kv.get_with_metadata(self.identity_key) if data: return data.get("value") return None def load_reputation(self) -> Dict: """Load reputation data.""" return self.kv.get(self.reputation_key, {"score": 0}) def load_attestations(self) -> List[Dict]: """Load all attestations.""" return self.kv.get(self.attestations_key, []) ``` ### Technical Analysis The handoff store persists email addresses, identity metadata, reputation values, and complete attestations as unencrypted JSON. As with the key store, directory and file permissions are not explicitly restricted. Records do not carry a message aut ...[truncated 1513 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
attestation_system_v3.py:369
Finding

v3 Scoring Bypasses Input Validation and Accepts Future Timestamps and Unbounded Stakes

Content
View full analysis
now: result["issues"].append("Timestamp is in the future") result["confidence"] *= 0.3 except: pass ``` However, verification only calls `is_expired()` and does not call `validate_input()`: ```python # Check expiration timestamp = attestation.get("timestamp") if timestamp and not self.is_expired(timestamp): result["not_expired"] = True else: result["errors"].append("Attestation expired") result["valid"] = result["signature_valid"] and result["not_expired"] ``` Scoring likewise omits schema validation and adds an unchecked stake: ```python for att in attestations: if att.get("subject") == agent_name: # Check expiration if self.is_expired(att.get("timestamp", "")): expired_attestations.append(att) continue # Check signature if requested if verify_signatures: signer = att.get("attestor", {}).get("name") if not self.verify(att, signer): invalid_signatures.append(att) continue valid_attestations.append(att) total_score = 0.0 vouch_count = 0 for att in valid_attestations: task_value = att.get("task_value", "medium") weight = TASK_VALUE_WEIGHTS.get(task_value, 1.0) score = 1.0 * weight if att.get("stake", {}).get("vouched", False): score += att.get("stake", {}).get("reputation_at_stake", 0.5) vouch_count += 1 total_score += score ``` ### Technical Analysis `validate_input()` is advisory and disconnected from both `verify_attesta ...[truncated 1662 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Advertising Ed25519 signatures and input validation when they are absent creates a false assurance boundary around identity, integrity, and data safety. Combined with undeclared local persistence of identity, reputation, and attestation data, this can enable forgery, corruption, or unintended retention of sensitive trust-state information.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Advertising Ed25519 signatures and input validation when they are absent creates a false assurance boundary around identity, integrity, and data safety. Combined with undeclared local persistence of identity, reputation, and attestation data, this can enable forgery, corruption, or unintended retention of sensitive trust-state information.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Advertising Ed25519 signatures and input validation when they are absent creates a false assurance boundary around identity, integrity, and data safety. Combined with undeclared local persistence of identity, reputation, and attestation data, this can enable forgery, corruption, or unintended retention of sensitive trust-state information.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

Advertising Ed25519 signatures and input validation when they are absent creates a false assurance boundary around identity, integrity, and data safety. Combined with undeclared local persistence of identity, reputation, and attestation data, this can enable forgery, corruption, or unintended retention of sensitive trust-state information.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The code claims to provide Ed25519-backed attestations, but the 'signature' is only a truncated SHA-256 digest over the JSON payload. This is not a digital signature because it uses no private key and provides no authenticity, so any party can forge attestations that appear valid. In a reputation or trust system, this enables impersonation and fabricated trust relationships.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill metadata claims Ed25519 signatures, but the implementation only stores the first 16 hex characters of a SHA-256 digest over the JSON object and never performs asymmetric signing or verification. This provides no authentic proof of origin because anyone can recompute the digest for forged attestations, and the truncation further weakens integrity by reducing collision resistance.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill metadata declares no explicit tool scope or permissions, yet the described implementation requires filesystem read/write access. This creates a trust and containment problem because operators cannot accurately assess what the skill may access or modify, and the skill could persist sensitive data or alter local state without clear declaration.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The manifest suggests a portable attestation system with 'handoff KV', implying some key-value handoff or exchange mechanism. The code persists attestations solely to a local file named attestations.json and exports that file's contents, with no KV abstraction or handoff-oriented storage behavior present.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The method is named and documented as verifying an agent's reputation, but it merely returns attestations whose subject matches the agent name. Because it performs no signature verification, provenance checks, or trust evaluation, callers may treat untrusted records as validated reputation data, enabling spoofed or malicious attestations to influence decisions.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The code advertises input validation, but it only normalizes the reason string and does not validate key fields such as subject, task_value, context structure, timestamp format at creation, or stake_amount bounds. In a reputation and attestation system, unvalidated inputs can enable malformed or manipulated attestations, score inflation, inconsistent processing, and downstream trust decisions based on attacker-controlled data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code writes unencrypted Ed25519 private keys to disk using predictable per-agent filenames and default filesystem behavior, with no permission hardening, passphrase protection, or user-facing warning. If the host is multi-user, the working directory is exposed, backups are accessible, or the path is attacker-controlled, compromise of the private key allows forged attestations and complete trust impersonation.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code persists broader identity data, including email and arbitrary metadata, to local disk in plaintext as part of an agent attestation/reputation system. In this skill context, durable identity storage is core functionality, but storing personal or sensitive fields beyond the minimum necessary increases privacy and data-exposure risk if the KV directory is readable by other users, included in backups, or checked into a workspace.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest description claims three major areas: Ed25519 signatures, input validation, and handoff KV. This file clearly implements signatures and input validation, but there is no code for any key-value handoff storage, retrieval, or exchange mechanism; instead it focuses on local attestation creation, verification, and scoring.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The inline comment frames duplicate-attestation checking as something that 'would be checked against a registry in production,' implying this validation step is part of the intended behavior. In reality, the function performs no duplicate detection whatsoever, so the documentation suggests a protective behavior that the code does not provide.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

This code persists identity metadata and reputation information via save_identity and save_reputation, which are file-writing operations affecting user data. While the file contains general security notes about using secure locations, it does not clearly warn the user at the point of persistence that these example calls will store data on disk.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
98% confidence
Finding

The docstring for get_history states that the store keeps the last 10 versions. However, the implementation explicitly notes that version storage is not implemented and returns only the current record if present, which directly contradicts the documented behavior.

Content

No source excerpt is available for this finding.