T09 · Insecure Skill Coding Practices
- Location
scripts/linkedin_auth.py:22- Finding
Predictable OAuth State Does Not Protect Authorization Requests
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This LinkedIn skill is mostly purpose-aligned, but it sends unpublished content and an undocumented agent memory file to Gemini and prints LinkedIn tokens in plaintext.
Review this skill before installing. Use it only with non-sensitive drafts unless you are comfortable sending them to Gemini, run preview with --no-feedback when you want local-only preview behavior, avoid sharing terminal output from token generation, and rotate any LinkedIn token that was printed or logged.
scripts/linkedin_auth.py:22Predictable OAuth State Does Not Protect Authorization Requests
scripts/linkedin_auth.py:78LinkedIn Access Tokens Are Printed in Plaintext
scripts/linkedin.py:253Preview Mode Transmits Draft Content to Gemini by Default
scripts/generate_draft.py:13Undocumented Agent Memory File Is Read and Sent to Gemini
scripts/linkedin.py:1Runtime Dependencies Are Not Version-Pinned
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"redirect_uri": REDIRECT_URI,
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url, data=data, headers=headers)
response.raise_for_status()
return response.json()
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
with console.status("[bold green]Generating draft post..."):
try:
response = requests.post(API_URL, params=params, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
draft_content = result['candidates'][0]['content']['parts'][0]['text']
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
with console.status("[bold green]Analyzing post against top-performers..."):
try:
response = requests.post(GEMINI_API_URL, params=params, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
"Content-Type": "application/x-www-form-urlencoded"
}
response = requests.post(url, data=data, headers=headers)
response.raise_for_status()
return response.json()
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
with console.status("[bold green]Analyzing post against top-performers..."):
try:
response = requests.post(API_URL, params=params, headers=headers, json=payload)
response.raise_for_status()
result = response.json()
Operating as a local CLI analysis tool with undeclared external generative AI usage is materially different from a straightforward LinkedIn integration. This can result in drafts or business content being transmitted to third-party AI services without clear user awareness, creating confidentiality and compliance concerns.
Operating as a local CLI analysis tool with undeclared external generative AI usage is materially different from a straightforward LinkedIn integration. This can result in drafts or business content being transmitted to third-party AI services without clear user awareness, creating confidentiality and compliance concerns.
Operating as a local CLI analysis tool with undeclared external generative AI usage is materially different from a straightforward LinkedIn integration. This can result in drafts or business content being transmitted to third-party AI services without clear user awareness, creating confidentiality and compliance concerns.
Operating as a local CLI analysis tool with undeclared external generative AI usage is materially different from a straightforward LinkedIn integration. This can result in drafts or business content being transmitted to third-party AI services without clear user awareness, creating confidentiality and compliance concerns.
Operating as a local CLI analysis tool with undeclared external generative AI usage is materially different from a straightforward LinkedIn integration. This can result in drafts or business content being transmitted to third-party AI services without clear user awareness, creating confidentiality and compliance concerns.
Operating as a local CLI analysis tool with undeclared external generative AI usage is materially different from a straightforward LinkedIn integration. This can result in drafts or business content being transmitted to third-party AI services without clear user awareness, creating confidentiality and compliance concerns.
Operating as a local CLI analysis tool with undeclared external generative AI usage is materially different from a straightforward LinkedIn integration. This can result in drafts or business content being transmitted to third-party AI services without clear user awareness, creating confidentiality and compliance concerns.
The skill instructs users to generate and handle OAuth access tokens and to export client credentials in their shell environment. Even if intended for legitimate setup, this increases the risk of token leakage through shell history, process inspection, logs, or accidental reuse in broader agent contexts.
## Setup & Authentication
1. **Create an App**: Go to [LinkedIn Developers](https://www.linkedin.com/developers/apps), create an app, and add the "Sign In with LinkedIn" and "Share on LinkedIn" products.
2. **Generate Token**: Use the included helper script to generate an OAuth 2.0 Access Token.
```bash
# Set your credentials
export LINKEDIN_CLIENT_ID="your_client_id"
The configuration section explicitly requires a LinkedIn access token and a Gemini API key, confirming the skill depends on sensitive credentials. Without clear secret-handling guidance and least-privilege scope restrictions, users may expose these credentials to the environment, logs, or unrelated tools.
## Configuration
- `LINKEDIN_ACCESS_TOKEN`: Valid OAuth 2.0 Access Token.
- `GEMINI_API_KEY`: Required for the Feedback Loop/Optimizer.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
return response.json()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Exchange LinkedIn OAuth Code for Access Token")
parser.add_argument("code", help="The authorization code from the redirect URL")
args = parser.parse_args()
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
return response.json()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Exchange LinkedIn OAuth Code for Access Token")
parser.add_argument("code", help="The authorization code from the redirect URL")
args = parser.parse_args()
The code path around this line culminates in displaying the full access token to the console, which is sensitive credential exposure. Even though the specific matched text is a status message, in context this operation is part of a credential disclosure flow that can leak usable bearer tokens.
print(f"\nExchanging code: {args.code[:10]}...")
try:
token_data = get_access_token(args.code)
print(f"\n✅ SUCCESS! Access Token:\n\n{token_data['access_token']}\n")
print("Copy this token and update your .env file:")
print(f"LINKEDIN_ACCESS_TOKEN={token_data['access_token']}")
except Exception as e:
This line instructs the user to copy the access token into .env and prints the full secret value, increasing exposure through terminal output and encouraging storage in a plaintext file. If the console output or .env file is accessible to other users, malware, backups, or logs, the token can be stolen and abused.
try:
token_data = get_access_token(args.code)
print(f"\n✅ SUCCESS! Access Token:\n\n{token_data['access_token']}\n")
print("Copy this token and update your .env file:")
print(f"LINKEDIN_ACCESS_TOKEN={token_data['access_token']}")
except Exception as e:
print(f"\n❌ Error: {e}")
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
SCOPE = "w_member_social profile openid"
if not CLIENT_ID or not REDIRECT_URI:
print("Error: LINKEDIN_CLIENT_ID and LINKEDIN_REDIRECT_URI must be set in .env")
exit(1)
params = {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
SCOPE = "w_member_social profile openid"
if not CLIENT_ID or not REDIRECT_URI:
print("Error: LINKEDIN_CLIENT_ID and LINKEDIN_REDIRECT_URI must be set in .env")
exit(1)
params = {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
return url
def get_access_token(auth_code):
"""Exchanges the authorization code for an access token."""
url = "https://www.linkedin.com/oauth/v2/accessToken"
data = {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
return url
def get_access_token(auth_code):
"""Exchanges the authorization code for an access token."""
url = "https://www.linkedin.com/oauth/v2/accessToken"
data = {
This code path reveals a live access token to stdout immediately after retrieval. In the context of a LinkedIn-posting skill, that token may permit unauthorized posting or access to associated account data, making accidental disclosure particularly dangerous.
access_token = token_data.get("access_token")
expires_in = token_data.get("expires_in")
print("\n✅ Access Token Generated Successfully!")
print("-" * 60)
print(f"Token: {access_token}")
print("-" * 60)
The skill declares no explicit tool scope or permissions even though its documented behavior requires environment access, local file access, and network connectivity. This weakens user visibility and reviewability, making it easier for the skill to access sensitive resources such as tokens or local history files without clear upfront disclosure.
The script prints the returned LinkedIn access token directly to stdout and again in an .env assignment line. Tokens printed to terminals, CI logs, shell history captures, or shared console recordings can be reused by anyone who obtains them to access the LinkedIn account within the token's scope and lifetime.
No suspicious patterns detected.