diff --git a/.claude/rules/drivers.md b/.claude/rules/drivers.md index ce0f95eaa..ddbd7dbc8 100644 --- a/.claude/rules/drivers.md +++ b/.claude/rules/drivers.md @@ -366,7 +366,11 @@ host logger factory (wired in `DriverFactoryBootstrap`; the FOCAS and TwinCAT fa (case-insensitive; another-instance match beats an own-name match) is refused at save (`DriverConfigSaveGate.ValidateAsync`), deploy (`CredentialReferenceOtherInstance`), Test Connect (`TestDriverConnect.DriverInstanceId`) and browse (`OpenAsync(…, driverInstanceId, …)`; no ConfigDb ⇒ fail closed). One rule: `DriverCredentialPolicy.DescribeOtherInstanceProblems` - + `DriverInstanceIdentities`. The runtime adds no check (drivers don't know the fleet); `env:` is not instance-scoped. + + `DriverInstanceIdentities`. The runtime adds no check (drivers don't know the fleet). Since #849 `env:_…` + is instance-scoped the same way (INSTANCE = id/name upper-cased, other chars `_`; `_`-bounded prefix match); an id-shaped + part (`DRV_<12 HEX>`, `secret:…/DRV-<12 hex>/`) matches by id only, and id-shaped names (`drv`, `DRV-<12 hex>…`) are + refused. #848: create/rename refuse a name another driver's stored reference uses, and a rename away from a name the + driver's own references use (`DescribeNameChangeProblemsAsync`). ⚠️ Test Connect/browse configs can carry UNSAVED edits: the claimed owner counts only when every endpoint value (`DriverCredentialRules.EndpointFields`) is one that driver stores (`EffectiveOwnerAsync`, server-side) — a new credential-sending driver must declare its endpoint keys there. Credential-carrying Test Connect/browse is audited diff --git a/docs/security.md b/docs/security.md index 180021073..3529816f8 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1445,6 +1445,17 @@ form or starts with it followed by `_` (so `PLANTB` names `env:OTOPCUA_GALAXY_PL for secrets, any match against another instance wins — use the id form (`env:OTOPCUA_GALAXY_DRV_1A2B_KEY`) for per-instance variables. Shared variables that start with no instance's id or name (`GALAXY_MXGW_API_KEY`) keep working. +- **Id-form references are matched by id only.** A reference whose instance part has the shape of a system-generated + id — `secret:…/DRV-<12 hex>/…` or `env:DRV_<12 HEX>` alone or followed by `_` — is never matched against + driver **names**, so a driver named `drv` (env form `DRV`), or one named like another's id, cannot make every + sibling's id-form variable "another instance's", whether that name was created before or after this rule. New names + of that form are refused outright at create / rename / modal update (`DriverCredentialPolicy.IsReservedInstanceName`: + env form `DRV` or starting with `DRV_<12 HEX>`). +- **Name-form variables are only reliable for ASCII letter/digit names.** Normalisation turns every other character — + including non-ASCII letters — into `_`, so `Linie Süd` and `Linie S_d` share `LINIE_S_D`, and a name with no ASCII + letter or digit at all normalises to empty and gets **no** name protection (only its id does). Use the id form for + per-instance variables. + Rules worth knowing: - **A match against another instance wins over a match against the owner.** Names are unique per folder, not @@ -1512,10 +1523,14 @@ re-checked against stored references except when they change. Residuals (accepted): a **deleted** driver's id and name stop protecting its secrets and variables — after the delete any driver may reference `secret:galaxy//…` — so remove (or rotate) a deleted driver's secrets with it. The -#848 rename pin can be side-stepped by a Designer who first **saves** an edit to the victim driver dropping its -name-scoped reference, renames it, and then uses the freed name; that is the same "may author the victim's config ⇒ may -redirect it" property as a saved endpoint change below (each step is a persisted, visible draft change, and the -credential use is audited) — use id-scoped references where this matters. A variable is node-wide, so the `env:` rule +#848 rename pin does not stop a Designer who may edit the victim driver: a **single** config-modal save can move (or +drop) its name-scoped reference and rename it in the same commit, after which the old name is free for any driver. That +is the same "may author the victim's config ⇒ may redirect it" property as a saved endpoint change below (the save is a +persisted, visible draft change, and the later credential use is audited) — use id-scoped references where this +matters. For the same reason a low-severity race is accepted: a rename and a **concurrent** device save that adds a +reference to the old name are not tied together (the driver's `RowVersion` does not cover its device rows), so both can +commit and leave a device reference named after a name the driver no longer has — again needs a Designer who may author that driver's +config, and audited at use. A variable is node-wide, so the `env:` rule constrains which driver may *name* it, not who can read the process environment. A reference to a **shared** name (one no instance is called) is not tied to any owner, so an unsaved endpoint can still be Test-Connected with it (audited); and a Designer who **saves** a new endpoint on an existing driver can then Test Connect / browse /