Skip to main content

RBAC audit: schemas that declare no authorization

Task 1 of openspec/changes/rbac-default-authenticated. Before the absent-authorization default can flip from public to authenticated, every unmarked schema needs a stated intent — by name, not by count, because a count cannot be reviewed and the review is the whole point.

Surveyed 2026-08-15. Re-run this survey at review time rather than quoting it; the denominator moves as apps ship schemas, and a stale denominator makes an audit read as complete when it is not.

The numbers

504 of 571 declared schemas (88%) carry no authorization block, across 15 apps.

appmarkedUNMARKED
scholiq0118
shillinq0114
procest685
openconnector039
decidesk534
hermiq128
pipelinq126
docudesk120
openregister1516
larpingapp19
portaliq09
petstore03
doriath · launchpad · nextcloud-app-template03
openbuild60
opencatalogi100
softwarecatalog210
total67504

Two ways an earlier version of this survey was WRONG

Both are recorded because they are the ways the next re-run will go wrong too.

1. It read one register file per app. The first pass globbed lib/Settings/*register*.json | head -1 and reported 321 of 368 across 6 apps. openregister alone ships 14 register files; procest ships 2. The undercount hid 183 schemas and nine entire apps — including openconnector (39) and hermiq (28), which had appeared to have none. Iterate every register file.

2. It reads whichever branch each checkout is on. These are shared checkouts and other workstreams move them. At survey time portaliq sat on fix/local-lib-guard-semver, which predates its CMS work — so page, menu, glossaryTerm and portal are absent from its nine rows below. Record the branch each app was read from, or re-read from a known ref.

How to read the proposed intent

The intent column is a first pass by keyword, not a decision. It is deliberately biased toward refusal: anything naming a session, account, token, secret, audit record, salary, invoice, submission or person is proposed restricted, and only four schemas in 504 are proposed as public candidates at all.

Each app's maintainers decide their own rows. This document collects and records; it does not decide on anyone's behalf. A row is settled when a maintainer has said so — an unreviewed authenticated here is a default, not an answer.

Distribution of the first pass: 432 authenticated · 68 restricted · 4 public candidates (ContactDetail, PublicationRecord, contact, portalPage). Note that ContactDetail and contact were flagged only because their names begin with "contact"; both plainly hold personal data and are the clearest example of why this column needs a human.

A second instrument: which schemas does a PUBLIC endpoint actually name?

The keyword pass above proposes four public candidates out of 504. That number is not trustworthy on its own, because it asks what a schema is called rather than who reads it. So this asks the other question: for each app, which schema names appear in a controller file that declares #[PublicPage] or @PublicPage?

Measured 2026-08-15: 24 unmarked schemas across 9 apps are named by a public endpoint. These — not the four keyword hits — are the rows that go blank the moment the default flips.

appunmarked schemas named by a public endpoint
decideskAgendaItem Decision Meeting Membership Minutes Person Report Vote
procestcase caseType decision document location result resultType statusType
scholiqCredential Order
openconnectorendpoint notificaties_abonnement
hermiqMessage
shillinqLocation
petstorepet
launchpadDashboard

Note what this does NOT say. It does not say these 24 should be public — it says a public endpoint mentions them, so each one is either an intended public surface or an existing hole, and both readings demand a decision. Person and Credential are on the list; if those are genuinely reachable anonymously today, the flip is a fix rather than an outage, and that is worth knowing before rather than after.

Three apps whose public surfaces are already fully declared — opencatalogi (catalog, page), softwarecatalog (module, service, usage) and pipelinq (lead) — appear in the measurement with zero unmarked hits. That is the shape the other nine are being asked to reach.

This number is a FLOOR, and here is exactly where it is blind

  • It sees one file. A controller that reaches a schema through a service two frames down, or names it in a variable, is invisible. procest is the proof that cross-register reads are real: its public endpoints name agendapunt, raadsdocument and vergadering, which live in ori_register.json, not in procest_register.json.
  • An app can read a register it does not ship. Only registers found under each app's own lib/Settings/ were considered.
  • It matches case-insensitively but not by plural. shillinq's public controllers quote 'slots', 'services' and 'appointment'; its register declares no such schemas, so those strings are something else. Checked rather than assumed — an unexplained gap between two instruments is where one of them is wrong.

#[PublicPage] DOES NOT MEAN "intended anonymous" — there is a THIRD population

Reading the endpoints behind the 24 rows splits them into two opposite kinds, and the split does not follow the attribute:

Genuinely anonymous — decidesk (8). OriController serves ORI 1.4 JSON-LD at /api/ori/v1/{resource} for council information transparency. Meeting, AgendaItem, Decision, Minutes, Vote and Report are the standard's published entities — the same open-government family as the WOO publication schemas. Person and Membership are council members and their party membership, which ORI also publishes; they still deserve an explicit maintainer nod, because "public by standard" and "public by accident" look identical in a register file.

NOT anonymous — procest (8). Its public controllers are the ZGW statutory APIs: ZRC (Zaken), BRC (Besluiten), AC (Autorisaties), Subsidieregister. Every method opens with $this->zgwService->validateJwtAuth($this->request). They are #[PublicPage] because they carry no Nextcloud session, not because they are open — they authenticate themselves, per the standard, with a JWT.

That distinction is load-bearing in both directions:

  • Marking case, decision, document, result and friends public would hand statutory case data to genuinely anonymous callers. It is the WRONG fix and it would be filed as a security improvement.
  • The flip breaks them anyway. A JWT-authenticated request has no NC user, so an unmarked schema resolved as "authenticated" refuses it. The endpoint is authorized and still gets nothing.

So Task 2's population is not two-valued. There is a third kind — an endpoint that authenticates itself and then reads OpenRegister with no Nextcloud identity — and neither public nor authenticated describes it. Task 3 cannot land safely until that kind has an answer, and deciding it per-app, twelve times, is how twelve different answers happen.

That answer now exists: ADR-091

hydra ADR-091 — "Externally-Authenticated API Surface Belongs to OpenConnector" (amends ADR-081 §2) resolves this, and it resolves it by removing the population rather than by giving it an RBAC value:

An HTTP surface that authenticates its caller by any scheme other than a Nextcloud session belongs to OpenConnector. The logic behind it becomes an OpenRegister flow the endpoint triggers; what stays in the app is a register, a schema and configuration — no controller.

Under it, an OpenConnector endpoint resolves a real identity before touching OpenRegister. Every schema is then public or authenticated and nothing falls between them, which is exactly the precondition Task 3 needs.

The deciding argument in that ADR is not code duplication — it is that ZGW, StUF, DSO, Notificaties, iWmo/iJw and Berichtenbox are national standards. A leaf app that implements one only works in one country.

⚠️ Sequencing, therefore: Task 3 waits on ADR-091's population having a migration path, not merely on ADR-091 being accepted. The fleet-wide census behind it — 44 controllers, 210 public methods across 10 apps — is in hydra/scripts/adr-091-self-auth-endpoint-census.py.

Before the default flips

Every row marked public must carry an explicit "group": "public" read rule first. Flipping the default before that turns those surfaces blank — which is the safe direction, but it is an outage, and calling an outage a security fix is how the next one gets reverted.

The 24 rows above are the concrete work-list for that "first". Task 2 is not done when 504 rows have an opinion typed next to them; it is done when those 24 have either an explicit public rule or a maintainer saying the endpoint was never meant to reach them.


Unmarked schemas, by app

scholiq — 118 unmarked

schemaproposed intent
AccessibilityFeedbackauthenticated
AccessibilityLimitationauthenticated
AccessibilityStatementauthenticated
AdmissionsRoundauthenticated
AiFeatureauthenticated
Applicationauthenticated
Assessmentauthenticated
AssessmentReliabilityauthenticated
AssessmentResultauthenticated
Assignmentauthenticated
AttendanceFlagauthenticated
AttendanceRecordauthenticated
AttendanceThresholdauthenticated
Attestationauthenticated
BehaviourIncidentauthenticated
BpvPlacementauthenticated
BpvVisitReportauthenticated
BsaDecisionauthenticated
BsaProgressFlagauthenticated
BsaTrajectoryauthenticated
BsaWarningauthenticated
Cohortauthenticated
Competencyauthenticated
CompetencyAttainmentauthenticated
CompetencyFrameworkauthenticated
ConferenceReportauthenticated
ConferenceRoundauthenticated
ConferenceSignupauthenticated
ConferenceSlotauthenticated
Courseauthenticated
CourseEvaluationResponseauthenticated
CoursePackageImportReportauthenticated
CourseQualityScoreauthenticated
CourseTemplateauthenticated
Credentialrestricted
CurriculumPlanauthenticated
DataExchangeJobauthenticated
DataMappingProfileauthenticated
DeliberationRecordauthenticated
DossierNoteauthenticated
EngagementLevelauthenticated
EngagementRiskFlagauthenticated
EngagementRiskThresholdauthenticated
EngagementScoreauthenticated
Enrolmentauthenticated
Entitlementauthenticated
EvaluationCampaignauthenticated
EvaluationInvitationauthenticated
ExamAccommodationauthenticated
ExchangeErrorCodeauthenticated
ExchangeRejectionauthenticated
ExcuseRequestrestricted
ExemptionCaseauthenticated
ExternalAssessorauthenticated
ExternalTrainingRecordauthenticated
FeeItemauthenticated
FinalGradeauthenticated
FraudCaseauthenticated
GradeEntryauthenticated
GradeNotificationrestricted
GradeScaleauthenticated
GroupPlanauthenticated
GroupPlanEvaluationauthenticated
GroupPlanSubgroupauthenticated
ImprovementActionauthenticated
Itemauthenticated
ItemBankauthenticated
ItemRevisionFlagauthenticated
ItemStatisticsauthenticated
Leaderboardauthenticated
LearnerEngagementauthenticated
LearnerProfileauthenticated
LearningPlanauthenticated
LearningPlanEvaluationauthenticated
LearningPlanTemplateauthenticated
LearningRecordExportauthenticated
LearningRecordImportauthenticated
LearningRecordShareauthenticated
Lessonauthenticated
LessonCompletionauthenticated
LtiToolPlacementauthenticated
Materialauthenticated
Orderauthenticated
OrderLineauthenticated
PaymentTransactionrestricted
PeerFeedbackSummaryauthenticated
PeerReviewauthenticated
PointAwardauthenticated
PointRuleauthenticated
PokSignatureauthenticated
Portfolioauthenticated
PortfolioEntryauthenticated
PortfolioShareauthenticated
PortfolioTemplateauthenticated
Praktijkopleiderauthenticated
Praktijkovereenkomstauthenticated
ProctoringSessionrestricted
Programmeauthenticated
Regulationauthenticated
ReportCardauthenticated
ReportCardParentNotificationrestricted
ReportPeriodauthenticated
RolloverPlanauthenticated
Roomauthenticated
Rubricauthenticated
SelfAssessmentauthenticated
Sessionrestricted
Signatureauthenticated
SovereigntyPolicyauthenticated
SubjectChoiceauthenticated
Submissionrestricted
SupportRequestauthenticated
TeacherAvailabilityauthenticated
TimetableConflictauthenticated
TlvApplicationauthenticated
WellbeingCheckInauthenticated
WerkprocesAssessmentauthenticated
XapiStatementauthenticated

shillinq — 114 unmarked

schemaproposed intent
APInvoicerestricted
Accountrestricted
AllocationRuleauthenticated
AnalyticalDimensionauthenticated
AuditDocumentrestricted
BBVProgrammaauthenticated
BalanceSheetauthenticated
BankAccountrestricted
BankConnectionauthenticated
BankStatementauthenticated
BankStatementLineauthenticated
BankingRuleauthenticated
BbvAccountMappingrestricted
BbvTaakveldauthenticated
Begrotingswijzigingauthenticated
BeleidsIndicatorauthenticated
CashflowAPScheduleauthenticated
CashflowARProjectionauthenticated
CashflowBufferPolicyauthenticated
CashflowCalibrationReportauthenticated
CashflowForecastHorizonauthenticated
CashflowRecurringauthenticated
CashflowScenarioauthenticated
CashflowWeekauthenticated
ClosingAccountrestricted
ClosingEntryauthenticated
ClosingEntryTemplateauthenticated
ComplianceAuditTrailrestricted
ComplianceReportauthenticated
ConsolidatedReportauthenticated
ConsolidationGroupauthenticated
CurrencyBalanceauthenticated
DepreciationScheduleauthenticated
EconomischeCategorieauthenticated
ExpenseClaimEntryauthenticated
FiscalYearauthenticated
FixedAssetauthenticated
FxRateauthenticated
GLLineauthenticated
GLTransactionauthenticated
GRDeelnemerauthenticated
GRVerdeelsleutelauthenticated
Grantauthenticated
IPAssetValuationauthenticated
IbAangifteExportauthenticated
IcpStatementauthenticated
InnovatieboxElectionauthenticated
InnovatieboxTariffauthenticated
InventoryReorderRuleauthenticated
InventoryStockTransferauthenticated
Iv3Exportauthenticated
KorRegimeauthenticated
KorThresholdauthenticated
Kostenpostauthenticated
Locationauthenticated
ManagementLetterauthenticated
MatchingRuleauthenticated
MaterieleVasteActivaauthenticated
MeerjarenBudgetauthenticated
MileageEntryauthenticated
MileageRateauthenticated
Paragraafauthenticated
PerDiemauthenticated
PerDiemRateauthenticated
Programmaauthenticated
Projectauthenticated
ProjectAssignmentauthenticated
ProjectBudgetauthenticated
ProvincialeFondsPostingauthenticated
RateCardauthenticated
RateCardTemplateauthenticated
RateCardVersionauthenticated
RateRecordauthenticated
RateScheduleauthenticated
Receiptauthenticated
ReconciliationMatchauthenticated
RepaymentInstallmentrestricted
Reserveauthenticated
RetainedEarningsauthenticated
RetentionRuleauthenticated
SBRDocumentTypeauthenticated
SalesOrderauthenticated
SalesOrderLineauthenticated
ServiceCategoryOverrideauthenticated
SisaRegelingIndicatorauthenticated
SisaReportauthenticated
SoProjectauthenticated
SoUrenStaatauthenticated
Subsidieauthenticated
Taakveldauthenticated
TaxEstimateauthenticated
TaxRegimeConfigurationauthenticated
TaxSummaryReportauthenticated
TreasuryAccountrestricted
TrialBalanceauthenticated
UrenRegistratieauthenticated
VATAuditRecordrestricted
VATGLAccountsrestricted
VatCorrectionauthenticated
VatReturnauthenticated
VatTariffauthenticated
Voorzieningauthenticated
WBSOActivityCodeauthenticated
WBSOExportLogrestricted
WBSOTagauthenticated
WaterschapHeffingPostingauthenticated
WinstToerekeningauthenticated
WipBalanceauthenticated
XBRLMappingauthenticated
XBRLTaxonomyauthenticated
ZzpDeductionauthenticated
ZzpDeductionAmountsauthenticated
exampleauthenticated
kernGegevensConfigauthenticated

procest — 85 unmarked

schemaproposed intent
abonnementauthenticated
adviceRequestauthenticated
adviceResponseauthenticated
adviesAanvraagauthenticated
advisoryBodyauthenticated
advisoryReportauthenticated
aiAuditEntryrestricted
appealDecisionauthenticated
automaticActionauthenticated
bacAdviceRequestauthenticated
beroepauthenticated
bezwaarauthenticated
bezwaarDecisionauthenticated
bezwaaradviescommissieauthenticated
caseauthenticated
caseDocumentauthenticated
caseFederatedActivityauthenticated
caseFederatedShareauthenticated
caseObjectauthenticated
casePropertyauthenticated
caseShareauthenticated
caseTypeauthenticated
casetransferauthenticated
catalogusrestricted
checklistItemauthenticated
complaintauthenticated
complaintCategoryauthenticated
complaintDispositionauthenticated
consultationauthenticated
customerContactauthenticated
decisionauthenticated
decisionDocumentauthenticated
decisionTypeauthenticated
dispatchauthenticated
documentauthenticated
documentLinkauthenticated
documentTypeauthenticated
emailTemplateauthenticated
handhavingsactieauthenticated
hearingauthenticated
hearingSessionrestricted
inspectieChecklistauthenticated
inspectieRapportauthenticated
inspectionChecklistauthenticated
inspectionChecklistRunauthenticated
inspectionChecklistTemplateauthenticated
inspectionResultauthenticated
kanaalauthenticated
lhsMatrixauthenticated
lhsMatrixCellauthenticated
lhsRecommendationauthenticated
locationauthenticated
mapLayerauthenticated
objectionauthenticated
parafeeractieauthenticated
parafeerrouteauthenticated
paraferingAuditEntryrestricted
partnerOrganizationauthenticated
propertyDefinitionauthenticated
resultauthenticated
resultTypeauthenticated
roleauthenticated
roleTypeauthenticated
statusRecordauthenticated
statusTypeauthenticated
supplierauthenticated
supplierContractauthenticated
supplierInvoicerestricted
supplierKpiauthenticated
supplierMessagerestricted
supplierTenderauthenticated
supplierUserrestricted
taskauthenticated
tenantauthenticated
tenantBillingEventauthenticated
tenantConfigurationauthenticated
tenantMandateauthenticated
tenantOnboardingTaskauthenticated
tenantQuotaauthenticated
tenantUserrestricted
usageRightsauthenticated
voorstelauthenticated
wmsLayerauthenticated
workflowTemplateauthenticated
zaaktypeInformatieobjecttypeauthenticated

openconnector — 39 unmarked

schemaproposed intent
bankfeed_batchauthenticated
bankfeed_connectionauthenticated
call_logrestricted
cardfeed_accountrestricted
cardfeed_batchauthenticated
consumerauthenticated
dso_messagerestricted
dso_verzoekauthenticated
endpointauthenticated
eventauthenticated
event_messagerestricted
event_subscriptionauthenticated
fsc_callauthenticated
fsc_serviceauthenticated
iwmo_ijw_messagerestricted
jobauthenticated
job_logrestricted
kiss_klantcontactauthenticated
lti_deploymentauthenticated
lti_identity_linkauthenticated
lti_platformauthenticated
lti_toolauthenticated
mappingauthenticated
notificaties_abonnementauthenticated
openformulieren_form_mappingauthenticated
openformulieren_submissionrestricted
payment_intentrestricted
peppol_transmissionauthenticated
ris_sync_recordauthenticated
ruleauthenticated
sms_messagerestricted
sourceauthenticated
stuf_messagerestricted
sync_item_dead_letterauthenticated
synchronizationauthenticated
synchronization_contractauthenticated
synchronization_contract_logrestricted
synchronization_logrestricted
zgw_version_translation_logrestricted

decidesk — 34 unmarked

schemaproposed intent
ActionItemauthenticated
AgendaItemauthenticated
BoardProxyauthenticated
BudgetProposalauthenticated
CitizenPanelauthenticated
CitizenVoteauthenticated
ConflictOfInterestauthenticated
ContactDetailpublic? — confirm
Decisionauthenticated
DecisionStageauthenticated
Deliberationauthenticated
DigitalDocumentauthenticated
EngagementRecordauthenticated
EvaluationResponseauthenticated
EvaluationTemplateauthenticated
GovernanceBodyauthenticated
GovernanceReportauthenticated
Meetingauthenticated
Membershiprestricted
Minutesauthenticated
MonetaryAmountauthenticated
Notificationrestricted
NotificationPreferencerestricted
Offerauthenticated
Orderauthenticated
Participantauthenticated
Personrestricted
Postauthenticated
Productauthenticated
PublicationRecordpublic? — confirm
Reportauthenticated
Transcriptauthenticated
Voteauthenticated
VotingRoundauthenticated

hermiq — 28 unmarked

schemaproposed intent
AgentFlowauthenticated
AgentFlowRunauthenticated
AgentTemplateauthenticated
AgentWebhookauthenticated
AiFeatureauthenticated
Approvalauthenticated
Budgetauthenticated
Contextauthenticated
Controlauthenticated
ControlFrameworkauthenticated
Conversationauthenticated
CourseRecommendationauthenticated
EvalDatasetauthenticated
EvalRunauthenticated
Feedbackauthenticated
GuardrailPolicyauthenticated
Incidentauthenticated
Memoryauthenticated
Messagerestricted
ModelPolicyauthenticated
Scheduleauthenticated
Sessionrestricted
SessionTurnrestricted
Skillauthenticated
SkillDraftauthenticated
SkillSourceauthenticated
TenantControlauthenticated
UserProfilerestricted

pipelinq — 26 unmarked

schemaproposed intent
agentProfileauthenticated
brpLookupVerzoekauthenticated
brpPersoonauthenticated
bsnAuditRecordrestricted
bsnValidatierestricted
clientauthenticated
contactpublic? — confirm
leadProductauthenticated
optOutVlagauthenticated
paymentProviderrestricted
pipelineauthenticated
posRefundauthenticated
posRefundLineauthenticated
posRoleauthenticated
posStaffauthenticated
posTransactionauthenticated
posTransactionLineauthenticated
productauthenticated
productCategoryauthenticated
queueauthenticated
receiptPrintLogrestricted
receiptTemplateauthenticated
refundReasonauthenticated
relationshipauthenticated
skillauthenticated
taskauthenticated

docudesk — 20 unmarked

schemaproposed intent
anonymizationLinkauthenticated
baseauthenticated
batchCorrespondenceJobauthenticated
correspondenceauthenticated
customDictionaryauthenticated
customDictionaryTermauthenticated
dossierauthenticated
financialExtractionauthenticated
generatedDocumentauthenticated
glAccountBookingrestricted
glAccountMappingRulerestricted
huisstijlauthenticated
prohibitionOverrideAuditrestricted
publicationConsentrestricted
signerRecordauthenticated
signingAuditEntryrestricted
signingRequestauthenticated
signingSessionrestricted
templateauthenticated
templateVersionauthenticated

openregister — 16 unmarked

schemaproposed intent
activiteitauthenticated
brokeredcredentialrestricted
dataSubjectRequestauthenticated
dsarPolicyPackauthenticated
edepotTransferauthenticated
edepotTransferProofauthenticated
locatieauthenticated
mergeOperationauthenticated
notificationrestricted
omgevingsdocumentauthenticated
scheduleauthenticated
triggerauthenticated
trustConfigurationauthenticated
vergunningaanvraagauthenticated
webhookauthenticated
workflowauthenticated

larpingapp — 9 unmarked

schemaproposed intent
abilityauthenticated
characterauthenticated
conditionauthenticated
effectauthenticated
eventauthenticated
itemauthenticated
playerauthenticated
settingauthenticated
skillauthenticated

portaliq — 9 unmarked

schemaproposed intent
exampleDocumentauthenticated
portalAccountrestricted
portalAuditEntryrestricted
portalMessagerestricted
portalNotificationrestricted
portalOidcStaterestricted
portalPagepublic? — confirm
portalSessionrestricted
portalSubmissionrestricted

petstore — 3 unmarked

schemaproposed intent
categoryauthenticated
orderauthenticated
petauthenticated

doriath — 1 unmarked

schemaproposed intent
exampleauthenticated

launchpad — 1 unmarked

schemaproposed intent
Dashboardauthenticated

nextcloud-app-template — 1 unmarked

schemaproposed intent
exampleauthenticated