Skip to content

PROCESS GATE

Run a program from a plan

Some of the work you want an agent to do has no API: a release script, a build, a report generator, an on-premises tool. EKKA runs that program under governance, and the plan never gets to write a command line.

This capability is on the Developer Edition

ekka org edition prints the edition your organization is on and what it carries.

A plan names a recipe, never a program

There is no field in a plan step that holds a program, a flag, or a command. A step names a recipe and supplies values for the parameters that recipe declares, and that is the whole surface.

A recipe is registered by a person, on the machine where the program lives. It has four parts:

  • One program, by absolute path. It is never looked up on PATH, so which binary runs cannot change with the environment a process happens to start in.
  • A fixed argument list. Written once, in the recipe. A plan cannot add an argument, reorder one, or remove one.
  • Named parameters, each restricted. Every parameter declares what its values may be. A parameter with no restriction is refused at registration, because a parameter that accepts anything is an open argument under another name.
  • Limits on the run: a time limit, output caps, whether the network is reachable, and which environment variables survive.

EKKA starts the program directly. There is no shell in this path, which is why a value can never be re-read as a second command.

Write a recipe

A recipe is one JSON file, and it carries its own name, so the same file registers the same recipe on your laptop or from CI.

{
  "name": "disk-report",
  "program": "/bin/df",
  "argv": ["-h", "{mount}"],
  "params": {
    "mount": {
      "description": "Which filesystem to report on",
      "restrict": { "kind": "enum", "values": ["/", "/var"] }
    }
  },
  "limits": {
    "timeout_ms": 10000,
    "stdout_max_bytes": 65536,
    "stderr_max_bytes": 65536
  }
}

Check it before you register it. validate applies every rule that add applies, reads the program so the digest is the one a real registration would pin, and registers nothing:

ekka process recipe validate disk-report.json
✓ disk-report would be registered. Nothing was registered.

IT WOULD RUN

  • /bin/df
  • -h
  • {mount}

  The program is pinned by its contents: editing it stops every grant for this
  recipe until somebody registers and grants again.
  fingerprint  sha256:a2ff32d800dac54fa1fad015f22b0e0264132145b421a79c23e6c7c264d3e1aa

  register it  ekka process recipe add <file.json>

The fingerprint covers the whole recipe: the program, its content digest, every argument, every parameter restriction, and the limits. Change any of them and the fingerprint changes. Raising the time limit in that file from 10000 to 20000 is enough:

ekka process recipe validate disk-report.json | grep fingerprint
  fingerprint  sha256:4bf4894c0bd8f6084894a2b80c171d8613fc5f95c06adb6ce5ec052a9aaf0dc4

What a parameter may accept

Every parameter declares one restriction.

restrict.kind Accepts
enum One of a fixed list of values written in the recipe. The only kind whose values may begin with -, because a person wrote the list and can read all of it before granting.
integer Digits, inside the min and max the recipe sets.
segment One path segment: letters, digits, dot, dash, underscore. No separator, so a value cannot climb into another folder.
relative-path Letters, digits, dot, dash, underscore and /, with no .. segment and no leading /.
text Any characters except control characters, up to the max_len the recipe sets.
url An http:// or https:// URL, with no whitespace and no user:password in front of the host.

A value that fails its restriction is refused. It is never trimmed, quoted or escaped into something acceptable, and the refusal does not print the value, because a parameter can carry a credential:

ekka process test disk-report --param mount=/etc
ekka process test: parameter `mount` is not one of the values this recipe allows
  what this recipe accepts  ekka process recipe show disk-report

One parameter is one argument

A value containing spaces stays one argument. It cannot split into two, and it cannot become a flag:

ekka process test note --param "message=release 12 shipped; see notes"
NOTE WOULD RUN, EXACTLY THIS

  • /bin/echo
  • release 12 shipped; see notes
✓ Nothing was started.

That is a property of how the program is started, not a filter that has to recognize dangerous input. One argument goes to the program as one word, exactly as written.

Two programs a recipe cannot name

A shell, or a program whose own first argument is another program, hands back everything the recipe grammar withholds. Registration refuses both, on the program's name, so a copy of bash under another directory is refused too:

