Bruteforce sinks
vulkro scan --bruteforce-sinks statically drives a corpus of
adversarial payloads at every critical-surface call site, then flags
the payloads that reach the sink without an effective recognized
guard.
The engine never touches the network. No real API call is made and 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
Taint flow tells you an input reaches stripe.charges.create.
Bruteforce sinks tells you which payload classes 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 run on load. Click Discover candidate functions to list the project's critical-surface functions (the ones that reach a classified sink), pick a subset, set sink categories / payload classes / confidence floor, then click Run bruteforce on N selected.
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.