Skip to content

Template trust model

Running a cxg code template is running a program. This page states exactly what that program can reach, backs it with a probe run against the shipped binary, and gives you the mitigations that actually apply. It is not a disclaimer to skim. It is the property you threat-model against before running a template you did not write.

There is no security boundary between cxg and the code template it runs. cxg resolves the template’s language, launches the interpreter or compiled binary as a child process, and reads its standard output. That is the whole relationship. See how cxg executes templates for where this sits in the engine.

Everything to the right of the cxg process inherits your identity:

flowchart LR
    U["your shell<br/>uid 1000"] --> C["cxg process"]
    C --> E["language engine"]
    E --> T["template<br/>child process"]

    T --> FS[("your filesystem<br/>~/.ssh, ~/.aws")]
    T --> NET(["any network host"])
    T --> P["further processes"]

    class T emphasis

The dashed boundary a reader might expect around the template, the box that stops it reaching your SSH keys or the internet, does not exist.

This is not inference from the source. A template run under a stock scan on cxg 1.3.0 reported the following about its own process:

{
"uid": 502,
"user": "animeshsrivastava",
"home_listing_count": 86,
"ssh_dir_readable": true,
"spawned_process": "child process ran",
"outbound_dns": true
}

It ran as the invoking user, listed that user’s home directory, read ~/.ssh, launched a child process, and resolved an external hostname. A template can do anything you can do: read your credentials, write to your disk, connect anywhere, spawn anything.

The sandbox: config never changed this, and 1.3.0 removed it

Section titled “The sandbox: config never changed this, and 1.3.0 removed it”

Through 1.2.0, cxg config generate wrote a sandbox: block holding enabled, memory_limit_mb, cpu_limit_percent, network_access, and filesystem_access. It looked like a confinement policy and enforced nothing. Re-running the probe above with a config that set enabled: true and filesystem_access: readonly produced the identical result: uid 502, ~/.ssh readable, child spawned, DNS resolved.

1.3.0 deleted the block. A config it generates has no sandbox: section, and a config that still carries one from an earlier version draws a warning rather than being silently accepted:

warning: ./legacy-sandbox.yaml contains a `sandbox:` section.
These settings never took effect. `enabled`, `memory_limit_mb`,
`cpu_limit_percent`, `network_access` and `filesystem_access` were
parsed and then ignored — no code path has ever read them to confine
anything. A config setting them was not protected by them.

If you inherited a config with those keys, delete them, and do not build a threat model on them in any version.

Because the boundary is the process, isolation is your responsibility at a layer cxg does not control. Assume any template you run has your full access, and place the boundary yourself:

  • Run scans somewhere disposable. A container or VM that holds no credentials worth stealing. The blast radius of a hostile template is whatever that environment can reach.
  • Use an unprivileged scanning identity. A dedicated user with no SSH keys and no cloud credentials in its environment. The probe above read ~/.ssh because it was there to read.
  • Review third-party templates before running them. A template is a script you are about to execute as yourself. Read it the way you would read any such script.
  • Prefer YAML for anything you have not read. A YAML template cannot execute arbitrary code. Its runtime is cxg’s own parser, and it can only issue requests and match responses. That is a genuinely smaller trust surface, by construction.

A code template is trusted code running as you. There is no layer inside cxg that reduces that, so the layer has to be outside cxg. Decide where your isolation boundary is before you run someone else’s template, not after.