T06 · System Persistence
- Location
scripts/install.py:75- Finding
Installer Automatically Enables Cross-Session Background Persistence
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This skill has a coherent financial-news-crawler purpose, but it installs persistent background execution by default and contains unsafe file and browser-crawling behavior that needs user review before installation.
Install only if you are comfortable with a user-level background crawler that persists across logins, downloads browser components, scrapes third-party sites, and writes/deletes local article files. Review or patch ticker validation, service opt-in/uninstall behavior, dependency pinning, and the Chromium sandbox setting before using it on a machine with sensitive files or private network access.
scripts/install.py:75Installer Automatically Enables Cross-Session Background Persistence
scripts/finviz_query.py:66Unvalidated Ticker Values Can Cause Recursive Deletion Outside the Article Directory
scripts/finviz_crawler.py:392Ticker Path Traversal Allows Article Writes Outside the Configured Storage Root
scripts/finviz_crawler.py:471Remote Headline Links Are Crawled Without SSRF Destination Validation
scripts/finviz_crawler.py:709Chromium Sandbox Is Disabled While Rendering Untrusted Third-Party Pages
scripts/install.py:30Installer Retrieves and Executes Unpinned Third-Party Packages and Browser Components
The documented behavior goes beyond a passive news crawler and query tool by including mutation of the database and destructive deletion of article files when removing tickers, while also omitting actual evidence of crawler behavior in this file. This mismatch is dangerous because users or orchestration systems may grant trust based on the stated purpose while the skill performs broader state-changing actions than expected.
The script is described and named as a read/query utility for summarization, but it also exposes state-changing management operations including adding/removing tickers and, via removal, deleting stored articles from both SQLite and disk. This capability mismatch is dangerous because operators or higher-level agents may invoke it under the assumption that it is read-only, enabling unintended destructive actions in automation contexts such as cron jobs or agent toolchains.
The remove_tickers function deletes database rows from the articles table and recursively removes article files from disk, which goes well beyond simple ticker untracking. In an agent skill advertised as a query tool for local news data, this creates a high-risk destructive primitive that can cause permanent data loss if misused, invoked unexpectedly, or targeted with the wrong ticker set.
The README states that installation will create persistent storage under the user's home directory and set up a background service, but it does not prominently warn that the install process makes ongoing system changes beyond a one-time local setup. For an agent skill, this can lead users to authorize installation without understanding that a continuously running service and retained data will persist after the initial command, which increases the risk of unintended resource use, privacy exposure, and difficult-to-notice long-lived behavior.
The skill advertises installation and operation of Python scripts that can read/write local files, access the network, inspect environment settings, and run shell commands, but it does not declare any explicit tool scope or permissions boundary. In an agent ecosystem, this broad undeclared capability increases the chance of accidental overreach or misuse because operators cannot easily constrain what the skill is allowed to do.
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.
🤖 **Built for AI summarization** — the query tool outputs clean text/JSON optimized for LLM digests. Pair with an OpenClaw cron job for automated morning briefings, evening wrap-ups, or weekly investment summaries.
💾 **Auto-cleanup** — configurable expiry automatically deletes old articles from both the database and disk. Set `--expiry-days 30` to keep a month of history, or `0` to keep everything forever.
🔄 **Daemon architecture** — runs as a background service that starts/stops with OpenClaw. No manual intervention needed after setup. Works with systemd (Linux) and launchd (macOS).
The architecture section describes the query tool as read-only, but earlier documented commands allow adding and removing tickers in the SQLite database. This contradiction can mislead users and higher-level agents into treating a mutating tool as safe for read-only contexts, increasing the risk of unauthorized or unintended data changes.
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.finviz.crawler</string>
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.finviz.crawler</string>
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.finviz.crawler</string>
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.finviz.crawler</string>
The code deletes stored .md files and corresponding SQLite records in expire_old_articles, and this behavior is only implied by implementation details and the --expiry-days option text. There is no explicit runtime warning, confirmation prompt, or broader user disclosure near startup that retained data will be automatically purged.
Irreversible deletion of files and database rows occurs immediately with no confirmation prompt, no --force gate, and no warning summarizing the scope of data to be removed. In unattended or agent-driven environments, this sharply increases the chance of accidental destructive execution and makes recovery difficult or impossible.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def run(cmd, check=True):
print(f" → {' '.join(cmd)}")
return subprocess.run(cmd, check=check, capture_output=True, text=True)
def pip_install(packages):
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
def setup_tickers_db(settings_dir):
"""Create tickers SQLite DB with default tickers."""
os.makedirs(settings_dir, exist_ok=True)
db_path = os.path.join(settings_dir, "finviz.db")
conn = sqlite3.connect(db_path)
This code creates a launchd LaunchAgent for automatic execution at login, which is a real persistence mechanism even if used for a legitimate background crawler. Persistence increases risk because compromised or modified crawler code would be re-executed automatically on future logins, and users may not realize the install script is establishing autorun behavior.
print(" Start now with: systemctl --user start finviz-crawler.service")
def setup_launchd_plist(script_dir, python_exe):
"""Create launchd plist (macOS only)."""
plist_dir = os.path.expanduser("~/Library/LaunchAgents")
os.makedirs(plist_dir, exist_ok=True)
The function sets up a LaunchAgents directory specifically for launchd-based persistence on macOS. While consistent with the skill's daemon/service context, it still establishes automatic execution and therefore meaningfully enlarges the blast radius of any later code compromise or tampering.
def setup_launchd_plist(script_dir, python_exe):
"""Create launchd plist (macOS only)."""
plist_dir = os.path.expanduser("~/Library/LaunchAgents")
os.makedirs(plist_dir, exist_ok=True)
crawler_path = os.path.join(script_dir, "finviz_crawler.py")
The installer prepares the crawler script path for inclusion in a persistent launchd configuration. Because this ties a local script to automatic execution, any unauthorized modification of that script would be repeatedly launched without further user action.
def setup_launchd_plist(script_dir, python_exe):
"""Create launchd plist (macOS only)."""
plist_dir = os.path.expanduser("~/Library/LaunchAgents")
os.makedirs(plist_dir, exist_ok=True)
crawler_path = os.path.join(script_dir, "finviz_crawler.py")
log_path = os.path.expanduser("~/Library/Logs/finviz-crawler.log")
The associated log path itself is not dangerous, but it is part of the broader LaunchAgent setup that creates persistence. In context, this line supports a login-triggered background service, which is a legitimate but still security-relevant persistence capability.
def setup_launchd_plist(script_dir, python_exe):
"""Create launchd plist (macOS only)."""
plist_dir = os.path.expanduser("~/Library/LaunchAgents")
os.makedirs(plist_dir, exist_ok=True)
crawler_path = os.path.join(script_dir, "finviz_crawler.py")
log_path = os.path.expanduser("~/Library/Logs/finviz-crawler.log")
The installer constructs the LaunchAgent plist content that will cause the crawler to run automatically. This is a true persistence mechanism; in a benign admin tool this may be expected, but from a security-analysis perspective it remains a notable capability because it can survive user sessions and repeatedly execute code.
crawler_path = os.path.join(script_dir, "finviz_crawler.py")
log_path = os.path.expanduser("~/Library/Logs/finviz-crawler.log")
plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
The PLIST header is another instance of the same launchd persistence artifact. In the context of a background financial crawler, autorun is operationally understandable, but it is still a security-relevant persistence capability because it ensures code execution at login.
log_path = os.path.expanduser("~/Library/Logs/finviz-crawler.log")
plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.finviz.crawler</string>
The PLIST header is another instance of the same launchd persistence artifact. In the context of a background financial crawler, autorun is operationally understandable, but it is still a security-relevant persistence capability because it ensures code execution at login.
log_path = os.path.expanduser("~/Library/Logs/finviz-crawler.log")
plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.finviz.crawler</string>
The LaunchAgent label and program arguments define the persisted task that will run at login. This becomes dangerous if an attacker can modify the referenced interpreter, script, or surrounding environment, since launchd will then repeatedly execute the altered payload.
plist_content = f"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.finviz.crawler</string>
<key>ProgramArguments</key>
Writing the plist file to ~/Library/LaunchAgents is the concrete step that establishes macOS persistence. The skill explicitly aims to run as a background daemon, so this is contextually expected, but it still creates long-lived automatic execution that would amplify any downstream compromise.
<key>StandardOutPath</key><string>{log_path}</string>
<key>StandardErrorPath</key><string>{log_path}</string>
</dict>
</plist>"""
plist_path = os.path.join(plist_dir, "com.finviz.crawler.plist")
with open(plist_path, "w") as f:
f.write(plist_content)
The plist write operation persists the autorun configuration onto disk. Even benign persistence should be tracked because installers that modify LaunchAgents can be abused or overlooked by users, leading to durable execution of changed code later on.
<key>StandardErrorPath</key><string>{log_path}</string>
</dict>
</plist>"""
plist_path = os.path.join(plist_dir, "com.finviz.crawler.plist")
with open(plist_path, "w") as f:
f.write(plist_content)
print(f" 📝 Plist file: {plist_path}")
No suspicious patterns detected.