“Remove hidden code” sounds like a text-cleanup task. I treat it as an execution-security problem: determine what an artifact can do, identify where its instructions came from, and restrict its capabilities before running it.
The source article cites community reports of approximately 800GB of deleted files, including the entire CursorAI application, after a developer executed code generated with assistance from Gemini 3 inside the CursorAI IDE. That is a reported incident, not a verified forensic account. The engineering lesson does not depend on attributing it to a particular model: generated cleanup code should never inherit unrestricted access to a workstation.
Separate Instructions From Executable Behavior
“Hidden code” covers several different problems. Prompt injection targets a model’s instruction-following behavior; malicious JavaScript rendered in a browser targets an execution environment. Both can appear in the same workflow, but stripping text will not solve both.
I divide the inputs into three groups:
- Adversarial instructions: directives in retrieved webpages, PDFs, plugin responses, comments, annotations, or document metadata. These become indirect prompt injection when external content attempts to redirect the model. OWASP recognizes prompt injection as an LLM risk category.
- Concealed content: zero-width characters, homoglyphs, base64 or hex payloads, compressed strings, hidden HTML/JS, and content embedded in images or document layers.
- Unsafe behavior: dynamic execution, subprocess creation, network access, dependency installation, or destructive filesystem operations. These do not need to be concealed or intentionally malicious to cause damage.
A request to “clean up” can produce rm -rf /path/to/dir or shutil.rmtree() without any attacker involved. Plausible code and confident explanations are not evidence of safe path handling. Conversely, threat reporting from 2025 describes AI-assisted obfuscation workflows that chain models to generate and transform malicious payloads. My controls need to cover both accidental destruction and deliberate concealment.
Put a Gate Between Generation and Execution
I would enforce this pipeline in CI/CD and agent execution policy, not leave it as a reminder beside the run button.
Preserve the Original, Then Extract
Keep the original response, conversation, system prompt, retrieved documents, and plugin results. Record the extracted source separately, along with every transformation and approval decision. Provenance helps investigate whether suspicious behavior appeared after a particular document or tool response entered the context.
Extract code from prose and Markdown or HTML wrappers using language-aware handling. Inspect comments and metadata for instructions rather than silently discarding the evidence. Format the extracted source with tools such as Black or Prettier, but do not confuse formatting with a security check.
Unicode normalization also needs review. U+200B, U+200C, U+200D, and U+FEFF deserve attention, but removing characters can change strings or language-specific behavior. I want a visible diff, not an undocumented “clean” result.
Classify Capabilities Before Approving Them
Start with inexpensive checks: parse the source, run linters and security rules, and scan for credentials, suspicious dependency sources, encoded blobs, and dynamically constructed commands. Useful review triggers include sudo, rm -rf, shutil.rmtree, subprocess.Popen, os.system, eval, exec, and sensitive or unexpected absolute paths such as /home/ or C:\.
Then examine data flow. Can an unvalidated path reach a recursive delete? Can retrieved text become part of a shell command? Does a decoded string reach an execution function? Concatenated API names, chr() chains, reflective calls, and long opaque strings deserve investigation, not automatic declarations of malware.
A rule engine or secondary model can classify behavior as read, write, delete, network, or install. I would use that classification to route review, especially for writes and deletes, never as the authorization boundary itself.
Block Capabilities Instead of Guessing What to Delete
Sanitization removes unwanted representation or wrapping. Neutralization prevents behavior. I prefer an explicit exception at an unapproved operation over deleting lines until the program looks harmless.
AST-backed rewriting can replace identified dynamic execution with a controlled failure such as “unsafe dynamic behavior blocked.” Approved wrappers can enforce subprocess timeouts and execution policy. Mock adapters can replace privileged service calls during testing. Neither technique removes the need to review the rewritten source.
Block generated pip and npm installation steps until dependencies are declared and approved through your registry. Remove access to real API keys and tokens during evaluation. Require explicit policy for allowed modules, endpoints, filesystem locations, and external executors.
Two Small Python Checks, With Their Limits
Make Invisible Characters Inspectable
The source example used re.compile(r''), which matches the empty string and does not remove zero-width characters. This version explicitly targets the four listed code points and returns their original positions for audit logging. It is a narrow transformation, not a general Unicode sanitizer.
import re
ZERO_WIDTH_RE = re.compile(r"[\u200B\u200C\u200D\uFEFF]")
def strip_zero_width(source: str) -> tuple[str, list[tuple[int, str]]]:
removed = [
(match.start(), f"U+{ord(match.group()):04X}")
for match in ZERO_WIDTH_RE.finditer(source)
]
return ZERO_WIDTH_RE.sub("", source), removed
The caller should persist the removal record and diff. Do not execute the transformed source merely because this function returned successfully.
Flag Obvious Dynamic Execution
This AST check catches direct eval and exec calls plus attribute references named popen or system. It remains deliberately limited: aliases, other subprocess APIs, reflective access, and string-built names can evade it, while unrelated attributes can trigger it.
import ast
def has_dynamic_exec(source: str) -> bool:
tree = ast.parse(source)
for node in ast.walk(tree):
if isinstance(node, ast.Call):
if getattr(node.func, "id", "") in ("eval", "exec"):
return True
if isinstance(node, ast.Attribute):
if node.attr in ("popen", "system"):
return True
return False
A positive result should block automatic execution and require review. A negative result is not a safety verdict. Parsing failures should also stop the pipeline rather than bypass inspection.
Test Behavior Without Exposing Real Data
Static analysis and runtime monitoring complement each other. Runtime tests can expose behavior hidden behind decoding or dynamic dispatch, but a single sandbox run cannot prove the absence of delayed or conditional payloads.
Use an ephemeral container or microVM with no network, no host credentials, and no host mounts. gVisor and Firecracker are options to evaluate. Apply syscall restrictions such as seccomp alongside filesystem isolation and appropriate security profiles; syscall filtering alone is not a path-based write policy. Limit CPU, memory, and execution time. Where I/O is necessary, mediate it through a policy-enforcing interface.
Run against synthetic directories containing sentinel files. Monitor deletes, overwrites, process spawning, attempted network connections, and unexpected I/O. Inspect parent-directory traversal, unbounded recursive operations, and repeated writes to the same destination. Canary testing is useful precisely because the files are disposable.
The Toolchain I Would Maintain
I would combine Python ast or language-appropriate parsers with Bandit, Semgrep, and ESLint security plugins. Add secret scanning, dependency provenance checks, Unicode inspection, and heuristics for base64, hex, and gzip content. Encoded data should go to inspection, not straight to an execution path.
Keep organization-specific rules for JavaScript’s Function constructor, dynamic imports, string-built API names, unapproved network destinations, process creation, and writes to system directories such as /etc. These are review policies, not universal proof of malicious intent. Store original output, transformed output, diffs, sandbox observations, and decisions in an immutable audit trail.
The boundary I care about is straightforward: model output does not authorize its own execution. Parsing, review, sandboxing, least privilege, and continued adversarial testing belong around that boundary. Removing suspicious characters is useful housekeeping; restricting what the resulting program can actually touch is the security control.
Originally published at cometapi.com
Top comments (0)