Back to skill

Security audit

Poetry Hub

Security checks for vulnerabilities and agentic risk

Overview

This skill is for a collaborative poetry hub, but its command wrapper and agent loop can unexpectedly run forever, post to the shared service, and reset shared posts even for commands that look read-only.

Review before installing. Use only in a disposable or classroom Poetry Hub where unexpected posting and reset are acceptable. Do not rely on the state, feed, or register commands as read-only or bounded until the entrypoint dispatch is fixed and reset is gated by explicit user or server authorization.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
poetry_hub_entrypoint.py:8
Finding
All CLI Commands Unexpectedly Launch an Infinite Mutating Agent<![CDATA[ ## Vulnerability Details **File Location**: `poetry_hub_entrypoint.py:8-20`; `poetry_hub_agent.py:58-87` **Vulnerability Type**: Command dispatch failure and unintended remote state mutation **Risk Level**: High ### Vulnerable Code `poetry_hub_entrypoint.py:8-20`: ```python def main(): if len(sys.argv) < 2: print("Usage: poetry_hub_entrypoint.py [register|run|state|feed|reset]") sys.exit(2) cmd = sys.argv[1] if cmd == "register": # Run the existing agent's register path subprocess.run([sys.executable, "poetry_hub_agent.py", "register"], check=True) elif cmd == "run": subprocess.run([sys.executable, "poetry_hub_agent.py"], check=True) else: # Fallback to delegating to the Python script for other commands if implemented subprocess.run([sys.executable, "poetry_hub_agent.py", cmd], check=True) if __name__ == "__main__": main() ``` `poetry_hub_agent.py:58-87`: ```python def main(): print(f"Initializing poetry-hub agent as {NAME} — {PROFILE}") register_agent() while True: state = get_state() if not state.get("is_running", True): print("Hub not running yet, waiting...") time.sleep(5) continue feed = get_feed() # Simple loop: if fewer than 4 lines, post a line; otherwise post feedback or reset via hub if first line poem_lines = [p for p in feed.get("posts", []) if p.get("agent_name")==NAME] # simplistic if len(poem_lines) < 4: line = f"A line from {NAME} in the voice of {NAME.split('-')[0]}" # placeholder, replace with real generation post_line(line) time.sleep(2) else: # Post a generic FEEDBACK line (start with FEEDBACK:) post_line("FEEDBACK: continue exploring imagery and rhythm.") time.sleep(5) # After some feedback, post FINAL before reset (simplified) post_line("FINAL:\nL ...[truncated 2311 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Implement explicit command dispatch and reject unsupported commands: ```python if cmd not in {"register", "run", "state", "feed", "reset"}: print(f"Unknown command: {cmd}", file=sys.stderr) sys.exit(2) ``` 2. Import and invoke single-purpose functions instead of spawning the same script for every operation: ```python from poetry_hub_agent import register_agent, get_state, get_feed, reset_hub, main if cmd == "register": print(json.dumps(register_agent())) elif cmd == "state": print(json.dumps(get_state())) elif cmd == "feed": print(json.dumps(get_feed())) elif cmd == "reset": print(json.dumps(reset_hub())) elif cmd == "run": main() ``` 3. Make `state` and `feed` strictly read-only and ensure they terminate after one request. 4. Make `register` perform exactly one registration and then exit. 5. Reserve the infinite loop exclusively for an explicit `run` command. 6. Add tests that assert each command's network methods and endpoints. In particular, verify that `state` and `feed` never issue POST requests. 7. Add bounded execution, graceful shutdown handling, retry limits, and backoff to the autonomous run mode. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
poetry_hub_agent.py:48
Finding
Shared Hub Reset Occurs Without Authorization or First-Line Ownership Verification<![CDATA[ ## Vulnerability Details **File Location**: `poetry_hub_agent.py:48-52, 68-85` **Vulnerability Type**: Missing authorization and ownership validation for a destructive shared operation **Risk Level**: High ### Vulnerable Code `poetry_hub_agent.py:48-52`: ```python def reset_hub(): url = f"{BASE_URL}/control/reset" r = requests.post(url, timeout=10) r.raise_for_status() return r.json() ``` `poetry_hub_agent.py:68-85`: ```python feed = get_feed() # Simple loop: if fewer than 4 lines, post a line; otherwise post feedback or reset via hub if first line poem_lines = [p for p in feed.get("posts", []) if p.get("agent_name")==NAME] # simplistic if len(poem_lines) < 4: line = f"A line from {NAME} in the voice of {NAME.split('-')[0]}" # placeholder, replace with real generation post_line(line) time.sleep(2) else: # Post a generic FEEDBACK line (start with FEEDBACK:) post_line("FEEDBACK: continue exploring imagery and rhythm.") time.sleep(5) # After some feedback, post FINAL before reset (simplified) post_line("FINAL:\nLine one\nLine two\nLine three\nLine four") time.sleep(3) reset_hub() time.sleep(2) ``` ### Technical Analysis The reset operation sends an unauthenticated POST request to the destructive `/control/reset` endpoint. No credential, ownership proof, round identifier, capability token, or confirmation is supplied. The decision to reset is based only on the number of posts made by the current agent identity: ```python poem_lines = [ p for p in feed.get("posts", []) if p.get("agent_name") == NAME ] ``` This calculation does not establish: - That the current agent authored the first line. - That the current round contains exactly four composition lines. - That all connected agents had an opportunity to provide feedback. - That the most recent post be ...[truncated 2129 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Protect `/control/reset` with server-enforced authentication and authorization. The server must not rely only on a caller-supplied agent name. 2. Issue an unforgeable per-round capability or ownership token to the agent that creates the first line. Require that token for finalization and reset. 3. Associate reset requests with an immutable round identifier: ```json { "round_id": "server-issued-round-id", "ownership_token": "unforgeable-capability" } ``` 4. Verify server-side that: - The requester owns the first post of the active round. - The supplied round identifier is still active. - The final revision has been posted. - Feedback and timing requirements have been satisfied. - The round has not already been reset. 5. Make reset idempotent and reject stale requests to prevent time-of-check/time-of-use races. 6. Correct the client-side phase calculation. Separate composition lines from `FEEDBACK:` and `FINAL:` posts, count all participants' composition lines, and explicitly verify the first post's author. 7. Re-fetch state immediately before finalization and reset, and abort if ownership or round state has changed. 8. Apply the documented waiting period rather than the current three-second delay. Timing should also be enforced by the server because client-side delays are not a security boundary. 9. Disable automatic reset by default. Require an explicit configuration option or orchestrator authorization before enabling destructive control operations. 10. Add rate limiting, audit logging, and abuse detection for control endpoints to reduce repeated reset attacks. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (20)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared description presents a simple interaction/observation skill, but the body instructs autonomous polling, posting, persona selection, and use of a destructive control/reset endpoint. This mismatch is dangerous because users or orchestrators may invoke the skill expecting passive participation while it can act continuously and modify shared state without explicit, informed consent.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The manifest describes participation, orchestration, and observation, but the code includes a destructive hub reset operation that is not disclosed. This mismatch is dangerous because users or systems may grant the skill broader trust than warranted, leading to unexpected deletion or interruption of shared hub activity.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill exposes a remote control endpoint that resets the shared Poetry Hub, which is a destructive capability not necessary for ordinary participation or observation. Because the main loop invokes this reset automatically after posting FINAL, it can erase or disrupt other agents' shared state and be abused for denial-of-service against the collaborative game.

Missing User Warnings

High
Confidence
99% confidence
Finding
The reset operation is destructive and is performed without any warning, confirmation, or safety check. In this context, that makes accidental or repeated remote deletion of shared session state very likely, especially since the main loop can trigger it automatically.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill specifies networked behavior and the static analysis indicates shell/code capabilities, but it does not declare any tool scope or permission boundaries. That increases the chance an agent runtime grants broader capabilities than necessary, enabling unintended outbound requests or local command execution beyond the stated poetry-hub use case.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
The specification repeatedly frames the game as English-only, including requiring "one line of English poetry." This imposes a language constraint without any opt-in, alternative language support, or justification such as a region-specific compliance requirement.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill documents a reset control action that clears shared posts, but it does not provide a clear warning that this is destructive to shared hub data. In a multi-agent collaborative service, an automatic reset can disrupt other participants, erase context, and be abused for denial-of-service or sabotage of the shared activity.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The skill allows use of externally supplied LLM_API_URL and LLM_API_KEY and suggests sending content to a custom LLM service, but it does not warn users that prompts, generated text, or possibly sensitive hub data may be transmitted to a third party. This creates data exfiltration and secret-handling risk, especially if agents forward conversation context or operational metadata to an untrusted endpoint.

External Transmission

Medium
Category
Data Exfiltration
Content
def register_agent():
    url = f"{BASE_URL}/agents/register"
    data = {"name": NAME, "profile": PROFILE}
    r = requests.post(url, json=data, timeout=10)
    r.raise_for_status()
    return r.json()
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The post_line function transmits caller-supplied text to an external service without any disclosure, validation, or consent mechanism. In a skill context, this can cause users to unknowingly send sensitive or inappropriate content to a third-party endpoint, creating privacy and data handling risks.

External Transmission

Medium
Category
Data Exfiltration
Content
def post_line(text):
    url = f"{BASE_URL}/posts"
    payload = {"agent_name": NAME, "text": text}
    r = requests.post(url, json=payload, timeout=10)
    r.raise_for_status()
    return r.json()
Confidence
80% confidence
Finding
This POST sends text content to an external service, which is core functionality, but it becomes security-relevant because the text parameter may originate from user input and there is no visible disclosure or boundary around data sharing. In a collaborative hub context this is expected behavior, but it still creates privacy and exfiltration risk if users are unaware.

Intent-Code Divergence

Medium
Confidence
92% confidence
Finding
The inline comment suggests reset should be conditional, but the implementation resets the hub unconditionally after posting FINAL. This discrepancy increases operational risk because reviewers may underestimate the behavior, while actual execution consistently disrupts the shared service state.

Session Persistence

Medium
Category
Rogue Agent
Content
feed = get_feed()
        # Simple loop: if fewer than 4 lines, post a line; otherwise post feedback or reset via hub if first line
        poem_lines = [p for p in feed.get("posts", []) if p.get("agent_name")==NAME]  # simplistic
        if len(poem_lines) < 4:
            line = f"A line from {NAME} in the voice of {NAME.split('-')[0]}"  # placeholder, replace with real generation
            post_line(line)
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
cmd = sys.argv[1]
    if cmd == "register":
        # Run the existing agent's register path
        subprocess.run([sys.executable, "poetry_hub_agent.py", "register"], check=True)
    elif cmd == "run":
        subprocess.run([sys.executable, "poetry_hub_agent.py"], check=True)
    else:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Run the existing agent's register path
        subprocess.run([sys.executable, "poetry_hub_agent.py", "register"], check=True)
    elif cmd == "run":
        subprocess.run([sys.executable, "poetry_hub_agent.py"], check=True)
    else:
        # Fallback to delegating to the Python script for other commands if implemented
        subprocess.run([sys.executable, "poetry_hub_agent.py", cmd], check=True)
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
subprocess.run([sys.executable, "poetry_hub_agent.py"], check=True)
    else:
        # Fallback to delegating to the Python script for other commands if implemented
        subprocess.run([sys.executable, "poetry_hub_agent.py", cmd], check=True)

if __name__ == "__main__":
    main()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The manifest advertises a collaborative poetry skill but does not disclose that it can call `/control/reset`, a destructive administrative action that can wipe or restart shared hub state. Hidden destructive capability increases the chance that users or orchestrators invoke the skill without understanding that it can disrupt other participants or destroy session continuity.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The manifest grants broad ability to perform network actions against an external service, including registration, posting, state inspection, and reset, without describing when those actions are allowed or what user intent should trigger them. Lack of trigger scope or activation constraints makes unintended external side effects more likely, especially in an autonomous agent setting where the skill could be invoked too broadly.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
An administrative reset endpoint is exposed even though the stated purpose is collaboration and observation, which do not inherently require destructive control over the service. This mismatch broadens the skill's privilege surface and can enable denial of service or sabotage of the poetry game if the endpoint is triggered accidentally or abusively.

Natural-Language Policy Violations

Low
Confidence
31% confidence
Finding
The file references a specific external service name, but it does not contain any explicit language or locale enforcement instructions. Because SQP-3 is limited to natural-language policy violations such as forcing a language or locale, this is at most a weak signal and likely not a true violation.

Static analysis

No suspicious patterns detected.