| layout | default |
|---|---|
| title | Security |
| nav_order | 4 |
VCoder's security helpers make the safe pattern the default one. They are a strong first line of defense — not a guarantee. This page documents what each helper does, and, just as importantly, what it does not.
Principle: validation is context-dependent. A value that is safe in one place (an HTML text node) is dangerous in another (a URL, a shell). VCoder gives you context-specific tools; you must pick the right one.
from vcoder import vc
vc.contains_traversal("../../etc/passwd") # True
vc.contains_traversal("%2e%2e/secret") # True (percent-encoded)
vc.safe_join("/srv/uploads", user_input) # Path, or UnsafePathError
vc.resolve_within("/srv/uploads", user_input) # resolves + confirms containment- Detects
..segments — raw, percent-encoded (%2e/%2f), double-encoded, and Unicode-normalized (NFKC) variants — plus embedded NUL bytes. safe_join/resolve_withinfully resolve the path (following symlinks) and confirm it stays within the allowed root.
Limitations. Filesystem access has races (TOCTOU): a path safe at check time can change before use. Symlinks, hardlinks, network and case-insensitive filesystems all complicate matters. Always run with least privilege and, where possible, operate on file descriptors rather than re-resolving paths.
from vcoder import vc
vc.validate_filename("report.pdf") # -> "report.pdf"
vc.validate_filename("../evil") # ValidationError (separator)
vc.sanitize_filename("my:report?.txt") # -> "my_report_.txt" (lossy)validate_filename rejects empty/./.., path separators, control and illegal
characters, trailing spaces/dots, Windows reserved device names (CON, NUL,
COM1...), and over-long names. sanitize_filename is a lossy best-effort that
always returns something usable — prefer validating when the exact name
matters.
from vcoder import vc
vc.run(["git", "status"])
vc.run_checked(["make", "test"]) # raises on non-zero exit
out = vc.run_capture(["echo", untrusted]) # captured stdout, metachars inert- Commands are always argument lists. There is no string-command form and
shell=Trueis not exposed, so shell metacharacters in arguments cannot start a subshell. - Arguments must be NUL-free strings/path-likes; the executable is resolved and
can be constrained with
allowed_dirs=to block attacker-planted binaries.
Limitations. Not using a shell stops shell injection, but the program you
run may still interpret arguments dangerously (find -exec, ssh ProxyCommand, tar flags). Understand the tool. Never pass untrusted data as
options (leading -) unless you have separated them with --.
from vcoder import vc
query, params = vc.sql.build_select("users", ["id", "email"], {"active": True})
# ("SELECT id, email FROM users WHERE active = ?", [True])
vc.sql.execute(cursor, query, params)
vc.sql.insert(cursor, "users", {"id": 1, "email": "a@b.com"})build_select/build_insertconstruct statements from validated identifiers and placeholders — values are always bound, never interpolated.executerefuses obviously unsafe queries (leftover{}/%dformatting, a bare string passed as parameters) viaguard_query.
Limitations. VCoder does not — and cannot — parse arbitrary SQL. If you build a query string yourself with an f-string, no library can retroactively tell the data from the code. Always pass values as parameters. Identifiers (table/column names) cannot be bound by drivers, so they are checked against a strict allow-list pattern; never feed unvalidated user input as an identifier.
from vcoder import vc
vc.html.escape(user_text) # safe inside an HTML text node
vc.html.escape_attribute(user_text) # safe inside a quoted attribute value
vc.html.strip_html(rich_text) # plain text, tags removed
vc.html.safe_html(untrusted) # treat input as text, escape everythingEscaping is context-dependent:
escape→ HTML text content.escape_attribute→ inside"..."/'...'attribute values (you supply the quotes).- Neither is sufficient inside
<script>,<style>, URLs, or unquoted attributes.
Limitations. To render a subset of user HTML (allow some tags), use a
dedicated sanitizer such as bleach with an allow-list. safe_html here escapes
everything — it never lets any tag through.
from vcoder import vc, PasswordPolicy
policy = PasswordPolicy(min_length=12, require_digit=True, min_bits=50)
vc.validate_password("correcthorsebatterystaple9", policy) # or PasswordTooWeak
pw = vc.password("Password: ", policy=policy, confirm=True)- Configurable length, character-class, entropy, and common-password rules.
- Optional Have I Been Pwned check (
check_pwned=True) using k-anonymity: only the first five characters of the SHA-1 hash leave the process. It is never called unless you opt in.
Limitations. Strength estimation is approximate; the built-in estimate is a simple model. VCoder validates strength — it does not store passwords. Always hash with a slow KDF (argon2/bcrypt/scrypt) before storage, and never log plaintext.
from vcoder import vc, RateLimitExceeded
# 5 attempts per 5 minutes, tracked per caller.
login_limiter = vc.RateLimiter(max_calls=5, per=300.0)
def login(body, request):
try:
login_limiter.check(key=request.client_ip)
except RateLimitExceeded as exc:
return 429, {"error": "too many attempts"}, {"Retry-After": int(exc.retry_after)}
... # then validate + verify credentials
# The same limiter can be attached to an interactive prompt:
vc.password("Password: ", rate_limit=login_limiter)RateLimiter(max_calls, per) allows at most max_calls events within any
per-second sliding window. check(key) raises RateLimitExceeded (with
retry_after) when the limit is hit; allow(key) is the non-raising form.
Passing a key (client IP, user id, ...) gives each caller an independent
budget — ideal for slowing down credential-guessing. Attaching a limiter to a
prompt via rate_limit= caps how fast that input can be submitted, counting
every attempt (including invalid reprompts).
Limitations. The limiter is in-process and in-memory: it does not coordinate across multiple workers or hosts. For distributed rate limiting, back it with a shared store (Redis, etc.) or a dedicated gateway. It throttles frequency; it is not a substitute for authentication or an account-lockout policy.
Logging is off by default. When enabled (vc.configure(logging=True)), secret
prompts route through a redacting path that records only that a secret was
received — never the value. Password, and any secret=True, inputs are never
echoed and never logged.