Skip to content

New-PfbObjectStoreAccessPolicyRule omits the required names query parameter; audit the 23 cmdlets that build an empty @{} body #106

Description

Found while fixing #101. Two related things: one confirmed defect, and a systematic audit the defect implies.

Part 1 — confirmed defect: New-PfbObjectStoreAccessPolicyRule cannot name a rule

POST /object-store-access-policies/rules declares these query parameters (tools/specs/fb2.28.json):

X-Request-ID, context_names, enforce_action_restrictions, names, policy_ids, policy_names

The names parameter resolves to the shared component Names_required, which is declared required: true:

Names_required: name="names" required=true
  desc="A comma-separated list of resource names."

Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1:51 sends only policy_names:

$body = if ($Attributes) { $Attributes } else { @{} }
$queryParams = @{ 'policy_names' = $PolicyName }

The rule's own name is never sent, and the API requires it. The cmdlet exposes no parameter that could supply it — -PolicyName is the containing policy, not the rule.

Needs a -Name parameter mapping to the names query key. Note this is one of the cases where -Name is genuinely correct, unlike #64/#87/#99 where names was the dead key — the distinction is whether the spec declares it, which here it does, as required.

Related but not a defect here: the empty @{} body is legal on this endpoint. PolicyRuleObjectAccessPost has no required array; actions, conditions, effect, and resources are all optional. A rule created with no body is presumably useless in practice, but it is not a schema violation. Exposing typed parameters for those four fields is worth considering as part of the usability work in #57 / #65, not as a wire fix.

Part 2 — audit: 23 cmdlets build an empty body the same way

$body = if ($Attributes) { $Attributes } else { @{} } is a module-wide convention, not a one-off:

Public/Admin/New-PfbApiClient.ps1
Public/Admin/New-PfbManagementAccessPolicy.ps1
Public/Admin/New-PfbOidcIdp.ps1
Public/Admin/New-PfbPublicKey.ps1
Public/Admin/New-PfbSaml2Idp.ps1
Public/Bucket/New-PfbBucketAuditFilter.ps1
Public/Certificate/New-PfbCertificateGroup.ps1
Public/DirectoryService/New-PfbDirectoryServiceRole.ps1
Public/FileSystem/New-PfbNlmReclamation.ps1
Public/Misc/New-PfbLag.ps1
Public/Misc/New-PfbLegalHold.ps1
Public/Misc/New-PfbLegalHoldEntity.ps1
Public/Misc/Update-PfbLegalHoldEntity.ps1
Public/Monitoring/New-PfbLogTargetFileSystem.ps1
Public/Monitoring/New-PfbLogTargetObjectStore.ps1
Public/ObjectStore/New-PfbObjectStoreAccessPolicy.ps1
Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1
Public/ObjectStore/New-PfbObjectStoreAccountExport.ps1   <- fixed by #101
Public/ObjectStore/New-PfbObjectStoreRemoteCredential.ps1
Public/ObjectStore/New-PfbObjectStoreRole.ps1
Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1
Public/ObjectStore/Update-PfbObjectStoreAccessPolicyRule.ps1
Public/ObjectStore/Update-PfbObjectStoreTrustPolicyRule.ps1

The shape itself is harmless where the endpoint requires nothing. It becomes a broken-on-arrival create wherever the endpoint declares a required body field or a required query parameter that the cmdlet has no parameter to supply. That is exactly how #101 shipped: POST /object-store-account-exports declares "required": ["server"], and New-PfbObjectStoreAccountExport had no way to send it, so the cmdlet could never create anything.

For each of the 23, check against the spec:

  1. Does the POST/PATCH body schema declare a required array? If so, can the cmdlet supply every required field without the caller hand-rolling -Attributes?
  2. Does the endpoint declare any required: true query parameter (the *_required component variants are the tell)? If so, does the cmdlet send it?
  3. Where a required field can only be supplied via -Attributes, treat that as broken by default — -Attributes is an escape hatch, not the intended interface.

Watch for schemas nesting allOf two levels deep; a single-level walk silently returns an incomplete field list and will make an affected cmdlet look clean.

Priority

Part 1 is P0 for that one cmdlet — it cannot create a rule at all. Part 2 is P1: the audit is likely to surface several more creates in the same state, and should be sized before being scheduled.

Related

