T09 · Insecure Skill Coding Practices
- Location
scripts/deckly.py:12- Finding
Unrestricted API Base Override Can Exfiltrate Credentials and Documents
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill does what it claims, but it handles passwords, verification codes, presentation uploads, paid actions, and long-lived API keys in ways users should review carefully before installing.
Install only if you are comfortable sending presentation contents to Deckly and letting the agent manage a Deckly account. Avoid entering passwords or verification codes directly in chat, prefer a pre-created API token if possible, do not use this on confidential decks without data-handling approval, avoid custom DECKLY_API_BASE values unless you trust the destination, and review/revoke saved API keys when finished.
scripts/deckly.py:12Unrestricted API Base Override Can Exfiltrate Credentials and Documents
scripts/deckly.py:526Account Passwords Are Collected in Chat and Exposed Through Command-Line Arguments
scripts/deckly.py:38Long-Lived API Key File Is Created Before Restrictive Permissions Are Enforced
README.md:22Quick-Install Instructions Execute an Unpinned Third-Party Package
The skill directs the agent to collect email, password, and a 6-digit email verification code in chat and then perform signup, verify, and login actions. This is highly sensitive authentication material, and handling it in the conversational channel creates a direct account-takeover and secret-exposure risk if logs, transcripts, or tool invocations are accessible.
The skill explicitly instructs the agent to ask the user for an email and password in chat, with no warning about sensitivity or safer alternatives. Collecting credentials in-band through the conversation exposes secrets to logging, retention, prompt leakage, and accidental disclosure to downstream tools or operators.
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
TOKEN=... # from /auth/login
# 1. create + upload
PID=$(curl -s -X POST "$BASE/projects" -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"name":"My Deck"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
curl -s -X POST "$BASE/projects/$PID/upload" -H "Authorization: Bearer $TOKEN" -F "file=@deck.pptx"
The README says the skill uses the Deckly API to analyze and restyle PowerPoint/PDF decks, but it does not clearly warn that user files will be transmitted to a third-party service. This creates a real privacy and data-handling risk because users may provide confidential presentations without understanding that the content leaves the local environment.
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.
The trigger phrase 'Redesign this presentation to look more professional' is broad and maps to a common user intent, which can cause the skill to auto-activate in situations where the user did not explicitly consent to using Deckly or sending files to an external service. In this skill's context, that matters because activation may lead to third-party processing of presentation content, including potentially sensitive business or academic materials.
The skill invokes shell commands, network access, environment-variable based credentials, and local file writes, but declares no explicit tool scope or permissions. That increases the chance an agent executes broader capabilities than necessary, making review, sandboxing, and least-privilege enforcement harder.
The instruction to onboard users 'right in the chat' and specifically forbid sending them to a website removes user choice and pushes them toward insecure credential sharing. In this context, that coercive workflow materially increases the likelihood of credential compromise because the agent becomes the collection point for secrets.
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
Notes:
- `verify` and `login` both create and save a `dk_` API key to `~/.deckly/credentials` (chmod 600). After that, never ask for credentials again unless the user switches accounts.
- New accounts get a free 3-slide preview (0 credits) — perfect for a first trial run.
- To use a different account, the user can pass `DECKLY_EMAIL`/`DECKLY_PASSWORD` or delete `~/.deckly/credentials`.
The skill persists an API key in ~/.deckly/credentials for reuse across sessions, creating long-lived local access to the user's Deckly account. Even with chmod 600, persistent tokens increase the blast radius of host compromise, multi-user environment mistakes, or unintended reuse by future tasks under the same agent context.
Notes:
- `verify` and `login` both create and save a `dk_` API key to `~/.deckly/credentials` (chmod 600). After that, never ask for credentials again unless the user switches accounts.
- New accounts get a free 3-slide preview (0 credits) — perfect for a first trial run.
- To use a different account, the user can pass `DECKLY_EMAIL`/`DECKLY_PASSWORD` or delete `~/.deckly/credentials`.
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
style_preset accepts: a text_style:<id> (e.g. text_style:minimalist_corporate), a template theme id (e.g. dark-blue-business), or any free-text style name (AI-generated). Also collect an optional --custom instruction from the user (e.g. "use our brand blue, keep it minimal").
3. Redesign — for first-time users this automatically runs as a free 3-slide preview. Otherwise confirm cost with quote first.
python scripts/deckly.py quote <project_id> --slides 1-12 # check cost / can_afford
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
# 1. register -> emails a 6-digit code
curl -s -X POST "$BASE/auth/register" -H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"chosen-pw"}'
# 2. verify with the code -> returns a JWT
curl -s -X POST "$BASE/auth/verify-email" -H 'Content-Type: application/json' \
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
TOKEN=... # from /auth/login
# 1. create + upload
PID=$(curl -s -X POST "$BASE/projects" -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"name":"My Deck"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
curl -s -X POST "$BASE/projects/$PID/upload" -H "Authorization: Bearer $TOKEN" -F "file=@deck.pptx"
This endpoint uploads an entire local presentation file to an external third-party service. In the context of a skill that may process sensitive corporate decks, that creates real confidentiality risk if users are not explicitly warned that deck contents leave the local environment and are stored/processed remotely.
# 1. create + upload
PID=$(curl -s -X POST "$BASE/projects" -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"name":"My Deck"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
curl -s -X POST "$BASE/projects/$PID/upload" -H "Authorization: Bearer $TOKEN" -F "file=@deck.pptx"
# 2. extract, then poll until status == extracted
curl -s -X POST "$BASE/projects/$PID/analyze" -H "Authorization: Bearer $TOKEN"
The skill goes beyond deck-redesign functionality by implementing signup, login, token minting, and persistent credential storage. That expands the trust boundary substantially: the agent can collect user credentials, create long-lived API keys, and retain them for future runs, which increases risk if the host, logs, or local filesystem are compromised.
The code reads DECKLY_EMAIL and DECKLY_PASSWORD from the environment, exchanges them for a JWT, then creates and persists a durable API key for the agent. This is dangerous because it silently converts highly sensitive credentials into reusable long-term access that can survive beyond the user’s immediate session and be abused if the config file is exposed.
When DECKLY_EMAIL and DECKLY_PASSWORD are present, the skill transmits them to the remote /auth/login endpoint without any user-facing disclosure in the workflow. Even over HTTPS, silently using environment-provided credentials in an agent context can surprise users and administrators and may violate expectations around secret handling.
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
password = os.environ.get("DECKLY_PASSWORD")
if email and password:
data = _request(base, "POST", "/auth/login",
body={"email": email, "password": password}, auth=False)
return data["access_token"]
raise DecklyError(
"No credentials. Run 'signup' + 'verify' to register in-conversation, "
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
password = os.environ.get("DECKLY_PASSWORD")
if email and password:
data = _request(base, "POST", "/auth/login",
body={"email": email, "password": password}, auth=False)
return data["access_token"]
raise DecklyError(
"No credentials. Run 'signup' + 'verify' to register in-conversation, "
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
password = os.environ.get("DECKLY_PASSWORD")
if email and password:
data = _request(base, "POST", "/auth/login",
body={"email": email, "password": password}, auth=False)
return data["access_token"]
raise DecklyError(
"No credentials. Run 'signup' + 'verify' to register in-conversation, "
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
password = os.environ.get("DECKLY_PASSWORD")
if email and password:
data = _request(base, "POST", "/auth/login",
body={"email": email, "password": password}, auth=False)
return data["access_token"]
raise DecklyError(
"No credentials. Run 'signup' + 'verify' to register in-conversation, "
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
password = os.environ.get("DECKLY_PASSWORD")
if email and password:
data = _request(base, "POST", "/auth/login",
body={"email": email, "password": password}, auth=False)
return data["access_token"]
raise DecklyError(
"No credentials. Run 'signup' + 'verify' to register in-conversation, "
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
password = os.environ.get("DECKLY_PASSWORD")
if email and password:
data = _request(base, "POST", "/auth/login",
body={"email": email, "password": password}, auth=False)
return data["access_token"]
raise DecklyError(
"No credentials. Run 'signup' + 'verify' to register in-conversation, "
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
password = os.environ.get("DECKLY_PASSWORD")
if email and password:
data = _request(base, "POST", "/auth/login",
body={"email": email, "password": password}, auth=False)
return data["access_token"]
raise DecklyError(
"No credentials. Run 'signup' + 'verify' to register in-conversation, "
The upload function sends the entire local presentation file to a remote service, but the code provides no disclosure, confirmation, or data-handling notice before transmission. Since decks commonly contain confidential business, financial, legal, or personal information, silent exfiltration to a third party is a meaningful privacy and compliance risk.
Detected: suspicious.secret_argv_exposure