Back to skill

Security audit

Gusnais Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real Gusnais OAuth/API helper, but it handles reusable credentials and content-changing actions with enough scoping and disclosure gaps that users should review it before installing.

Install only if you trust the Gusnais account and OAuth client involved, are comfortable with a local plaintext token store, and will use the read/write actions deliberately. Prefer a private token-store path outside shared directories, rotate/revoke tokens if the file may have been exposed, and treat delete/publish/update actions as potentially irreversible or public.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/gusnais_plugin_client.py:166
Finding

Credential Exfiltration Through Attacker-Controlled OAuth and API Destinations

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gusnais_bootstrap.py:89
Finding

Bearer Tokens Are Disclosed in Request Query Strings

Content
View full analysis
Any: url = f"{SITE}{API_BASE}{path}" query = dict(params or {}) headers = {} if access_token: headers["Authorization"] = f"Bearer {access_token}" # compatibility with Homeland API docs query["access_token"] = access_token res = requests.get(url, params=query, headers=headers, timeout=20) ``` ```python url = f"{self.site}{path}" params = dict(query or {}) params["access_token"] = access_token headers = {"Authorization": f"Bearer {access_token}"} res = requests.request(method, url, params=params, json=body, headers=headers, timeout=self.timeout) if res.status_code == 401: self.refresh() access_token = self.token.get("access_token") params["access_token"] = access_token headers["Authorization"] = f"Bearer {access_token}" res = requests.request(method, url, params=params, json=body, headers=headers, timeout=self.timeout) ``` ### Technical Analysis Both scripts place the bearer token in the URL query string for every authenticated request, even though they already send the token through the standard `Authorization` header. Query strings are commonly captured by: - Web-server and reverse-proxy access logs. - API gateway telemetry. - Network monitoring and diagnostic systems. - Error reports and request traces. - Browser or intermediary history when equivalent URLs are exposed. - Application analytics that record full request URLs. The declared compatibility fallback is implemented unconditionally rather than only when a legacy server explicitly requires it. ### Attack Path 1. A user performs any authenticated API request. 2. The client c ...[truncated 737 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gusnais_bootstrap.py:141
Finding

Non-Atomic Token-Store Writes Permit Symlink Attacks and Temporary Secret Exposure

Content
View full analysis
None: self.store["token"] = self.token self.path.write_text(json.dumps(self.store, ensure_ascii=False, indent=2), encoding="utf-8") os.chmod(self.path, 0o600) ``` The usage documentation also demonstrates a predictable shared-directory path: ```python python3 scripts/gusnais_plugin_client.py --token-store /tmp/gusnais-token.json --action jobs.topics.list ``` ### Technical Analysis The token store contains the client secret, access token, and refresh token. It is written using `Path.write_text()`, after which `chmod(0600)` is applied. This sequence has several security weaknesses: - A newly created file initially receives permissions determined by the process umask before the later `chmod`. - Existing symbolic links are followed. - The path is not checked for file ownership or regular-file status. - Updates are not atomic. - Predictable paths under `/tmp` are particularly susceptible to local pre-creation and symlink attacks. Applying restrictive permissions after writing does not protect against disclosure or redirection that occurs during file creation. ### Attack Path 1. A local attacker predicts the token-store path, such as `/tmp/gusnais-token.json`. 2. Before the victim runs the bootstrap, the attacker creates that path as a symbolic link to another suitable file. 3. The victim runs the bootstrap or refreshes a token. 4. `write_text()` follows the symbolic link and writes the token store to the link target. 5. The attacker reads the resulting client secret a ...[truncated 615 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gusnais_bootstrap.py:40
Finding

Predictable OAuth State Is Not Validated During Authorization-Code Exchange

Content
View full analysis
str: params = { "client_id": client_id, "redirect_uri": redirect_uri, "response_type": "code", "state": state, } if scope: params["scope"] = scope return f"{SITE}{AUTHORIZE_PATH}?{urllib.parse.urlencode(params)}" ``` ```python redirect_uri = os.getenv("REDIRECT_URI", "urn:ietf:wg:oauth:2.0:oob") state = os.getenv("OAUTH_STATE", "gusnais-state") scope = os.getenv("OAUTH_SCOPE", "") code = os.getenv("OAUTH_CODE", "").strip() token_store_path = os.getenv("TOKEN_STORE_PATH", "").strip() output: Dict[str, Any] = { "site": SITE, "oauth": { "authorize_endpoint": f"{SITE}{AUTHORIZE_PATH}", "token_endpoint": f"{SITE}{TOKEN_PATH}", "revoke_endpoint": f"{SITE}{REVOKE_PATH}", }, "api_base": f"{SITE}{API_BASE}", "authorize_url": build_authorize_url(client_id, redirect_uri, state, scope), "token_ready": False, } ``` ### Technical Analysis The default OAuth state is the constant `gusnais-state`. The script includes this value in the authorization URL but does not persist a per-request state value or validate state before exchanging the authorization code. OAuth state is intended to bind the authorization response to the authorization request initiated by the client. A constant and unverified value does not provide that binding. Because the workflow asks the user to copy an authorization code into `OAUTH_CODE`, the script cannot determine whether that code originated from the authorization request it generated. ### Attack Path 1. An attacker initiates an authorization flow for the same OAuth client and authorizes an ...[truncated 935 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/gusnais_plugin_client.py:104
Finding

