T09 · Insecure Skill Coding Practices
- Location
scripts/intranet_web.py:345- Finding
Plugin CGI Hash Verification Bypass
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill is a disclosed local file server, but its CGI/plugin code has real code-execution and secret-handling risks that need review before installation.
Install only if you understand that enabling CGI lets local index.py files run as code under your user account. Avoid enabling CGI for writable or untrusted plugin directories, do not expose it beyond loopback unless authentication and host allowlists are configured, keep tokens out of shared or committed files, and treat the plugin hash check as unreliable until fixed.
scripts/intranet_web.py:345Plugin CGI Hash Verification Bypass
scripts/intranet.py:106Bearer Token Stored in Plaintext Runtime Configuration
scripts/intranet_web.py:409CGI Processes Inherit the Complete Server Environment
scripts/intranet.py:230Unverified PID File Can Terminate an Unrelated Process
The documented purpose presents the skill mainly as a lightweight static file server with plugin/CGI support, but the behavior also includes process management, persistent state handling, auth policy enforcement, dynamic module loading, and workspace autodiscovery. This mismatch is security-relevant because users may approve or deploy the skill under an incomplete mental model, while the extra behaviors expand attack surface and create opportunities for unauthorized persistence, exposure, or execution paths.
The CGI environment starts from os.environ.copy(), passing the server process's full ambient environment into executed scripts. In this skill context, CGI scripts are reachable through HTTP and may be plugin-provided, so secrets in environment variables (API keys, tokens, credentials, proxy settings, workspace paths) can be exposed to script authors or leaked back to clients.
parsed = urllib.parse.urlparse(self.path)
env = os.environ.copy()
env.update({
"REQUEST_METHOD": "GET",
"SCRIPT_NAME": url_path,
The documentation explicitly recommends storing the bearer token in config.json, which is a plaintext file under the workspace. In a local developer environment, workspace files are often readable by other users, included in backups, accidentally committed to source control, or exposed through adjacent tooling, so this guidance can lead to credential disclosure and unauthorized access to the intranet service.
The skill describes capabilities that require broad access to environment variables, filesystem, networking, and shell execution, but it does not declare any explicit tool scope or permission boundaries. That omission weakens reviewability and least-privilege enforcement, making it easier for the skill to be granted more power than operators expect, especially because it starts a server, writes runtime files, and can execute CGI scripts.
The start command stores the bearer token into the runtime config file in the intranet workspace, and there is no indication that file permissions are restricted or that users are warned credentials will be written to disk. On multi-user systems, shared workspaces, backups, or accidental file disclosure, this can expose the token and allow unauthorized access to the server.
This code executes local index.py files via subprocess.run, which is a safety-critical operation because it runs arbitrary executable code from the served directory or plugin root. While the module docstring documents CGI support, there is no confirmation prompt, user-facing log at execution time, or explicit warning in this file near the operation to disclose that requests may trigger code execution.
The server executes local CGI scripts via subprocess based on HTTP requests, which creates a direct remote-to-code-execution path whenever CGI is enabled. Although execution is limited to index.py and some path controls exist, this still means any writable or compromised webroot/plugin content can be executed by a requester, making the feature inherently dangerous in this context.
})
try:
result = subprocess.run(
[sys.executable, str(actual_script)],
capture_output=True,
timeout=_CGI_TIMEOUT,
The generated HTML sets lang="en", which forces an English locale in the skill's user-facing output. Under the policy, locale-specific behavior should either offer user choice or be clearly documented and justified; this file provides neither.
No suspicious patterns detected.