v0.328.0: the catalog allow-list — the box refuses container rights a template asks for and the box does not give (R-971, D2 = A)
gates / gates (push) Successful in 45s
gates / gates (push) Successful in 45s
internal/privpolicy checks the RENDERED compose (privileged, cap_add, devices, host namespaces, security_opt,
sysctls, volumes_from, build/include/extends, env_file/config files outside the stack dir, aliasing named volumes,
binds outside the box's data roots, the Docker socket, propagation, ${VAR} in a privileged key) against an
allow-list compiled into the controller (one entry: homepage's read-only socket). Asked before every compose
write: the 15-minute sync, the guarded update/ladder step, the install, both settings changes, both restores
(before the stop). A refusal writes nothing, keeps the app on its last good compose, raises the operator-only
event app_definition_refused once per definition and puts one line on the app page. The sync never copies a
template named like a controller infra stack. Structural guard TestAllowList_IsWiredIntoEveryComposeWriter.
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1468,6 +1468,25 @@ func (n *Notifier) NotifyAppHoldNoWholeCopy(d AppHoldNoWholeCopyDetails) {
|
||||
n.pushEventBoth("app_hold_no_whole_copy", "critical", msg, "", d)
|
||||
}
|
||||
|
||||
// AppDefinitionRefusedDetails is the R-971 event's structured part.
|
||||
type AppDefinitionRefusedDetails struct {
|
||||
App string `json:"app"`
|
||||
Refused string `json:"refused"`
|
||||
}
|
||||
|
||||
// NotifyAppDefinitionRefused (v0.328.0, R-971; operator decision D2 = A): the box's catalog allow-list refused an
|
||||
// app's definition — a catalog correction or update, a restore, a settings change. Nothing was written; the app keeps
|
||||
// its last good compose. OPERATOR-ONLY on the hub (allowedEventTypes AND operatorOnlyEvents, hub >= v0.157.0): the
|
||||
// household can do nothing about it — someone pushed a catalog definition the box does not trust. One event per
|
||||
// distinct refused definition (stacks.Manager.noteAdmission), not one every 15 minutes.
|
||||
// HUB DEPENDENCY: a hub without the type answers 400 and the event is lost; the box still refuses and the app page
|
||||
// still says so.
|
||||
func (n *Notifier) NotifyAppDefinitionRefused(app, detail string) {
|
||||
msg := fmt.Sprintf("%s: the box REFUSED a catalog definition — it asks for container rights not on the box's allow-list. Nothing was written; the app keeps its last good compose. Check who pushed to the catalog. %s",
|
||||
app, detail)
|
||||
n.pushEventBoth("app_definition_refused", "warning", msg, "", AppDefinitionRefusedDetails{App: app, Refused: detail})
|
||||
}
|
||||
|
||||
// healthSeverity is the event severity a health status would be sent at (R-647): the disabled path
|
||||
// names what it drops, and "warn" is a health STATUS, not a severity the hub knows.
|
||||
func healthSeverity(status string) string {
|
||||
|
||||
Reference in New Issue
Block a user