Back to skill

Security audit

Akaunting

Security checks for vulnerabilities and agentic risk

Overview

This is a real Akaunting integration, but it handles financial data with insecure HTTP/default-credential setup and includes privileged container patching that users should review before installing.

Review before installing. Use only with a dedicated low-privilege Akaunting account, change all default passwords, bind the service to localhost or protect it behind HTTPS and firewall rules, avoid sending Basic Auth over HTTP, restrict permissions on any credential file, and treat the Docker repair script as an administrator maintenance action that changes application code.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
assets/docker-compose.yml:8
Finding
Deployment Uses Predictable Default Credentials on a Network-Exposed Service<![CDATA[ ## Vulnerability Details **File Location**: `assets/docker-compose.yml:8-34` **Vulnerability Type**: Hardcoded and predictable credentials **Risk Level**: High ### Vulnerable Code ```yaml ports: - "8080:80" environment: - LOCALE=en-US - DB_HOST=akaunting-db - DB_PORT=3306 - DB_DATABASE=akaunting - DB_USERNAME=akaunting - DB_PASSWORD=akaunting_secure_password - DB_PREFIX=ak_ - COMPANY_NAME=My Company - COMPANY_EMAIL=admin@example.com - ADMIN_EMAIL=admin@example.com - ADMIN_PASSWORD=changeme123 # ... environment: - MYSQL_ROOT_PASSWORD=root_secure_password - MYSQL_DATABASE=akaunting - MYSQL_USER=akaunting - MYSQL_PASSWORD=akaunting_secure_password ``` ### Technical Analysis The Compose configuration embeds fixed administrator, database-user, and database-root passwords. These values are distributed with the project and are therefore not secret. Users following the deployment instructions may run the service without replacing them. The port mapping `8080:80` binds the Akaunting web service to all host interfaces by default. If the host is reachable from an untrusted network, an attacker can attempt to authenticate with the documented administrator identity and password. The database is not directly published by this Compose file, which limits immediate remote database exposure. Nevertheless, the static database credentials remain available to processes with access to the Compose configuration, container environment, Docker API, application container, or internal Docker network. ### Attack Path 1. A user deploys the supplied Compose configuration without changing its default values. 2. Docker exposes the Akaunting application on host port 8080. 3. An attacker discovers the reachable service. 4. The attacker authenticates using `admin@example.com` and `changeme123`. 5. The attacker gains the privileges assigned to the initialized administrator account. 6. If the attacker subsequently obtains container or internal-n ...[truncated 669 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove all passwords from the committed Compose file. - Require credentials to be supplied through Docker secrets, a protected environment file, or an external secrets manager. - Generate unique, cryptographically random administrator, database-user, and database-root passwords for every deployment. - Add startup validation that rejects known example or default passwords. - Bind the service to loopback by default, for example: ```yaml ports: - "127.0.0.1:8080:80" ``` - If remote access is required, place the service behind an authenticated TLS reverse proxy and restrict access with firewall rules. - Rotate all credentials on deployments that may already have used these defaults. - Avoid using the application administrator account for routine API automation; create a dedicated account with only the required API permissions. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/akaunting.py:36
Finding
Basic Authentication Credentials Can Be Transmitted over Plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/akaunting.py:36-47` **Related Documentation**: `SKILL.md:49-59`, `references/api.md:5-9` **Vulnerability Type**: Cleartext transmission of reusable credentials **Risk Level**: High ### Vulnerable Code ```python def api_request(method, endpoint, config, data=None, params=None): """Make an API request to Akaunting.""" url = f"{config['url'].rstrip('/')}/api/{endpoint.lstrip('/')}" auth = HTTPBasicAuth(config["email"], config["password"]) headers = {"Content-Type": "application/json", "Accept": "application/json"} try: response = requests.request( method=method, url=url, auth=auth, headers=headers, json=data, params=params, timeout=30 ) ``` The documented configuration explicitly demonstrates an HTTP endpoint: ```json { "url": "http://YOUR_IP:8080", "email": "your@email.com", "password": "your-password" } ``` The API reference also demonstrates Basic Authentication over HTTP: ```bash curl -u "email:password" http://localhost:8080/api/ping ``` ### Technical Analysis HTTP Basic Authentication encodes the email and password but does not encrypt them. Its security depends entirely on transport-layer encryption. The client accepts an arbitrary configured URL and sends the reusable credentials without checking whether the scheme is HTTPS. Although loopback HTTP may be acceptable in a tightly controlled local setup, the primary setup example uses `YOUR_IP`, encouraging use across a network. When used over HTTP, network observers, compromised gateways, malicious wireless access points, or other on-path actors can capture both credentials and accounting API traffic. The client also does not distinguish loopback development endpoints from remote hosts or require explicit acknowledgment before sending credentials over an insecure transport. ### Attack Path 1. A user fol ...[truncated 936 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require HTTPS for all non-loopback endpoints. - Parse the configured URL and reject `http://` unless the hostname is explicitly recognized as a loopback address. - If an insecure transport override is needed for development, require a deliberate flag and display a prominent warning. - Update all setup and API examples to use HTTPS. - Deploy Akaunting behind a TLS-enabled reverse proxy using a valid certificate. - Prefer revocable, narrowly scoped API tokens over reusable account passwords where supported. - Use a dedicated automation account with least-privilege API permissions. - Rotate credentials that may previously have traversed an untrusted HTTP network. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:49
Finding
Plaintext Credential File Is Created Without Enforced Restrictive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:49-59` **Related Code**: `scripts/akaunting.py:14`, `scripts/akaunting.py:27-30` **Vulnerability Type**: Insecure local storage of authentication credentials **Risk Level**: Medium ### Vulnerable Code ```bash mkdir -p ~/.config/akaunting cat > ~/.config/akaunting/config.json << EOF { "url": "http://YOUR_IP:8080", "email": "your@email.com", "password": "your-password" } EOF ``` The client loads the stored password directly from that file: ```python CONFIG_PATH = Path.home() / ".config" / "akaunting" / "config.json" # ... if not all(config.values()) and CONFIG_PATH.exists(): with open(CONFIG_PATH) as f: file_config = json.load(f) config.update({k: v for k, v in file_config.items() if v}) ``` ### Technical Analysis The setup instructions store a reusable password in plaintext but do not set a restrictive `umask`, directory mode, or file mode. Consequently, the resulting permissions depend on the user's environment. Under a permissive umask or unusual home-directory configuration, the credential file may be readable by other local users or processes. The client does not inspect the owner or permissions of the configuration file before loading credentials, so it provides no warning when secrets are stored insecurely. Environment variables offered as an alternative are not automatically a complete solution because they may be exposed through process-inspection or debugging facilities. A protected credential store is preferable. ### Attack Path 1. A user follows the setup instructions in an environment with permissive default permissions. 2. The generated configuration file becomes group-readable or world-readable. 3. Another local user or compromised process reads `~/.config/akaunting/config.json`. 4. The attacker extracts the Akaunting email, password, and service URL. 5. The attacker authenticates to the configured Akaunting instance. ### Impact Assessment The ...[truncated 490 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create the configuration directory and file with explicit restrictive permissions: ```bash install -d -m 700 ~/.config/akaunting umask 077 cat > ~/.config/akaunting/config.json <<'EOF' { "url": "https://YOUR_HOST", "email": "your@email.com", "password": "your-password" } EOF chmod 600 ~/.config/akaunting/config.json ``` - In the Python client, verify that the file is owned by the current user and is not accessible by group or other users. - Refuse to load an insecurely permissioned file, or at minimum issue a clear warning. - Prefer an operating-system credential store or secrets manager rather than a plaintext JSON password. - Use a dedicated API account with minimal permissions and rotate credentials if the file may have been exposed. - Ensure error messages and diagnostic output never print the password or Authorization header. ]]>

T08 · Insecure Dependencies

Warning
Location
assets/docker-compose.yml:5
Finding
Unpinned Mutable Container Image Creates a Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `assets/docker-compose.yml:5` **Vulnerability Type**: Mutable and unverified third-party dependency **Risk Level**: Medium ### Vulnerable Code ```yaml image: akaunting/akaunting:latest ``` ### Technical Analysis The `latest` tag is mutable and does not identify a specific reviewed image. Two deployments using the same project revision can therefore execute different container contents. A normal upstream update, accidental publication, registry-account compromise, or malicious image replacement can silently alter the code deployed by users. The container receives application configuration and database credentials and has write access to persistent application storage. Consequently, a compromised image would execute in a security-sensitive context. No evidence was found that the current upstream image is malicious. The vulnerability is the lack of dependency immutability and reproducibility. ### Attack Path 1. A user deploys or updates the project using the supplied Compose file. 2. Docker resolves `akaunting/akaunting:latest` to whatever manifest the registry currently associates with that tag. 3. The tag has changed since review, either through a legitimate release or upstream compromise. 4. Docker downloads and runs the unreviewed image. 5. Malicious image code accesses container environment variables, application data, and reachable services. ### Impact Assessment A compromised image could read the configured database credentials, access or alter persistent Akaunting application data, communicate with the MariaDB service, and exfiltrate financial records over the network. The immediate scope is the container, its mounted volume, environment secrets, and services reachable from its Docker network. Further host impact depends on the Docker runtime configuration and whether additional host resources are exposed. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Pin Akaunting to an explicitly reviewed release and immutable digest, for example: ```yaml image: akaunting/akaunting:<reviewed-version>@sha256:<verified-digest> ``` - Verify the digest against a trusted release source before committing it. - Use an explicit dependency-update process that includes release-note review, vulnerability scanning, and functional testing. - Enable automated container-image scanning in CI. - Where available, verify image signatures or provenance attestations. - Avoid automatic pulls of mutable production tags. - Apply container hardening such as dropping unnecessary Linux capabilities, using a read-only root filesystem where compatible, and restricting outbound network access. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The documented behavior goes beyond REST API interaction and includes deployment, application patching, and source/configuration modification. This mismatch is dangerous because users may invoke an accounting skill expecting limited API operations, while the skill can perform maintenance or invasive host/container changes that materially expand the attack surface.

Missing User Warnings

High
Confidence
98% confidence
Finding
The setup instructions expose the service over plain HTTP and later pair that with Basic Auth credentials. This creates a clear risk of credential interception and financial data disclosure via network sniffing or man-in-the-middle attacks, especially if users follow the example on non-local or shared networks.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
GET /api/accounts/{id}         # Get single account
POST /api/accounts             # Create account
PUT /api/accounts/{id}         # Update account
DELETE /api/accounts/{id}      # Delete account
```

**Create account:**
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill advertises network, shell, and environment-dependent behavior but does not declare any tool scope or permission boundaries. In an agent setting, this makes the skill harder to constrain and review, increasing the chance of unintended command execution, credential access, or network actions beyond the user's expectations.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger conditions are broad enough to activate on many ordinary accounting or invoicing conversations. In an agent ecosystem, overbroad routing can cause this skill to handle prompts it should not, leading to unnecessary access to financial systems, credentials, or transaction-creation capabilities.

Session Persistence

Medium
Category
Rogue Agent
Content
akaunting categories  
akaunting transactions

# Create transactions
akaunting income --amount 100 --category Sales --description "Payment received"
akaunting expense --amount 50 --category Other --description "Office supplies"
```
Confidence
60% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The documentation instructs users to store credentials in plaintext config and use HTTP Basic Auth, but provides no warning about local secret exposure, process leakage, or transport risks. Because this skill manages financial data, compromise of these credentials could allow unauthorized access to accounts, transactions, and other sensitive bookkeeping records.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This markdown API reference documents `DELETE /api/accounts/{id}` as an available operation, but nowhere around the endpoint or elsewhere in the file does it warn that the action may permanently remove account data. For markdown files, destructive or irreversible behaviors should be disclosed so users understand the risk before using the skill or API.

Session Persistence

Medium
Category
Rogue Agent
Content
def cmd_income(args, config):
    """Create income transaction."""
    # Get category ID
    categories = api_request("GET", "categories", config, params={"search": f"name:{args.category}"})
    cat_id = None
Confidence
60% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script performs a POST that creates an income transaction immediately from command-line inputs, with no confirmation prompt, dry-run mode, or safety warning before mutating accounting data. In an agent/automation context, this raises the risk of accidental or prompt-induced financial record creation, which can corrupt books or trigger downstream business processes.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The expense command issues a POST to create a financial expense record without any user confirmation or review step. For accounting software, unintended expense creation can alter ledgers, misstate reporting, and create audit/compliance issues if triggered erroneously by automation.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
This file performs local Docker-based file patching and cache clearing inside the Akaunting container, including modifying application PHP source under `/var/www/html/app/Providers/Event.php`. That behavior is inconsistent with the skill's declared purpose of interacting with Akaunting over REST and creates an unexpected privileged maintenance backdoor that can alter application code and runtime behavior.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill has an unjustified ability to execute arbitrary commands in the Akaunting container despite being presented as a bookkeeping/API integration skill. In context, this mismatch is especially dangerous because users or orchestrators may grant the skill trust appropriate for invoice/accounting actions, not local code execution and application patching.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
def run_docker(cmd: str) -> tuple[int, str]:
    """Run command in Docker container."""
    result = subprocess.run(
        ["docker", "exec", CONTAINER_NAME, "bash", "-c", cmd],
        capture_output=True,
        text=True
Confidence
83% confidence
Finding
The script executes shell commands inside a Docker container via `docker exec ... bash -c`, which creates a command-execution capability beyond normal accounting/API operations. Although the current call sites use hardcoded commands rather than direct user input, this still increases attack surface and enables container-side code modification if the script is invoked in the wrong context or later extended unsafely.

Vague Triggers

Low
Confidence
95% confidence
Finding
This manifest sets LOCALE=en-US directly, which imposes a specific language/locale choice in configuration without any indication of user selection or region-specific justification. That matches the policy concern for natural-language locale restrictions when no opt-in is evident.

Missing User Warnings

Low
Confidence
91% confidence
Finding
The item creation command sends a POST to create products/services directly, again without confirmation or warning. While less severe than financial transactions, unauthorized or accidental item creation can still pollute inventory/catalog data and affect invoicing workflows.

Static analysis

No suspicious patterns detected.