Back to skill

Security audit

Edgar Risk Diff

Security checks for vulnerabilities and agentic risk

Overview

This skill coherently downloads public SEC filings, caches them locally, and produces risk-factor diffs with no evidence of hidden exfiltration or unsafe persistence.

Install only if you are comfortable with the skill making SEC GET requests, sending an EDGAR User-Agent/contact string to sec.gov, and storing cached filings plus an optional license key under ~/.edgar-risk-diff/. Treat the premium licensing as weak/placeholder-quality and do not store unrelated secrets in the license file or EDGAR_USER_AGENT.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/risk_diff.py:319
Finding
Weak and Intentionally Bypassable Premium License Validation## Vulnerability Details **File Location**: `scripts/risk_diff.py`, lines 319–343 **Vulnerability Type**: Weak authorization and license verification **Risk Level**: Medium ```python # Replace with your Gumroad/LemonSqueezy verify endpoint in production. # For now, accept any key listed here OR any key whose SHA-256 prefix matches. _VALID_LICENSE_PREFIXES = { # SHA-256(b"DEMO-FOR-OPERATOR")[:8] — operator bypass. "9b2d6fb1", # SHA-256(b"EDGARRISK-NOVELTY-2026-A4G7")[:8] — production v1 key (Gumroad). "bc4c1307", } def license_active() -> bool: key = "" if "EDGAR_RISK_LICENSE" in os.environ: key = os.environ["EDGAR_RISK_LICENSE"].strip() elif LICENSE_PATH.exists(): key = LICENSE_PATH.read_text(encoding="utf-8").strip() if not key: return False digest = hashlib.sha256(key.encode()).hexdigest()[:8] return digest in _VALID_LICENSE_PREFIXES def require_license(feature: str) -> None: if license_active(): return ``` ### Technical Analysis License validation compares only the first eight hexadecimal characters of a SHA-256 digest. This reduces the effective verification space to 32 bits rather than relying on the full cryptographic digest or a digital signature. Because both accepted prefixes are embedded in publicly readable source code, an attacker can generate arbitrary candidate strings offline until one produces a matching prefix. The code also explicitly includes an operator-bypass prefix, creating an alternate entitlement path in the distributed application. Although the corresponding plaintext key is shown only in a comment as the hash input description, the presence of a bypass mechanism further undermines the authorization boundary. This issue does not enable operating-system privilege escalation, code execution, credential access, or data exfiltration. It is an application-level authorization weakness affecting the premium feature gate. ### Attack Path 1. Inspect `scripts/ ...[truncated 949 chars]
Remediation
## Remediation Suggestions 1. Replace truncated-hash comparison with cryptographically signed offline licenses. Sign entitlement data with a vendor-controlled private key and verify the complete signature using an embedded public key. 2. If online verification is acceptable, validate licenses through an authenticated HTTPS vendor endpoint and verify the response securely. 3. Remove the operator-bypass value and all alternate production authorization paths from distributed code. 4. Do not treat an unsalted hash or hash prefix as proof that a license was issued by the vendor. 5. Bind licenses to explicit entitlement metadata, such as product, enabled feature, expiration date, and license identifier, and include all metadata in the signed payload. 6. Fail closed on malformed licenses, invalid signatures, expired entitlements, and verification errors. 7. Add automated tests confirming that arbitrary values and prefix collisions cannot activate premium functionality.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (6)

Tainted flow: 'EDGAR_HEADERS' from os.environ.get (line 37, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
def _http_get(url: str) -> bytes:
    _throttle()
    r = requests.get(url, headers=EDGAR_HEADERS, timeout=30)
    r.raise_for_status()
    return r.content
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill explicitly documents capabilities to read environment variables, perform file writes under the user's home directory, and make network requests to SEC domains, but it does not declare any tool scope or permission boundaries in the manifest. That mismatch creates a least-privilege problem: an agent may invoke the skill with broader implicit access than reviewers or policy engines can reliably constrain, increasing the risk of unintended data access or network behavior if the implementation changes or the documentation is inaccurate.

External Transmission

Medium
Category
Data Exfiltration
Content
def list_10k_filings(cik: str, limit: int = 8) -> list[Filing]:
    url = f"https://data.sec.gov/submissions/CIK{cik}.json"
    sub = json.loads(_cached_get(url))
    recent = sub["filings"]["recent"]
    out: list[Filing] = []
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The script sends the EDGAR_USER_AGENT value in every SEC HTTP request, and the default value includes what appears to be an email address. While the module docstring mentions SEC EDGAR as the data source, there is no explicit user-facing warning that a potentially identifying value from the environment will be transmitted over the network.

Intent-Code Divergence

Low
Confidence
97% confidence
Finding
The docstring for `extract_risk_factors` states it 'Picks the *last* Item 1A occurrence to skip table-of-contents references.' However, the implementation iterates all Item 1A matches and chooses the best candidate by validating end markers and maximizing section length, which is materially different behavior.

Description-Behavior Mismatch

Low
Confidence
91% confidence
Finding
The manifest describes the premium feature as embedding-based novelty scoring, which implies use of semantic embeddings. In code, `_hash_vec` builds a deterministic hashed bag-of-bigrams vector and `novelty_scores` computes cosine similarity over that heuristic representation; no embedding model or embedding service is used.

Static analysis

No suspicious patterns detected.