🔬 Ultimate Bug Scanner
The AI Coding Agent's Secret Weapon: Flagging Likely Bugs for Fixing Early On
# One command to catch 1000+ bug patterns (always main, cache-busted)
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/main/install.sh?$(date +%s)" \
| bash -s --
Or via Homebrew (macOS/Linux):
brew install dicklesworthstone/tap/ubs
Just want it to do everything without confirmations? Live life on the edge with easy-mode to auto-install every dependency, accept all prompts, detect local coding agents, and wire their quality guardrails with zero extra questions:
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/main/install.sh?$(date +%s)" \
| bash -s -- --easy-mode
Note: Windows users must run the installer one-liner from within Git Bash, or use WSL for Windows.
🤖 Agent Quickstart (JSON/TOON)
Use machine-readable output in agent contexts. stdout = data, stderr = diagnostics, exit 0 = success.
# Scan current repo (JSON)
ubs . --format=json
Token-optimized output (TOON; needs toon_rust's encoder — install.sh or ubs doctor --fix fetches it; without it ubs exits 2 with the error envelope)
ubs . --format=toon
Scan only staged changes
ubs --staged --format=json
CI-strict (fail on warnings)
ubs . --profile=strict --fail-on-warning --format=json
Machine-readable docs and schemas for agent loops
ubs robot-docs # guide, commands/flags, examples, exit codes, formats, env — one JSON document
ubs robot-docs exit-codes # a single topic
ubs explain py.security.open-redirect # rule explanation: remediation, fixture examples, confidence
ubs --schema=json # JSON Schema (draft 2020-12) for --format=json; also jsonl|sarif|toon|error|all
Every machine format prints exactly one document (or one JSONL stream) on stdout and validates against
ubs --schema; status is ok (every requested scanner completed), partial (the scan is incomplete but
something usable came back) or error (a module could not run at all and no other scanner produced a
result), and pre-scan refusals are an error envelope with exit 2 (see
"Machine-readable failures" below). The status field, the human status line and the exit code always
agree: anything other than ok exits 2. UBS_ALLOW_PARTIAL=1 makes a partial run exit on the findings of
the scanners that completed instead (0 clean, 1 findings). A run in which no scanner completed (an error run,
or one whose only requested modules timed out or crashed) has none and still exits 2.
Checkouts also expose the optional local service through ubs serve --repo .,
ubs --client --repo . src/main.py, and ubs daemon status|stop|cancel --repo ..
The client falls back to an ordinary scan when no matching service is running.
Installers and self-update carry its checksum-pinned companion; portable bundles
include it too. A warmed engine for edited files remains unfinished.
See the service guide.
💥 The Problem: AI Moves Fast, Bugs Move Faster
You're coding faster than ever with Claude Code, Codex, Cursor, and other AI coding agents. You're shipping features in minutes that used to take days. But here's the painful truth:
Even the best AI makes these mistakes:
JavaScript/TypeScript example (similar patterns exist in Python, Go, Rust, Java, C++, Ruby):
// ❌ CRITICAL BUG #1: Null pointer crash waiting to happen
const submitButton = document.getElementById('submit');
submitButton.addEventListener('click', handleSubmit); // 💥 Crashes if element doesn't exist
// ❌ CRITICAL BUG #2: XSS vulnerability
function displayUserComment(comment) {
document.getElementById('comments').innerHTML = comment; // 🚨 Security hole
}
// ❌ CRITICAL BUG #3: Silent failure (missing await)
async function saveUser(data) {
const result = validateUser(data); // 💥 Should be 'await validateUser(data)'
await saveToDatabase(result); // Saves undefined!
}
// ❌ CRITICAL BUG #4: Always false comparison
if (calculatedValue === NaN) { // 💥 This NEVER works (always false)
console.log("Invalid calculation");
}
// ❌ CRITICAL BUG #5: parseInt footgun
const zipCode = parseInt(userInput); // 💥 "08" becomes 0 in old browsers (octal!)
Each of these bugs could cost 3-6 hours to debug in production. Similar issues plague every language: unguarded null access, missing await, security holes from eval(), buffer overflows from strcpy(), .unwrap() panics, goroutine leaks... You've probably hit all of them.
🎯 The Solution: Your 24/7 Bug Hunting Partner
🧠 Language-Aware Meta-Runner
ubsauto-detects JavaScript/TypeScript, Python, C/C++, Rust, Go, Java, Kotlin, Ruby, Swift, C#, Elixir, Bash, PHP, PostgreSQL SQL, and Terraform HCL in the same repo and fans out to per-language scanners.- Each scanner lives under
modules/ubs-, ships independently, and supports.sh --format text|json|jsonl|sarif|toonfor consistent downstream tooling. - Installed releases use a complete verified module generation under
${XDG_DATA_HOME:-$HOME/.local/share}/ubs/modules. Source checkouts and legacy installations retain lazy module resolution (PATH → repomodules/→ cache). - Results from every language merge into one text/JSON/SARIF report via
jq, so CI systems and AI agents only have to parse a single artifact.
🔐 Supply-Chain Safeguards
- Every lazily-downloaded module (and its helper assets) ships with pinned SHA-256 checksums baked into the meta-runner. Files fetched from GitHub are verified before they can execute, preventing tampering between releases.
- The cache lives under
${XDG_DATA_HOME:-$HOME/.local/share}/ubs/modulesby default; use--module-dirto relocate it (e.g., inside a CI workspace) while retaining the same verification guarantees. - Run
ubs doctorat any time to audit your environment. It checks for curl/wget availability, writable cache directories, and per-language module integrity. Add--fixto redownload missing or corrupted modules proactively. - Scanner runs still respect
--update-modules, but an invalid checksum now causes an immediate failure with remediation guidance rather than executing unverified code. - Developer Pre-commit Hook: The repository ships with a
.githooks/pre-commithook that auto-updatesSHA256SUMSwhen modules change and blocks commits with stale checksums. This ensures every release has verified checksums without manual intervention. - Authenticated Updates: Install minisign to authenticate release manifests with the embedded public key.
UBS_MINISIGN_PUBKEYselects an explicitly trusted alternate key; signatures remain mandatory forubs --update, including unattended cron jobs.
🎛 Category Packs & Shareable Reports
--category=resource-lifecyclefocuses the scanners on Python/Go/Java resource hygiene (context managers, defer symmetry, try-with-resources). UBS automatically narrows the language set to those with lifecycle packs enabled and suppresses unrelated categories.--comparison=diff the latest combined summary against a stored run. Deltas feed into console output, JSON, HTML, and SARIF automation metadata so CI can detect regressions.--report-json=writes an enriched summary (project, totals, git metadata, optional comparison block) that you can archive or share with teammates/CI.--python-pattern-counts=writes unthresholded per-file/per-rule Python pattern counts, with threshold, severity, context and completeness metadata. See the count contract; this is independent of capped finding samples.--all-occurrencesopts JSONL into individual source occurrences with code, stable identities and explicit completeness.--max-occurrences=Nbounds the export without changing scan counts. See complete occurrence export.--html-report=emits a standalone HTML preview showing totals, trends vs. baseline, and per-language breakdowns—ideal for attaching to PRs or chat updates.--also=FORMAT:FILEwrites another rendering of the same scan, including the full findings ledger. Repeat it for JSON, JSONL, SARIF, or TOON; each selected module runs once. The primary--formatstill controls stdout. Relative destinations use the invocation directory, and two reports cannot target the same file. TOON requires the same verified encoder as--format=toon.- All shareable outputs inject GitHub permalinks when UBS is run inside a git repo with a GitHub remote. Text output annotates
path:linereferences, stdout JSON gainsgit.*metadata plus apermalinkon every finding sample, and merged SARIF runs carryproperties.permalinkon each result location together withversionControlProvenanceandautomationDetailskeyed by the comparison id. Permalinks are relative to the repository root even when you scan a subdirectory.
Resource lifecycle heuristics in each language
- Python – Category 16 now correlates every
open()call against matchingwith open(...)usage and explicitencoding=parameters, while Category 19 uses the new AST helper atmodules/helpers/resource_lifecycle_py.pyto walk every file, socket, subprocess, asyncio task, and context cancellation path. The helper resolves alias imports, context managers, and awaited tasks so the diff counts (acquire=X, release=Y, context-managed=Z) show the exact imbalance per file. - Go – Category 5/17 now run a Go AST walker (
modules/helpers/resource_lifecycle_go.go) that detectscontext.With*calls missing cancel,time.NewTicker/NewTimerwithoutStop,os.Open/sql.OpenwithoutClose, and mutexLock/Unlocksymmetry. Category 9 also tracks request query/header/form/framework values into response headers unless they strip or reject CR/LF, tracks redirect targets intohttp.Redirect, frameworkRedirectcalls, andLocationheaders unless they pass through same-origin or explicit allow-list validation, flags request-derived reverse proxy targets flowing intohttputil.NewSingleHostReverseProxy,ProxyRequest.SetURL, orDirectorURL mutation without HTTPS plus host allow-list validation, flags request-derived SQL text flowing intoExecContext/QueryContext, sqlx-style helpers, or query-builder predicates unless request data is passed as bound parameters, flags credentialed wildcard or reflected-origin CORS responses, and catches auth/session cookies missingHttpOnly,Secure, orSameSiteprotections. Findings come straight from the AST/resource helper or taint pass positions, so “ticker missing Stop()” and header/open-redirect/reverse-proxy/CORS/cookie/SQL lines map to exactfile:linereferences instead of coarse regex summaries. - Java / Kotlin – Category 5 surfaces
FileInputStream, readers/writers, JDBC handles, etc. that were created outside try-with-resources, while Category 19 keeps tracking executor services and file streams that never close. Category 4 also tracks servlet/Spring/Ktor request parameters, headers, and annotated parameters into response headers unless they strip/reject CR/LF or encode header fragments, and into redirect sinks such assendRedirect,respondRedirect, Springredirect:views,RedirectView,ModelAndView, andLocationheaders unless a same-origin or explicit allow-list helper is applied first. The summary text matches the manifest fixtures, so CI will fail if regression swallows these warnings.
Java and Kotlin request identity
The redirect, path traversal, response-header and outbound-URL analyzers recognize
selected jakarta.servlet and javax.servlet request APIs by their declared types.
A ServletRequest or HttpServletRequest can have an arbitrary variable name;
selected aliases and same-file helper returns carry its request identity. The
supported sources include getParameter and the HTTP request getters getHeader,
getQueryString, getRequestURI, getPathInfo and getServletPath.
An unrelated local type named HttpServletRequest, or a non-Servlet object named
request, does not establish Servlet provenance. Unsupported overloads retain
ordinary argument-flow analysis, and unknown external transformations do not
inherit a request type merely from their input. These checks extend the shared
bounded request-flow engine without resolving arbitrary external Servlet
implementations. Existing public rule IDs and sink locations are unchanged.
The same four analyzers recognize selected Kotlin Ktor member properties by their actual API receiver type:
| Proven receiver | Supported request members |
| --- | --- |
| ApplicationCall, PipelineCall, RoutingCall | .request carries request-object identity; .parameters supplies request input |
| ApplicationRequest, PipelineRequest, RoutingRequest | .queryParameters, .rawQueryParameters, .headers supply request input |
| RoutingCall specifically | .pathParameters, .queryParameters also supply request input |
Import aliases, selected type aliases, local aliases and supported same-file
helper arguments/returns preserve this identity. Extracted collections carry
request input through indexing and .get(). A bare Parameters or Headers
value, an ordinary map, or a proven local lookalike does not become request input
from its type or member names alone. Calls named request(), headers() or
queryParameters() remain distinct from the API's member properties.
This bounded Ktor adapter does not infer implicit route-DSL receivers or resolve
extension-only request APIs such as uri, path(), header() and receive*.
Request-derived field initialization that requires unmodeled object state reports
incomplete analysis. Unknown calls preserve existing input taint while dropping
unsupported request-object identity; the separate JDBC analysis is unchanged.
Kotlin JDBC SQL extensions
Kotlin category 13 follows selected Servlet and JDK request values into JDBC
execution. Same-file extension functions on supported request and JDBC types
carry their actual receiver through the existing function summaries. Explicit
this and unqualified selected getter or SQL calls use that receiver; a local
getter overload keeps the provenance of its actual fallback argument.
Extension dispatch follows the declared receiver type, including supported import and type aliases. Compatible API members keep precedence over a same-named extension. A prepared-statement receiver retains its captured SQL template across helper calls: binding a request value to a static placeholder stays clean, while binding a value cannot sanitize an already tainted template.
This bounded adapter supports nonnullable top-level extensions on receivers whose declared type can be proved. Nullable or generic receivers, nested and member extensions, grouped or chained receiver calls, callback scopes and unresolved mutable receiver effects remain explicitly incomplete.
Kotlin resource analysis and custom catch handlers
Resource-free Kotlin code can continue through a supported catch handler whose
exception hierarchy is unresolved. The analyzer retains both caught and
uncaught outcomes, including assignments, returns, rethrows and finally;
it does not infer that one exception type inherits from another.
The unresolved relation stays attached to those paths. If a path subsequently acquires or observes a selected resource, including through a local helper or captured handle, analysis remains explicitly incomplete. Owned, borrowed and previously closed handles still require the hierarchy proof. Independent functions keep their own ownership state, so an unrelated cancellation handler does not prevent their resource findings from being analyzed.
Elixir raw SQL request flow
Elixir category 4 tracks request-derived query text through local helpers, aliases,
immutable rebinding, branch joins and iodata into Ecto.Adapters.SQL.query,
query!, query_many and query_many!. Selected imports and same-file repositories
with an explicit Ecto.Repo declaration and a Postgres, MyXQL or Tds adapter establish
the supported SQL receivers. A separate parameter list does not sanitize SQL text
that already contains request data; fixed SQL with bound values remains clean.
The existing ex.security.sql-interpolation rule identifies the execution site in
JSON and SARIF. Request sources follow the existing Plug/Phoenix conventions:
connection members, request-map patterns and params/query_params bindings.
This bounded analysis does not expand arbitrary controller macros, discover external
repository declarations or model lazy SQL stream consumption. Unresolved tainted
calls and exhausted analysis budgets produce an explicit incomplete result while
preserving findings already established. The separate legacy fragment heuristic
remains; it is not evidence of complete Ecto query-macro support.
Selected anonymous functions participate in the same request-flow analysis.
Direct callback.(...) invocation and synchronous Enum.each/Enum.map over
finite lists carry arguments, captured values and local helper effects into
the actual sink. A callback retains the value captured when it was created;
later rebinding does not change that capture. Creating an unused callback does
not execute its body. The shared callback semantics also let the resource
analyzer recognize a handle closed through a stored local callback.
Selected zero-argument workers passed to Task.async, spawn, Kernel.spawn
and :erlang.spawn retain their immutable captures and expose request-derived
sinks in the worker body. Task.await follows the result of its exact task,
including aliases, helper returns and tuple members. Worker-only validation or
rebinding cannot certify the caller's values; a failed spawned worker does not
erase the caller's reachable findings. Task handles remain distinct from their
results, and awaiting a task requires its creating process and a single reply.
The existing lifecycle and File-result policies keep their separate ownership
contracts.
Unknown callback targets, other deferred consumers, cross-process task waits,
unresolved timeout values and external code evaluation remain explicit
boundaries. Static Erlang module calls are distinguished from dynamic
module dispatch. Only a bounded, pure arithmetic subset of literal
Code.eval_string payloads is modeled; arbitrary dynamic or call-bearing payloads
and Code.eval_file do not become trusted merely because their arguments are
literal strings.
C# Dapper SQL request flow
C# security category 8 includes cs.security.sql-injection in ordinary scans.
It follows selected ASP.NET Core request inputs through aliases, branches and
same-file helpers into Dapper's Execute, ExecuteScalar, Query, QueryFirst,
QueryFirstOrDefault, QuerySingle and QuerySingleOrDefault families, including
their asynchronous and supported Type-leading overloads. The scanner resolves
ADO.NET connection types, Dapper imports and SqlMapper aliases before identifying
an execution. Application methods with the same names are not automatically SQL
sinks, and an arbitrary parameter named request is not an ASP.NET source.
SQL text and the separate parameter object retain distinct provenance. A fixed SQL template with request values bound to placeholders remains clean; supplying a parameter object cannot sanitize SQL text that already contains request data. Named arguments bind to their actual signature slots. JSON and SARIF findings point to the execution call and retain the source-to-sink trace.
Run ubs --only=csharp --format=json . or inspect the rule with
ubs explain cs.security.sql-injection. No .NET compiler is required by the
native rule. Category skipping, file selection, suppression and incremental cache
policy apply normally. Its confidence remains unknown pending independent
corpus calibration.
The bounded model covers selected text-SQL execution. CommandDefinition,
deferred enumeration, multimapping, non-text command modes and unresolved
request-bearing execution effects produce an explicit partial result. Known
findings and independent files remain in the report, and partial scans are not
cached as complete. The per-source limits are 100,000 tokens, 96 nesting levels
and 500,000 steps, configurable through UBS_CSHARP_SQL_MAX_TOKENS,
UBS_CSHARP_SQL_MAX_NESTING and UBS_CSHARP_SQL_MAX_STEPS.
PHP request flow and template output
PHP and PHTML files are scanned by a bounded native analyzer without requiring
PHP or ast-grep to be installed. Six critical rules track request data into SQL,
shell commands, dynamic code, includes, unsafe deserialization and HTML output.
Use ubs --only=php --format=json . or inspect a rule with
ubs explain php.security.sql-injection.
The analysis follows local assignments, selected helpers and request APIs, and distinguishes actual PDO/mysqli query receivers from unrelated methods with the same names. Prepared statements retain the SQL template captured at preparation; passing request data as bound values is safe only when that template is trusted. Output escaping is evaluated for its supported HTML context, rather than being a universal sanitizer for SQL, shell commands, URLs or script expressions.
PHP's five category IDs are php.sql, php.execution, php.includes,
php.deserialization and php.output. Native rules appear in --list-rules and
share ordinary file selection, exclusions, profiles, suppression, incremental
cache and JSON/SARIF reporting. Custom ast-grep rules additionally require
ast-grep. PHP native rules do not have an ast-grep --dump-rules representation.
This is selected PHP 8.1+ syntax and local flow, not whole-program framework
resolution. Unsupported syntax, unresolved effects and exhausted analysis limits
produce a partial scan while retaining findings already established. Default
limits per source are 100,000 tokens, 128 nesting levels and 500,000 analysis steps,
configurable through UBS_PHP_MAX_TOKENS, UBS_PHP_MAX_NESTING and
UBS_PHP_MAX_STEPS. PHP confidence remains unknown pending independent corpus
calibration; fixture coverage is not a calibrated confidence score.
PostgreSQL migration and function privileges
.sql, .psql, and .pgsql files use a bounded PostgreSQL DDL analyzer without
requiring a database, PostgreSQL executable, or ast-grep:
ubs --only=sql --fail-on-warning --format=json migrations/
ubs --staged --format=sarif --fail-on-warning
ubs explain sql.security.definer-public-execute
Two warning rules check SECURITY DEFINER functions: function-local search paths
that permit object shadowing, and PUBLIC EXECUTE exposure. The analyzer tracks
literal input signatures, PostgreSQL type aliases, overloads, named/default/OUT
parameters, selected ALTER FUNCTION changes, grants/revokes, default execution
privileges, transactions and savepoints. Creating and restricting a function in
one transaction stays clean; revoking access after a commit retains the earlier
exposure finding. Revoking a grant option does not revoke execution.
Comments, quoted identifiers, strings and dollar-quoted bodies are separated
lexically. A body containing fake DDL or suppression text cannot alter the outer
migration. Actual SQL comments support ubs:ignore[rule-id]. SQL categories are
sql.search-path (1) and sql.privileges (2); --skip-sql=1 and persistent
.ubs.json policy use the same selection. JSON, JSONL, SARIF, text, staged scans,
baseline filtering and the normal incremental cache use the shared runner.
The analysis covers each selected file in its own statement order. It does not
infer migration order across files, execute function bodies, inspect a deployed
catalog, or certify all database access controls. Fresh function defaults follow
PostgreSQL's standard PUBLIC execution grant unless the selected migration changes
it. Existing CREATE OR REPLACE ACLs, unproved schema CREATE privileges, role
changes, dynamic SQL, psql includes, unsupported procedural DDL and malformed or
budget-exhausted input produce explicit incomplete reports (exit 2). An empty
search path is accepted only for the bounded SQL-body proof; unqualified relations
still expose implicit temporary-schema lookup. Custom --rules for SQL are
explicitly unsupported. No warning is emitted merely for ordinary DELETE or
UPDATE statements. Limits are 4 MiB, 200,000 tokens, 10,000 statements and 2,000
function identities and 64 retained savepoints per source. Confidence remains uncalibrated.
The underlying privilege and search-path semantics follow PostgreSQL's CREATE FUNCTION security guidance, search_path documentation, and REVOKE semantics.
Terraform AWS configuration
Native .tf and generated .tf.json configuration files have four bounded AWS
checks, without running Terraform, loading provider plugins, or contacting AWS:
ubs --only=hcl --fail-on-warning --format=json infrastructure/
ubs --staged --format=sarif --fail-on-warning
ubs explain hcl.aws.public-admin-ingress
hcl.aws.public-admin-ingress warns when an explicit world IPv4 or IPv6 CIDR
allows TCP SSH/RDP ports (22/3389), including a containing port range, or all IP
protocols. It supports native inline and attribute-style ingress on
aws_security_group and aws_default_security_group, legacy
aws_security_group_rule, and aws_vpc_security_group_ingress_rule. Public
HTTP/HTTPS, UDP-only rules, restricted source networks and egress are separate
from this administrative-ingress policy.
Ingress attributes may contain literal rule-object lists or selected local
values in either syntax. Terraform's
attribute-as-block conversion
requires all nine rule fields; optional fields can use null. Empty or null
collections add no explicit rule. Provider conversions preserve numeric protocol
aliases and exact integer ports, including zero ports for inline all-protocol
rules. A proven public rule produces one warning at its consuming ingress
attribute, even when another row is unresolved. Unknown relevant values, invalid
rows, or a mixture of ingress attributes and blocks make the report partial.
Undeclared default-group rules and deployed prefix-list/group reachability are
not inferred.
hcl.aws.public-s3-acl warns for explicit public canned ACLs on
aws_s3_bucket and aws_s3_bucket_acl. A selected ignore_public_acls = true
control must refer to the same bucket and provider to neutralize the warning.
Conflicting ACL blocking, BucketOwnerEnforced, competing controls or unknown
protection produce incomplete analysis, rather than a claim about deployed
access. Missing account-wide controls and provider defaults are not inferred.
hcl.aws.iam-wildcard-privilege warns for unconditional identity-policy Allow
statements that select "*" in both Action and Resource. It supports
aws_iam_policy, aws_iam_role_policy, aws_iam_user_policy and
aws_iam_group_policy with literal JSON strings, heredocs, or bounded
jsonencode expressions over selected literals and locals. It also resolves
.json and .minified_json from selected aws_iam_policy_document data sources
in the same Terraform module, including local aliases, static action/resource
lists and condition blocks, and the provider's default Allow effect. Static
conditions group values by operator and context-variable name, preserving the
provider's IAM-variable substitutions without evaluating condition truth.
Findings point to the consuming
resource's policy attribute; an unused data document does not create a finding.
Static source_policy_documents and override_policy_documents follow the
provider's statement-ID merge order: sources first, inline statements next,
then overrides in list order. Matching nonblank Sid values replace whole
statements; unnamed statements always survive. Duplicate named source or inline
statements are invalid even when an override would replace them. Conditions
and deny statements are checked after replacement; those still constraining a
candidate grant produce incomplete analysis. Malformed JSON, duplicate keys,
unsupported complement operators and unresolved policy
documents also remain incomplete. The warning describes policy intent; it does
not establish attachment, effective IAM permissions or permission-boundary state.
hcl.aws.insecure-provider-tls warns when an actual HashiCorp AWS provider
configuration sets insecure = true, including provider aliases and renamed
local provider bindings. Literal string booleans follow Terraform's primitive
conversion; omitted or null values use the documented secure default. Unknown
settings and duplicate provider identities remain incomplete. Unrelated provider
plugins do not acquire AWS semantics from a similarly named option.
Selected files in the same directory share literal/local values and protective
resource identities; nested directories remain distinct Terraform modules.
Native and JSON files can provide configuration for each other. JSON files
participate in automatic discovery, direct-file scans, and staged selections.
The JSON frontend follows Terraform's expression and block mappings:
a single ${...} interpolation retains its value type, ordinary reference-shaped
strings remain literal, and provider references and literal settings keep their
distinct semantics. Repeated block properties retain their order; duplicate
attributes or evaluated expression keys are invalid. Unsupported mixed templates
remain explicitly incomplete.
Changes to any selected context file invalidate cached results. Native #,
// and block comments support scoped ubs:ignore[rule-id]; quoted strings,
templates and heredocs cannot act as suppression comments. JSON "//" body
properties are ignored as configuration comments, while "//" expression-object
keys remain ordinary data. Categories are
hcl.network (1), hcl.storage (2), hcl.iam (3) and hcl.tls (4), including
--skip-hcl=3 and .ubs.json policy. All four rules emit warnings; --fail-on-warning or the strict profile
makes them fail the scan.
This bounded model does not evaluate Terraform plans, state, remote modules,
input variables, .tfvars/.tfvars.json, arbitrary functions or dynamic ingress.
Relevant unresolved expressions, malformed syntax and exhausted budgets
produce partial reports and exit 2 while retaining proven findings. Ordinary
directory scans exclude downloaded .terraform dependencies. Source bounds
are 4 MiB per file, 32 MiB per selected context, 200,000 tokens and 64 nesting
levels. Local evaluation is memoized and bounded; JSON policy input and encoded
output are capped at 256 KiB, including expansion of shared local expressions
and intermediate merged documents. Generated JSON and repeated composition
input parsing each have an additional 32 MiB budget per selected module.
Ingress analysis shares memoized row, string-list and CIDR conversions, with
module-wide limits of 4,096 selected rules and 200,000 work items.
Custom --rules and extra non-.tf extensions are explicitly refused. Other
providers, dynamic data statements,
replicated data instances, arbitrary policy evaluation and deployed reachability
remain outside this slice. Unresolved or cyclic data references stay incomplete.
Confidence is uncalibrated.
The checks follow the AWS provider's ingress rule contract, S3 public-access controls, IAM policy grammar, IAM policy-document generation, AWS provider TLS setting, and Terraform's module file boundaries.
Shareable output quickstart
# 1) Capture a baseline JSON (checked into CI artifacts or local history)
ubs --ci --only=python --category=resource-lifecycle \
--report-json .ubs/baseline.json test-suite/python/buggy
2) Re-run with comparison + HTML preview for PRs or chat threads
ubs --ci --only=python --category=resource-lifecycle \
--comparison .ubs/baseline.json \
--report-json .ubs/latest.json \
--html-report .ubs/latest.html \
test-suite/python/buggy
latest.json now contains the git metadata (repo URL, commit, blob_base) plus a comparison.delta block, and latest.html renders a lightweight dashboard summarising the deltas. SARIF uploads also pick up the comparison id so repeating runs in CI stay grouped by automation id.
To publish SARIF and keep JSON and JSONL copies of the reported findings from one scan:
ubs . --ci --format=sarif --output=report.sarif \
--also=json:report.json --also=jsonl:findings.jsonl
Every rendering shares the same findings, baseline filtering, and scan-completion state. Partial scans still export the available evidence and retain exit 2; an empty scan exports its explicit no-coverage result and retains exit 3. A failed additional export exits 2, names the failed path, preserves its previous report, and still attempts the other requested exports.
Detailed JSONL preserves the public canonical finding fields, including nested
analyzer evidence in extras (such as an ordered extras.taint_path), project
scope, explicit counts including zero, and suppression flags. JSONL uses
language for the canonical lang field. Its type, project and timestamp
describe the export; a finding's original timestamp becomes finding_timestamp.
Evidence fields absent from the canonical finding remain absent.
With --new-only, an empty canonical findings array emits no finding records,
even when scanner summaries retain previews from before baseline filtering.
Ordinary legacy summaries still use their available finding previews. Their
sample limits, rule thresholds and exit policy stay unchanged; JSONL export
does not imply complete occurrence coverage for every analyzer or additivity
for analyses that depend on cross-file context. The separate
Python pattern-count export defines its own
distinct-line count and completeness contract. Request
--all-occurrences when a consumer needs every
available source occurrence and an explicit check that none are missing.
💡 Basic Usage
# Scan current directory
ubs .
Scan specific directory
ubs /path/to/your/project
Verbose mode (show more code examples)
ubs -v .
Save report to file
ubs . bug-report.txt
CI mode (exit code 1 on warnings)
ubs . --fail-on-warning
Quiet mode (summary only)
ubs -q .
Skip specific categories (by stable id or category number)
ubs . --skip=js.debug,python.todo
Custom file extensions
ubs . --include-ext=js,ts,vue,svelte
Handy switches
# Git-aware quick scans (changed files only)
ubs --staged # Scan files staged for commit
ubs --diff # Scan working tree changes vs HEAD
Strictness profiles
ubs --profile=strict # Fail on warnings, enforce high standards
ubs --profile=loose # Skip TODO/debug/code-quality nits when prototyping
Machine-readable output
ubs . --format=json # Pure JSON on stdout; logs go to stderr
ubs . --format=jsonl # Line-delimited summary per scanner + totals
ubs . --format=toon # TOON format (~50% smaller than JSON, LLM-optimized); exit 2 + envelope when the toon_rust encoder is missing
ubs . --format=jsonl --beads-jsonl out/findings.jsonl # Save JSONL for Beads/"strung"
Keeping noise low
- UBS auto-ignores common junk (
node_modules, virtualenvs, dist/build/target/vendor, editor caches, etc.). - Inline suppression is available when a finding is intentional:
eval("print('safe')") # ubs:ignore
🚀 Quick Install (30 Seconds)
Recommended: Homebrew (macOS/Linux)
brew install dicklesworthstone/tap/ubs
This method provides:
- Automatic updates via
brew upgrade - Dependency management
- Easy uninstall via
brew uninstall
Alternative: Automated Install
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/main/install.sh?$(date +%s)" | bash
Option 2: Integrity-first install (signed checksums)
export UBS_MINISIGN_PUBKEY="RWS+jJ7psytzl3v4znpraY9VWBQrICXBFmT3VwvxpTzbuV2Q/CBTDmVJ"
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/main/scripts/verify.sh | bash
The verifier downloads SHA256SUMS from the matching release and validates its signature — with minisign when UBS_MINISIGN_PUBKEY is set, otherwise with cosign verify-blob against the release's keyless Sigstore bundle (SHA256SUMS.sigstore.json, so no key to distribute) — checks install.sh, and only then executes it. UBS_VERIFY_WITH=minisign|cosign forces a path; --insecure bypasses verification (not recommended).
Option 3: Nix
Run directly (no install):
nix run github:Dicklesworthstone/ultimate_bug_scanner
Dev shell for contributors:
nix develop
Option 4: Docker / OCI
Pull & inspect:
docker run --rm ghcr.io/dicklesworthstone/ubs-tools ubs --help
Scan host code (risk-aware: grants container access to host FS):
docker run --rm -v /:/host ghcr.io/dicklesworthstone/ubs-tools bash -c "cd /host/path && ubs ."
⚠️ Use the host-mount pattern only when you understand the write-access implications.
[!NOTE]
The repositoryDockerfile(debian:trixie-slim) installsbash,curl,git,jq,
ripgrep,rsync,python3, andunzip, and pre-provisions the digest-verifiedast-grep
binary and language modules at build time for offline container scans. Verify any published
registry tag against the release digest before assuming parity with the checkout Dockerfile.
Deployment & Security
- Release playbook (how we cut signed releases): docs/release.md
- Supply chain & verification model: docs/security.md
- ✅ Install the
ubscommand globally - ✅ Install/ensure
ast-grep(required for accurate JS/TS scanning; UBS can auto-provision a pinned binary) - ✅ Optionally install
ripgrep(for 10x faster scanning) - ✅ Optionally install
jq(needed for JSON/SARIF merging across all language scanners) - ✅ Optionally install
typos(smart spellchecker for docs and identifiers) - ✅ Optionally install toon_rust's
toonencoder for--format=toon(digest-pinned release binary;cargo install truas the fallback) - ✅ Optionally install
Node.js + typescript(enables deep TypeScript type narrowing analysis) - ✅ Auto-run
ubs doctorpost-install and append a session summary to~/.config/ubs/session.md - ✅ Capture readiness facts (ripgrep/jq/typos/toon/type narrowing) and store them for
ubs sessions --entries 1 - ✅ Set up git hooks (block commits with critical bugs)
- ✅ Set up Claude Code hooks (scan on file save)
- ✅ Add documentation to your AGENTS.md
ubs sessions --entries 1 to view the most recent session log (or point teammates at the same summary).
Need the “just make it work” button? Run the installer with --easy-mode to auto-install every dependency, accept all prompts, detect local coding agents, and wire their quality guardrails with zero extra questions:
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/main/install.sh?$(date +%s)" \
| bash -s -- --easy-mode
Total time: 30 seconds to 2 minutes (depending on dependencies)
Need to keep your shell RC files untouched? Combine --no-path-modify (and optionally --skip-hooks) with the command above—the installer will still drop ubs into your chosen --install-dir, but it will skip both PATH edits and the alias helper entirely.
Option 2: Manual Install
# Download and install the unified runner
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/main/ubs \
-o /usr/local/bin/ubs && chmod +x /usr/local/bin/ubs
Verify it works
ubs --help
Install dependencies (ast-grep required for JS/TS scanning)
Required for JS/TS scanning (syntax-aware AST engine)
brew install ast-grep # or: cargo install ast-grep, npm i -g @ast-grep/cli
brew install ripgrep # 10x faster searching (or: apt/dnf/cargo install)
brew install typos-cli # Spellchecker tuned for code (or: cargo install typos-cli)
npm install -g typescript # Enables full tsserver-based type narrowing checks
Option 3: Use Without Installing
# Download once
curl -fsSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/main/ubs \
-o ubs && chmod +x ubs
Run it
./ubs .
Installer Safety Nets
Uninstall from any shell
Run the installer in --uninstall mode via curl if you want to remove UBS and all of its integrations:
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/main/install.sh?$(date +%s)" | bash -s -- --uninstall --non-interactive
This command deletes the UBS binary, shell RC snippets/aliases, config under ~/.config/ubs, and the optional Claude/Git hooks that the installer set up. Because it passes --non-interactive, it auto-confirms all prompts and runs unattended.
| Flag | What it does | Why it matters |
|------|--------------|----------------|
| --dry-run | Prints every install action (downloads, PATH edits, hook writes, cleanup) without touching disk. Dry runs still resolve config, detect agents, and show you exactly what would change. | Audit the installer, demo it to teammates, or validate CI steps without modifying a workstation. |
| --self-test | Immediately runs test-suite/install/run_tests.sh after installation and exits non-zero if the smoke suite fails. | CI/CD jobs and verified setups can prove the installer still works end-to-end before trusting a release. |
| --skip-type-narrowing | Skip the Node.js + TypeScript readiness probe and the cross-language guard analyzers (JS/Rust/Kotlin/Swift/C#). | Useful for air-gapped hosts or environments that want to stay in heuristic-only mode. |
| --skip-toon | Skip installing toon_rust's toon encoder, the digest-verified release binary that --format=toon needs. Non-interactive installs fetch it by default; interactive ones ask. | For hosts that already ship toon/tru, or when you never use TOON output. ubs doctor --fix can fetch it later. |
| --skip-typos | Skip the Typos spellchecker installation + diagnostics. | Handy when corp images already provide Typos or when you deliberately disable spellcheck automation. |
| --skip-doctor | Skip the automatic ubs doctor run + session summary after install. | Use when CI already runs doctor separately or when you're iterating locally and want a faster finish. |
[!WARNING]
--self-testrequires runninginstall.shfrom a working tree that containstest-suite/install/run_tests.sh(i.e., the repo root). Curl-piping the installer from GitHub can’t self-test because the harness isn’t present, so the flag will error out early instead of giving a false sense of safety.
[!NOTE]
After every install the script now double-checks command -v ubs. If another copy shadows the freshly written binary, you’ll get an explicit warning with both paths so you can fix PATH order before running scans.
[!TIP]
Type narrowing relies on Node.js plus thetypescriptnpm package and the Python helpers that power the Rust/Kotlin/Swift/C# checks. The installer now checks Node/TypeScript readiness, can optionally runnpm install -g typescript, and surfaces the status insideinstall.sh --diagnose. Use--skip-type-narrowingif you’re on an air-gapped host or plan to keep the heuristic-only mode.
[!TIP]
To avoid global npm permission issues, the installer now detects/installs bun just like other dependencies and uses bun install --global typescript by default, falling back to npm only if bun isn’t available.
> The diagnostics also call out Swift guard readiness: if python3 is available we count.swiftfiles under your repo and record whether the guard helper will actually run. That fact shows up ininstall.sh --diagnoseoutput and the auto-generated session log so iOS/macOS teams can tell at a glance whether the ObjC-bridging heuristics are active.
Common combos
# Preview everything without touching dotfiles or hooks
bash install.sh --dry-run --no-path-modify --skip-hooks --non-interactive
CI-friendly install that self-tests the smoke harness
bash install.sh --easy-mode --self-test --skip-hooks
Install the ubs in the current directory (a checkout's install.sh does this by itself;
without --local a stray ./ubs is ignored and the verified release is installed)
bash install.sh --local --skip-hooks
Dependency binaries the installer downloads (ast-grep, jq, typos, ripgrep, toon) are verified against
pinned SHA-256 digests or the upstream .sha256 sidecar before they are installed; a mismatch
aborts that install, and --skip-verification prints exactly which check it skipped.
🔄 Auto-Update
The ubs meta-runner supports an opt-in auto-update check (once every 24 hours). This is disabled by default for supply-chain safety.
To enable auto-update:
export UBS_ENABLE_AUTO_UPDATE=1
To disable it (even if enabled):
export UBS_NO_AUTO_UPDATE=1
or
ubs --no-auto-update .
Ultimate Bug Scanner is like having a senior developer review every line of code in under 5 seconds; it's the perfect automated companion to your favorite coding agent:
$ ubs .
╔══════════════════════════════════════════════════════════════════════╗
║ 🔬 ULTIMATE BUG SCANNER v5.3 - Scanning your project... ║
╚══════════════════════════════════════════════════════════════════════╝
Project: /Users/you/awesome-app
Files: 247 JS/TS + 58 Python + 24 Go + 16 Java + 11 Ruby + 12 C++/Rust files
Finished: 3.2 seconds
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Summary Statistics:
Files scanned: 247
🔥 Critical: 0 ← Would have crashed in production!
⚠️ Warnings: 8 ← Should fix before shipping
ℹ️ Info: 23 ← Code quality improvements
✨ EXCELLENT! No critical issues found ✨
⚡ Why Developers + AI Agents Will Love This Tool
🚀 1. Catches What Humans & AI Miss
18 specialized detection categories covering the bugs that actually matter:
| Category | What It Prevents | Time Saved Per Bug |
|---|---|---|
| Null Safety | "Cannot read property of undefined" crashes | 2-4 hours |
| Security Holes | XSS, code injection, prototype pollution | 8-20 hours + reputation damage |
| Async/Await Bugs | Race conditions, unhandled rejections | 4-8 hours |
| Memory Leaks | Event listeners, timers, detached DOM | 6-12 hours |
| Type Coercion | JavaScript's === vs == madness | 1-3 hours |
| + 13 more categories | 100+ hours/month saved | |
💨 2. Fast Enough for the Edit Loop When You Scope It
scripts/bench.sh also records the agent-hook case as single_file: the meta-runner on one file (--ci, text mode) against the Python module alone on the same file, overhead p50/p95 in ms (target p95 ≤ 150 ms, tracked by scripts/bench_cusum.py).
Measured on 2026-09-02 (v5.3.13, single runs on a 16-core Linux box; ast-grep on PATH). Reproduce with `scripts/bench
... (README truncated for length)