Skip to content

allow_private_destination should not be a dataflow-writable port #830

Description

@sroussey

Follow-up from the notify-task private-destination work (PR for claude/notify-private-widening-fail-closed).

The residual

allow_private_destination is declared on the input schema of WebhookNotifyTask, SlackNotifyTask and DiscordNotifyTask, so it is an ordinary data port: it can be written by a dataflow edge or by graph run-input, exactly like url.

That means it travels the same trust channel as the destination it authorizes. A node upstream of the notify task can set both, and the operator who configured the graph authorized neither.

The entitlement declaration cannot compensate, because it is computed from runInputData before run-input lands — that is precisely why assertPrivateDestinationGranted exists as an execute-time re-check.

The scoped fail-closed gate that just shipped narrows this: a destination whose URL does not itself read as private now needs a graded network:private grant, so the dataflow-supplied case is refused where there is no enforcer. What remains uncovered is the URL that already reads private — with no enforcer registered, the flag on it is authorized by configuration alone, and if the URL arrived by dataflow there was no configuration to read.

The shape it should have

Config-only, mirroring credential_scheme: settable through the constructor's defaults (and/or x-ui-hidden), not writable by an edge or by run-input. A declaration about what the task is permitted to do belongs to whoever assembled the graph, not to whoever feeds it data.

Why it was not done in that PR

It is a breaking schema change with three separate blast radii:

  • saved graph JSON — instances that persist the port in their inputs;
  • the builder UI's port rendering — the port disappears from the node's input list;
  • Task.setInput merge semantics — what happens when a run-input carries a key the schema no longer accepts.

Each needs its own decision, and none of them belongs in a security fix that is otherwise a one-line gate.

Acceptance

  • The flag cannot be set by a dataflow edge or by graph run-input on any of the three notify tasks.
  • Existing saved graphs that carry it in their inputs have a stated migration (accept-and-warn, or reject with a message naming the config form).
  • The graph run-input cannot smuggle allow_private_destination past a denied network:private test keeps a meaningful shape, or is replaced by one that pins the new refusal.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HJRf3YFa8DjmjsZvXz8xDT

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions