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:

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

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

const { AuditPlugin } = await import(auditPkg);
await kernel.use(new AuditPlugin());

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

await kernel.use(new SecurityPlugin(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.ts plugins 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).

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions