rusackas commented on issue #35:
URL: https://github.com/apache/tooling-agents/issues/35#issuecomment-4598331471

   Sounds good... I'll ping when the smoke clears on open PRs, and we can 
re-scan. Meanwhile, I had two sessions chipping away at this (each with 
sub-agents galore) so here are the no-op items from each one. Using Opus 4.8 
FWIW. 
   
   # Session 1
   
   ## A. False positives — investigated, no real vulnerability
   
   ### 032 — Explore payload field filtering
   **Verdict: False positive.** The flagged fields are already 
access-controlled upstream: the Explore payload passes through 
`raise_for_access(datasource=...)` before use, and the specific `sql` / `extra` 
fields called out are load-bearing for the Explore UI (removing/filtering them 
breaks legitimate functionality). No unauthorized exposure path exists. No 
change.
   
   ### FINDING-002 — Column-name regex validation
   **Verdict: False positive (remediation misdirected).** The proposed 
remediation (regex-validating column names) was the wrong shape for the problem 
and would have given a false sense of safety while breaking legitimate 
identifiers. The correct control — structured/parameterized filters at the 
backend — is already enforced on the affected paths (and was hardened directly 
in the SQL-handling fixes). The report as written does not describe an 
exploitable gap. No change.
   
   ### useListViewResource — list-view resource hook
   **Verdict: False positive.** The flagged value is not a user-controlled 
injection sink in this path; it is handled/encoded before use. No exploitable 
behavior. No change.
   
   ### 008 (original ag-grid report) — duplicate finding
   **Verdict: False positive / duplicate.** This restated an ag-grid concern 
already covered by the dedicated ag-grid hardening work; the specific path 
named here was not independently exploitable. No change.
   
   ---
   
   ## B. Already mitigated by existing controls
   
   > These were **not** real gaps because a control already existed. Optional 
belt-and-suspenders hardening was shipped anyway where cheap, but the original 
report did not represent an exposure.
   
   ### 046 — Visualization stack-trace exposure
   **Verdict: Already mitigated.** Stack traces are emitted through 
`get_stacktrace()`, which internally gates on the `SHOW_STACKTRACE` config flag 
(off by default) — so traces are not exposed in a default deployment. A minor 
tightening was folded into the viz hardening PR as defense-in-depth, but no 
exposure existed out of the box.
   
   ### 003 — CSV formula injection on export
   **Verdict: Already mitigated.** CSV export already runs values through 
`escape_value` / `df_to_escaped_csv`, which neutralize formula-trigger 
prefixes. The only gap was missing coverage for the tab/carriage-return prefix 
variants, which was added as a small extension — the core protection was 
already present.
   
   ---
   
   ## C. Duplicate of an already-tracked fix
   
   ### TaskProperties exception/stack-trace gating
   **Verdict: Duplicate.** Same issue already addressed by the gating of 
`exception_type` + `stack_trace` (shipped in #40587). No separate change needed.
   
   ---
   
   ## D. Won't-fix / out-of-scope skips
   
   > Real-enough observations, but deliberately not changed — either 
already-mitigated by platform behavior, no actual sink in the code, or the 
change carries more regression risk than the marginal hardening is worth.
   
   ### 023 — CSRF-related header
   **Verdict: Skip.** Mitigated in practice by SameSite cookie behavior, and 
the proposed change was regression-prone for limited marginal benefit. Not 
worth the risk.
   
   ### 025 — Redirect allowlist
   **Verdict: Skip.** No actual open-redirect sink is present in the referenced 
code path — the user-controlled value is not used as a redirect target. Nothing 
to fix.
   
   ### 039 — Swagger/API docs exposure
   **Verdict: Skip.** The report self-downgraded on inspection; the exposure 
described is configuration-controlled and not a default-deployment concern. No 
change.
   
   ---
   
   # Session 2
   
   
   > **Legend** — **FALSE POSITIVE**: the described vulnerability does not 
exist. **NON-ISSUE**:
   > the observation is literally true but has no security impact (working as 
intended /
   > already mitigated).
   
   ## Summary
   
   | # | Topic | Source finding(s) | Verdict |
   |---|-------|-------------------|---------|
   | 1 | Frontend hooks "missing column specification" | column-spec cluster 
(`15.3.1.md`) | FALSE POSITIVE |
   | 2 | SSH tunnel credentials unmasked in read endpoints | SSH read endpoints 
(`15.3.1.md`) | FALSE POSITIVE |
   | 3 | SSH tunnel raw `.data` exposes internal fields | SSH read endpoints 
(`15.3.1.md`) | FALSE POSITIVE |
   | 4 | `get_updated_since` `to_dict()` over-exposure | queries API 
(`15.3.1.md`) | FALSE POSITIVE |
   | 5 | CSRF-exempt endpoints lack Content-Type/header enforcement | CSRF 
(`3.5.2.md`) | NON-ISSUE |
   | 6 | `sanitize_clause()` MySQL dialect fallback | SQL parse (`1.2.4.md`) | 
FALSE POSITIVE |
   | 7 | `getattr` post-processing dynamic dispatch | `query_object.py` 
(`1.3.2.md`) | FALSE POSITIVE |
   | 8 | MCP JWT verifier (algorithm / `none` / `jku` / `exp`-`nbf`) | 
`jwt_verifier.py` (`9.1.x`, `9.2.1`) | FALSE POSITIVE |
   | 9 | `get_datasource_by_id` no access check | `commands/utils.py` 
(`8.2.2.md`) | FALSE POSITIVE |
   | 10 | Chart screenshot missing `X-Content-Type-Options` | `charts/api.py` 
(`3.2.1.md`) | FALSE POSITIVE |
   | 11 | Import/extension ZIP missing `check_is_safe_zip` | import/extension 
(`5.2.1`, `5.3.2`) | FALSE POSITIVE |
   | 12 | `uuid3`/MD5 used for cache key | `metastore_cache.py` (`11.4.1.md`) | 
FALSE POSITIVE |
   | 13 | No startup cipher-mode (AEAD) validation | `encrypt.py` (`11.3.2.md`) 
| NON-ISSUE |
   | 14 | Legacy DB URI validator missing safety check | 
`views/database/validators.py` (`2.2.1.md`) | FALSE POSITIVE (dead code) |
   | 15 | `row_limit` missing upper bound | `charts/schemas.py` (`2.2.1.md`) | 
FALSE POSITIVE |
   | 16 | Authentication rate limiting not active by default | 
`security/manager.py` (`6.3.1.md`) | FALSE POSITIVE |
   
   ---
   
   ## 1. Frontend hooks return data "without column specification"
   
   **Reports:** `useDashboardCharts`, `useDatasetDrillInfo`, 
`useEmbeddedDashboard`, `queryApi`
   generic base query (ASVS 15.3.1, CWE-200).
   
   **Claim:** these hooks request full objects without a `columns` projection 
(unlike `useDashboard`),
   over-exposing fields.
   
   **Verdict — FALSE POSITIVE.** Field exposure in Superset is enforced 
**server-side** by marshmallow
   schemas; the frontend `columns` param is only a bandwidth optimization, and 
**only the generic
   `/dashboard/{id}` GET accepts it.** The flagged hooks call dedicated 
sub-resource endpoints that do
   not accept a `columns` param and already return curated, minimal schemas:
   
   - `useDashboardCharts` → `GET /dashboard/{id}/charts` 
(`superset/dashboards/api.py:641`) dumps a fixed
     `ChartEntityResponseSchema` (10 curated fields).
   - `useDatasetDrillInfo` → `GET /dataset/{id}/drill_info/` 
(`superset/datasets/api.py:1263`) already
     restricts to a hard-coded `drill_info_select_columns` allowlist 
(`api.py:1307`). The claim "the full
     dataset response is stored and cached" is factually incorrect.
   - `useEmbeddedDashboard` → `GET /dashboard/{id}/embedded` returns 
`EmbeddedDashboardResponseSchema` — 5
     fields (`uuid`, `allowed_domains`, `dashboard_id`, `changed_on`, 
`changed_by`), nothing sensitive.
   - `queryApi` (`superset/hooks/apiResources/queryApi.ts`) is a generic 
RTK-Query transport that passes
     data through unless a per-endpoint `transformResponse` is set — standard 
behavior; it has no
     knowledge of endpoint schemas, so a "default field-limiting mechanism" is 
architecturally
     meaningless. Field exposure belongs server-side, where it already lives.
   
   The proposed remediation (add a `columns` spec to the hook) would be dead 
query-string the backend
   silently ignores. Reporter self-downgraded all to "Low — no trust boundary 
violation."
   
   ## 2. SSH tunnel credentials returned unmasked in read endpoints
   
   **Report:** `get_connection()` / `get()` in `superset/databases/api.py` 
return SSH tunnel data
   without `mask_password_info()` (ASVS 15.3.1, CWE-200) — labeled **High**.
   
   **Verdict — FALSE POSITIVE.** The read endpoints add 
`database.ssh_tunnel.data`, and the
   `SSHTunnel.data` property (`superset/databases/ssh_tunnel/models.py:88-102`) 
**already masks**
   `password`, `private_key`, and `private_key_password` with `PASSWORD_MASK` 
before returning. It emits
   only `id`, `server_address`, `server_port`, `username` plus masked 
placeholders. No plaintext secret is
   ever returned. `post()`/`put()` use `mask_password_info()` only because they 
build the response from
   the raw request body, not the model. The existing test
   `test_get_database_returns_related_ssh_tunnel` already asserts masked output 
on read.
   
   ## 3. SSH tunnel data appended outside schema serialization (internal-field 
exposure)
   
   **Report:** read endpoints use the raw `.data` property, "exposing internal 
model fields beyond the
   API contract" (ASVS 15.3.1, CWE-200).
   
   **Verdict — FALSE POSITIVE.** `SSHTunnel.data` is itself a hand-curated 
allowlist (see #2). It does
   **not** emit `database_id`, audit columns, or any other internal field — 
only the four public fields
   and masked credential placeholders. There is no internal-field leakage.
   
   ## 4. `get_updated_since` bypasses schema via `to_dict()`
   
   **Report:** `superset/queries/api.py` `get_updated_since` uses `q.to_dict()` 
instead of the declared
   `list_model_schema`, exposing all `Query` fields (ASVS 15.3.1, CWE-200).
   
   **Verdict — FALSE POSITIVE (as a vulnerability).** The endpoint and DAO
   (`QueryDAO.get_queries_changed_after`) hard-filter on `Query.user_id == 
get_user_id()` — it returns
   **only the caller's own queries**, so this is self-exposure of a few extra 
fields about one's own
   queries (e.g. `resultsKey`, `extra`), not cross-user disclosure. Furthermore 
the proposed fix
   (`schema.dump()`) would **break SQL Lab**: `to_dict()` emits a camelCase 
shape the frontend depends on,
   and `test_get_updated_since` asserts that exact shape. Low value, real 
regression risk.
   
   ## 5. CSRF-exempt endpoints lack Content-Type / custom-header enforcement
   
   **Report:** CSRF-exempt endpoints rely on CORS preflight without explicit 
Content-Type validation
   (ASVS 3.5.2).
   
   **Verdict — NON-ISSUE.** The effective control is present and appropriate: 
`SESSION_COOKIE_SAMESITE =
   "Lax"` (`superset/config.py`) already blocks cross-site cookie-bearing POSTs 
to the JSON endpoints. The
   report concedes this and proposes only vague defense-in-depth; adding 
per-endpoint Content-Type
   enforcement to public APIs (some accept form-encoded bodies) risks 
regressions for no real gain. (The
   report also cites the wrong config lines — the CSRF block is 
`WTF_CSRF_EXEMPT_LIST`, not the
   `SQLALCHEMY_ENGINE_OPTIONS` lines referenced.)
   
   ## 6. `sanitize_clause()` MySQL dialect fallback parser-differential
   
   **Report:** `superset/sql/parse.py` falls back to the MySQL dialect when an 
unknown engine's SQL with
   backticks fails to parse, creating a parser-differential risk (ASVS 1.2.4, 
CWE-436).
   
   **Verdict — FALSE POSITIVE.** The fallback only changes which 
**identifier-quoting grammar** is used to
   parse; it does **not** relax the single-statement / balanced-expression 
invariant that provides the
   actual injection protection (a multi-statement payload still parses to >1 
statement and is rejected).
   It fires only for unknown/base dialects (where there was no correct dialect 
anyway) and only when a
   literal backtick is present. It was added deliberately (PR #36545) to 
support "Other"-type databases
   with MySQL-style backtick identifiers. The clause input is 
operator-controlled (requires
   SQL-in-adhoc-filter permission). No bypass.
   
   ## 7. `getattr` dynamic dispatch in post-processing
   
   **Reports:** `exec_post_processing()` in `superset/common/query_object.py` 
uses
   `getattr(pandas_postprocessing, operation)` guarded only by `hasattr` (ASVS 
1.3.2, CWE-470). *(Raised
   twice — `1.3.2.md`.)*
   
   **Verdict — FALSE POSITIVE.** The `operation` name is **already allowlisted 
upstream** by the
   marshmallow schema: `ChartDataPostProcessingOperationSchema.operation` uses
   `validate.OneOf(choices=[functions of pandas_postprocessing])` 
(`superset/charts/schemas.py`). The
   module namespace contains no dangerous callables (`pd`, `DataFrame`, 
builtins are not imported there);
   the only reachable "extra" attributes are two harmless string helpers. A 
user cannot invoke an
   unintended callable. `hasattr` is not the only guard.
   
   ## 8. MCP JWT verifier hardening (algorithm / `none` / `jku`-`x5u`-`jwk` / 
`exp`-`nbf`)
   
   **Reports:** `superset/mcp_service/jwt_verifier.py` — conditional algorithm 
check, no explicit `alg:
   none` rejection, no stripping of `jku`/`x5u`/`jwk` headers, and no 
`exp`/`nbf` enforcement (ASVS 9.1.1,
   9.1.2, 9.1.3, 9.2.1). *(Raised across multiple batches.)*
   
   **Verdict — FALSE POSITIVE.** `DetailedJWTVerifier` delegates real 
verification to authlib's
   `JsonWebToken([self.algorithm])` via `self.jwt.decode(...)`:
   
   - **Algorithm:** `self.algorithm` is always set by the sole factory 
(`mcp_config.py`, hardcoded
     `HS256` or `MCP_JWT_ALGORITHM` defaulting to `RS256`); authlib 
independently rejects any algorithm
     not in its single-element allowlist — including `none`.
   - **`jku`/`x5u`/`jwk`:** `_get_verification_key()` resolves keys **only** 
from the pre-configured static
     public key or the operator-configured JWKS URI; only `kid` is read from 
the token header and it can't
     point outside the configured JWKS. Token headers cannot influence key 
resolution.
   - **`exp`/`nbf`:** authlib validates registered time claims during 
`decode()`; an expired or
     not-yet-valid token is rejected.
   
   The explicit checks the reports request are redundant with authlib 
(defense-in-depth at most), not
   closing a real hole. (A separate, *valid* concern — tokens minted 
**without** an `exp` claim — is
   about token *issuance*, addressed in PR #40651, not the verifier.)
   
   ## 9. `get_datasource_by_id` returns data without access verification
   
   **Report:** `superset/commands/utils.py` `get_datasource_by_id` performs no 
access check; proposed
   `check_access=True` default (ASVS 8.2.2).
   
   **Verdict — FALSE POSITIVE (for the proposed default-on fix).** The function 
has two callers
   (`CreateChartCommand.validate`, `UpdateChartCommand.validate`), both of 
which only read
   `datasource.name`; datasource authorization for chart create/update is 
enforced elsewhere in the chart
   API flow. Flipping `check_access=True` by default would inject a new 
authorization gate into those
   paths with no demonstrated vulnerability (the report itself states "no 
evidence of exploitable paths").
   If hardened at all, it should be an opt-in parameter defaulting to `False` 
to preserve behavior.
   
   ## 10. Chart screenshot/thumbnail endpoints missing `X-Content-Type-Options: 
nosniff`
   
   **Report:** `superset/charts/api.py` image endpoints lack a `nosniff` header 
(ASVS 3.2.1, CWE-116).
   
   **Verdict — FALSE POSITIVE.** Flask-Talisman is initialized globally 
(`TALISMAN_ENABLED` defaults
   `True`) and its default `x_content_type_options=True` adds 
`X-Content-Type-Options: nosniff` to **every**
   response via an `after_request` hook. The header is already present on the 
image responses. A
   per-endpoint header would be redundant.
   
   ## 11. Import / extension ZIP processing missing `check_is_safe_zip`
   
   **Reports:** import endpoint (`superset/importexport/api.py`) and
   `get_bundle_files_from_zip` (`superset/extensions/utils.py`) extract ZIP 
entries "without"
   `check_is_safe_zip` (ASVS 5.2.1 / 5.3.2, CWE-22/400).
   
   **Verdict — FALSE POSITIVE.** Both paths **do** call `check_is_safe_zip` 
before reading entries:
   `get_contents_from_bundle` (`superset/commands/importers/v1/utils.py:254`) 
calls it as its first line,
   and `get_bundle_files_from_zip` (`superset/extensions/utils.py:159`) calls 
it before yielding. It
   enforces per-file size and compression-ratio limits. *(A genuine residual 
gap — no aggregate-total cap
   and a zero-division edge — was addressed separately in PR #40664.)*
   
   ## 12. `uuid3` (MD5-based) used for cache-key generation
   
   **Report:** `superset/extensions/metastore_cache.py` uses `uuid3` (MD5) for 
cache keys (ASVS 11.4.1,
   CWE-328).
   
   **Verdict — FALSE POSITIVE.** This is deterministic key derivation (mapping 
a cache-key string to a
   UUID for lookup), not a cryptographic use. MD5's collision/preimage 
weaknesses are irrelevant; there is
   no confidentiality or authentication dependency. The report itself concedes 
it is "not for
   security-critical cryptographic operations." Switching to `uuid5` would only 
invalidate existing cache
   keys (a one-time cache miss) for no security benefit.
   
   ## 13. No startup validation that the encryption engine is AEAD
   
   **Report:** `EncryptedFieldFactory` accepts any adapter without verifying an 
approved AEAD mode
   (ASVS 11.3.2, CWE-327).
   
   **Verdict — NON-ISSUE (as stated).** The default engine is `AesEngine` 
(AES-CBC), so a startup check
   that *requires* AEAD would fail the default configuration. A hard AEAD 
assertion only becomes
   meaningful **after** an AES-GCM migration — which is tracked as a separate, 
deliberate change (the
   authenticated-encryption SIP / draft PR #40654). Adding the check now would 
either be a no-op or break
   default installs.
   
   ## 14. Legacy database URI validator missing `check_sqlalchemy_uri()`
   
   **Report:** `superset/views/database/validators.py` 
`sqlalchemy_uri_validator` omits
   `check_sqlalchemy_uri()`, potentially bypassing 
`PREVENT_UNSAFE_DB_CONNECTIONS` (ASVS 2.2.1, CWE-20).
   
   **Verdict — FALSE POSITIVE (dead code).** The claim that the function omits 
the check is literally true,
   but the function is **unreachable** — the only symbol imported from that 
module anywhere is
   `schema_allows_file_upload`; `sqlalchemy_uri_validator` has zero callers, 
and the legacy `DatabaseView`
   exposes no URI-submission endpoint. All DB create/edit flows go through the 
REST API, whose validator
   (`superset/databases/schemas.py:197-217`) correctly gates on 
`PREVENT_UNSAFE_DB_CONNECTIONS` and calls
   `check_sqlalchemy_uri`. No bypass exists; optional dead-code removal only.
   
   ## 15. `row_limit` missing upper-bound validation
   
   **Report:** `ChartDataQueryObjectSchema.row_limit` validates only `min=0`, 
enabling resource
   exhaustion (ASVS 2.2.1, CWE-770).
   
   **Verdict — FALSE POSITIVE.** Every requested `row_limit` is hard-capped 
server-side at execution time
   by `apply_max_row_limit()` (`superset/utils/core.py`), which clamps to 
`SQL_MAX_ROW` (default 100000)
   or `TABLE_VIZ_MAX_ROW_SERVER`. The resource-exhaustion premise is not met — 
execution never honors an
   unbounded limit. A schema-level `max` would be cosmetic and risks 
contradicting deployments that raise
   `SQL_MAX_ROW`.
   
   ## 16. Authentication rate limiting not active by default
   
   **Report:** `AUTH_RATE_LIMITED` defaults to `False` (per Flask-AppBuilder), 
leaving login unprotected
   (ASVS 6.3.1, CWE-307).
   
   **Verdict — FALSE POSITIVE (for Superset).** Superset's `config.py:344-345` 
already overrides the FAB
   default: `AUTH_RATE_LIMITED = True` and `AUTH_RATE_LIMIT = "5 per second"`. 
The limiter is gated to
   production via `RATELIMIT_ENABLED = os.environ.get("SUPERSET_ENV") == 
"production"` by design (avoiding
   rate-limiting noise in development). The premise — that a default Superset 
deployment has
   `AUTH_RATE_LIMITED = False` — does not hold.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to