ponytail-review
DIDietrichGebert/ponytail162k近 7 天新增 5,405 个 star
Security hardening guidance for AI agent frameworks that process untrusted content, invoke tools, write workspace files, manage runtime identifiers, or handle credentials. Use when building or reviewing an agent runtime, autonomous worker, tool gateway, memory service, or multi-tenant agent deployment. Do not use for general web application security or offensive testing.
Harden the boundaries where an agent turns untrusted data into filesystem, network, memory, or tool actions. Produce concrete controls and tests for the framework being reviewed; a checklist without enforcement evidence is incomplete.
List every boundary as source -> parser -> validator -> side effect. Include direct user input, model output, retrieved documents, memory, environment variables, tool results, and messages from other agents.
For each side effect, record:
This step is complete when every write, command, network mutation, and credential use has one named authorization boundary.
Use an allowlist for identifiers that become filenames, keys, selectors, or command arguments. Agent IDs have the issue-defined hard maximum of 64 characters; deployment configuration may choose a stricter positive limit but cannot raise that boundary:
import re
SAFE_AGENT_ID_CHARS = re.compile(r"^[A-Za-z0-9_][A-Za-z0-9_-]*$")
HARD_MAX_AGENT_ID_LENGTH = 64
def require_agent_id(value: object, *, max_length: int) -> str:
if (
isinstance(max_length, bool)
or not isinstance(max_length, int)
or not 1 <= max_length <= HARD_MAX_AGENT_ID_LENGTH
):
raise RuntimeError("configured agent ID limit must be between 1 and 64")
if (
not isinstance(value, str)
or len(value) > max_length
or not SAFE_AGENT_ID_CHARS.fullmatch(value)
):
raise ValueError("invalid agent identifier")
return value
When an identifier becomes a CLI argument, pass it through an argv API and place it after the command's end-of-options marker where supported. Character validation is not a substitute for argument separation.
Reject missing and whitespace-only environment values before constructing a client:
def require_env(name: str, environ: dict[str, str]) -> str:
value = environ.get(name)
if value is None or not value.strip():
raise RuntimeError(f"{name} is required")
return value
Parse URLs, allow only schemes required by the deployment, require a host, reject embedded credentials, and block private-network destinations when the URL is attacker-controlled. For every outbound connection and redirect hop, pin the connection to a validated public address or verify the connected peer address at connection time so DNS rebinding cannot cross the boundary. A credential-bearing request must use HTTPS with certificate verification before credentials are attached. Disable automatic cross-origin credential forwarding; when the scheme or origin changes, strip the credentials and reauthorize the new destination or reject the redirect. Re-run destination, connected-address, and transport validation on every hop.
This step is complete when tests reject empty, oversized, malformed, traversal-shaped, credential-bearing, and unauthorized-destination values before any side effect, and prove that DNS rebinding and cross-origin redirects cannot carry credentials to an unapproved peer.
Reject absolute user-controlled paths and traversal components before joining. Reject Windows-unsafe components on every platform so behavior stays portable: reserved device names (CON, NUL, COM1, LPT1, including with an extension such as nul.txt), names ending in a dot or space (Win32 strips them), and : (drive-relative or NTFS alternate data stream syntax such as report.txt:ads). Resolve both the workspace and candidate path, then prove containment:
import re
from pathlib import Path
WINDOWS_RESERVED_NAME = re.compile(
r"(CON|PRN|AUX|NUL|COM[1-9¹²³]|LPT[1-9¹²³])(\..*)?",
re.IGNORECASE,
)
def workspace_path(workspace: Path, requested: str) -> Path:
# Treat both platform separators consistently before checking components.
relative = Path(requested.replace("\\", "/"))
if relative.is_absolute() or ".." in relative.parts:
raise ValueError("path must be workspace-relative")
for part in relative.parts:
if ":" in part or part.endswith((".", " ")) or WINDOWS_RESERVED_NAME.fullmatch(part):
raise ValueError("path component is not portable")
root = workspace.resolve(strict=True)
candidate = (root / relative).resolve(strict=False)
if not candidate.is_relative_to(root):
raise ValueError("path escapes workspace")
return candidate
Containment checks do not eliminate symlink races. For sensitive writes, open relative to a trusted directory handle where the platform supports it, reject symlink targets, create files exclusively, and verify ownership and permissions after opening.
This step is complete when tests cover sibling-prefix paths, nested .., absolute paths, symlink escapes, reserved device names, trailing dots or spaces, alternate data stream syntax, and a valid nested workspace path.
0o600; create private directories with 0o700.finally, while preserving a redacted error record when cleanup fails.This step is complete when file-mode checks, forced-error cleanup tests, and log-capture tests show that secret values never persist outside the approved boundary.
Read tool output with a byte limit rather than collecting an unbounded stream. Preserve structured fields, remove disallowed control characters, and append an explicit truncation marker with the original byte count. Keep a content hash when later forensic comparison matters.
Do not silently turn malformed output into an empty success value. Return a typed failure that names the producing tool, boundary, and validation reason without echoing sensitive content.
This step is complete when oversized output, invalid encoding, terminal-control sequences, malformed structured data, and a normal response all have deterministic tests.
Before calling the framework hardened, verify all of these behaviors through its real entry point:
Report the command used, exit status, rejected input, and observed absence of the side effect. Mock-only tests do not prove operating-system permissions, symlink handling, subprocess isolation, or network egress controls.
Prompt instructions are advisory. Move the same invariant into deterministic code immediately before the side effect and test a model response that violates it.
String prefix checks confuse sibling paths such as /work/app and /work/application. Resolve paths and use component-aware containment, then test symlinks separately.
Redact before values enter the logger, tracer, exception, or model context. Add a capture test using a sentinel secret and assert the sentinel is absent from every emitted channel.
security-review for ordinary application authentication, authorization, SQL injection, XSS, CSRF, and API security.security-scan for repository-wide automated vulnerability scanning.agent-harness-constructionsecurity-reviewsecurity-scanDIDietrichGebert/ponytail162k近 7 天新增 5,405 个 star
DIDietrichGebert/ponytail162k近 7 天新增 5,405 个 star
DIDietrichGebert/ponytail162k近 7 天新增 5,405 个 star
GAgarrytan/gstack136k近 7 天新增 883 个 star
按 OWASP Top 10 检查输入处理、登录和依赖,加固代码。
ADaddyosmani/agent-skills106k近 7 天新增 3,628 个 star
ADaddyosmani/agent-skills106k近 7 天新增 3,628 个 star