Destructive Actions Execute Before Documented Capability Checks

Content
View full analysis
Dict[str, Any]: if not abilities: return { "visible": True, "enabled": True, "reason": "ok", "source": "server_status", } enabled = bool(abilities.get(action, False)) if action in abilities else True return { "visible": True, "enabled": enabled, "reason": "ok" if enabled else "no_permission", "source": "abilities", } ``` ```python result = self.request(method, path, query=q, body=b if method in {"POST", "PUT", "PATCH"} else None) # Try to infer ability map for common payload shapes. abilities = None if isinstance(result.get("data"), dict): payload = result["data"] if isinstance(payload.get("abilities"), dict): abilities = payload.get("abilities") elif isinstance(payload.get("topic"), dict) and isinstance(payload["topic"].get("abilities"), dict): abilities = payload["topic"].get("abilities") elif isinstance(payload.get("post"), dict) and isinstance(payload["post"].get("abilities"), dict): abilities = payload["post"].get("abilities") elif isinstance(payload.get("note"), dict) and isinstance(payload["note"].get("abilities"), dict): abilities = payload["note"].get("abilities") elif isinstance(payload.get("site"), dict) and isinstance(payload["site"].get("abilities"), dict): abilities = payload["site"].get("abilities") verb = action.split(".")[-1] result["capability"] = evaluate_capability(abilities, verb) ``` ### Technical Analysis The project documentation requires resource abilities to be read before an action when available. The implementation inst ...[truncated 1871 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The description overstates the scope substantially. The code does support part of the claim about connecting with only CLIENT_ID and CLIENT_SECRET and auto-filling core OAuth/API endpoints, but its actual function is narrowly limited to OAuth bootstrap: generating an authorization URL, exchanging a code, optionally refreshing/persisting tokens, and calling /users/me to verify identity. There is no evidence of parity with gusnais.com capabilities, no permissions model handling, no UX logic, and no API read/write support for the named plugin domains. Therefore the declared description does not accurately represent the code chunk's actual behavior.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
Use `scripts/gusnais_plugin_client.py` for plugin API read/write calls with:

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · references/endpoints.md (reported line 102)May include surrounding context.

md
- `GET /posts/:id`
- `POST /posts`
- `PUT /posts/:id`
- `DELETE /posts/:id`
- `PUT /posts/:id/publish`
- `POST /posts/preview`
- `GET /admin/posts`

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · references/endpoints.md (reported line 113)May include surrounding context.

