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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests_guard_and_run.py (reported line 16)May include surrounding context.

python
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 (

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/guard_and_run.py (reported line 174)May include surrounding context.

python
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",

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests_guard_and_run.py (reported line 139)May include surrounding context.

python
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",

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests_guard_and_run.py (reported line 225)May include surrounding context.

python
"--",
    "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)

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests_guard_and_run.py (reported line 278)May include surrounding context.

python
"--",
    "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)

Env Variable Harvesting

High
Category
Data Exfiltration
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.

Content

Scanner excerpt · scripts/tests_guard_and_run.py (reported line 450)May include surrounding context.

python
"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",

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/guard_and_run.py (reported line 272)May include surrounding context.

python
args.extend(["--block-threshold", str(block_threshold)])

    try:
        proc = subprocess.run(
            args,
            text=True,
            capture_output=True,

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · scripts/guard_and_run.py (reported line 638)May include surrounding context.

python
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(

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/tests_guard_and_run.py (reported line 30)May include surrounding context.

python
base_env = dict(env)

    command = [sys.executable, str(SCRIPT), *args]
    proc = subprocess.run(
        command,
        env=base_env,
        input=input_text,

Autonomous Decision Making

Medium
Category
Excessive Agency
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.

Content

Scanner excerpt · scripts/guard_and_run.py (reported line 537)May include surrounding context.

python
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(

Autonomous Decision Making

Medium
Category
Excessive Agency
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.

Content

Scanner excerpt · scripts/tests_guard_and_run.py (reported line 320)May include surrounding context.

python
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(

Intent-Code Divergence

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.