Back to trackLesson 5 of 5

Security 2: Org Policy vs. Instance Preferences

The problem

Security 1 covered allowlists an operator sets on one instance’s own ~/.spicegrinder/preferences.properties. That works fine for a single machine, but it doesn’t scale as a security boundary across an organization with many installs. Anyone with a text editor and their own preferences file could open up any of those same allowlists, including pointing FlatFile at an absolute path to a network drive or adding a new database or service target nobody signed off on. Organization policy exists to take specific knobs out of an individual instance’s hands, without requiring every machine to be locked down individually by hand.

Locking a key

An org bundle (SPICEGRINDER_ORG_HOME pointing at a directory with manifest.properties/policy.properties/preferences.properties/libraries/) can name specific preference keys that a local preferences file is no longer allowed to set at all:

# policy.properties
preferences.locked_keys=scan.packages

When an instance loads its local preferences, every key in the local file is checked against the org policy before being applied — a locked key is dropped and logged ([OrgPolicy] DENY [preferences.locked_keys] ...), not silently ignored. The org’s own baseline value for that key (or the hard default, if the org didn’t set one either) is what’s actually in effect; the local file’s attempt never takes hold. The shipped example bundle only locks scan.packages, but the mechanism is generic — an org that wanted to freeze allowlists (which we discussed in the Security 1 lesson) at whatever it decides would add flatFile.path.allowlist, database.url.allowlist, database.allowQuery, and service.url.allowlist to that same list, and no instance could loosen any of them locally afterward.

The enforcement dial

Locking is absolute, but most policy checks go through a three-way dial instead:

enforcement.mode=warn

enforce denies the action outright. warn and audit_only both let it through but log it — the difference between “blocked” and “watched” is one property, and warn is the shipped default for a pilot rollout: visibility first, without breaking anyone’s workflow while the policy itself is still being tuned.

Two more controls

Preferences aren’t the only thing org policy governs. Custom-component package scanning defaults to the opposite posture from preferences — components.local_override=deny by default, not allow — and only packages matching an approved prefix get in:

components.local_override=deny
components.allowed_package_prefixes=com.example.spicegrinder.

A component in com.example.spicegrinder.filters scans fine; one in com.someone.random doesn’t, regardless of what the local instance would otherwise permit — same shape as Security 1’s classpath story, but the gate here is the package name itself, decided by the org rather than by whether the class happens to be present.

Import gets its own resolution order, and it’s not “local file wins”: the organization’s own libraries/ directory is searched first, ahead of the importing model’s own directory, then an optional project root, then user library directories — and only if libraries.local_override permits user directories being searched at all. An absolute Import path outside the org/project/model roots is denied unless libraries.allow_absolute_external_paths=true is set explicitly — the same “empty/false means disabled” posture Security 1’s allowlists use, applied to which files a model is allowed to pull in rather than which files a generator can read.

An escape hatch

SPICEGRINDER_DEV_MODE=true (with components.dev_mode.allowed=true in policy) lets a developer scan extra packages beyond the org allowlist for local sandbox work — audited, not silent, and it still doesn’t touch the things dev mode was never meant to open: com.obsvra.spicegrinder.* stays off-limits to custom code, and shadowing an org-registered short name with a same-named local component stays denied by default even in dev mode. An escape hatch for one specific friction point isn’t a bypass of every other one.

Free tier doesn’t see any of this

Org policy is a Pro/Enterprise capability. It’s not that we want SpiceGrinder Free to be less secure than SpiceGrinder Pro, it’s that pretty much all of the things an organization can control through these policy files apply only to Pro-level features: files, database, services, and custom classes.

Point a Free build at an org home and it just logs that the bundle exists and is being ignored, then behaves exactly like a solo install with no org at all. Detected, not silently pretended-away, but not enforced either.

Instance vs. org, in one line

Security 1 answered “what does this one process trust?” Org policy answers “what is any instance in this fleet even allowed to decide for itself?” — and it can lock a setting shut, allow it with an audit trail, or just watch and log, independently, per key, without touching the other two.