Back to skill

Security audit

Job Tracker

Security checks for vulnerabilities and agentic risk

Overview

This skill is a local job-application tracker that stores the user's job search records in a disclosed SQLite database, with no evidence of hidden network access or deceptive behavior.

Install this only if you are comfortable storing job applications, recruiter contact details, deadlines, and notes locally in a plaintext SQLite database. Avoid putting passwords, tokens, government identifiers, or highly confidential interview details into notes, especially on shared machines or permissive filesystems.

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

Note
Location
scripts/job_tracker.py:8
Finding

Plaintext Storage of Sensitive Employment and Contact Data Without Explicit Access Controls

Content
View full analysis

Vulnerability Details

File Location: scripts/job_tracker.py:8-10, scripts/job_tracker.py:17-20, scripts/job_tracker.py:43-50, and scripts/job_tracker.py:90-105
Vulnerability Type: Sensitive data stored in plaintext with permissions inherited from the runtime environment
Risk Level: Low

Vulnerable Code

python
def runtime_dir(base_dir):
    path = Path(base_dir) / ".runtime"
    path.mkdir(parents=True, exist_ok=True)
    return path
python
def get_connection(base_dir):
    connection = sqlite3.connect(db_path(base_dir))
    connection.row_factory = sqlite3.Row
    ensure_schema(connection)
    return connection
python
CREATE TABLE IF NOT EXISTS contacts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    application_id INTEGER NOT NULL,
    name TEXT NOT NULL,
    email TEXT,
    linkedin TEXT,
    role TEXT,
    notes TEXT,
    FOREIGN KEY(application_id) REFERENCES applications(id)
);
python
if args.contact_name:
    connection.execute(
        """
        INSERT INTO contacts (application_id, name, email, linkedin, role, notes)
        VALUES (?, ?, ?, ?, ?, ?)
        """,
        (
            application_id,
            args.contact_name,
            args.contact_email,
            args.contact_linkedin,
            args.contact_role,
            args.contact_notes,
        ),
    )

Technical Analysis

The skill stores recruiter names, email addresses, LinkedIn profiles, employment activity, and free-form notes in an unencrypted SQLite database. The .runtime directory is created without an explicit restrictive mode, and the database file created through sqlite3.connect() is not subsequently restricted to owner-only access.

Consequently, the effective permissions of both the directory and database depend on the process umask and permissions of the parent directory. On a shared system with a permissive umask or accessible parent directory, other local users may be able to inspect the database. ...[truncated 1635 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create the runtime directory with owner-only permissions and correct existing permissions:
python
def runtime_dir(base_dir):
    path = Path(base_dir) / ".runtime"
    path.mkdir(parents=True, exist_ok=True, mode=0o700)
    path.chmod(0o700)
    return path
  1. Restrict the database to owner read/write access immediately after opening or creating it:
python
def get_connection(base_dir):
    path = db_path(base_dir)
    connection = sqlite3.connect(path)
    path.chmod(0o600)
    connection.row_factory = sqlite3.Row
    ensure_schema(connection)
    return connection
  1. Validate that .runtime and the database are not symbolic links before creating or opening them when the base directory could be influenced by an untrusted local user.

  2. Document that credentials, authentication tokens, government identifiers, and other secrets must not be entered into free-form notes.

  3. If the deployment threat model includes unauthorized filesystem access, consider application-level encryption for sensitive fields with keys stored separately from the database.

  4. Add automated tests that verify .runtime is mode 0700 and job-tracker.db is mode 0600 on platforms supporting POSIX permissions.

Vulnerability Patterns
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (4)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill invokes shell commands to run a local Python helper script, but it does not declare any tool scope such as permissions or allowed-tools. This creates an authorization gap where the agent may execute shell-capable actions without an explicit contract, reducing sandboxing clarity and making unintended command execution harder to govern or audit.

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 · test_job_tracker.py (reported line 19)May include surrounding context.

python
self.temp_dir.cleanup()

    def run_cli(self, *args):
        result = subprocess.run(
            [sys.executable, str(self.base_dir / "scripts" / "job_tracker.py"), *self.base_arg, *args],
            capture_output=True,
            text=True,

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code creates a runtime directory and stores data in a SQLite database, and later inserts application, contact, and notes records, but there is no confirmation prompt, warning comment, or user-facing notice explaining that personal job-search information will be persisted locally. Because the file handles potentially sensitive personal and contact data, some visible disclosure would improve transparency.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/job_tracker.py (reported line 143)May include surrounding context.

python
updates = []
    values = []
    for field in ["status", "deadline", "next_follow_up", "priority", "source"]:
        value = getattr(args, field)
        if value is not None:
            updates.append(f"{field} = ?")
            values.append(value)

Static analysis

No suspicious patterns detected.