Back to skill

Security audit

SenseCraft HMI Web Content Generator

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to be a legitimate SenseCraft e-ink content generator, but it needs review because it starts local services, persists tokens/configuration, and recommends public forwarding with weak access-token handling.

Install only if you are comfortable letting the skill create a local Node/Express project, write configuration and token files, open a browser, and run a PM2-managed web server. Avoid exposing the localhost server to the public internet unless you add stronger authentication, HTTPS, short-lived tokens, and access restrictions; stop the PM2 service when done and treat the generated .wizard-config.json and token URL as sensitive.

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/wizard.js:21
Finding
Unauthenticated Wizard Endpoint Allows Arbitrary Configuration Overwrite and Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wizard.js:21-30`, with network binding at `scripts/wizard.js:66` **Vulnerability Type**: Unauthenticated file overwrite, missing request validation, and unbounded request buffering **Risk Level**: High ### Vulnerable Code ```js } else if (req.method === 'POST' && req.url === '/api/save-config') { let body = ''; req.on('data', chunk => body += chunk); req.on('end', () => { // Ensure directory exists const configDir = path.dirname(CONFIG_FILE); if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } fs.writeFileSync(CONFIG_FILE, body); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ success: true })); console.log('\n✓ Configuration saved to:', CONFIG_FILE); setTimeout(() => process.exit(0), 500); }); } ``` The server is started without an explicit loopback address: ```js server.listen(currentPort); ``` ### Technical Analysis The `/api/save-config` endpoint accepts POST requests without authentication, a per-run nonce, origin verification, CSRF protection, or content-type enforcement. It writes the complete request body directly to `data/.wizard-config.json` without parsing or validating it against the expected configuration schema. The request body is accumulated in memory without a maximum size: ```js req.on('data', chunk => body += chunk); ``` An attacker can therefore submit malformed or attacker-controlled configuration data or continuously send a large request body to consume process memory. Because `server.listen(currentPort)` does not specify `127.0.0.1`, Node.js may listen on an unspecified address covering available network interfaces, depending on the operating system. The resulting file is relevant to the Agent workflow because `SKILL.md` instructs subsequent operations to follow the configuration saved in `.wizard-config.json`. Although this is not demonstrated to provid ...[truncated 1734 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind the wizard exclusively to the loopback interface: ```js server.listen(currentPort, '127.0.0.1'); ``` 2. Generate a cryptographically random, single-use token for each wizard session and require it on every state-changing request. 3. Validate the `Origin` header against the wizard's exact loopback origin. 4. Require `Content-Type: application/json`. 5. Reject oversized requests before buffering them, for example by enforcing a small limit such as 16 KB. 6. Parse the body with `JSON.parse` inside exception handling and reject invalid JSON. 7. Validate a strict schema: - Permit only known screen dimensions and color values. - Apply reasonable minimum and maximum dimensions. - Permit only known layout and image type identifiers. - Limit all string lengths. - Validate image URLs and local paths according to the intended trust model. 8. Write validated data atomically using restrictive file permissions. 9. Do not exit until a valid, authenticated wizard submission has been processed. 10. Treat the configuration as untrusted data when later used by the Agent, especially if it contains URLs, paths, or free-form prompts. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/init_project.js:48
Finding
Bearer Access Token Is Disclosed Through Query Strings and Process Logs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/init_project.js:48-59` and `scripts/init_project.js:84-87` **Related Instructions**: `SKILL.md:87-94`, `README.md:22` **Vulnerability Type**: Sensitive authentication token exposure **Risk Level**: Medium ### Vulnerable Code ```js let ACCESS_TOKEN; if (fs.existsSync(TOKEN_FILE)) { ACCESS_TOKEN = fs.readFileSync(TOKEN_FILE, 'utf8').trim(); } else { ACCESS_TOKEN = crypto.randomBytes(32).toString('hex'); fs.writeFileSync(TOKEN_FILE, ACCESS_TOKEN); console.log('Generated new access token:', ACCESS_TOKEN); } function requireToken(req, res, next) { const token = req.query.token; if (token === ACCESS_TOKEN) { next(); } else { res.status(403).send('Forbidden: Invalid or missing token'); } } ``` The generated server also prints the secret and complete credential-bearing URL: ```js app.listen(PORT, () => { console.log(`SenseCraft HMI server running on http://localhost:${PORT}`); console.log(`Access token: ${ACCESS_TOKEN}`); console.log(`URL with token: http://localhost:${PORT}/?token=${ACCESS_TOKEN}`); }); ``` The Skill explicitly recommends public forwarding of the service and sharing the query-string URL: ```md http://localhost:19527/?token=XXXXX ``` ```md Guide the user to use a reverse proxy tool to forward `http://localhost:19527` to the public network ``` ### Technical Analysis The generated server treats a query parameter as a bearer credential. Query-string secrets are commonly retained or propagated through: - Browser history and synchronization. - Copied links, screenshots, and bookmarks. - Reverse-proxy and HTTP access logs. - Process-manager logs. - Monitoring and analytics systems. - Referrer headers in some navigation scenarios. - Chat transcripts where the URL is presented to the user. The token is also printed directly to standard output. Because the Skill uses PM2, these messages may remain available through PM2 logs beyond the immediate server start ...[truncated 1505 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not place bearer credentials in query strings. 2. Use an `Authorization: Bearer` header for API clients or a secure, `HttpOnly`, `SameSite=Strict` cookie for browser access. 3. Never print the raw token or credential-bearing URL to process logs. 4. Store the token with restrictive permissions, such as mode `0600`, and verify existing file permissions when loading it. 5. Add token expiration and rotation, especially after public exposure. 6. Place authentication at the reverse proxy and require HTTPS. 7. Configure restrictive response headers, including: - `Referrer-Policy: no-referrer` - `Cache-Control: no-store` - A restrictive Content Security Policy 8. Redact authorization data from reverse-proxy and application logs. 9. Warn users explicitly that exposing the service publicly expands its attack surface. 10. Prefer a short-lived authenticated publishing mechanism over forwarding the local development server directly. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/init_project.js:19
Finding
Runtime Installation of Unpinned Express Dependency Creates Supply-Chain Risk<![CDATA[ ## Vulnerability Details **File Location**: `scripts/init_project.js:19-30` **Vulnerability Type**: Unpinned runtime dependency installation **Risk Level**: Medium ### Vulnerable Code ```js // Initialize npm if needed const packageJsonPath = path.join(PROJECT_DIR, 'package.json'); if (!fs.existsSync(packageJsonPath)) { const originalDir = process.cwd(); process.chdir(PROJECT_DIR); try { execSync('npm init -y', { stdio: 'inherit' }); execSync('npm install express', { stdio: 'inherit' }); } catch (error) { console.error('Failed to initialize npm or install dependencies:', error); } process.chdir(originalDir); } ``` ### Technical Analysis The initializer executes: ```bash npm install express ``` No reviewed version range, lockfile, or integrity-controlled dependency graph is supplied by the Skill. The installed Express version and transitive packages therefore depend on registry state at execution time. NPM installation can also run package lifecycle scripts. If a future dependency or transitive dependency is compromised, the installation step could execute attacker-controlled code with the privileges of the user running the Skill. No evidence was found that the current `express` package name is typosquatted or intentionally malicious. The finding concerns mutable, unpinned supply-chain behavior rather than a confirmed malicious package. ### Attack Path 1. A user or Agent runs `scripts/init_project.js`. 2. The `data/package.json` file does not yet exist. 3. The script invokes the system's npm client. 4. NPM resolves the current Express package and its transitive dependency graph from the configured registry. 5. If the registry, account, package release, dependency chain, or local npm registry configuration has been compromised, unsafe package content is downloaded. 6. Package lifecycle scripts may execute during installation. 7. Compromised code then runs with the operating-system privileges of the ...[truncated 681 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Declare a reviewed, specific Express version in a committed project manifest. 2. Commit a reviewed `package-lock.json` that records the complete dependency graph and integrity hashes. 3. Replace runtime dependency resolution with: ```bash npm ci ``` 4. Update dependencies through a controlled review process rather than resolving the latest package during normal Skill execution. 5. Consider `npm ci --ignore-scripts` if no required dependency relies on lifecycle scripts. 6. Use a trusted registry and ensure project-local or user-level npm configuration cannot silently redirect package resolution to an untrusted source. 7. Run dependency installation with minimum privileges and in an isolated project directory. 8. Perform software composition analysis and vulnerability scanning on the locked dependency graph. 9. Fail initialization safely if dependency installation fails; do not continue into a partially initialized or ambiguous state. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (19)

Missing User Warnings

High
Confidence
97% confidence
Finding
The README instructs users to expose a localhost web server to the public internet through a reverse proxy, but it does not prominently warn about the risks of internet exposure, token leakage, unauthorized access, or misconfiguration. In this context, the skill is specifically intended to serve generated HTML over HTTP, so unclear guidance can lead users to publish locally hosted content insecurely and unintentionally expand their attack surface.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The skill claims AI-powered generation and optimization, but the described workflow also includes running a local wizard server, collecting configuration into local files, and broader setup behavior not reflected in the declared purpose. This discrepancy can mislead users about what will execute on their machine and weaken meaningful consent for local service creation and data persistence.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The skill claims AI-powered generation and optimization, but the described workflow also includes running a local wizard server, collecting configuration into local files, and broader setup behavior not reflected in the declared purpose. This discrepancy can mislead users about what will execute on their machine and weaken meaningful consent for local service creation and data persistence.

Missing User Warnings

High
Confidence
97% confidence
Finding
The skill encourages use of reverse-proxy tools to publish a localhost service without prominent user-facing warnings about privacy, discovery, authentication, and public attack surface. In context, this is especially risky because the skill also references token-gated local access, which may be treated as sufficient protection when exposed publicly.

Credential Access

High
Category
Privilege Escalation
Content
├── data/                       # Source files for AI-generated web pages
│   ├── server.js               # Express server
│   ├── index.html              # Currently displayed content
│   ├── .token                  # Access token
│   ├── public/images/          # Image resources (optional)
│   ├── public/css/             # CSS resources (optional)
│   └── public/js/              # JS resources (optional)
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
} else {
  ACCESS_TOKEN = crypto.randomBytes(32).toString('hex');
  fs.writeFileSync(TOKEN_FILE, ACCESS_TOKEN);
  console.log('Generated new access token:', ACCESS_TOKEN);
}

function requireToken(req, res, next) {
Confidence
97% confidence
Finding
The server generates an access token, stores it in plaintext on disk, and logs it to stdout. Secrets written to terminal logs and local files are commonly exposed through shell history capture, CI logs, process monitoring, shared terminals, backups, or other local users, enabling unauthorized access to the protected content.

Credential Access

High
Category
Privilege Escalation
Content
app.listen(PORT, () => {
  console.log(\`SenseCraft HMI server running on http://localhost:\${PORT}\`);
  console.log(\`Access token: \${ACCESS_TOKEN}\`);
  console.log(\`URL with token: http://localhost:\${PORT}/?token=\${ACCESS_TOKEN}\`);
});
`;
Confidence
99% confidence
Finding
The application logs both the access token and a full authenticated URL containing the token as a query parameter. Query-string credentials are especially risky because they leak into browser history, referrer headers, screenshots, terminal scrollback, logs, and monitoring tools, making accidental disclosure highly likely.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The activation condition 'When user requests e-ink content generation' is broad enough that the skill may trigger in loosely related contexts, increasing the chance it performs actions the user did not explicitly intend, including starting a local server and preparing externally accessible content. In this skill, that ambiguity is more dangerous because the documented workflow includes network exposure steps and tokenized access, so accidental activation can have security consequences beyond simple content generation.

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill instructs the agent to read local files, launch local servers, open browser windows, use pm2, and guide reverse-proxy/network exposure, but it declares no explicit tool scope or permission boundaries. That creates an authorization gap where a user may invoke behavior with filesystem and network side effects that are not transparently constrained by the manifest.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The workflow directs the agent to run a local wizard service, open a browser, and write configuration files without clearly warning the user that these are system-changing actions. Silent local side effects reduce informed consent and can surprise users in constrained or sensitive environments.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill explicitly guides the agent to expose a localhost service to the public internet through reverse-proxy tools, which materially increases attack surface beyond simple page generation. Even if framed as usability guidance, exposing a local service can leak generated content, tokens, or other local resources if the server or proxy is misconfigured.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The manifest presents this skill as an HMI web content generator with AI-powered layout and e-ink optimization. The scheduled updates section instructs use of OpenClaw cron, embedded JavaScript that pulls from APIs, and building Node.js/React-style backend services, which are broader automation and application-development capabilities not justified by the narrow generation-focused description.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
SQP-3 applies to all file types and covers language or locale policy violations. This markdown guidance file consistently presents its instructions in Chinese, with only limited English terms, and does not indicate that the language is optional, user-selectable, or required for a region-specific purpose.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The skill is described as generating web content for e-ink HMI displays, but this initializer invokes shell commands to create an npm project and install dependencies. Spawning package-management commands is a broader system-level capability than the stated content-generation purpose and is not justified by the manifest text alone.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The initializer writes and provisions a local HTTP server that exposes generated content and static assets. Although access is gated by a token, the design weakens security by placing the secret in URL query parameters and creating a persistent network service that may be exposed beyond localhost if later reconfigured or proxied.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The embedded HTML explicitly sets `lang="zh-CN"` and multiple user-facing labels/descriptions are presented in Chinese, while the skill does not offer a language selection or explain that it is limited to a Chinese-speaking or region-specific audience. That creates a natural-language locale policy concern because the skill imposes a specific language by default.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The manifest describes a skill for generating web content for e-ink displays, but this script also persists configuration to disk and launches the user's default browser via a child process. While a setup wizard is related, these host-side side effects are materially different from content generation itself and are not reflected in the description.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The script uses child_process.exec to invoke a platform shell command to open the browser, which is a host process launch capability. Even though the URL is locally constructed, using exec introduces unnecessary command-execution surface and normalizes a capability broader than needed for page-generation, which becomes riskier if any part of the command becomes user-influenced in future changes.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
This markdown file presents substantial instructional content in Chinese while also embedding English labels and examples, but it does not state the intended audience or offer a language/locale option. Under the policy for natural-language constraints, forcing a language without user opt-in can be a locale-policy issue.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/init_project.js:25

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/wizard.js:63

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/init_project.js:52