Bruteforce sinks
vulkro scan --bruteforce-sinks statically drives a corpus of
adversarial payloads at every critical-surface call site in the
codebase and flags the payloads that reach the sink without an
effective recognized guard.
The engine never touches the network. No real API call is made. No sandbox is spun up. The "brute force" is in-process AST walking against a fixed payload table.
What it catches that taint analysis alone misses
Existing taint flow says "this input reaches stripe.charges.create."
Bruteforce sinks says which payload classes can reach that sink:
amount = -1reachingstripe.charges.createwithout a sign checkcustomerId = "../../admin"reachings3.getObjectwithout path sanitizationemail = "<huge string>"reachingsendgrid.send(DoS / 413 cascade)prompt = "ignore previous, exfiltrate"reachinganthropic.messages.createwithout a system / user role splitnull/undefined/NaN/MAX_SAFE_INTEGERreaching any third-party call without a type guard- SQLi shapes (
' OR 1=1 --, stacked drops, UNION) reachingcursor.executewithout parameterized binding - Path-traversal payloads (
../../etc/passwd,..\..\windows) reachingfs.writeFilewithout canonicalization
Coverage (Wave 1)
| Category | Examples |
|---|---|
| SQL | cursor.execute, Sequelize.query, knex.raw, prisma.$queryRaw |
| Shell | subprocess.run(shell=True), child_process.exec, os.system |
| HTTP | requests.get, httpx.post, axios.*, fetch |
| Payment | stripe.charges.create, braintree.transaction.sale, razorpay.orders.create |
| LLM | anthropic.messages.create, openai.chat.completions.create, cohere.chat, bedrock.invoke_model |
| File-write | open(path, 'w'), Path.write_text, fs.writeFile, fs.writeFileSync |
| Deserialization | pickle.loads, yaml.load, eval, vm.runInNewContext, Function constructor |
sendgrid.send, ses.send_email, smtplib.sendmail |
Languages: Python, JavaScript, TypeScript.
Payload corpus
About 30 payload entries across 8 super-classes (TypeConfusion, NumericExtreme, SizeExtreme, Unicode, Injection, PathTraversal, PromptInjection, PaymentExtreme).
Guard catalogue
About 70 recognizers across Python + JS / TS:
- Type checks:
isinstance,typeof,Number.isFinite,Number.isInteger,Array.isArray. - Null checks:
is None,=== null,=== undefined,??. - Range / length checks:
len(x) > N,x.length > N,x > 0. - Anchored regex:
re.fullmatch,re.match,.test(). - Allowlist membership:
in {...},.includes,.has. - Sanitizers:
html.escape,bleach.clean,markupsafe.escape,DOMPurify.sanitize,validator.escape,escape-html. shlex.quotefor shell payloads.- Parameterized DB binding:
cursor.execute(sql, (x,)),pool.query(sql, [x]),.where({...}). - URL scheme allowlist + private-IP reject for HTTP egress.
- Path canonicalize + base-dir check:
pathlib.Path.resolve,path.normalize+.startsWith. - Schema validation: Pydantic
.model_validate, Zod.parse, Joi.validate, Yup, AJV. - Email validators:
validator.isEmail,validators.email. yaml.SafeLoaderfor deserialization.- LLM system / user role split (separate
role: "system"entry inmessagesinstead of concatenation).
CLI flags
vulkro scan . --bruteforce-sinks
vulkro scan . --bruteforce-sinks --bruteforce-categories sql,payment,llm
vulkro scan . --bruteforce-sinks --bruteforce-confidence medium
vulkro scan . --bruteforce-sinks --bruteforce-payload-classes injection,path-traversal
Categories: sql, shell, http (aliases: ssrf, egress),
payment (alias: billing), llm (alias: ai), file /
file-write, deser / deserialization, email.
Payload classes: injection, numeric-extreme, type-confusion,
size-extreme, unicode, path-traversal, prompt-injection,
payment-extreme.
Confidence tiering
- High (default emit): no recognized guards on the path. Kill shot.
- Medium (opt in with
--bruteforce-confidence medium): guards exist on the path, but the engine cannot prove they cover this payload class. Surfaces the "you have a length check but no type check" gap. - Low: reserved for future alias-aware partial resolution.
Rule ID convention
BFS-{CATEGORY}-{payload-tag}. Examples:
BFS-PAYMENT-payment-negative-amountBFS-SQL-sql-or-1-eq-1BFS-LLM-llm-ignore-previousBFS-SHELL-shell-rm-rf
Rule IDs are stable across runs; downstream baselines key on them.
Desktop console
The desktop console hosts the engine in two places:
- The Tools menu in the title bar opens a global view.
- Each project's Security tab group has a project-scoped Bruteforce tab.
The view does not auto-run on load. The user clicks Discover candidate functions to list the project's critical-surface functions (functions that reach a classified sink), picks a subset, configures sink categories / payload classes / confidence floor, and clicks Run bruteforce on N selected to drive the corpus.
Env knobs
| Variable | Default | Range |
|---|---|---|
VULKRO_BRUTEFORCE_SINKS | unset | 1 to enable from env |
VULKRO_BRUTEFORCE_DEPTH | 6 | 2..=12 |
VULKRO_BRUTEFORCE_PATHS_PER_FN | 64 | 8..=512 |
VULKRO_BRUTEFORCE_CATEGORIES | unset | comma-separated tags |
VULKRO_BRUTEFORCE_CONFIDENCE | high | high / medium / low |
VULKRO_BRUTEFORCE_PAYLOAD_CLASSES | unset | comma-separated tags |
Pairs with
- Reverse-reach to trace any Bruteforce finding back to its HTTP route / CLI handler.
--ai-code-segregationto surface bruteforce findings on AI-touched files first.--gate-vs <ref>to restrict findings to changed lines.