T09 · Insecure Skill Coding Practices
- Location
nex-life-logger.py:750- Finding
API Key Is Duplicated into Plaintext and Reversibly Encoded Files
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a real local life-logging tool, but it installs continuous activity monitoring and has under-disclosed privacy and credential-handling risks.
Install only if you intentionally want a persistent local activity recorder. Review setup.sh before running it, stop or disable the service when not needed, avoid configuring an API key unless you accept activity summaries being sent to that provider, prefer environment variables over the built-in key storage, and treat exports as highly private files.
nex-life-logger.py:750API Key Is Duplicated into Plaintext and Reversibly Encoded Files
lib/collector_headless.py:151YouTube Video Identifiers Are Disclosed Before Privacy Filtering
setup.sh:74Setup Installs Mutable and Unverified Third-Party Dependency Versions
The file implements continuous background collection of browser history, active-window details, and transcript-derived context, but the usage/help text does not provide a meaningful privacy warning at the point of execution. Because this is surveillance-style functionality running headlessly, insufficient disclosure materially increases the risk of uninformed or accidental monitoring.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# Sensitive window title keywords - windows matching these are NEVER logged
SENSITIVE_WINDOW_KEYWORDS = [
"password", "passwd", "credential", "keychain", "vault",
"1password", "lastpass", "bitwarden", "keepass", "dashlane",
"private browsing", "incognito", "inprivate",
"bank", "banking",
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# Sensitive window title keywords - windows matching these are NEVER logged
SENSITIVE_WINDOW_KEYWORDS = [
"password", "passwd", "credential", "keychain", "vault",
"1password", "lastpass", "bitwarden", "keepass", "dashlane",
"private browsing", "incognito", "inprivate",
"bank", "banking",
The installer unconditionally creates and starts a persistent background collector via systemd or launchd, which causes continuous monitoring after setup. In the context of a life-logging tool that captures browser history, active windows, and media activity, auto-starting collection without an explicit consent prompt or strong warning materially increases privacy risk and can surprise users about ongoing surveillance.
The README promotes silent tracking of browser history, active windows, and YouTube transcripts without presenting an equally prominent upfront warning about the scope and sensitivity of the collected data. Even if the tool is local-only, users may install it without fully understanding that it continuously records behavioral history, which creates meaningful privacy and consent risk.
The skill exposes significant capabilities—shell execution, file access, environment access, and potential network use—without declaring an explicit tool scope or permission boundary. Because the skill handles highly sensitive local activity data and includes setup/service commands, an agent could invoke powerful operations without clear user-visible constraints, increasing the chance of overreach or unintended data exposure.
The skill centers on persistent background collection of browser history, active window focus, and transcripts, but the user-facing description does not prominently warn that this is highly sensitive surveillance data. Users may invoke or install the skill without appreciating the privacy implications, especially since it can run a background collector and retain detailed behavioral history.
The trigger phrases are broad everyday requests like browsing history, productivity, and what I did yesterday, which can cause the skill to activate in loosely related conversations. In a skill that accesses sensitive local surveillance-style data and can start services or export data, overbroad activation increases the risk of unintended collection, querying, or disclosure.
The collector imports and later uses YouTube transcript retrieval even though the skill description emphasizes local-only activity tracking. Fetching transcripts can send watched video identifiers or related metadata to external services, creating a discrepancy between user expectations and actual data flows.
This code fetches YouTube transcripts for visited videos, extending collection beyond passive local logging into active retrieval of remote content. In a life-logging skill, that increases sensitivity because user viewing behavior is enriched and potentially disclosed to third parties without obvious consent at the collection point.
Transcript retrieval introduces outbound network activity tied to browsing history, but that behavior is not clearly disclosed in this collector flow. In a tool marketed around local activity tracking, undisclosed network transmission is especially risky because it violates user expectations about data locality and exposure.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
return appName & "|" & winTitle
end tell
'''
result = subprocess.run(["osascript", "-e", script], capture_output=True, text=True, timeout=5)
if result.returncode == 0:
parts = result.stdout.strip().split("|", 1)
process = parts[0] if parts else ""
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
title = parts[1] if len(parts) > 1 else ""
else:
import subprocess
result = subprocess.run(["xdotool", "getactivewindow", "getwindowname"],
capture_output=True, text=True, timeout=5)
title = result.stdout.strip() if result.returncode == 0 else ""
try:
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
capture_output=True, text=True, timeout=5)
title = result.stdout.strip() if result.returncode == 0 else ""
try:
pid_result = subprocess.run(["xdotool", "getactivewindow", "getwindowpid"],
capture_output=True, text=True, timeout=5)
if pid_result.returncode == 0:
import psutil
The function exports multiple database tables into a single JSON file, including activities, summaries, keywords, and transcripts, which likely contain sensitive personal or behavioral data. Although the docstring states that it exports everything, there is no confirmation prompt, user-facing log/print, or explicit warning about the privacy impact of creating a full data dump.
This code exports life-logging summaries into an HTML file, which can contain sensitive personal information, but the function only has a minimal docstring and provides no visible disclosure, confirmation, or logging to warn the user that private data is being written out. Under the code-file criteria, file writes affecting user data should have some form of user disclosure unless clearly warned elsewhere in the skill, which is not shown here.
This code extracts keywords from user activity data, including search queries, and elsewhere processes transcript text, which are privacy-sensitive inputs. There is no confirmation prompt, user-facing log/print, or explanatory comment/docstring in this file disclosing that personal activity content is being analyzed.
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
Storage methods by platform:
- Windows: Windows Credential Manager (preferred) or DPAPI-encrypted file
- macOS/Linux: Owner-only permission file (chmod 600). For stronger security,
set the AI_API_KEY environment variable instead.
No API keys are stored or transmitted unless the user explicitly configures them.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def _win_store(key):
import subprocess
subprocess.run(["cmdkey", "/delete:" + SERVICE_NAME], capture_output=True)
result = subprocess.run(
["cmdkey", "/generic:" + SERVICE_NAME, "/user:" + ACCOUNT_NAME, "/pass:" + key],
capture_output=True, text=True,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def _win_store(key):
import subprocess
subprocess.run(["cmdkey", "/delete:" + SERVICE_NAME], capture_output=True)
result = subprocess.run(
["cmdkey", "/generic:" + SERVICE_NAME, "/user:" + ACCOUNT_NAME, "/pass:" + key],
capture_output=True, text=True,
The code passes the API key as a command-line argument to cmdkey (/pass:<key>). On Windows, command-line arguments may be exposed to local process inspection, logging, crash dumps, EDR tooling, or other administrative monitoring, which can leak the secret even though the ultimate storage target is Credential Manager.
def _win_store(key):
import subprocess
subprocess.run(["cmdkey", "/delete:" + SERVICE_NAME], capture_output=True)
result = subprocess.run(
["cmdkey", "/generic:" + SERVICE_NAME, "/user:" + ACCOUNT_NAME, "/pass:" + key],
capture_output=True, text=True,
)
This code initializes and persists a local SQLite database containing behavioral data such as activities, summaries, keywords, and transcripts. For code files, SQP-2 applies when file writes affecting user data occur without confirmation, visible logging/print statements, or user-facing warnings; the docstrings here are implementation-focused and do not clearly warn users that this personal data is being stored on disk.
The function stores full transcript text in the transcripts table, which may contain sensitive personal or proprietary content. There is no confirmation prompt, visible user-facing notice, or warning in this file indicating that transcript data will be retained locally, so the persistence happens without disclosure under SQP-2's code-file criteria.
This code sends raw user activity logs and YouTube transcript snippets to a configured external LLM provider via client.chat.completions.create(...). Although the project requires explicit provider configuration, there is no per-request consent, transmission-time warning, or visible data minimization at the point sensitive browsing/application history is exported, so highly personal behavioral data may be disclosed to third parties once configured.
reset_to_defaults() removes the existing user_filters.json file via unlink(), which is a destructive operation because it discards customized settings. This function contains no confirmation prompt and no user-facing disclosure explaining that persisted user configuration will be deleted.
No suspicious patterns detected.