T09 · Insecure Skill Coding Practices
- Location
publisher.py:538- Finding
Unrestricted Remote Image Retrieval Enables Server-Side Request Forgery
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This Ghost publishing skill does what it says, but it gives an agent live publishing, deletion, newsletter, network-fetch, and local-file upload power with weak safety boundaries.
Install only if you trust the calling agent and can point it at a staging or tightly controlled Ghost integration first. Use HTTPS Ghost URLs, restrict who can invoke publish/delete/newsletter actions, avoid passing untrusted image URLs or local paths, and run the skill with filesystem/network access limited to the content and media it genuinely needs.
publisher.py:538Unrestricted Remote Image Retrieval Enables Server-Side Request Forgery
publisher.py:543Arbitrary Local File Read and Upload Through the Image Interface
publisher.py:68Ghost Administrative Credentials and Content May Be Transmitted Over Plaintext HTTP
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
req = urllib.request.Request(url, headers={
"Authorization": f"Ghost {jwt}", "Accept": "application/json"
})
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
req = urllib.request.Request(url, headers={
"Authorization": f"Ghost {jwt}", "Accept": "application/json"
})
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
req = urllib.request.Request(url, headers={
"Authorization": f"Ghost {jwt}", "Accept": "application/json"
})
with urllib.request.urlopen(req) as r:
return json.loads(r.read())
There is a clear mismatch between description and behavior. The description claims an operational Ghost CMS publisher with end-to-end publishing features. The actual code chunk is a non-networked test file whose primary purpose is validating interface conformance and constructor credential behavior. It does not itself publish articles, convert markdown, upload media, or interact with Ghost. This is not just an incomplete excerpt of one helper function; the supplied code's purpose is materially different from the declared skill purpose.
The declared description presents a production publishing skill for Ghost CMS with broad publishing and content-management capabilities. The supplied code chunk, however, is only a test module for one narrow internal function: converting markdown into Ghost Lexical JSON. It contains no Ghost API calls, no publishing workflow, no upload or scheduling logic, and no interface method implementations. This is a materially different primary purpose from the declared skill behavior, so it should be flagged as a mismatch.
publishPost transitions content to a live public URL, and the documented send_newsletter option can notify subscribers, but the interface does not warn about these externally visible effects. In markdown descriptions, actions affecting user data, privacy, or system integrity should include clear disclosure.
This markdown interface defines deletePost(id) as deleting a post, but provides no warning about data loss, confirmation requirement, or irreversible impact. Under the markdown-file criteria, destructive behavior that can affect user data should be accompanied by a warning in the skill description.
The interface explicitly allows adapters to fetch arbitrary remote URLs or read local file paths for image upload, which can enable SSRF against internal services or unintended local file access if untrusted input reaches this method. In the context of an agent-facing publishing skill, this is more dangerous because agents may pass through user-supplied URLs or paths without understanding the privacy and host-impact implications.
The README documents commands that can publish content, trigger newsletters, schedule releases, delete posts, and upload media, but it does not clearly warn users that these actions have real external side effects on a live Ghost instance. In an agent-skill context, missing safety guidance increases the risk of accidental destructive or high-impact actions being invoked against production sites, especially because newsletter sends and deletes may be irreversible or hard to recover from.
The skill declares access to sensitive capabilities via its documented behavior and required environment variables, but it does not define any explicit tool scope such as allowed-tools or permissions. For a publishing skill that can read local files, use network access, and operate with an admin API key, missing scope boundaries increases the chance that an agent invokes it with broader-than-necessary privileges or without adequate operator awareness.
The skill exposes a delete operation against a live CMS but does not prominently warn that deletion is destructive and may remove content irreversibly. In an agent-driven workflow, weak safety wording raises the risk of accidental destructive actions, especially because the skill is designed to operate directly on production Ghost sites using admin credentials.
The uploadImage method will fetch arbitrary user-supplied http(s) URLs server-side and then upload the retrieved bytes to Ghost. In an agent/server environment, this can be abused as SSRF to reach internal services or cloud metadata endpoints, and the fetched content is then relayed onward, increasing the risk of internal data exposure; the publisher context makes this more dangerous because remote asset ingestion is an advertised workflow and likely exposed to untrusted inputs.
The skill can publish with newsletter delivery to subscribers, but the description does not clearly warn that enabling this may trigger outbound email to real users and create privacy, reputational, and operational impact. In agent automation, omission of that warning makes unintended mass communication more likely.
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
cls = pub_mod.GhostPublisher
for name in REQUIRED_METHODS:
assert hasattr(cls, name), f"method {name} missing from GhostPublisher"
assert callable(getattr(cls, name)), f"{name} is not callable"
print(f"[PASS] all seven Publisher Interface v1 methods present: "
f"{sorted(REQUIRED_METHODS)}")
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.
def test_method_signatures():
cls = pub_mod.GhostPublisher
for name, expected_params in REQUIRED_METHODS.items():
sig = inspect.signature(getattr(cls, name))
# Drop 'self'
params = [p for p in sig.parameters if p != "self"]
for p in expected_params:
No suspicious patterns detected.