Activity

  1. juemerson-at-purestorage commented on Aug 20, 2026

    @juemerson-at-purestorage
    CollaboratorAuthor

    Part 2 audit complete

    Audited the full 23-cmdlet population against all 29 published OpenAPI specs (fb2.0–fb2.28) at origin/main@415f05f19fea898086598cb5598299b2f819894d.

    The current exact empty-body convention appears in 22 cmdlets; the 23rd historical row, New-PfbObjectStoreAccountExport, was repaired by #101.

    Results

    Of the 21 new audit candidates:

    • 1 confirmed impossible-through-public-interface defect
    • 1 typed-but-not-enforced contract mismatch
    • 11 satisfied required contracts
    • 8 legal empty requests
    • 0 unresolved

    The two controls behaved as expected:

    New findings

    1. New-PfbApiClient cannot supply required body fields through typed parameters

    POST /api-clients requires:

    • public_key in REST 2.0–2.28
    • max_role in REST 2.0–2.18

    Public/Admin/New-PfbApiClient.ps1:29-45 exposes only -Name, -Attributes, and -Array. The required body fields can only be supplied by hand-building -Attributes, which this issue defines as an escape hatch rather than the intended typed interface.

    A minimal invocation binds successfully but sends {} without public_key:

    New-PfbApiClient -Name 'automation-client'

    Classification: confirmed impossible through the public typed interface.

    2. Update-PfbLegalHoldEntity does not enforce required released

    PATCH /legal-holds/held-entities requires query parameter released in every supported version, REST 2.17–2.28.

    Public/Misc/Update-PfbLegalHoldEntity.ps1:63-64 exposes typed -Released, but it is optional. Line 91 sends released only when explicitly bound, so this valid PowerShell invocation reaches the API without the required key:

    Update-PfbLegalHoldEntity -Name 'fs1' -Recursive $true

    The cmdlet’s own examples also include calls that omit -Released.

    Classification: typed but not enforced.

    Proposed follow-up split

    I suggest separate follow-ups for:

    1. Add typed required-body coverage to New-PfbApiClient.
    2. Enforce required -Released on Update-PfbLegalHoldEntity and correct its examples.
    3. Extend drift reporting to detect typed parameters that exist but do not enforce API requiredness.
    4. Separately investigate New-PfbNlmReclamation -Name, which sends an undeclared names key for an API operation described as system-wide.

    Optional body-property coverage found on otherwise valid cmdlets remains broader usability work for #57/#65 rather than a required-field defect.

    No source changes, live calls, or GitHub mutations were made during this audit.

  2. juemerson-at-purestorage commented on Aug 21, 2026

    @juemerson-at-purestorage
    CollaboratorAuthor

    Part 2's audit list is short by three, and the reason is worth recording because it will bite the next sweep of this convention too.

    The list of 23 was built by matching the exact literal:

    $body = if ($Attributes) { $Attributes } else { @{} }

    Three cmdlets use a .Clone() variant, which that pattern does not match:

    $body = if ($Attributes) { $Attributes.Clone() } else { @{} }
    • Public/DirectoryService/New-PfbLocalDirectoryService.ps1
    • Public/DirectoryService/New-PfbLocalGroup.ps1
    • Public/FileSystem/New-PfbFileSystemExport.ps1

    The arithmetic, since the numbers move: the body lists 23 paths, one of which (New-PfbObjectStoreAccountExport.ps1) it already annotates as fixed by #101 and which no longer uses the one-liner at all — so 22 still match the literal today. Adding the three above puts the convention at 25 cmdlets in scope. Same semantics for audit purposes either way — an empty body plus whatever query keys the cmdlet chose to send.

    New-PfbLocalGroup is not a hypothetical: it is confirmed broken on the wire for exactly the failure mode this issue is about, a required query key the cmdlet never sends. Filed separately as #136 with live evidence, since the missing key there is local_directory_service_names rather than names and the fix spans three cmdlets in that family. Flagging it here so the audit does not re-derive it, and so New-PfbLocalDirectoryService and New-PfbFileSystemExport get checked rather than skipped.

    Worth suggesting for the audit itself: match on else { @{} } alone, or on the AST, rather than on the whole assignment. .Clone() is a semantically meaningful variation someone added deliberately (it avoids mutating the caller's hashtable — arguably the better form, and the other 22 could be argued to have a latent aliasing bug), so more such variants are likely as this convention gets touched. A literal-string sweep silently under-reports, and an under-reporting audit reads exactly like a complete one.

  3. juemerson-at-purestorage commented on Aug 21, 2026

    @juemerson-at-purestorage
    CollaboratorAuthor

    Part 2 audit: complete

    The Part 2 audit is done. Every cmdlet building the semantic empty-@{} body was checked against every published REST version for required query parameters and required body properties, and both surviving findings were sent to a fresh reviewer instructed only to refute them. Both survived as CONFIRMED.

    Two real defects, both being fixed directly rather than filed as separate issues, since they are exactly what Part 2 was chartered to find:

    Cmdlet Endpoint What the contract requires What the cmdlet did
    New-PfbApiClient POST /api-clients body public_key required 2.0-2.28; max_role required 2.0-2.18 Exposed only -Name and -Attributes, so New-PfbApiClient -Name 'x' bound cleanly and sent {}. No typed route to either required field existed
    Update-PfbLegalHoldEntity PATCH /legal-holds/held-entities query released required: true on 2.17-2.28 -Released was optional and the key was sent only when explicitly bound, so -Name 'fs1' -Recursive $true omitted it. Two of the cmdlet's own examples documented that shape

    The first is the "impossible through the public interface" case this issue is named for. The second is a new category the issue did not anticipate — typed but not enforced. -Released existed, which is why nothing flagged it: Reports/PfbApiDriftReport.json scores typed-parameter presence, not whether the parameter is mandatory, so the endpoint read as fully covered. That blind spot is filed separately as a tooling follow-up.

    Neither shared helper closes either gap. Invoke-PfbApiRequest can centrally inject only context_names; Assert-PfbApiCapability version-checks keys already present rather than supplying missing ones.

    Everything else is clean

    • 11 cmdlets satisfy their required contract already — a mandatory -Name writing a required names key, in each case with the source line confirmed rather than assumed.
    • 8 cmdlets issue a legally empty request: no required body property and no unsatisfied required query parameter in any published version. New-PfbOidcIdp, New-PfbCertificateGroup, New-PfbDirectoryServiceRole, New-PfbNlmReclamation, New-PfbLegalHoldEntity, New-PfbObjectStoreTrustPolicyRule, Update-PfbObjectStoreAccessPolicyRule, Update-PfbObjectStoreTrustPolicyRule.

    Optional body properties reachable only through -Attributes are a real usability gap on many of these, but they are not required-field defects and stay with #57/#65.

    Two controls, both behaved

    A correction to this issue's own arithmetic

    The list of 23 was built by matching one exact literal, $body = if ($Attributes) { $Attributes } else { @{} }. Three cmdlets use a .Clone() variant that the pattern does not match:

    • Public/DirectoryService/New-PfbLocalDirectoryService.ps1
    • Public/DirectoryService/New-PfbLocalGroup.ps1
    • Public/FileSystem/New-PfbFileSystemExport.ps1

    So the convention is 25 cmdlets, not 23 — of which 22 still match the original literal today, New-PfbObjectStoreAccountExport having been rewritten by #101. All three were audited.

    New-PfbLocalGroup is not hypothetical: it is confirmed broken on the wire for exactly this failure mode, a required query key the cmdlet never sends. That is #136, where the missing key is local_directory_service_names and the fix spans three cmdlets. The other two came back clean.

    Worth noting for the next sweep of this convention: match on else { @{} } or on the AST, not on the whole assignment. .Clone() is a deliberate improvement — it avoids mutating the caller's hashtable, which arguably makes the other 22 the ones carrying a latent aliasing bug — so more such variants are likely as this convention gets touched. A literal-string sweep under-reports, and an under-reporting audit reads exactly like a complete one.

    Follow-ups

    Three, all ancillary to the required-field question rather than part of it:

    1. Drift reporting cannot distinguish a typed optional parameter from a typed mandatory guarantee, which is why the released defect was invisible to it.
    2. New-PfbNlmReclamation -Name is a mandatory dead selector — the endpoint declares no names parameter and the operation is array-wide.
    3. Update-PfbLegalHoldEntity -Name is a mandatory but unusable selector: names is declared on the endpoint, but a held entity has no name (LegalHoldHeldEntity carries only file_system, legal_hold, path, status), and a live array rejects a request selected only by it. Found while live-verifying the released fix.

    Method

    Endpoint literals were derived with Get-PfbModuleCalledEndpoints rather than read by eye; all resolved, none required guessing a dynamic endpoint. Requirements were resolved with Get-PfbSchemaPropertyDetails -MaxDepth 8, which walks $ref and allOf and unions every visited required array, and by resolving path-item and operation parameter arrays through Resolve-PfbRef and accepting only resolved query parameters with required: true. Versions were iterated as strings from file names — a numeric literal silently truncates 2.20 to 2.2 and reads the wrong spec. Data/PfbCapabilityMap.json and Reports/PfbApiDriftReport.json were cross-checked but never treated as the verdict.

    The audit itself changed nothing in the repository, made no live call, and touched no issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions