Back to skill

Security audit

Postiz Extended

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly aligned with managing Postiz social posts, but it handles credentials, reusable session cookies, media uploads, and public posting/deletion with weak safeguards.

Install only if you are comfortable giving the skill access to a Postiz account that can publish, upload media, and modify or delete posts. Use an HTTPS Postiz URL, avoid running it on shared machines, clear `/tmp/postiz-cookies.txt` after use, and require explicit confirmation before immediate publishing, file uploads, updates, or deletions.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/post.py:40
Finding

Reusable Authentication Cookies Stored in an Unsafe Shared Temporary File

Content
View full analysis

Vulnerability Details

File Location: scripts/post.py:40, 91-116; scripts/check_duplicates.py:29, 58-88
Vulnerability Type: Unsafe temporary-file handling and plaintext session-token storage
Risk Level: High

Vulnerable Code

scripts/post.py:40, 91-116:

python
COOKIE_FILE = "/tmp/postiz-cookies.txt"

def _load_cookies(self):
    """Load cookies from file if they exist."""
    cookie_path = Path(COOKIE_FILE)
    if cookie_path.exists():
        # Parse Netscape cookie file format
        with open(cookie_path) as f:
            for line in f:
                if line.startswith('#') or not line.strip():
                    continue
                parts = line.strip().split('\t')
                if len(parts) >= 7:
                    self.session.cookies.set(parts[5], parts[6], domain=parts[0])

def _save_cookies(self):
    """Save cookies to file in Netscape format."""
    with open(COOKIE_FILE, 'w') as f:
        f.write("# Netscape HTTP Cookie File\n")
        for cookie in self.session.cookies:
            f.write(f"{cookie.domain}\tTRUE\t{cookie.path}\t"
                   f"{'TRUE' if cookie.secure else 'FALSE'}\t"
                   f"{cookie.expires or 0}\t{cookie.name}\t{cookie.value}\n")

scripts/check_duplicates.py:29, 58-88:

python
COOKIE_FILE = "/tmp/postiz-cookies.txt"

