Back to skill

Security audit

Deep HJB Solver Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a coherent HJB code generator, but it directs automatic file creation and includes a scaffold script that can write outside the intended project paths if given an unsafe problem name.

Review before installing or using in automation. Only use simple snake_case problem slugs, run it in a disposable or project-scoped directory, inspect generated files before training, and consider pinning dependencies in a virtual environment. There is no evidence of exfiltration or persistence, but the path-handling and automatic copy behavior warrant caution.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/scaffold_hjb_problem.py:213
Finding
Unvalidated problem slug allows arbitrary file creation outside the project directory<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scaffold_hjb_problem.py`, lines 213–240 **Vulnerability Type**: Path traversal and arbitrary file creation **Risk Level**: Medium ### Vulnerable Code ```python module_slug = args.name.strip().lower() class_prefix = snake_to_camel(module_slug) control_names = [c.strip() for c in args.control_names.split(",") if c.strip()] if len(control_names) != args.num_controls: raise ValueError("num-controls must match number of control-names") # Ensure DGM framework is present; copy from bundled assets if not. repo_root = Path.cwd() bootstrap_framework(repo_root) write_file( repo_root / "src" / "configs" / f"{module_slug}_config.py", build_config(module_slug, class_prefix, args.dimension, args.num_controls, control_names), ) write_file( repo_root / "src" / "problems" / f"{module_slug}_problem.py", build_problem(class_prefix), ) write_file( repo_root / "src" / "losses" / f"{module_slug}_loss.py", build_loss(class_prefix), ) write_file( repo_root / "examples" / f"{module_slug}_train.py", build_example(module_slug, class_prefix), ) ``` The destination-writing helper also creates attacker-selected parent directories: ```python def write_file(path: Path, content: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) if path.exists(): raise FileExistsError(f"File already exists: {path}") path.write_text(content, encoding="utf-8") ``` ### Technical Analysis The value supplied through `--name` is only stripped and converted to lowercase. It is not validated as a Python identifier or restricted to a safe slug format. Path separators, `..` components, and absolute path syntax can consequently become part of the destination passed to `Path`. Python path composition normalizes traversal components when the path is used by the filesystem. In addition, if a later path component is absolute, `pathlib` can discard preceding components. The generated filen ...[truncated 2285 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict problem names to a conservative slug syntax before performing any filesystem operation: ```python import re SAFE_SLUG = re.compile(r"^[a-z][a-z0-9_]*$") module_slug = args.name.strip().lower() if not SAFE_SLUG.fullmatch(module_slug): raise ValueError( "--name must begin with a letter and contain only lowercase " "letters, digits, and underscores" ) ``` 2. Resolve and verify each output path against an explicitly resolved repository root: ```python repo_root = Path.cwd().resolve() def safe_destination(relative_path: Path) -> Path: destination = (repo_root / relative_path).resolve() try: destination.relative_to(repo_root) except ValueError as exc: raise ValueError(f"Output path escapes repository root: {destination}") from exc return destination ``` 3. Build destinations exclusively from validated relative components: ```python config_path = safe_destination( Path("src") / "configs" / f"{module_slug}_config.py" ) ``` 4. Reject all absolute paths, path separators, `.` components, and `..` components even if additional validation is added elsewhere. 5. Where hostile local filesystem state is in scope, account for symbolic links and time-of-check/time-of-use races. Avoid following untrusted symlinks and use atomic, exclusive file creation rather than a separate existence check followed by `write_text()`. 6. Run scaffolding with the minimum filesystem permissions required and avoid executing it from privileged or broadly writable automation contexts. ]]>

T08 · Insecure Dependencies

