KUJO THE COURSE

THE KUJO COURSE · VERIFIED 1.3.1

Processes without unnecessary shells

Use argv, bounded output, and an explicit result receipt.

STAGE 4 / Native automation · LESSON 23

What can this code touch?

Process execution can invoke another program with the current user's privileges. A Kujo capability gate controls access to that native surface; it does not automatically restrict everything the child executable can do.

Native contract

spawn_process accepts an argv array and returns a ProcessResult struct. Inspect exitcode, stdout, stderr, success, timed_out, cancelled, and output truncation flags. Options bound timeout and captured output, and can control inherited environment and working directory.

execute_status uses a shell string and requires separate shell-exec authority. Prefer argv when you do not need shell syntax. This keeps spaces and punctuation inside an argument from becoming shell operators. It does not prevent a dangerous argument from being interpreted by the chosen executable itself.

The example invokes /usr/bin/printf with a fixed format and string on macOS/Linux. This is a platform-specific native integration drill. In your own program, select an operator-approved executable rather than accepting an arbitrary model-proposed program name.

Machine receipt

ProcessResult is a struct, not directly a JSON dictionary. Copy the fields your caller needs. Refuse to treat truncated output as a complete artifact. A timeout or cancellation forces success false, but your application still needs a cleanup and retry policy.

Professional pattern

Use a fixed executable, validated arguments, a small environment, finite timeout, and bounded output. Redact sensitive values at the process capture boundary. A child process with broad credentials can leak them regardless of whether the parent prints a Secret wrapper safely.

Common mistakes

Do not use shell quoting as your only command policy. Do not infer success from stdout text while ignoring exit status. Do not grant shell-exec just to avoid assembling an argv array. The stage build uses a controlled process and serializes only selected fields.

Working example

let result := spawn_process(["/usr/bin/printf", "%s", "course-process"], {"timeout_ms": 2000, "max_output_bytes": 1024, "inherit_env": false})
assert_equal(result.success, true)
assert_equal(result.stdout, "course-process")
print(to_json({"ok": result.success, "exitcode": result.exitcode, "stdout": result.stdout}))

Run it

From the course repository root, use the pinned Kujo 1.3.1 runtime.

kujo check examples/23.kujo
kujo run --untrusted --allow-process-exec examples/23.kujo

Captured output

{"exitcode":0,"ok":true,"stdout":"course-process"}

Break it and diagnose it

The argv array is empty. The runtime must reject the invalid arguments before starting a child process.

spawn_process([], {"timeout_ms": 2000})

kujo run --untrusted --allow-process-exec examples/23-break.kujo

Exit status: 4. Captured diagnostic:

[KUJOVM001] [vm] Runtime Error: spawn_process requires a non-empty array of command arguments
  --> 0:0


Exercise

Replace printf with an explicitly configured equivalent on your operating system. Test a nonzero exit and a bounded timeout. Produce a dictionary receipt that rejects truncation and never includes inherited secrets.

Checkpoint

  • I use argv for direct process calls.
  • I inspect status and truncation.
  • I understand child-process authority.

Verified 2026-09-06 · Official Kujo 1.3.1 release · Source contract · Download example