md
- `GET /api/v3/press/posts/:id`
- `POST /api/v3/press/posts`
- `PUT /api/v3/press/posts/:id`
- `DELETE /api/v3/press/posts/:id`
- `PUT /api/v3/press/posts/:id/publish`
- `POST /api/v3/press/posts/preview`

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · references/endpoints.md (reported line 124)May include surrounding context.

md
- `GET /notes/:id`
- `POST /notes`
- `PUT /notes/:id`
- `DELETE /notes/:id`
- `POST /notes/preview`
- `GET /admin/notes`

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · references/endpoints.md (reported line 133)May include surrounding context.

md
- `GET /api/v3/note/notes/:id`
- `POST /api/v3/note/notes`
- `PUT /api/v3/note/notes/:id`
- `DELETE /api/v3/note/notes/:id`
- `POST /api/v3/note/notes/preview`

### Site (`site-master`)

Tool Parameter Abuse

High
Category
Tool Misuse
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).

Content

Scanner excerpt · references/endpoints.md (reported line 150)May include surrounding context.

md
- `GET /api/v3/site/sites/:id`
- `POST /api/v3/site/sites`
- `PUT /api/v3/site/sites/:id`
- `DELETE /api/v3/site/sites/:id`
- `GET /api/v3/site/site_nodes`

Notes:

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/gusnais_plugin_client.py (reported line 9)May include surrounding context.

python
Features:
- Load/save token store JSON (generated by gusnais_bootstrap.py)
- Auto-refresh access token before expiry or on 401
- One-shot action runner for plugin read/write endpoints
- Capability gating helper from `abilities`
- Action-specific payload guardrails (avoid obvious 400/500)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill advertises behavior that depends on network, file, environment, and shell-capable helper scripts, but it declares no explicit tool scope or permission boundaries. In practice this increases the blast radius of the skill because a caller cannot tell in advance that it may read secrets, persist tokens, or invoke local scripts, which is especially risky for an OAuth-integrating skill handling credentials.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill explicitly asks for CLIENT_ID and CLIENT_SECRET and recommends persisting refreshable tokens to a JSON file, but it does not warn about secret exposure, local filesystem risk, or secure storage requirements. For OAuth material, silent persistence can lead to credential theft, token replay, or accidental disclosure through logs, backups, or permissive file permissions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The file documents OAuth authorization, token issuance, and token revocation endpoints, which are security-sensitive behaviors affecting authentication state. The markdown does not include any caution about handling tokens securely, protecting credentials, or the impact of revoking access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This markdown file documents POST, PUT, and DELETE API operations such as creating, updating, and deleting topics, but it provides no warning that these actions modify or remove user data. Under the markdown-specific SQP-2 criteria, descriptions of behaviors that can affect user data or system integrity should disclose those risks.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The plugin API contract includes DELETE endpoints and publish/update operations for posts, notes, and sites, but the markdown does not warn that these actions can permanently remove content or make changes publicly visible. For markdown files, safety-relevant descriptions should disclose effects on user data and system state.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% 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.

Content

Scanner excerpt · references/endpoints.md (reported line 163)May include surrounding context.

md
API mapping used by this skill:
- List jobs: `GET /api/v3/topics?node_id=25&type=last_actived`
- Create job topic: `POST /api/v3/topics` with `node_id=25`
- Detail/update/delete: core topic endpoints (`/api/v3/topics/:id`)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The script persists highly sensitive OAuth material to disk, including the client_secret, access_token, and refresh_token, in a long-lived JSON file. Although it sets file mode 0600, storing reusable credentials on disk increases exposure through backup systems, accidental path selection, local compromise, container volume leakage, or later exfiltration by other processes.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The client refresh flow sends a refresh token, client ID, and client secret to the OAuth token endpoint, and the main request flow also transmits access tokens to remote API endpoints. Although the module docstring describes token refresh behavior, it does not warn users that credentials and bearer tokens will be sent over the network when the script runs.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.