Back to skill

Security audit

Enterprise Legal Guardrails Public

Security checks for vulnerabilities and agentic risk

Overview

The skill is a disclosed outbound compliance checker, but its command-wrapper path can still execute publishing commands after REVIEW decisions and relies on under-scoped safety controls.

Install only if you intend to use it as an execution wrapper, not just a passive checker. Prefer direct checker use for sensitive drafts, enable --strict, use absolute-path allowlists, enable --sanitize-env with narrow keep rules, avoid --allow-any-command in production, and avoid passing sensitive text through process arguments where local process metadata may be logged.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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)

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/guard_and_run.py:117
Finding
Executable Allowlist Bypass Through PATH Shadowing## Vulnerability Details **File Location**: `scripts/guard_and_run.py:117-150` and `scripts/guard_and_run.py:633-638` **Vulnerability Type**: Executable spoofing and allowlist bypass **Risk Level**: High ### Vulnerable Code ```python def _is_allowed(command: list[str], allowed: list[str]) -> bool: if not allowed: return True target = command[0] target_name = Path(target).name target_lower = target.lower() target_name_lower = target_name.lower() for pattern in allowed: if not pattern: continue pattern = pattern.strip() if not pattern: continue candidate = pattern.lower() if candidate.startswith("regex:"): expr = candidate.split(":", 1)[1] try: if re.fullmatch(expr, target_lower): return True except re.error as exc: raise RuntimeError(f"Invalid regex allowlist pattern '{pattern}': {exc}") from exc continue if fnmatch.fnmatch(target_lower, candidate) or fnmatch.fnmatch(target_name_lower, candidate): return True if Path(pattern).is_absolute(): try: if Path(target).resolve() == Path(pattern).resolve(): return True except OSError: if target_lower == pattern.lower(): return True elif target_lower == pattern.lower() or target_name_lower == pattern.lower(): return True return False ``` The validated command is subsequently executed as follows: ```python env = None if args.sanitize_env: env = _sanitize_env(args.keep_env, args.keep_env_prefix) try: proc = subprocess.run(command, check=False, env=env, timeout=args.command_timeout) ``` ### Technical Analysis The command allowlist can authorize an executable solely ...[truncated 2289 chars]
Remediation
## Remediation Suggestions 1. Require absolute executable paths in production allowlists. 2. Before authorization, resolve the executable with `shutil.which()` when a basename is supplied. 3. Canonicalize the resolved path with `Path.resolve(strict=True)` and compare that canonical path against canonical absolute allowlist entries. 4. Execute the resolved and validated absolute path rather than the original basename. 5. Use a fixed, minimal trusted `PATH` instead of preserving the caller-controlled value. 6. Reject executables located in directories writable by untrusted users or groups. 7. Prefer exact path matching over wildcard and regex rules. If patterns remain supported, apply them only to canonical absolute paths. 8. Where feasible, verify executable ownership, permissions, and an expected file digest before execution. 9. Add regression tests that create a fake allowed executable in a temporary directory, prepend that directory to `PATH`, and confirm that the wrapper rejects it.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/guard_and_run.py:247
Finding
Sensitive Draft Content Exposed Through Process Arguments## Vulnerability Details **File Location**: `scripts/guard_and_run.py:247-278` **Vulnerability Type**: Plaintext sensitive-data exposure in process metadata **Risk Level**: Medium ### Vulnerable Code ```python args = [ sys.executable, str(CHECKER_SCRIPT), "--action", action, "--text", text, "--json", ] if app: args.extend(["--app", app]) if scope: args.extend(["--scope", scope]) if apps: args.extend(["--apps", *apps]) if policies: args.extend(["--policies", *policies]) if review_threshold is not None: args.extend(["--review-threshold", str(review_threshold)]) if block_threshold is not None: args.extend(["--block-threshold", str(block_threshold)]) try: proc = subprocess.run( args, text=True, capture_output=True, check=False, timeout=checker_timeout, ) except subprocess.TimeoutExpired as exc: raise RuntimeError(f"Guardrail check timed out after {checker_timeout}s.") from exc ``` ### Technical Analysis The complete outbound draft is inserted into the checker process's argument vector using `--text`. Process arguments are not an appropriate secret-bearing transport. Depending on the operating system and deployment configuration, they may be observable through process-listing tools, `/proc` interfaces, endpoint monitoring, application-performance monitoring, audit systems, crash reports, or container and orchestration telemetry. This is especially relevant because the Skill is explicitly intended to inspect personal identifiers, HR-sensitive statements, private messages, and other legally sensitive content. The draft may therefore contain precisely the information that should receive heightened confidentiality protection. The absence of shell execution prevents shell metacharacter injection here, but it does not prevent command-line metadata disclosure. ### Attack Path 1 ...[truncated 1157 chars]
Remediation
## Remediation Suggestions 1. Remove the draft from the child process's command-line arguments. 2. Send content to the checker through stdin, for example by passing `input=text` to `subprocess.run()` and omitting `--text`. 3. Preserve command-line arguments only for non-sensitive configuration such as action and threshold values. 4. If stdin cannot be used, pass an already-open file descriptor backed by protected storage rather than a predictable or ordinary temporary file. 5. If temporary storage is unavoidable, use a securely created file with mode `0600`, avoid shared directories where possible, and delete it reliably after processing. 6. Review process-monitoring and telemetry rules to ensure historical invocations containing `--text` are not retained. 7. Update documentation to recommend stdin or protected file input for sensitive drafts. 8. Add a regression test that inspects the checker invocation and confirms that the draft does not appear in the argument vector.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/guard_and_run.py:301
Finding
REVIEW Decisions Fail Open and Permit Outbound Execution## Vulnerability Details **File Location**: `scripts/guard_and_run.py:301-316` **Related Documentation**: `SKILL.md:25-30` **Vulnerability Type**: Fail-open policy enforcement and unsafe default configuration **Risk Level**: Medium ### Vulnerable Code ```python blocked = status == "BLOCK" or (strict and status == "REVIEW") if blocked: print( f"Blocked by enterprise legal guardrails ({status}) for {action} on {app or 'unknown'} " f"before command execution. Score: {report.get('score', 'n/a')}, " f"Findings: {report.get('findings_count', 'n/a')}", file=sys.stderr, ) return report, True if status == "REVIEW": suggestion = (report.get("suggestions") or ["Consider rewriting before execution."])[0] print(f"Guardrail REVIEW for {action} on {app or 'unknown'}: {suggestion}", file=sys.stderr) return report, False ``` The documented workflow states: ```markdown 1. Draft text. 2. Run the checker with the matching action/profile. 3. If result is **PASS/WATCH**, proceed. 4. If **REVIEW**, rewrite or route for human/legal review. 5. If **BLOCK**, do not execute. ``` ### Technical Analysis The wrapper treats `REVIEW` as non-blocking unless optional strict mode is enabled. Default parser configuration leaves strict mode disabled unless the caller supplies `--strict` or a corresponding environment variable. As a result, content that the checker identifies as requiring human or legal review proceeds to command execution after only a warning on stderr. This contradicts the documented workflow, which limits automatic execution to `PASS` and `WATCH`. The behavior is intentional enough to be covered by a regression test, but it remains an unsafe default for a component described as an execution boundary. Operators relying on the documentation may reasonably assume that a `REVIEW` result prevents publication. ### Attack Path 1. An outbound draft contain ...[truncated 1081 chars]
Remediation
## Remediation Suggestions 1. Treat `REVIEW` as blocking by default. 2. Permit execution after `REVIEW` only through an explicit approval mechanism rather than a general non-strict default. 3. Require an authenticated approval token, reviewer identity, reason, timestamp, and audit-log destination for any review override. 4. Make the safer behavior independent of mutable environment variables where the caller is not trusted. 5. Clearly distinguish policy decisions from process exit codes and ensure all integrations stop on both `REVIEW` and `BLOCK`. 6. Update tests so that default `REVIEW` behavior confirms the outbound command does not run. 7. If backward compatibility requires a transition period, emit a deprecation warning and provide a separately named, explicit `--proceed-after-review` option. 8. Align `README.md`, `SKILL.md`, wrapper behavior, and checker exit-code documentation so operators receive one consistent enforcement model.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The advertised purpose is a legal/compliance checker, but the skill also serves as a generic outbound execution wrapper that can run external commands, pass through selected environment variables, and optionally bypass allowlist protections with an 'allow-any-command' mode. This description-behavior mismatch is dangerous because users or agents may invoke it under the assumption that it only classifies text, while in reality it can become a privileged execution path for publishing actions or arbitrary subprocesses if misconfigured.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def run(*args: str, env: dict[str, str] | None = None, input_text: str | None = None) -> tuple[int, str, str]:
    if env is None:
        base_env = dict(os.environ)
        if not any(
            base_env.get(name)
            for name in (
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
assert "requires delimiter --" in err, (out, err)

# 6b) Missing allowlist by explicit env var should block execution.
env_no_allowlist = {k: v for k, v in os.environ.items() if k not in {
    "ENTERPRISE_LEGAL_GUARDRAILS_ALLOWED_COMMANDS",
    "ELG_ALLOWED_COMMANDS",
    "BABYLON_ALLOWED_COMMANDS",
Confidence
70% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
assert "requires delimiter --" in err, (out, err)

# 6b) Missing allowlist by explicit env var should block execution.
env_no_allowlist = {k: v for k, v in os.environ.items() if k not in {
    "ENTERPRISE_LEGAL_GUARDRAILS_ALLOWED_COMMANDS",
    "ELG_ALLOWED_COMMANDS",
    "BABYLON_ALLOWED_COMMANDS",
Confidence
70% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
"--",
    "cat",
    "/etc/hosts",
    env={**os.environ.copy(), "BABYLON_ALLOWED_COMMANDS": "python3"},
)
assert code == 1, (code, out, err)
assert "not in the allowlist" in err, (out, err)
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
"--",
    "cat",
    "/etc/hosts",
    env={**os.environ.copy(), "BABYLON_ALLOWED_COMMANDS": "python3"},
)
assert code == 1, (code, out, err)
assert "not in the allowlist" in err, (out, err)
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
"python3",
        "-c",
        "import os; print('KEEP_ME' in os.environ, any(k.startswith('SHARED_') for k in os.environ), 'DROP_ME' in os.environ)",
        env={
            **os.environ,
            "KEEP_ME": "1",
            "SHARED_TOKEN": "2",
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill documents shell execution, environment-variable handling, file writes, and wrapper-based command execution, but it does not declare any explicit tool scope or permission boundary in the skill metadata. That omission increases the chance that downstream agents or reviewers will treat it as a narrow text-only guardrail when it can actually launch commands and write audit data, creating avoidable execution and policy-bypass risk.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
This script is itself a legal/compliance guardrail, yet enforcement can be bypassed entirely via `--no-guard` or environment flags that set guardrails disabled. In a production pipeline, anyone who can influence CLI arguments or environment configuration can force a `PASS` result and allow unreviewed outbound content, defeating the skill’s core safety purpose.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The skill is framed as legal/compliance guardrails, but it proceeds to execute external commands after validation. That coupling is dangerous because users or integrators may trust it as a passive policy check while it actually has the ability to trigger side effects and run tooling, expanding the attack surface substantially.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
args.extend(["--block-threshold", str(block_threshold)])

    try:
        proc = subprocess.run(
            args,
            text=True,
            capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The code includes a built-in bypass that disables command allowlisting with `--allow-any-command` or equivalent environment flags. Although it asks for a reason and approval token, those checks are only syntactic and do not verify authorization, so anyone able to set arguments or environment can defeat the primary execution safeguard.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
env = _sanitize_env(args.keep_env, args.keep_env_prefix)
        
    try:
        proc = subprocess.run(command, check=False, env=env, timeout=args.command_timeout)
    except FileNotFoundError:
        print(f"Command not found: {command[0]}", file=sys.stderr)
        _append_audit_log(
Confidence
92% confidence
Finding
This code executes a downstream command supplied at runtime after only a pattern-based allowlist check, and it also supports an explicit allowlist bypass via `--allow-any-command`. In a guardrail skill, this turns a compliance validator into a command launcher, so misconfiguration, overbroad allowlist patterns, or bypass approval abuse can lead to arbitrary program execution.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
base_env = dict(env)

    command = [sys.executable, str(SCRIPT), *args]
    proc = subprocess.run(
        command,
        env=base_env,
        input=input_text,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
env=env_no_allowlist,
)
assert code == 2, (code, out, err)
assert "Refusing --allow-any-command without approval token" in err, (out, err)

# 12d) Unsafe escape hatch: allow any command only when explicitly enabled + reason+token.
code, out, err = run(
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
env=env_no_allowlist,
)
assert code == 2, (code, out, err)
assert "Refusing --allow-any-command without approval token" in err, (out, err)

# 12d) Unsafe escape hatch: allow any command only when explicitly enabled + reason+token.
code, out, err = run(
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Intent-Code Divergence

Low
Confidence
77% confidence
Finding
The top-level documentation presents the tool as deterministic and dependency-free, which suggests a self-contained scan, but runtime behavior is materially influenced by environment variables and optional file input. This is not a direct contradiction about network/model usage, but it does make the documented operational picture misleading for an auditor evaluating how the guardrails are actually controlled.

Static analysis

No suspicious patterns detected.