📝 Description
One Guardrail form and one Data Masking form for agents and sidecars. Built on the sidecar vocabulary (the richer one), each form writes both halves of a rule in one save: the agent fields (input/output rules, or entity types, custom entity types and threshold) and the sidecar block (sidecar_spec), plus the resource roles, attributes and sidecar listeners it binds to. An option one engine does not run carries an inline Sidecar only / Agent only tag; a rule bound to resource roles that uses a sidecar-only option stores an empty agent half, and the form says so. The two forms per feature (Gateway*Form / Sidecar*Form) and the dead-end for a rule carrying both halves are gone.
No backend behaviour change. The list endpoints add sidecar_targets per rule so the list page can tell which traffic a rule protects.
📣 User-facing impact
Admins write one rule and it protects a database whether the traffic goes through an agent or a sidecar. Every option says which engine runs it.
🔗 Related Issue
ENG-536
🚀 Type of Change
- ✨ New feature (non-breaking change which adds functionality)
- 🎨 Style/UI update
📋 Changes Made
webapp_v2/src/pages/Guardrails/Create/{GuardrailForm,GuardrailFields,useGuardrailEditor}: one form; each rule hasChecks: Statements | Results(Results is agent-only, so it is disabled for a type only the sidecar runs);sidecar_specis written from the statement rules,input/outputfrom the two directions; a results rule beside a sidecar-only rule, or a results rule on a listener-only binding, is refused with the reasonwebapp_v2/src/pages/Features/DataMasking/Create/{DataMaskingForm,DataMaskingFields,useDataMaskingEditor}: one form; presets became a quick-fill of a rule's entity list; the agent-only threshold and custom entity types live beside the rule list;supported_entity_typesis written from the entity rules, emptied when a rule matches by columnwebapp_v2/src/utils/ruleSupport.js,components/RuleSupportTag: what each engine runs, and the inline markerwebapp_v2/src/utils/ruleTraffic.js: a rule's traffic is where it is bound (connection_ids/attributes,sidecar_targets), not which fields it has;<ByRuleTraffic>andNewRuleButtonstay for the analyzer onlywebapp_v2/src/components/ConnectionsMultiSelect:disabled- Deleted:
Gateway{Guardrail,DataMasking}Form, bothRulesTables, the guardrail presets and the row/payload helpers gateway/api/guardrails,gateway/api/datamasking,gateway/api/sidecarbind.LoadAll: list responses carrysidecar_targets
🧪 Testing
Result: an admin writes one Guardrail or Data Masking rule, binds it to resource roles and sidecar listeners in the same form, every option says which engine runs it, and the saved rule carries both the agent half and the sidecar half.
Same seed on origin/main 4eb14ab7 and on 1c947acd (resource role pg-prod, sidecar qa536 with listener appdb), same steps.
1. One guardrail for agents and sidecars
Full size: 1-one-guardrail-form.mp4
- Main: "Create new rule" asks "For agent resources" or "For sidecar listeners"; the sidecar form has Listeners only. Branch: one form with Resource Roles, Attributes and Listeners.
- Branch, rule 1: the Type dropdown tags
Operation,Table,PIIasSidecar only;ChecksoffersStatementsandResults · Agent only; the operations scope isSidecar only. Rule 2 is aPattern matchresults rule. - Last frame: main lists the rule as
Sidecar, the branch asAgent and sidecar.
2. One masking rule for agents and sidecars
Full size: 2-one-masking-form.mp4
- Branch:
Analyzer confidence threshold · Agent only; a preset fills the entity list;Strategy · Sidecar only;Custom entity types · Agent onlywithORDER_ID.
3. The saved rule carries both halves
GET /:id after the flows. Main stores the sidecar half only (connection_ids: [], input.rules: [], supported_entity_types: []). The branch stores both from one save.
What it solves: a customer with agents and sidecars keeps one rule per policy and one form to edit it, and sees on each option which engine enforces it.
Also checked by hand on the final backend: a legacy agent-only rule opens and an unchanged save adds the sidecar half; a results rule beside a sidecar-only rule and a custom entity beside a column rule are refused by the form; a results-only rule bound to a listener gets the gateway's 422; the Sidecars page dialog creates and edits with the same fields; select pg_sleep(1) through a listener is denied by a rule saved from the form.
Recording caught and fixed: the Checks select hidden on a new rule, the Agent only tag ellipsized in its dropdown, the operations list surviving a type switch as a scope.
Re-run:
cd webapp_v2 && npm run lint && npm run build
cd gateway && go build ./... && go test -count=1 ./api/sidecarbind/... ./api/datamasking ./api/sidecar/... ./services ./modelsTest Configuration:
- Browser(s): Brave (Chromium 156), recorded over CDP
- OS: macOS arm64
Tests performed:
- Unit tests pass
- Integration tests pass (CI)
- Manual testing completed
✅ Checklist
- My code follows the style guidelines of this project
- I have performed a self-review of my own code
- I have commented my code, particularly in hard-to-understand areas
- My changes generate no new warnings
- New and existing unit tests pass locally with my changes
- I have checked my code and corrected any misspellings
📄 Additional Notes
- The branch first translated agent rules into sidecar rules at handshake (
services/sidecargatewayrules.go); that was removed in favour of the form writing the sidecar half itself. A rule without asidecar_specbound to a listener is still refused by the gateway, as onmain; an old agent-only rule reaches a sidecar after one save in the form. - The evidence stacks had no live DLP provider, so agent-side enforcement of the saved rules was not exercised; the agent half keeps the shape the old agent form sent.
- Gateway↔agent and gateway↔sidecar contracts: unchanged. No flag, migration, env var or analytics event.
- Sidecar
deny_words_listis case-insensitive and the agent's is case-sensitive; the Words field says so. - Pre-existing, not fixed here: the
Contact Informationpreset carriesPERSON, which no shipped sidecar loads, so that preset bound to a listener is refused with the daemon's message.
Activity
33✅ Build Completed with Success, Version=2062.0.0-g278a6fa
✅ Build Completed with Success, Version=2062.0.0-gd3ad9b0
topzera
📋 API Changelog
API Changelog unknown vs. unknown
No changes detected