with one click
pysa-false-negative-debugger
// Use when debugging a Pysa false negative (missing taint issue), comparing two Pysa output directories, or finding where taint flow is lost.
// Use when debugging a Pysa false negative (missing taint issue), comparing two Pysa output directories, or finding where taint flow is lost.
Use when you need to understand the Pyre or Pysa codebase architecture, find where specific functionality is implemented, or navigate the source directory structure. Use when working on OCaml code, debugging type checking or taint analysis issues, or exploring the codebase.
Use when creating commits or diffs to apply the correct title prefix, reviewer, and test plan based on the area of the codebase being changed.
Use when running, debugging, updating, or creating Pysa end-to-end integration tests. Use when taint analysis tests fail, when expected output files need updating, or when working with .models, .cg, .hofcg, .overrides files under `source/interprocedural_analyses/taint/test/integration`.
Use when reading, writing, or debugging Pysa JSON model output (.models files). Use when working with taint models that describe sources, sinks, TITO, sanitizers, or issues in JSON format.
| name | pysa-false-negative-debugger |
| description | Use when debugging a Pysa false negative (missing taint issue), comparing two Pysa output directories, or finding where taint flow is lost. |
| oncalls | ["pysa"] |
Systematic workflow that identifies exactly where and why taint flow is lost by comparing two Pysa result directories. Accepts static analysis issue URLs (e.g., https://www.internalfb.com/security/static_analysis/issue/<issue_instance_id>?database=<database>).
REQUIRED BACKGROUND: Load the pysa-json-models skill to understand trace element syntax (ports, call info, kinds).
The user must provide:
https://www.internalfb.com/security/static_analysis/issue/<issue_instance_id>?database=<database>From the URL, extract:
216172782209137158)database query parameter (e.g., xdb.pysa-instagram-sharded.1)Get the issue handle:
db <database> -e "SELECT handle FROM issues WHERE id=(SELECT issue_id FROM issue_instances WHERE id=<issue_instance_id>);"
If the query fails, ask the user for help.
Example handle:
accounts.service.Service.async_is_phone_suspicious:5120:0:Call|accounts.service.Service._async_helper|0|f:ec82abb59a9d0e207fe4f5acc361f0ad
Extract:
: (e.g., accounts.service.Service.async_is_phone_suspicious): (e.g., 5120)All commands below use buck run to invoke the explorer. The shorthand <explorer> means:
buck run fbcode//tools/pyre/tools/pysa_model_explorer_cli:pysa_model_explorer --
Run <explorer> --help for usage details.
Suppressing build noise: Always append 2>/dev/null to explorer commands to suppress buck build output that clutters the results. Only retry without 2>/dev/null if the command produces empty or unexpected output, so you can see the actual error message from buck.
Confirm the issue exists in FOUND:
<explorer> /tmp/FOUND get-issues <root-callable> --handle <handle>
If not found, ask the user for help.
Confirm it does NOT exist (or is very different) in NOT-FOUND:
<explorer> /tmp/NOT-FOUND get-issues <root-callable> --code <code>
The result should be empty or contain very different issues (different locations).
Check these options in order: A → B → C.
In the issue from FOUND, examine "traces" → entries with "name": "forward".
Each root (trace element) may have multiple kinds with different "length" values. Pick the root whose minimum "length" across its kinds is smallest (missing length = 0, which is always shortest).
If it's an origin: the source comes from a user-annotated function. Check if at least one of the leaves have a model in NOT-FOUND:
<explorer> /tmp/NOT-FOUND get-model <leaf-callable> --show sources --kind <kind>
If none of the leaves have source taint in NOT-FOUND, taint is lost at one of these leaf callables. If at least one does, the source trace is intact — move to Option B.
If it's a call with "resolves_to": [<callee-callable>, ...]: check if those callables have source taint in NOT-FOUND. If there are multiple entries (overrides), check all of them — taint is lost only if ALL are missing:
<explorer> /tmp/NOT-FOUND get-model <callee-callable> --show sources --kind <kind>
Interpret the results:
"port": "formal(a)" in the "call" section, but model has sources for "formal(b)") → these don't count. Taint is still lost.When you find a missing source, double-check it exists in FOUND:
<explorer> /tmp/FOUND get-model <leaf-or-callee-callable> --show sources --kind <kind>
Same process as Option A, but for "name": "backward" traces. Use --show sinks instead of --show sources with get-model.
When Option A or B identifies a callee whose model is present in FOUND but missing in NOT-FOUND, you need to find exactly which function in the call chain lost the taint. Recurse into that callee's model:
# For a missing source (Option A):
<explorer> /tmp/FOUND get-model <first-source-callee> --show sources --kind <kind>
# For a missing sink (Option B):
<explorer> /tmp/FOUND get-model <first-sink-callee> --show sinks --kind <kind>
Pick the frame with the shortest length (same strategy as in Option A/B).
Identify the next callee: if the frame is a "call", use "resolves_to" to get the callee. If it's an "origin", use the leaf names.
Check the next callee's model in NOT-FOUND:
<explorer> /tmp/NOT-FOUND get-model <next-callee> --show sources --kind <kind>
<first-source-callee>). Use the Option C strategy on that callable: read its source code, check its call graph and TITO models to find the break.Continue until you find the function where taint is present in its callees but lost in its own model. Then apply Option C's strategy (call graph + TITO checks) to that function to pinpoint the root cause.
If both forward and backward traces are correct, taint is lost within the root callable itself.
Read the source code using "filename" and location from the issue. Filenames are usually relative to ~/fbsource.
Check the call graph:
<explorer> /tmp/NOT-FOUND get-call-graph <root-callable>
If you know the relevant line numbers (e.g., from the issue location or source code), use --start-line and --end-line to filter to that range:
<explorer> /tmp/NOT-FOUND get-call-graph <root-callable> --start-line <line> --end-line <line>
This avoids noise from irrelevant call graph edges in large functions.
Verify calls to source and sink are present at the right locations.
a = source()
b = foo(a)
sink(b)
foo is an intermediate call. Check:
foo resolved in the call graph?my_module.foo), check its TITO model:<explorer> /tmp/NOT-FOUND get-model my_module.foo --show tito
foo's call graph and models to find where propagation breaks.Your output must include:
| Command | Purpose |
|---|---|
<explorer> <dir> get-issues <callable> --handle <handle> | Find specific issue |
<explorer> <dir> get-issues <callable> --code <code> | Find issues by code |
<explorer> <dir> get-issues <callable> --show-leaf-names | Show leaf callables |
<explorer> <dir> get-model <callable> --show sources --kind <kind> | Check source taint |
<explorer> <dir> get-model <callable> --show sinks --kind <kind> | Check sink taint |
<explorer> <dir> get-model <callable> --show tito | Check TITO propagation |
<explorer> <dir> get-call-graph <callable> | Check call resolution (--start-line, --end-line to filter by line range) |
<explorer> <dir> get-overrides <callable> | List all overrides for a method |
<explorer> <dir> search <regex> | Search for callables |
<explorer> is shorthand for buck run fbcode//tools/pyre/tools/pysa_model_explorer_cli:pysa_model_explorer --.
Additional flags: --show-features, --show-tito-positions, --show-class-intervals, --format text.
| Mistake | Fix |
|---|---|
| Searching the wrong trace first | Always check forward (A) → backward (B) → local (C) in order |
| Ignoring port mismatches | formal(a) ≠ formal(b) — matching kind with wrong port means taint is still lost |
| Not cross-checking against FOUND | Always confirm the model exists in FOUND before concluding it's missing in NOT-FOUND |
| Skipping call graph check | Unresolved calls are a common root cause for lost taint |
| Stopping at first missing model | The missing model might itself be caused by a deeper missing propagation — keep diving |
Checking only one resolves_to entry | When there are multiple entries (overrides), check ALL of them — taint flows if any has the model |
Using get-model on Overrides{module.Class.foo} | Override targets don't have models. Use get-overrides module.Class.foo to list all overrides, then get-model on each override |
| Using grep/jq on raw JSON files | Always use pysa_model_explorer — it handles indexing and filtering efficiently |
Forgetting --show-leaf-names on origins | Without this flag, you can't see which callables originated the taint |
Forgetting 2>/dev/null on explorer commands | Buck build output clutters results — always append 2>/dev/null and only remove it to diagnose errors |
| Using knowledge_load | Avoid using knowledge_load on the given issue URL, prefer local results |