Skip to content

finding: record-view auditing has no configuration path on the os serve boot path — the CLI registers AuditPlugin with no options #9863

Description

@os-steve

Observation-class finding, measured while writing the record-view auditing docs page (#9540 / PR #9860). Not a defect claim about correctness — the capability works exactly as designed when it is constructed with options. The gap is reachability from the shipped CLI boot path.

Measurement

Record-view auditing is enabled only by AuditPlugin's constructor:

newAuditPlugin({readAudit: {objects: ['contact','account']}});

packages/cli/src/commands/serve.ts:2438-2440 auto-registers the plugin like this:

const{ AuditPlugin }=awaitimport(auditPkg);awaitkernel.use(newAuditPlugin());

No options, and no config-derived helper. Compare the sibling six lines above, where SecurityPlugin gets one:

awaitkernel.use(newSecurityPlugin(appSecurityPluginOptions(config)));

appSecurityPluginOptions exists precisely so the CLI boot and @objectstack/verify's bootStack cannot disagree (#7001). There is no appAuditPluginOptions, and grep -rn "readAudit" packages/ examples/ apps/ finds zero call sites outside plugin-audit itself — nothing in the repo ever supplies an audited object list.

⇒ A deployment served with os serve has record-view auditing off and no declared way to turn it on. The capability is reachable only from code that composes the kernel itself.

The half-path, and why it should not just be documented

An app can put new AuditPlugin({ readAudit: { … } }) in its objectstack.config.tsplugins array. That array is consumed at serve.ts:2455, i.e. after the auto-registration, and ObjectKernel.use() (packages/core/src/kernel.ts:194) stores plugins in a name-keyed map:

this.plugins.set(pluginMeta.name,pluginMeta);

Both instances share the name com.objectstack.audit, so the config-declared one overwrites the auto-registered one and the opt-in does take effect. But that is an undeclared last-wins overwrite, not a contract: nothing documents it, no test pins it, and LiteKernel.use()throws on the same input (filed separately). Documenting it would be teaching an accident, so PR #9860's page deliberately does not.

Dispositions

Not ranked — this needs a decision, not a guess.

  • Add appAuditPluginOptions(config) mirroring the security helper, reading the audited object list off the stack config. Most consistent with the existing pattern, and gives os serve and bootStack one shape.
  • Leave it composition-only and say so in the plugin README and the docs page. Defensible if the intended consumer really is @objectstack/security-enterprise composing on top — read-audit.ts's header says the policy "belongs to the caller, which in the enterprise packaging is @objectstack/security-enterprise". But the open edition then has a shipped compliance capability its own CLI cannot reach.
  • Make the auto-registration skip when the config declares its own audit plugin, using the hasPluginMatching helper already at serve.ts:2739 for exactly this purpose on other plugins. Turns the accidental overwrite into a declared one.

Refs: #9540 / PR #9860 (the docs card that measured it) · #8992 / PR #9515 (the capability) · #7001 (appSecurityPluginOptions).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions