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]