Back to skill

Security audit

TopxAI Equity Research

Security checks for vulnerabilities and agentic risk

Overview

This research skill needs review because it can send your contact details to the wrong website and change its own future instructions without clear approval.

Require approval before applying monthly changes to skill instructions. Fix the exhibit downloader's hostname and redirect checks before supplying contact information, and keep secrets out of SEC_USER_AGENT. Preserve independent ledger history and verify calculated financial figures before publishing reports or relying on performance claims. Research files persist in your project, and optional parallel research can consume substantial tokens.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (15)

Tainted flow: 'req' from os.environ.get (line 68, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
96% confidence
Finding

Sending SEC_USER_AGENT to the SEC is intentional and appropriate for this research workflow, and the value is not inherently a credential. However, the destination check uses the substring "sec.gov" anywhere in the URL, so an attacker-controlled URL such as https://attacker.example/sec.gov can receive the configured name and email address; redirects can also forward this header to an unintended destination.

Content

Scanner excerpt · scripts/exhibit.py (reported line 69)May include surrounding context.

python
else:
        ua = BROWSER_UA
    req = urllib.request.Request(src, headers={"User-Agent": ua, "Accept": "*/*"})
    with urllib.request.urlopen(req, timeout=90) as r:
        dst.write_bytes(r.read())
    return dst

Tainted flow: 'req' from os.environ.get (line 98, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/sec.py (reported line 100)May include surrounding context.

python
time.sleep(wait)
    req = urllib.request.Request(url, headers={"User-Agent": user_agent(), "Accept": "application/json"})
    try:
        with urllib.request.urlopen(req, timeout=60) as r:
            data = json.load(r)
    except urllib.error.HTTPError as e:
        raise SystemExit(f"SEC returned HTTP {e.code} for {url}")

Anti-Refusal Statement

High
Category
Anti-Refusal
Confidence
80% confidence
Finding

Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Content

Scanner excerpt · references/pitfalls.md (reported line 44)May include surrounding context.

md
sed wholesale as the next few quarters arrive, and the real-time value and the final value can have opposite signs. It cannot be the sole basis for a real-time warning. **How to check**: read it together with the BIS debt service ratio (DSR) for the same country. State the retrieval date. Re-run a one-sided HP filter yourself on data up to the current period and compare how much the last several quarters' gaps were revised. If the revision is the same order of magnitude as the current gap, draw no warning conclusion.
- **M-15** [L1] A narrowing credit spread does not necessarily mean better risk appetite. It can come from a shorter index duration or a change in rating mix (a smaller CCC weight in the HY index, a changed energy share). Always read the spread against its historical percentile, the index composition and the quality spread (CCC minus BB). The quality spread usually turns before the aggregate spread.
- **M-16** [L1] Reading low volatility as safety is a Minsky-type error. V

Skill declares no tool scope ('permissions' or 'allowed-tools') but code capabilities were detected: env, file_read, file_write, network, shell.

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Monthly research review extends into modifying the skill's own instructions.

Medium
Category
Excessive Agency
Confidence
88% confidence
Finding

The monthly review authorizes editing the skill's own instructions if a proposed change is adopted, without explicitly requiring user approval or restricting which instructions may change. This creates a path for research-derived or injected content to become persistent agent instructions affecting later sessions. The evidence and commit-message requirements improve traceability, but do not establish an authorization boundary; the apparent intent is legitimate workflow improvement rather than malicious modification.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/indicators.md (reported line 27)May include surrounding context.

md
>   For SEC data use `scripts/sec.py` (`facts`, `filings`, `feed`); it caches the raw JSON on disk.
> - **FRED**: the no-key CSV `https://fred.stlouisfed.org/graph/fredgraph.csv?id=<ID>` (add `&cosd=YYYY-MM-DD` to set the start date).
>   Send an honest, plain user agent. If the CSV endpoint is unreachable, switch to the JSON API
>   `https://api.stlouisfed.org/fred/series/observations?series_id=<ID>&api_key=<KEY>&file_type=json&observation_start=YYYY-MM-DD`
>   (free key: fredaccount.stlouisfed.org; keep it in an environment variable such as `FRED_API_KEY`, and never write it in a report or a source list;
>   without a key the API returns 400 "api_key is not set"; add `&vintage_dates=YYYY-MM-DD` to retrieve a historical vintage).
>   Every FRED series in this library (written "FRED: <ID>", "FRED series …" and similar) is retrieved by one of these two routes.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/indicators.md (reported line 255)May include surrounding context.

md
Cause** | SEC EDGAR Form 144 (mandatory electronic filing since 2023-04) | Event-driven | It discloses a "proposed sale", which comes earlier than the "already sold" of Form 4, so it is the earliest link in the chain of insider selling. |
| Biweekly short interest and days-to-cover | _Outcome_ | FINRA Equity Short Interest (reported for the 15th and the month-end settlement dates, published 7 business days after the settlement date) plus the exchanges' short-interest files; by script [A]: `POST https://api.finra.org/data/group/otcMarket/name/consolidatedShortInterest`, headers `Content-Type: application/json` and `Accept: application/json`, body `{"compareFilters":[{"compareType":"EQUAL","fieldName":"symbolCode","fieldValue":"<T>"}],"limit":5000}`, and for a range add `"dateRangeFilters":[{"fieldName":"settlementDate","startDate":"YYYY-MM-DD","endDate":"YYYY-MM-DD"}]`; Warning: sortFields is allowed only when compareFilters gives both symbolCode and settlementDate (the partition keys)

The self-check enforces a CJK-character ban without a language choice or documented justification.

Medium
Category
Output Handling
Confidence
90% confidence
Finding

The check fails any Markdown file other than SKILL.md that contains CJK characters, including quoted source material or multilingual examples. Although the docstring calls these English method files, it provides no justification or opt-in for this blanket language restriction.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/exhibit.py (reported line 95)May include surrounding context.

python
src.write_text(html, encoding="utf-8")
        # Judge by the file, not the exit code: Chrome often exits 2 after writing the PDF ("Teardown watchdog expired").
        out.unlink(missing_ok=True)
        r = subprocess.run([CHROME, "--headless", "--disable-gpu", "--no-pdf-header-footer",
                            "--virtual-time-budget=8000", f"--print-to-pdf={out}", src.as_uri()],
                           capture_output=True, timeout=180)
    if not out.exists() or out.stat().st_size < 1000:

Tainted flow: 'CHROME' from os.environ.get (line 44, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/exhibit.py (reported line 95)May include surrounding context.

python
src.write_text(html, encoding="utf-8")
        # Judge by the file, not the exit code: Chrome often exits 2 after writing the PDF ("Teardown watchdog expired").
        out.unlink(missing_ok=True)
        r = subprocess.run([CHROME, "--headless", "--disable-gpu", "--no-pdf-header-footer",
                            "--virtual-time-budget=8000", f"--print-to-pdf={out}", src.as_uri()],
                           capture_output=True, timeout=180)
    if not out.exists() or out.stat().st_size < 1000:

The void command can overwrite finalized outcomes despite the documented append-only track-record guarantee.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

cmd_void rejects only calls already marked void, allowing a finalized hit or miss to be overwritten and its settlement date replaced. Because cmd_stats excludes void calls from the hit-rate denominator, an operator can selectively void misses to inflate reported performance while retaining only the original evidence and a supplied reason. This is especially relevant to a research skill that promises an append-only, trustworthy public track record, although exploitation requires write access through the ledger tool.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · references/indicators.md (reported line 319)May include surrounding context.

md
over-date value is not the quarter-end value, see the row "XBRL retrieval" in this section) plus the SBC amount in the cash flow statement | Quarterly | Net change in shares = buybacks − incentive issuance; looking only at the buyback amount overstates shareholder return. |
| XBRL retrieval (single-company companyfacts / peer frames / filing instance) | — | Single company: `data.sec.gov/api/xbrl/companyfacts/CIK##########.json` (no dimensions and no company-extension tags); peer cross-section: `https://data.sec.gov/api/xbrl/frames/us-gaap/<tag>/USD/CY2026Q2.json` (duration value, single quarter) or `…/CY2026Q2I.json` (instant value), which for each company and each period returns the latest filing that best fits that calendar period; for dimensional and extension facts, parse the filing instance (see the L5-A row "Revenue disaggregation"); sec.gov requires a declared SEC-format User-Agent. For one company's recent quarters, use `python3 scripts/sec.py facts TICKER`: it handles the Q4-d

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/sec.py (reported line 277)May include surrounding context.

python
def cmd_facts(a) -> None:
    cik = cik_of(a.ticker)
    url = f"https://data.sec.gov/api/xbrl/companyfacts/CIK{cik:010d}.json"
    raw = get(url, f"companyfacts-{cik}.json")
    facts = raw.get("facts", {})
    notes: list = []

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/sec.py (reported line 309)May include surrounding context.

python
def recent(cik: int, since: dt.datetime, forms) -> list:
    d = get(f"https://data.sec.gov/submissions/CIK{cik:010d}.json", f"submissions-{cik}.json")
    r = d["filings"]["recent"]
    rows = []
    for i, acc in enumerate(r["accessionNumber"]):

Historical financial outputs discard promised per-value provenance and derived-quarter flags.

Medium
Category
Excessive Agency
Confidence
99% confidence
Finding

Historical quarterly and annual outputs retain only numeric values, discarding the per-value tag, filing date, period end, and derived-quarter status available during extraction. In an agent research workflow that promises traceable numbers, the blanket evidence grade A can cause calculated quarters to be treated as directly reported facts and makes errors or restatement inconsistencies harder to audit; this is an integrity weakness, not a code-execution vulnerability.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dynamic_code_execution, suspicious.exposed_secret_literal

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/desk.py:41

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
references/indicators.md:27