ekka process recipe validate shell-recipe.json
ekka process recipe validate: `program` is `bash`, which is a shell or takes a program as an argument. Its first argument would be a new program, which is what the argv template exists to prevent
  what a recipe looks like  ekka process recipe show <an existing recipe>

A program named without a full path is refused for the same family of reason:

ekka process recipe validate: `program` must be an absolute path, so which binary runs cannot depend on PATH

Register it, then grant it

ekka process recipe add disk-report.json
✓ Registered recipe disk-report

IT RUNS

  • /bin/df
  • -h
  • {mount}

  The program is pinned by its contents: editing it stops every grant for this
  recipe until somebody registers and grants again.
  fingerprint  sha256:a2ff32d800dac54fa1fad015f22b0e0264132145b421a79c23e6c7c264d3e1aa

  Nothing can invoke this yet. Granting is a separate decision, and the grant
  covers exactly what is printed above.

Registering a recipe permits nothing. Granting is a second command, and that gap is where a person reads what they are about to allow:

ekka gate list                       # find the process gate in your Enclave
ekka process recipe show disk-report # the whole of what a grant would cover
ekka process test disk-report --param mount=/
DISK-REPORT WOULD RUN, EXACTLY THIS

  • /bin/df
  • -h
  • /
✓ Nothing was started.

ekka process test resolves the recipe with real values and prints the program and every argument, one per line, so the boundary between arguments is visible. It starts nothing.

Then grant it:

ekka gate grant add --type process --instance <the process gate> \
    --resource recipes/disk-report --capability process.invoke \
    --no-fingerprint

--instance is the process gate as ekka gate list prints it, for example process/enclave3a7e592fProcess. --no-fingerprint states that the resource is a recipe name on that machine rather than an alias held in a gate directory.

Call it from a plan

The step names the recipe and the declared parameters, and nothing else:

{
  "id": "runIt",
  "action_ref": "ekka.gate.process.v1",
  "executor_type": "process_gate",
  "op": "invoke",
  "feature": "process.invoke",
  "resource": "recipes/disk-report",
  "target": "enclave3a7e592fProcess",
  "identity": { "requires_user_context": false },
  "inputs": { "params": { "mount": "/" } },
  "step_input_contract": {
    "type": "object",
    "properties": { "params": { "type": "object" } },
    "required": ["params"]
  },
  "step_output_contract": {
    "type": "object",
    "properties": { "exit_code": { "type": "number" } },
    "required": ["exit_code"]
  }
}

target is the process gate inside your Enclave, as ekka gate list prints it. resource is the recipe, always as recipes/<name>. Every parameter value is a string. Check the whole file the way plan create would, creating nothing:

ekka plan validate disk-report.plan.json
✓ Valid: [email protected]. Nothing was created.
  It would run as agent core-c31f485e.

See Multi-step plans and schedules for the rest of the plan file, and CLI reference for every ekka process command.

What the receipt records

A run of a recipe produces a signed receipt like every other governed operation. It records:

  • the recipe name and its fingerprint,
  • the outcome and the exit code,
  • how long the program ran,
  • how many bytes it wrote to output and error, and a sha256 digest of each,
  • whether output was cut at the recipe's cap,
  • the names of the parameters that were supplied,
  • whether the program was pinned by its contents for that run.

It does not record the program path, the arguments as they were resolved, the environment, the text the program wrote, or any parameter value. The recipe name plus its fingerprint is better evidence than a copied command line: a fingerprint can be checked against the registered recipe, while a copied argument list is only a claim about it.

ekka receipts show <receipt-id> -v    # the recorded facts, in full
ekka receipts verify                  # the chain those facts sit in

Limits

  • A recipe bounds how a program starts. Once running, it has the access to the machine that the account running your Enclave has.
  • Programs run in the data folder your Enclave was started with, stated by the Enclave. A plan does not choose the working directory.
  • The environment is empty unless the recipe lists variable names to pass through, and the network is closed unless the recipe opens it.
  • Removing a recipe stops every grant naming it. Registering that name again with different contents produces a different fingerprint.
  • Output above the recipe's cap is discarded, and the receipt records that the output was cut.

A recipe whose program must be confined further needs confinement from the operating system around it, the same way an API alias whose query surface must be bounded needs an endpoint built for the purpose.