Note
Location
assets/requirements.txt:1
Finding
Mutable dependency ranges create avoidable Python supply-chain exposure<![CDATA[ ## Vulnerability Details **File Location**: `assets/requirements.txt`, lines 1–3; installation instruction at `SKILL.md`, lines 385–393 **Vulnerability Type**: Unpinned third-party dependencies **Risk Level**: Low ### Vulnerable Code `assets/requirements.txt`: ```text tensorflow>=2.15.0 numpy>=1.21.0 matplotlib>=3.5.0 ``` `SKILL.md`: ```bash cd <slug> pip install -r requirements.txt ``` ### Technical Analysis Each dependency uses only a minimum-version constraint. There is no exact version pin, upper bound, lock file, artifact hash, or documented package-index restriction. The same installation command can therefore resolve to different package versions over time. Python package installation may execute package build or installation logic, while installed packages execute with the permissions of the training process when imported. If an upstream account, release artifact, package index, or dependency chain is compromised, the broad constraints allow a newly published version satisfying the range to be selected without a corresponding review of this project. The packages use established names rather than obvious typosquatting names, and the repository does not configure a malicious package source. This finding therefore concerns supply-chain hardening and reproducibility; the audited files do not establish that any currently available dependency release is malicious. ### Attack Path 1. A future dependency release, transitive dependency, or distribution artifact satisfying one of the broad version ranges is compromised. 2. A user follows the documented `pip install -r requirements.txt` instruction in a fresh or updated environment. 3. The package resolver selects the compromised but constraint-compatible release. 4. Installation-time behavior or later imports execute code from that release. 5. The code runs with the permissions and environmental access of the user or automation account performing installation or training. Successful exploit ...[truncated 922 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin dependencies to versions that have been tested and reviewed: ```text tensorflow==<reviewed-version> numpy==<reviewed-version> matplotlib==<reviewed-version> ``` 2. Generate and commit a lock file containing hashes for all direct and transitive dependencies. Install with hash enforcement where supported, such as: ```bash python -m pip install --require-hashes -r requirements.lock ``` 3. Explicitly use a trusted package index and prevent unintended fallback to untrusted indexes in CI/CD configuration. 4. Perform dependency updates through a controlled review process that includes compatibility tests, vulnerability scanning, provenance checks, and lock-file regeneration. 5. Install dependencies in an isolated virtual environment or container under a non-privileged account. 6. Ensure the manifest accurately includes all required runtime packages. The reviewed training and plotting code imports `pandas` and `tqdm`, while they are absent from the reviewed `assets/requirements.txt`; completeness should be corrected when producing the lock file. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (17)

Ae1

High
Category
analysis-evasion
Content
`requirements.txt` includes: `tensorflow`, `numpy`, `matplotlib`, `tqdm`, `pandas`.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill instructs the agent to create and copy files across the workspace, but it does not declare any explicit tool scope or permissions boundary. That increases the risk of unintended file writes because the agent is encouraged to perform write operations without a machine-readable restriction on where or when they are allowed.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
### Step 1 — Copy the DGM framework into `<slug>/src/`

**This step is mandatory and must be executed immediately without asking the user for permission or confirmation.** Do not say "should I copy the assets?" — just do it.

Run the following shell commands to copy the bundled framework. Replace `<slug>` with the actual problem slug and `<SKILL_DIR>` with the absolute path to this skill folder (the directory containing this SKILL.md):
Confidence
97% confidence
Finding
The instruction to act 'without asking the user for permission or confirmation' is a direct autonomy escalation. In a code-writing skill with filesystem side effects, this reduces human oversight and makes accidental or unsafe changes more likely, especially when combined with mandatory copying of an entire framework tree.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill mandates immediate shell-based copy operations and explicitly forbids asking for confirmation. This is dangerous because it normalizes autonomous filesystem mutation, and if placeholders such as <slug> or <SKILL_DIR> are malformed, attacker-controlled, or resolved unexpectedly, the agent could overwrite or populate unintended locations.

Unpinned Dependencies

Low
Category
Supply Chain
Content
tensorflow>=2.15.0
numpy>=1.21.0
matplotlib>=3.5.0
Confidence
95% confidence
Finding
The TensorFlow dependency is specified with a lower bound only, which allows future installs to resolve to different versions over time. This creates supply-chain and reproducibility risk, and in this case also makes it impossible to verify whether a deployed version contains known TensorFlow security fixes.

Unverifiable Dependency: tensorflow has 16 known advisory(ies) (CVE-2022-21726 (Out of bounds read in Tensorflow); CVE-2022-35968 (TensorFlow vulnerable to `CHECK` fail in `AvgPoolGrad`); CVE-2022-23591 (Stack overflow in TensorFlow) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding
TensorFlow has multiple known advisories, and because the manifest does not pin a concrete version, there is no way to determine whether installations will use a patched or affected release. In a code-generation skill focused on TensorFlow-based HJB training, this is more relevant because TensorFlow is a core runtime dependency rather than an incidental tool.

Unpinned Dependencies

Low
Category
Supply Chain
Content
tensorflow>=2.15.0
numpy>=1.21.0
matplotlib>=3.5.0
Confidence
93% confidence
Finding
NumPy is also unpinned, so installations are not deterministic and may pull in different versions depending on when and where the skill is installed. That increases the chance of unexpected vulnerable or incompatible versions entering the environment.

Unverifiable Dependency: numpy has 16 known advisory(ies) (CVE-2014-1859 (Numpy arbitrary file write via symlink attack); CVE-2021-41495 (NumPy NULL Pointer Dereference); CVE-2021-33430 (NumPy Buffer Overflow (Disputed)) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
84% confidence
Finding
NumPy also has known advisories, and the unpinned requirement prevents verification that a safe version will be installed. Although this file alone does not prove exploitation, it does represent a genuine dependency-hygiene weakness that can permit vulnerable versions into the environment.

Unpinned Dependencies

Low
Category
Supply Chain
Content
tensorflow>=2.15.0
numpy>=1.21.0
matplotlib>=3.5.0
Confidence
90% confidence
Finding
Matplotlib is not pinned to an exact version, which introduces non-deterministic builds and weakens supply-chain control. While the direct security impact is usually lower than for a runtime framework, it still increases exposure to regressions or newly introduced vulnerable releases.

Natural-Language Policy Violations

Low
Confidence
90% confidence
Finding
The file includes user-facing natural language in Chinese in the class docstring and multiple comments, while the file itself does not indicate that the skill is region-specific or provide any opt-in for language/locale. Under the policy rule, forcing a specific language without user choice can be a natural-language policy violation.

Natural-Language Policy Violations

Low
Confidence
92% confidence
Finding
This code file contains natural-language comments written in Chinese, including default-configuration guidance, without any indication that language choice is optional or user-selectable. The stated policy flags language or locale constraints that force a specific language without opt-in, and these comments impose a fixed language for maintainers/readers.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
Several inline comments switch to Chinese-only explanations for boundary handling, which can violate the language-policy rule when no opt-in or justification is provided. This is a natural-language issue rather than a code defect, but it still creates a fixed-language expectation for readers.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
if key in ('T', 'd', 'dimension'):  # 已经手动设置了
                continue
            if hasattr(config, key):
                setattr(self, key, getattr(config, key))
    
    def get_domain_bounds(self):
        """
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Natural-Language Policy Violations

Low
Confidence
94% confidence
Finding
The custom-metric section includes Chinese-only comments such as explanations for automatic computation and ignored errors, again imposing a specific language on maintainers without opt-in. Under the policy, this is a language/locale natural-language violation unless the constraint is explicitly justified.

Natural-Language Policy Violations

Low
Confidence
93% confidence
Finding
User-facing developer guidance in comments and docstrings for formatting output and constraint collection is written partly in Chinese without any documented language choice. This creates an implicit locale restriction that falls under the natural-language policy check.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
params = {}
        for key in self.problem_params_keys:
            if hasattr(self, key):
                val = getattr(self, key)
                # Convert numpy arrays to lists for JSON serialization
                if hasattr(val, 'tolist'):
                    val = val.tolist()
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
# 自动收集以 'C' 开头的约束参数(如 Cbeta_up, CbetaZ_up)
        for key in self.problem_params_keys:
            if key.startswith('C') and hasattr(self, key):
                constraints[key] = getattr(self, key)
        # 添加 case 参数(如果存在)
        if hasattr(self, 'case'):
            constraints['case'] = self.case
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Static analysis

No suspicious patterns detected.