The gap
Pattern 8 (#[PublicPage] handlers, added in #373/#398) decides scope by asking
whether a caller-supplied SCALAR reaches a selection call, and explicitly
discounts payloads — from check_no_admin_idor.py:
in scope — a caller-supplied SCALAR value reaches a selection call
(array $data is a payload, not a selector; a $token / $secret IS the
authorisation … and neither is a selector).
That is right for REST. It is structurally blind to the SOAP / webhook /
raw-body family, where the method takes no parameters at all and the
selector arrives inside the request body.
Measured, in procest at 55280c070
#[PublicPage]
#[NoCSRFRequired]
public function zaken(): DataDisplayResponse {
return $this->dispatcher->dispatch(
rawBody: file_get_contents('php://input'),
service: 'zaken'
);
}
StufController::zaken() and ::personen() are #[PublicPage] +
#[NoCSRFRequired] and reached StufZknMessageResponder::respond() with no
authentication of any kind — no token, no JWT, no signature. That responder
dispatches zakLk01 (case create/update), zakLv01 (case query), npsLv01
(person query by BSN) and edcLk01 (document create/update).
gate-7 reports zero findings on procest's 79 @PublicPage methods, these two
included. Every finding in the app carries rule=no-auth-guard-in-body; not one
carries rule=publicpage-unscoped-object-lookup.
The silence is correct for the other 77 — the ZGW controllers all call
validateJwtAuth(), the share routes are $token-gated, and the
zaakeigenschappen* / publish* one-liners are cleared by clear (e), the
resolved delegation hop. Clear (e) in particular did better than my own body
grep, which called nine guarded delegations unguarded. The gap is narrow and
specific: a parameterless method whose selector is in the body.
Fixed app-side in ConductionNL/procest#828 — the guard is the WSSE verification
the same controller's third public route already performed.
Why it matters more than one app
The shape is common in this fleet's integration surface: StUF, notification
webhooks, NrcController::notificatieCreate(), payment callbacks. All of them
are legitimately #[PublicPage] (the caller has no Nextcloud session) and all
of them carry their identifiers in the body.
⚠️ And it is exactly the failure mode Pattern 8's own commentary warns about,
one level up. The comment says the repaired failure mode "is DISTRUST" and that
the fleet "came to trust its silences". A zero on 79 methods is a very loud
silence, and here it was right 77 times and wrong twice — which is precisely the
ratio that makes a silence persuasive.
Suggested predicate
Treat a #[PublicPage] method that reads the raw request body
(file_get_contents('php://input'), $this->request->getContent(), or a
collaborator call passing one of those) as in scope, with the same clears
already defined — a signature/WSSE/HMAC verification, a JWT validation, or a
resolved delegation hop reaching one, should clear it.
The two-directional control is available in-tree: StufController::inkomend()
verified WSSE before ConductionNL/procest#828 and ::zaken() did not, in the
same file, same commit, same checker.
What I did not do
I did not write a fixture or run the arms. This is a report from a per-app
audit, not a verified gate change — the shape above is a suggestion and needs
the usual both-arms fixture before it lands.
The gap
Pattern 8 (
#[PublicPage]handlers, added in #373/#398) decides scope by askingwhether a caller-supplied SCALAR reaches a selection call, and explicitly
discounts payloads — from
check_no_admin_idor.py:That is right for REST. It is structurally blind to the SOAP / webhook /
raw-body family, where the method takes no parameters at all and the
selector arrives inside the request body.
Measured, in procest at
55280c070StufController::zaken()and::personen()are#[PublicPage]+#[NoCSRFRequired]and reachedStufZknMessageResponder::respond()with noauthentication of any kind — no token, no JWT, no signature. That responder
dispatches
zakLk01(case create/update),zakLv01(case query),npsLv01(person query by BSN) and
edcLk01(document create/update).gate-7 reports zero findings on procest's 79
@PublicPagemethods, these twoincluded. Every finding in the app carries
rule=no-auth-guard-in-body; not onecarries
rule=publicpage-unscoped-object-lookup.The silence is correct for the other 77 — the ZGW controllers all call
validateJwtAuth(), the share routes are$token-gated, and thezaakeigenschappen*/publish*one-liners are cleared by clear (e), theresolved delegation hop. Clear (e) in particular did better than my own body
grep, which called nine guarded delegations unguarded. The gap is narrow and
specific: a parameterless method whose selector is in the body.
Fixed app-side in ConductionNL/procest#828 — the guard is the WSSE verification
the same controller's third public route already performed.
Why it matters more than one app
The shape is common in this fleet's integration surface: StUF, notification
webhooks,
NrcController::notificatieCreate(), payment callbacks. All of themare legitimately
#[PublicPage](the caller has no Nextcloud session) and allof them carry their identifiers in the body.
one level up. The comment says the repaired failure mode "is DISTRUST" and that
the fleet "came to trust its silences". A zero on 79 methods is a very loud
silence, and here it was right 77 times and wrong twice — which is precisely the
ratio that makes a silence persuasive.
Suggested predicate
Treat a
#[PublicPage]method that reads the raw request body(
file_get_contents('php://input'),$this->request->getContent(), or acollaborator call passing one of those) as in scope, with the same clears
already defined — a signature/WSSE/HMAC verification, a JWT validation, or a
resolved delegation hop reaching one, should clear it.
The two-directional control is available in-tree:
StufController::inkomend()verified WSSE before ConductionNL/procest#828 and
::zaken()did not, in thesame file, same commit, same checker.
What I did not do
I did not write a fixture or run the arms. This is a report from a per-app
audit, not a verified gate change — the shape above is a suggestion and needs
the usual both-arms fixture before it lands.