mikebridge opened a new pull request, #43783:
URL: https://github.com/apache/superset/pull/43783

   ### SUMMARY
   
   The semantic-layer API returned a layer's `configuration` verbatim on both
   `GET /api/v1/semantic_layer/` and `GET /api/v1/semantic_layer/<uuid>`. A 
layer
   configuration is a credentialed connection payload — the analogue of a
   `Database` row's `encrypted_extra` (for the Snowflake provider it holds the
   password, or the private key and passphrase). The `Database` API masks
   `sqlalchemy_uri` / `masked_encrypted_extra` on read; the semantic-layer API 
had
   no equivalent, so any user who could read a layer got its secrets in 
plaintext.
   
   Separately, `SemanticLayer` and `SemanticView` were in none of the FAB
   restriction sets (`GAMMA_READ_ONLY_MODEL_VIEWS`, `ALPHA_ONLY_VIEW_MENUS`,
   `ADMIN_ONLY_VIEW_MENUS`), so `sync_role_definitions` granted the stock Gamma
   role every permission on both — including `SemanticLayer: can_write` (create 
a
   credentialed connection) and `SemanticView: can_write`.
   
   This is pre-release, experimental code: `semantic_layers` is in no numbered
   release, and the `SEMANTIC_LAYERS` feature flag defaults to `False` and is
   marked `@lifecycle: development`. There is no released version to exploit, so
   this is a normal public bug fix rather than an embargoed security report.
   
   **What changed**
   
   1. New `superset/semantic_layers/masking.py` walks the registered provider's
      pydantic configuration schema and replaces every `SecretStr`-typed field
      (`format=password` / `writeOnly`, including union variants, nested models,
      lists, and `additionalProperties`) with `PASSWORD_MASK`. It fails closed: 
an
      unregistered layer type, an unusable schema, or a key the schema does not
      describe masks every scalar in that subtree rather than risk exposing it,
      and union handling masks a field unless every matching variant agrees it 
is
      safe. `_serialize_layer` applies the mask on both GET endpoints.
   2. `UpdateSemanticLayerCommand` swaps each echoed `PASSWORD_MASK` sentinel 
back
      for the stored value at the same path before validation and persistence
      (the same round-trip `Database` uses for `masked_encrypted_extra`), so
      editing a layer without retyping its credential does not wipe it. A mask
      with no stored counterpart is passed through for provider validation to
      reject.
   3. FAB classification:
      - `SemanticLayer` → `READ_ONLY_MODEL_VIEWS` (treated like `Database`): a
        semantic layer is a credentialed connection, so writes are admin-only 
and
        reads stay broadly accessible but return masked secrets. Its two custom
        read endpoints (`views`, `connections`) are mapped to `can_read` so they
        are not caught by the admin-only rule.
      - `SemanticView` → `GAMMA_READ_ONLY_MODEL_VIEWS` (treated like 
`Dataset`): a
        view is a query definition with no credentials of its own, so writes are
        Alpha-tier and reads Gamma-tier.
   
   A companion change on the Preset shell (superset-shell #4135, in review)
   adjusts its role policy to match — matching `SemanticView: can_write` to
   `Dataset` and removing view-write from Gamma-based roles. That is the
   deployment-side half; this PR is the upstream half. Once this lands, the 
shell
   policy can relax `SemanticLayer: can_read` back to `Database: can_read` 
parity
   now that reads are masked.
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   
   N/A — API/authorization change with no UI surface.
   
   Before: `GET /api/v1/semantic_layer/<uuid>` →
   `configuration: {"account": "acme", "password": "hunter2", ...}`.
   After: `configuration: {"account": "acme", "password": "XXXXXXXXXX", ...}`,
   with the plaintext secret asserted absent from the response body.
   
   ### TESTING INSTRUCTIONS
   
   Automated (all pass locally on Python 3.12):
   
   ```
   pytest tests/unit_tests/semantic_layers/ \
          tests/unit_tests/commands/semantic_layer/ \
          tests/unit_tests/security/manager_test.py
   ```
   
   New coverage: masking unit tests (schema walk, every fail-closed path, a
   mask→unmask round-trip property, and a secret nested in a single union 
variant);
   API-level list/detail masking including the unregistered-type fail-closed 
path,
   each asserting the plaintext never appears in the response body; 
update-command
   round-trip (echoed mask reuses the stored secret, a retyped secret persists);
   classifier unit tests with a Gamma negative control and a pin that the 
layer's
   custom read endpoints are not admin-only; and Gamma/Alpha assertions in
   `tests/integration_tests/security_tests.py` (with `SEMANTIC_LAYERS` enabled 
in
   the integration test config so the permissions exist).
   
   Manual: with `SEMANTIC_LAYERS=True` and a Snowflake semantic layer 
configured,
   `GET /api/v1/semantic_layer/<uuid>` returns secret fields as `XXXXXXXXXX`;
   editing the layer without retyping the credential (echoing the masked payload
   back on `PUT`) preserves the stored secret; and a Gamma user has no 
`can_write`
   on `SemanticLayer` or `SemanticView`.
   
   ### ADDITIONAL INFORMATION
   
   - [x] Required feature flags: `SEMANTIC_LAYERS` (development, default off)
   - [ ] Changes UI
   - [ ] Includes DB Migration
   
   Companion deployment-side change: superset-shell #4135 (in review). No SIP or
   `UPDATING.md` entry — the feature is default-off and pre-release.
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
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]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to