def get_session():
    """Get authenticated session."""
    session = requests.Session()
    
    # Try to load existing cookies
    try:
        with open(COOKIE_FILE) as f:
            for line in f:
                if line.startswith('#') or not line.strip():
                    continue
                parts = line.strip().split('\t')
                if len(parts) >= 7:
                    session.cookies.set(parts[5], parts[6], domain=parts[0])
    except FileNotFoundError:
        pass
    
    # Check if auth works
    resp = session.get(f"{PO
...[truncated 3150 chars]
Remediation
View remediation

Remediation Suggestions

  1. Avoid persistent cookie storage unless it is operationally required. Prefer an in-memory requests.Session.
  2. If persistence is required, store the cookie jar in a private per-user state directory, such as one derived from XDG_STATE_HOME, rather than directly under /tmp.
  3. Create the containing directory with mode 0700.
  4. Create the cookie file atomically with exclusive access and mode 0600, for example by using os.open() with O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW.
  5. Before loading an existing file, use os.lstat() and verify that it:
    • Is a regular file
    • Is owned by the current effective user
    • Is not a symbolic link
    • Is not accessible by group or other users
  6. Use a random, user-specific filename if temporary storage is unavoidable.
  7. Delete the cookie file when the session is no longer needed and provide a logout or credential-revocation workflow.
  8. Apply the same hardened cookie-storage implementation to both scripts rather than maintaining duplicate implementations.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/post.py:39
Finding

Postiz Credentials and Session Data May Be Transmitted over Plaintext HTTP

Content
View full analysis

Vulnerability Details

File Location: scripts/post.py:39, 118-123; scripts/check_duplicates.py:28, 73-78
Vulnerability Type: Missing enforcement of encrypted transport for credentials and authenticated requests
Risk Level: High

Vulnerable Code

scripts/post.py:39, 118-123:

python
POSTIZ_URL = os.environ.get("POSTIZ_URL", "").rstrip("/")

def login(self):
    """Authenticate with Postiz."""
    resp = self.session.post(
        f"{POSTIZ_URL}/api/auth/login",
        json=CREDENTIALS
    )
    if resp.status_code == 200:
        self._save_cookies()
        return True
    return False

scripts/check_duplicates.py:28, 73-78:

python
POSTIZ_URL = os.environ.get("POSTIZ_URL", "").rstrip("/")

resp = session.get(f"{POSTIZ_URL}/api/integrations/list")
if resp.status_code == 401:
    # Login
    resp = session.post(f"{POSTIZ_URL}/api/auth/login", json=CREDENTIALS)
    if resp.status_code != 200:
        raise Exception("Failed to authenticate")

Technical Analysis

POSTIZ_URL is accepted directly from an environment variable without validating its URL scheme. Consequently, values beginning with http:// are accepted.

The login request contains the Postiz email and password in its JSON body. Subsequent requests carry authenticated cookies and may contain social-media content or uploaded media. When HTTP is used, these values are not protected by transport encryption or server authentication.

The requests library verifies TLS certificates when HTTPS is used, but this protection is irrelevant when the configured URL uses plaintext HTTP. The scripts do not warn the user or restrict plaintext connections to a loopback development environment.

Attack Path

  1. A user configures POSTIZ_URL with an http:// endpoint, whether accidentally, for convenience, or through malicious setup instructions.
  2. The script submits the Postiz email and password t ...[truncated 1019 chars]
Remediation
View remediation

Remediation Suggestions

  1. Parse POSTIZ_URL using urllib.parse.urlparse.
  2. Require the scheme to be https before any credential or authenticated request is made.
  3. Reject URLs containing embedded user information or unexpected components.
  4. If local plaintext development must be supported, require an explicit option such as --allow-insecure-http and restrict it to loopback hosts such as 127.0.0.1, ::1, or localhost.
  5. Display a prominent warning when the development override is used.
  6. Keep TLS certificate verification enabled and do not introduce verify=False.
  7. Consider allowing administrators to configure a private certificate-authority bundle for self-hosted deployments instead of disabling TLS verification.
  8. Apply the URL validation centrally and consistently in both scripts.

T08 · Insecure Dependencies

Warning
Location
scripts/post.py:30
Finding

Runtime Dependency Is Not Version-Pinned or Integrity-Locked

Content
View full analysis

Vulnerability Details

File Location: scripts/post.py:30-33; scripts/check_duplicates.py:19-22
Vulnerability Type: Unpinned third-party dependency and non-reproducible dependency resolution
Risk Level: Medium

Vulnerable Code

scripts/post.py:30-33:

python
# /// script
# requires-python = ">=3.10"
# dependencies = ["requests"]
# ///

scripts/check_duplicates.py:19-22:

python
# /// script
# requires-python = ">=3.10"
# dependencies = ["requests"]
# ///

Technical Analysis

Both scripts declare requests without a version constraint or integrity hash. When the scripts are executed through a tool that resolves inline dependencies, the actual package version may vary over time and between environments.

No malicious dependency is present in the audited project, and requests is not a typosquatted package name. The risk arises from allowing a future, unreviewed package release and its transitive dependency graph to enter the execution environment automatically. This also prevents deterministic reproduction of the audited environment.

A dependency resolver may install a later incompatible or compromised release after the project itself has already passed review. Any package installed into the Python environment can execute code during import and then operate with the privileges of the user invoking the script.

Attack Path

  1. A user invokes a script through uv run.
  2. The dependency resolver resolves requests and its transitive dependencies at execution time.
  3. A future release or dependency version differs from the version used during security review.
  4. If that release or the package source is compromised, malicious code is installed or imported.
  5. The malicious dependency executes with the invoking user's local privileges and can access environment variables supplied to the process, including POSTIZ_EMAIL and POSTIZ_PASSWORD.
  6. It may also acce ...[truncated 689 chars]
Remediation
View remediation

Remediation Suggestions

  1. Pin requests to a reviewed, compatible version rather than leaving it unconstrained.
  2. Commit a lockfile that captures the complete transitive dependency graph.
  3. Use package hashes or another integrity-verification mechanism where supported.
  4. Configure dependency resolution to use an approved package index over HTTPS.
  5. Run automated vulnerability scanning against the lockfile and update dependencies through a reviewed process.
  6. Test dependency updates before deployment rather than resolving arbitrary future versions during normal script execution.
  7. Keep the dependency declaration synchronized across both scripts to avoid inconsistent runtime environments.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill advertises broader capabilities than are actually implemented, including extensive platform support, deduplication, media handling, CRUD operations, and scheduling helpers. In security-sensitive agent workflows, this mismatch can cause operators or downstream agents to rely on safeguards or behaviors that do not exist, leading to accidental posting, missed checks, or unsafe automation decisions.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/check_duplicates.py (reported line 52)May include surrounding context.

python
if missing:
        print(f"❌ Missing required environment variables: {', '.join(missing)}")
        print("\nSet them in your shell or .env file:")
        print("  export POSTIZ_URL='https://your-postiz.example.com'")
        print("  export POSTIZ_EMAIL='your@email.com'")
        print("  export POSTIZ_PASSWORD='your-password'")

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/post.py (reported line 89)May include surrounding context.

python
if missing:
        print(f"❌ Missing required environment variables: {', '.join(missing)}")
        print("\nSet them in your shell or .env file:")
        print("  export POSTIZ_URL='https://your-postiz.example.com'")
        print("  export POSTIZ_EMAIL='your@email.com'")
        print("  export POSTIZ_PASSWORD='your-password'")

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill invokes shell commands, reads secrets from environment variables, writes cookies to /tmp, and performs network requests, but it declares no explicit tool scope or permission boundaries. In an agent setting this increases the chance of unintended execution or credential use because the runtime cannot constrain what capabilities the skill is allowed to access.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The activation guidance is broad enough that an agent may select this skill in loosely related contexts involving social content or automation. Because the skill can authenticate, upload media, and publish or modify external content, overly broad triggering raises the risk of unintended or premature use.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
91% confidence
Finding

The skill sends credentials to an external service and stores the resulting session cookie in a local temp file. Although authentication is expected for this use case, this still represents sensitive external transmission and local session materialization that can be mishandled by shared environments or over-broad agent execution.

Content

Scanner excerpt · SKILL.md (reported line 79)May include surrounding context.

bash
# Login and save cookie (required before any API call)
curl -s -c /tmp/postiz-cookies.txt \
  "$POSTIZ_URL/api/auth/login" \
  -H 'Content-Type: application/json' \
  -d "{\"email\":\"$POSTIZ_EMAIL\",\"password\":\"$POSTIZ_PASSWORD\",\"provider\":\"LOCAL\"}"

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The skill documents immediate publishing and deletion operations without a clear warning or confirmation requirement, even though both actions can be irreversible and affect public-facing accounts. In an agent environment, this increases the chance of destructive or reputation-damaging actions from misunderstanding, prompt confusion, or accidental invocation.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

The skill uploads a local file to an external service, which is an intentional capability but still a data exfiltration boundary. Without explicit consent and scoping, an agent could send sensitive local files or media to a remote Postiz instance.

Content

Scanner excerpt · SKILL.md (reported line 189)May include surrounding context.

Upload Image

bash
# Upload returns {id, path}
curl -s -b /tmp/postiz-cookies.txt \
  "$POSTIZ_URL/api/media/upload-simple" \
  -F 'file=@/path/to/image.png'

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The script persists authenticated session cookies to a predictable path in /tmp, which is commonly shared and more exposed than a user-private config directory. If another local user or process can read or replace that file, they may hijack the Postiz session or interfere with authentication, especially since the script reuses cookies without validating ownership or file permissions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

Uploading media from an external URL causes the Postiz service to fetch third-party content, but the skill provides no privacy or data-handling warning. This can expose internal usage patterns, fetch untrusted content, or create SSRF-like risk depending on how the backend validates URLs.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.