Profile
Back to NewsBack
GitHub Trending 32 min
Reader Mode
Dicklesworthstone/ultimate_bug_scanner: Static analysis tool that catches 1000+ bug patterns across all popular programming languages, with auto-wiring into AI coding agent quality guardrails

Dicklesworthstone/ultimate_bug_scanner: Static analysis tool that catches 1000+ bug patterns across all popular programming languages, with auto-wiring into AI coding agent quality guardrails

19 hours ago

🔬 Ultimate Bug Scanner

Ultimate Bug Scanner - The AI Coding Agent's Secret Weapon

The AI Coding Agent's Secret Weapon: Flagging Likely Bugs for Fixing Early On

License: MIT</a> Platform</a> Version</a>

# 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

  • ubs auto-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-.sh, ships independently, and supports --format text|json|jsonl|sarif|toon for 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 → repo modules/ → 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/modules by default; use --module-dir to relocate it (e.g., inside a CI workspace) while retaining the same verification guarantees.
  • Run ubs doctor at any time to audit your environment. It checks for curl/wget availability, writable cache directories, and per-language module integrity. Add --fix to 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-commit hook that auto-updates SHA256SUMS when 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_PUBKEY selects an explicitly trusted alternate key; signatures remain mandatory for ubs --update, including unattended cron jobs.

🎛 Category Packs & Shareable Reports

  • --category=resource-lifecycle focuses 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-occurrences opts JSONL into individual source occurrences with code, stable identities and explicit completeness. --max-occurrences=N bounds 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:FILE writes 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 --format still 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:line references, stdout JSON gains git.* metadata plus a permalink on every finding sample, and merged SARIF runs carry properties.permalink on each result location together with versionControlProvenance and automationDetails keyed 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 matching with open(...) usage and explicit encoding= parameters, while Category 19 uses the new AST helper at modules/helpers/resource_lifecycle_py.py to 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 detects context.With* calls missing cancel, time.NewTicker/NewTimer without Stop, os.Open/sql.Open without Close, and mutex Lock/Unlock symmetry. Category 9 also tracks request query/header/form/framework values into response headers unless they strip or reject CR/LF, tracks redirect targets into http.Redirect, framework Redirect calls, and Location headers unless they pass through same-origin or explicit allow-list validation, flags request-derived reverse proxy targets flowing into httputil.NewSingleHostReverseProxy, ProxyRequest.SetURL, or Director URL mutation without HTTPS plus host allow-list validation, flags request-derived SQL text flowing into ExecContext/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 missing HttpOnly, Secure, or SameSite protections. 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 exact file:line references 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 as sendRedirect, respondRedirect, Spring redirect: views, RedirectView, ModelAndView, and Location headers 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 repository Dockerfile (debian:trixie-slim) installs bash, curl, git, jq,
ripgrep, rsync, python3, and unzip, and pre-provisions the digest-verified ast-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

The installer will:
  • ✅ Install the ubs command 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 toon encoder for --format=toon (digest-pinned release binary; cargo install tru as the fallback)
  • ✅ Optionally install Node.js + typescript (enables deep TypeScript type narrowing analysis)
  • ✅ Auto-run ubs doctor post-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
Need to revisit what the installer discovered later? Run 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-test requires running install.sh from a working tree that contains test-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 the typescript npm package and the Python helpers that power the Rust/Kotlin/Swift/C# checks. The installer now checks Node/TypeScript readiness, can optionally run npm install -g typescript, and surfaces the status inside install.sh --diagnose. Use --skip-type-narrowing if 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 .swift files under your repo and record whether the guard helper will actually run. That fact shows up in install.sh --diagnose output 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)

Chat with me