T09 · Insecure Skill Coding Practices
- Location
scripts/cross_post.py:41- Finding
API credentials are collected through visible terminal input
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill is a legitimate social-posting helper, but it can publish content to public accounts with weak safeguards and some behavior does not match its description.
Review carefully before installing. Use it only when you intend immediate public posting, run preview first, provide narrowly scoped social API credentials, avoid entering secrets in recorded or shared terminals, and treat the all-platform and scheduling claims as unreliable in this version.
scripts/cross_post.py:41API credentials are collected through visible terminal input
scripts/cross_post.py:24Credential file is secured only after plaintext secrets are written
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
user_id = data["data"]["id"]
config["twitter_user_id"] = user_id
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
user_id = data["data"]["id"]
config["twitter_user_id"] = user_id
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
user_id = data["data"]["id"]
config["twitter_user_id"] = user_id
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
user_id = data["data"]["id"]
config["twitter_user_id"] = user_id
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
user_id = data["data"]["id"]
config["twitter_user_id"] = user_id
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
user_id = data["data"]["id"]
config["twitter_user_id"] = user_id
The description mostly matches the general domain and intent: the script is clearly for social posting to Twitter/X, Reddit, and LinkedIn, and it includes Reddit title/subreddit handling plus Twitter thread support. However, there are material mismatches. First, the headline capability of cross-posting to multiple platforms from one prompt is not functioning as described: when --platform all is selected, the code sets platforms = ['all'] and then never dispatches to twitter/reddit/linkedin, so no posts occur. Second, the description explicitly claims scheduling social posts, but there is no scheduling logic, delayed execution, queueing, or time-based posting. Third, the claimed professional formatting for LinkedIn is not really implemented; format_platforms() is a no-op and is never used. These are material description-to-behavior mismatches, even though the core social-posting purpose is aligned.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# LinkedIn
print("\nLinkedIn (optional):")
config["linkedin_access_token"] = input(" Access Token (leave blank to skip): ").strip()
config["linkedin_person_urn"] = input(" Person URN: ").strip()
write_config(config)
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# LinkedIn
print("\nLinkedIn (optional):")
config["linkedin_access_token"] = input(" Access Token (leave blank to skip): ").strip()
config["linkedin_person_urn"] = input(" Person URN: ").strip()
write_config(config)
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
"""Post to LinkedIn."""
token = config.get("linkedin_access_token", "")
if not token:
print("[LinkedIn] SKIPPED: No access token configured")
return
headers = {
LinkedIn posts are hardcoded to PUBLIC visibility and are published immediately without warning or confirmation. In an agent setting, this is especially risky because a mistaken prompt or inclusion of internal information could cause instant public disclosure under the user's professional identity, creating reputational and confidentiality impact.
The skill invokes a Python script with access to filesystem, environment variables, network, and shell execution but does not declare any tool scope or permission boundaries in the manifest. In an agent setting, that omission weakens reviewability and can lead to overbroad execution of sensitive actions such as reading credentials and posting externally without explicit constraint.
The manifest uses broad trigger phrases like 'post this' or 'share on twitter/reddit/linkedin,' which can cause the skill to activate on common user requests without sufficient precision. In a skill that performs external posting, over-triggering increases the risk of unintended publication of sensitive, draft, or private content to third-party platforms.
The skill description does not clearly warn that using it transmits user content to external social-media services. In this context, omission of that warning is risky because users may provide sensitive or unpublished text expecting local assistance, not immediate distribution to Twitter/X, Reddit, and LinkedIn.
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
## Security
Config file is chmod 600. Never commit config.json.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
if not user_id:
try:
req = urllib.request.Request(
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
if not user_id:
try:
req = urllib.request.Request(
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
if not user_id:
try:
req = urllib.request.Request(
"https://api.twitter.com/2/users/me",
headers=headers,
)
resp = urllib.request.urlopen(req, timeout=10)
Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.
auth = urllib.request.HTTPBasicAuthHandler()
credentials = f"{client_id}:{client_secret}"
encoded = credentials.encode()
encoded_b64 = __import__("base64").b64encode(encoded).decode()
data = urllib.parse.urlencode({
"grant_type": "password",
The tool submits Reddit posts immediately without a confirmation step, despite transmitting potentially sensitive or unintended user-provided content to an external public platform. In an agent skill context, where generated text may include confidential data or be triggered with ambiguous user intent, one-step live posting materially increases the chance of accidental disclosure.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
try:
req = urllib.request.Request(
"https://api.linkedin.com/v2/ugcPosts",
data=json.dumps(payload).encode(),
headers=headers,
method="POST",
The main post command performs live external publication with no overall confirmation barrier, increasing the risk of accidental or prompt-induced posting. Because this skill is designed for cross-platform publication, a single mistaken invocation can amplify unintended disclosure across multiple social accounts.
The default 'all' option is broken because the code sets platforms = ['all'] and then never maps that value to twitter, reddit, and linkedin, so nothing is posted while still printing 'Done!'. In a publishing tool, this mismatch between claimed and actual behavior is dangerous because users may rely on false assumptions about what was published, potentially causing missed disclosures, inconsistent communications, or accidental retries to the wrong platforms.
The manifest says the skill can be used to schedule social posts, but the CLI only supports immediate post, preview, and init-config actions. There is no code for delayed execution, storing scheduled jobs, or accepting a schedule time parameter, so the implementation does not match the described scope.