Back to skill

Security audit

zion-baas-skill

Security checks for vulnerabilities and agentic risk

Overview

This skill appears purpose-built for Zion backend administration, but it handles powerful credentials in ways users should review carefully before installing.

Install only if you are comfortable with an agent and local scripts administering a Zion backend. Prefer OAuth over email/password CLI login, keep .zion/credentials.yaml out of source control and backups, restrict its permissions, rotate tokens if exposed, and avoid using admin tokens unless the requested backend change is explicit and intended.

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
scripts/authenticateEmail.ts:64
Finding
Password Exposed Through Command-Line Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authenticateEmail.ts:64-74` **Vulnerability Type**: Plaintext credential exposure through process arguments **Risk Level**: High ### Vulnerable Code ```typescript // Example usage when run directly if (import.meta.url === `file://${process.argv[1]}`) { const args = process.argv.slice(2); if (args.length < 2) { console.error("Usage: npm run auth:email <email> <password>"); process.exit(1); } const [email, password] = args; authenticateWithEmail(email, password) ``` The same invocation pattern is prescribed in `SKILL.md:45-50` and `README.md:36-40`. ### Technical Analysis The authentication script obtains the Zion account password directly from `process.argv`. Command-line arguments are not an appropriate secret-input channel because they may be exposed through: - Shell history files. - Process inspection utilities while the command is running. - Terminal session recording. - CI/CD and automation logs. - Command auditing and endpoint monitoring software. - Parent-process telemetry. The script then sends the supplied credentials to the declared Zion Meta API over HTTPS. Sending credentials to that authentication service is expected functionality; the vulnerability is exposing the password locally before transmission. ### Attack Path 1. A user follows the documented command and supplies an email address and password as command-line arguments. 2. The shell records the command in its history, or another local process observes the active process arguments. 3. A local attacker, support bundle, monitoring agent, or compromised process retrieves the plaintext password. 4. The attacker authenticates directly to Zion using the stolen account credentials. 5. The attacker may access every project and platform capability authorized to that developer account, rather than only the project being used by the Skill. ### Impact Assessment Successful exploitation exposes the user's reus ...[truncated 279 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Read the password from an interactive hidden TTY prompt rather than `process.argv`. - Support protected standard input for noninteractive automation. - Do not recommend environment variables as the primary alternative because they can also leak through diagnostics and process configuration. - Remove all password-bearing command examples from `README.md` and `SKILL.md`. - Ensure authentication errors never include submitted credentials. - Advise affected users to clear relevant shell history and rotate passwords if the documented command has already been used. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/authenticate.ts:75
Finding
Developer and Administrator JWTs Stored Without Restrictive File Permissions<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/authenticate.ts:75-95` - `scripts/authenticateEmail.ts:78-98` - `scripts/fetchRuntimeToken.ts:92-134` **Vulnerability Type**: Insecure plaintext credential storage **Risk Level**: High ### Vulnerable Code From `scripts/authenticate.ts`: ```typescript const credentialsDir = path.resolve(process.cwd(), ".zion"); const credentialsPath = path.join(credentialsDir, "credentials.yaml"); let data: any = {}; if (fs.existsSync(credentialsPath)) { try { data = yaml.load(fs.readFileSync(credentialsPath, "utf8")) || {}; } catch (e) { console.error("Failed to parse existing credentials file, overwriting."); } } data.developer_token = { token: token, expiry: getJwtExpiry(token) }; if (!fs.existsSync(credentialsDir)) { fs.mkdirSync(credentialsDir, { recursive: true }); } fs.writeFileSync(credentialsPath, yaml.dump(data)); ``` From `scripts/fetchRuntimeToken.ts`: ```typescript data.project = data.project || {}; data.project.exId = projectExId; data.project.name = projectName; data.project.admin_token = { token: adminToken, expiry: getJwtExpiry(adminToken) }; if (!data.project.other_users) { data.project.other_users = []; } if (!fs.existsSync(credentialsDir)) { fs.mkdirSync(credentialsDir, { recursive: true }); } fs.writeFileSync(credentialsPath, yaml.dump(data)); ``` ### Technical Analysis The Skill persists developer, runtime administrator, and optional user JWTs in plaintext in `.zion/credentials.yaml`. Neither `mkdirSync` nor `writeFileSync` supplies an explicit restrictive mode. Consequently, permissions depend on the user's operating-system umask. Under common configurations, the file may be created with permissions equivalent to `0644`, allowing other local users to read it. The directory may similarly be accessible. The implementation also does not verify or repair permissions on an existing credentials file. The project does not include repository-level protectio ...[truncated 1434 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Create `.zion` with mode `0700`. - Create and rewrite `credentials.yaml` with mode `0600`. - Verify existing ownership and permissions before reading or updating the file. - Use atomic writes through a protected temporary file followed by a rename. - Add `.zion/` to a supplied `.gitignore` and document that credentials must never be committed. - Prefer an operating-system credential manager or secret store instead of a project-local plaintext YAML file. - Store only credentials required for the current operation. - Fetch short-lived administrator tokens on demand rather than retaining them indefinitely. - Enforce expiry checks before using stored tokens. - Rotate and revoke tokens after suspected filesystem or repository exposure. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/authenticate.ts:14
Finding
OAuth Callback Accepts Uncorrelated Tokens and Is Not Explicitly Bound to Loopback<![CDATA[ ## Vulnerability Details **File Location**: `scripts/authenticate.ts:14-45` **Vulnerability Type**: OAuth login CSRF and token substitution weakness **Risk Level**: Medium ### Vulnerable Code ```typescript export async function authenticate(authEndpoint = "https://auth.functorz.com/login", port = 8088): Promise<string> { const redirectUri = `http://localhost:${port}/callback`; return new Promise<string>((resolve, reject) => { const server = createServer((req, res) => { if (!req.url) return; const url = new URL(req.url, `http://localhost:${port}`); if (url.pathname === "/callback") { const token = url.searchParams.get("token"); if (token) { res.writeHead(200, { "Content-Type": "text/html" }); res.end("<h1>Token Received. You can close this window.</h1><script>setTimeout(() => window.close(), 2000)</script>"); setTimeout(() => { server.close(); resolve(token); }, 1000); } else { res.writeHead(400, { "Content-Type": "text/html" }); res.end("<h1>Error: No token received</h1>"); server.close(); reject(new Error("No token parameter")); } } }); server.listen(port, async () => { const authUrl = new URL(authEndpoint); authUrl.searchParams.set("redirect_uri", redirectUri); await open(authUrl.toString()); console.log(`Waiting for authentication on port ${port}...`); }); ``` ### Technical Analysis The local callback flow does not generate or validate an OAuth `state` value. It accepts the first nonempty `token` query parameter sent to `/callback`, without correlating the response to the browser session initiated by the script. The token is subsequently stored without validating its signature, issuer, audience, nonce, or account identity. This enables login CSRF or token substitution when an attacker can cause a crafted callback to reach the listene ...[truncated 1381 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Use OAuth Authorization Code Flow with PKCE instead of receiving a bearer token directly in the callback URL. - Generate a cryptographically random `state` value for each authentication attempt. - Include `state` in the authorization request and reject callbacks whose value does not match. - Explicitly bind the callback server to `127.0.0.1` or `::1`. - Reject requests whose remote address is not a loopback address. - Validate the returned token's signature, issuer, audience, expiry, and expected account context where supported. - Accept only one valid callback and close the listener immediately afterward. - Return explicit responses for unrelated paths and limit request size and method. - Avoid placing bearer tokens in URL query strings because URLs may be captured in browser history or logs. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/package.json:14
Finding
Security-Sensitive Dependencies Are Installed Without a Lockfile<![CDATA[ ## Vulnerability Details **File Location**: `scripts/package.json:14-29` **Vulnerability Type**: Non-reproducible dependency resolution **Risk Level**: Medium ### Vulnerable Code ```json "dependencies": { "graphql": "^16.8.1", "graphql-request": "^6.1.0", "js-yaml": "^4.1.0", "open": "^10.0.3", "subscriptions-transport-ws": "^0.11.0", "ws": "^8.20.0" }, "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/node": "^20.11.24", "@types/ws": "^8.18.1", "ts-node": "^10.9.2", "tsx": "^4.21.0", "typescript": "^5.3.3" } ``` No npm lockfile is present in the audited project. The installation documentation directs users to run `npm install`. ### Technical Analysis All dependencies use mutable semantic-version ranges, and no reviewed `package-lock.json` or equivalent lockfile is included. Each installation can therefore resolve to dependency and transitive-dependency versions that differ from those originally tested or audited. This is especially consequential because the dependency tree participates in: - Opening the OAuth browser flow. - Processing account passwords. - Reading and writing developer and administrator JWTs. - Sending authenticated HTTP and WebSocket requests. - Parsing attacker- or server-controlled YAML and GraphQL data. The audit did not identify a currently malicious package name or source. The confirmed issue is the absence of reproducible dependency resolution, which increases future supply-chain risk. ### Attack Path 1. A user follows the installation instructions and runs `npm install`. 2. npm resolves the caret ranges and transitive dependency graph at installation time. 3. A newly published, compromised, or otherwise unsafe compatible release is selected. 4. That package executes during installation or when the authentication scripts run. 5. Because the process handles passwords and high-privilege JWTs, compromised dependency code can read and transmit those secrets or alter authenticated requests. ...[truncated 411 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate and commit a reviewed `package-lock.json`. - Direct users and automation to run `npm ci` instead of `npm install`. - Review and pin security-sensitive runtime dependencies where practical. - Use automated dependency vulnerability and provenance scanning. - Review lockfile changes as security-sensitive code changes. - Disable lifecycle scripts where they are unnecessary, such as with `npm ci --ignore-scripts`, after verifying that the project remains functional. - Periodically update dependencies through controlled, tested pull requests rather than resolving unreviewed versions during installation. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (38)

Missing User Warnings

High
Confidence
98% confidence
Finding
The README instructs users to pass email and password directly on the command line, which can leak secrets through shell history, terminal logs, CI logs, or process listings visible to other local users. This is especially dangerous for a backend administration skill because the same workflow later retrieves project runtime admin tokens, so initial credential exposure can cascade into broader compromise.

Credential Access

High
Category
Privilege Escalation
Content
## 凭证存储

所有令牌均自动保存在项目根目录的 `.zion/credentials.yaml` 文件中,结构如下:

```yaml
developer_token:
Confidence
95% confidence
Finding
The documented `credentials.yaml` contains developer and project admin tokens, making it a credential storage location that an attacker or malicious local process would target. In the context of a skill designed to connect an agent directly to a live BaaS backend, exposing this file could enable unauthorized API access, data exfiltration, workflow execution, or destructive administrative actions.

Credential Access

High
Category
Privilege Escalation
Content
### 2. Querying the Meta API & Fetching Runtime Backend Token
Use the developer JWT token as a Bearer token against the Meta API (`https://zionbackend.functorz.com/api/graphql`) to get the schema or data visualizer tokens. The data visualizer token grants administrative access to the project's runtime backend (`zeroUrl`).

You can run the bundled script to fetch the token. It requires the developer token to be present in `.zion/credentials.yaml`.

```bash
cd ~/.openclaw/skills/zion_baas/scripts
Confidence
90% confidence
Finding
This section instructs the workflow to read a developer JWT from `.zion/credentials.yaml` in order to fetch a runtime backend token that grants administrative access. That creates a direct credential-access path from local project files to privileged platform operations, so any unintended file access by the agent, plugins, or local compromise can lead to admin-level backend access.

Credential Access

High
Category
Privilege Escalation
Content
## Credential Management & State Persistence

All credentials and project state MUST be persisted in the `.zion` directory at the root of the user's project in YAML format, typically in a file named `.zion/credentials.yaml`.

### Required YAML Format
Confidence
94% confidence
Finding
The mandate that all credentials must be persisted in `.zion/credentials.yaml` turns credential access into a built-in feature of the skill. Because these are bearer tokens, anyone or any component that can read the file can act as the associated developer, admin, or user without additional proof of possession.

Ssd 3

High
Confidence
97% confidence
Finding
The skill instructs persistent storage not only of an admin token but also multiple end-user JWTs and notable user metadata in project files. This centralizes credentials and identity context in one plaintext artifact, increasing blast radius if the file is read by other tools, committed to a repository, copied into tickets, or accessed on a compromised workstation.

Credential Access

High
Category
Privilege Escalation
Content
The credentials file must adhere to the following structure:

```yaml
# .zion/credentials.yaml
developer_token:
  token: "<your_developer_jwt_token>" # Used to communicate with zionbackend.functorz.com
  expiry: "<timestamp_or_date_of_expiry>"
Confidence
94% confidence
Finding
The documented YAML schema includes raw developer and admin JWTs in plaintext, making the exact secret locations predictable and easy to parse automatically. Predictable secret placement lowers the bar for credential harvesting by malicious local software, over-permissioned tooling, or accidental repository exposure.

Credential Access

High
Category
Privilege Escalation
Content
- **`expiry`**: All stored tokens must include an associated expiry.

## Executing GraphQL Queries & Subscriptions via CLI
You can use the bundled scripts to quickly test GraphQL queries, mutations, and subscriptions from the command line without writing frontend boilerplate. These scripts automatically read the correct token from your project's `.zion/credentials.yaml`.

### 1. Execute a Query or Mutation
Pass your GraphQL query or mutation as a string.
Confidence
87% confidence
Finding
The CLI guidance states that scripts automatically read tokens from `.zion/credentials.yaml`, enabling implicit credential use without an explicit access check each time. This increases the chance of unintended privileged actions and makes secret consumption by automation less visible to the user.

Credential Access

High
Category
Privilege Escalation
Content
cd ~/.openclaw/skills/zion_baas/scripts
npm run gql -- <projectExId> <role> '<query_string>' '<optional_variables_json>'
```
- `<role>`: Can be `admin` (uses data visualizer token), `anonymous` (no token), or a specific `user_id` (fetches token from `other_users` in `.zion/credentials.yaml`).

*Example:*
```bash
Confidence
90% confidence
Finding
Allowing a caller to select `admin` or another `user_id` and have the script automatically fetch the matching token from local credential storage operationalizes multi-identity impersonation from a single file. In context, this is especially dangerous because the admin token is described as granting administrative runtime access, so compromise of the credential store enables broad unauthorized actions.

Credential Access

High
Category
Privilege Escalation
Content
let token = "";
  if (role !== "anonymous") {
    const credentialsPath = path.resolve(process.cwd(), ".zion/credentials.yaml");
    if (!fs.existsSync(credentialsPath)) {
      console.error(`Credentials file not found at ${credentialsPath}. Please run auth/fetch-token first.`);
      process.exit(1);
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
let token = "";
  if (role !== "anonymous") {
    const credentialsPath = path.resolve(process.cwd(), ".zion/credentials.yaml");
    if (!fs.existsSync(credentialsPath)) {
      console.error(`Credentials file not found at ${credentialsPath}. Please run auth/fetch-token first.`);
      process.exit(1);
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
let token = "";
  if (role !== "anonymous") {
    const credentialsPath = path.resolve(process.cwd(), ".zion/credentials.yaml");
    if (!fs.existsSync(credentialsPath)) {
      console.error(`Credentials file not found at ${credentialsPath}. Please run auth/fetch-token first.`);
      process.exit(1);
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
let token = "";
  if (role !== "anonymous") {
    const credentialsPath = path.resolve(process.cwd(), ".zion/credentials.yaml");
    if (!fs.existsSync(credentialsPath)) {
      console.error(`Credentials file not found at ${credentialsPath}. Please run auth/fetch-token first.`);
      process.exit(1);
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
let token = "";
  if (role !== "anonymous") {
    const credentialsPath = path.resolve(process.cwd(), ".zion/credentials.yaml");
    if (!fs.existsSync(credentialsPath)) {
      console.error(`Credentials file not found at ${credentialsPath}. Please run auth/fetch-token first.`);
      process.exit(1);
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
let token = "";
  if (role !== "anonymous") {
    const credentialsPath = path.resolve(process.cwd(), ".zion/credentials.yaml");
    if (!fs.existsSync(credentialsPath)) {
      console.error(`Credentials file not found at ${credentialsPath}. Please run auth/fetch-token first.`);
      process.exit(1);
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
let token = "";
  if (role !== "anonymous") {
    const credentialsPath = path.resolve(process.cwd(), ".zion/credentials.yaml");
    if (!fs.existsSync(credentialsPath)) {
      console.error(`Credentials file not found at ${credentialsPath}. Please run auth/fetch-token first.`);
      process.exit(1);
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The README explicitly states that developer tokens and project admin tokens are automatically persisted to a local `.zion/credentials.yaml` file, but it provides no warning about the sensitivity of those secrets or the risks of plaintext local storage. Because these tokens grant backend and project-admin access, compromise of the workstation, accidental inclusion in source control, weak file permissions, or malware could expose powerful credentials and lead to unauthorized access or data modification.

Ssd 3

Medium
Confidence
88% confidence
Finding
Directing the agent to ask users for usernames and passwords normalizes collection of raw credentials by the tool instead of delegating authentication to a browser-based or provider-hosted flow. Even if intended for legitimate login, this increases phishing surface and raises the chance credentials are exposed in chat logs, telemetry, screenshots, or mishandled storage.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill tells users to pass email and password directly on the command line to an npm script. Command-line arguments are commonly exposed in shell history, terminal scrollback, CI logs, and process listings, so this guidance can leak credentials to local users, monitoring tools, or later forensic artifacts.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The skill explicitly requires persistent storage of developer, admin, and user JWTs in a project-local YAML file, including an administrative runtime token. Storing long-lived bearer tokens in plaintext within a workspace creates a high risk of accidental disclosure through source control, backups, logs, local compromise, or other tools that can read the project directory, and the file structure encourages retention of multiple identities' credentials.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script writes a live access token to .zion/credentials.yaml on disk without setting restrictive file permissions, using a secure OS credential store, or clearly warning the user that sensitive bearer credentials are being persisted. If the working directory is shared, backed up, committed, or readable by other local users/processes, the token can be stolen and reused to access the associated backend account.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
This code reads authentication tokens from a local credentials file and uses them to send GraphQL requests to a remote endpoint. While the script has basic usage output, it does not clearly warn the user that supplied queries and variables will be transmitted to an external service using those credentials.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
This code reads authentication tokens from a local credentials file and sends them as connection parameters over a WebSocket subscription, but it does not disclose this sensitive action to the user beyond generic connection status messages. The script also subscribes to and prints returned data, yet there is no comment, prompt, or explicit warning describing that credentials and subscription data will be used/transmitted.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"auth:email": "tsx authenticateEmail.ts"
  },
  "dependencies": {
    "graphql": "^16.8.1",
    "graphql-request": "^6.1.0",
    "js-yaml": "^4.1.0",
    "open": "^10.0.3",
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unverifiable Dependency: graphql has 1 known advisory(ies) (CVE-2023-26144 (graphql Uncontrolled Resource Consumption vulnerability)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "graphql": "^16.8.1",
    "graphql-request": "^6.1.0",
    "js-yaml": "^4.1.0",
    "open": "^10.0.3",
    "subscriptions-transport-ws": "^0.11.0",
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

No suspicious patterns detected.