ops

package
v0.2.30 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 16, 2026 License: MPL-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package ops hosts chassis-core op handlers that consume the per-tenant secret store. These are computed-secret ops: they read cleartext from op.Secrets (via context), compute a digest or signature in-process, and write the **non-secret derived value** back into the response envelope. The cleartext is consumed once inside the op and goes nowhere else.

See internal docs/todo-secret-store.md §4.2 for the substitution-vs- computation split.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BasicAuthEncode

func BasicAuthEncode(ctx context.Context, opName string, in, _ []byte) (event.Payload, error)

BasicAuthEncode is the handler for `txco://basic-auth-encode`. It produces the `base64(user:password)` value used by HTTP Basic auth (RFC 7617), with the password coming from op.Secrets.

WITH parameters (op.Meta):

secrets.password.secret = "TWILIO_AUTH_TOKEN"  // required
user                    = "AC1234567890"        // required
output_path             = "_txc.computed.basic_auth"  // default

The handler writes the encoded value (NOT the cleartext) at output_path. Downstream ops set the Authorization header as `Basic <encoded>` — typically via a `secrets.headers.authorization` declaration with `format = "Basic {output_path-value}"`, but since the encoded value is not itself a secret, simpler: just a normal SET that interpolates the envelope value into the header.

Cleartext is consumed inside this function and never leaves it. The base64 encoding is one-way only in the sense that base64- decoding reveals the cleartext — DO NOT trace this output as "non-secret"; it IS a wire credential. The op writes it at _txc.computed.* by default, where the operator chooses how to route it.

func BasicAuthVerify added in v0.2.25

func BasicAuthVerify(ctx context.Context, opName string, in, _ []byte) (event.Payload, error)

BasicAuthVerify is the handler for `txco://basic-auth-verify`. It checks an inbound `Authorization: Basic <base64(user:password)>` header against a literal user and a password held in the per-op secret bag, and writes a verdict — never the credential — so a later rule can branch.

It is the inbound counterpart to txco://basic-auth-encode and the same shape as txco://hmac-verify: a txcl `==` cannot see the secret bag and would short-circuit on the first differing byte, and encode's output (the base64 token) is itself the wire credential, which must not be written to the envelope for a comparison and then linger in every step trace. Here the presented user and password are compared in constant time, both halves always evaluated, and the decoded bytes are wiped.

Gate it with a WHEN on the route; it runs only when its rule fires. A rule after it acts on the verdict:

WHEN @computed.basic_auth_ok != true
  EMIT @web.res.status = 401,
       @web.res.headers.www-authenticate.0 = "Basic realm=provision",
       @halt = true

WITH parameters (op.Meta):

user                     = "provision"                          // required: the expected user-id
secrets.password.secret  = "PROVISION_PASSWORD"                 // required: NAME of the password secret
secrets.password.optional = true                                // the secret MAY be unset (see below)
allow_unconfigured       = true                                 // unset secret ⇒ ok=true (default false)
header_path              = "_txc.web.req.headers.Authorization.0" // gjson path to the header value
output_path              = "_txc.computed.basic_auth_ok"        // sjson path for the verdict (bool)
configured_path          = "_txc.computed.basic_auth_configured" // sjson path: was a password present (bool)

A missing, empty, or malformed header, another scheme, or a wrong user or password is NOT an op error — it is ok=false (fail closed). Config problems (no user, no secret ref, no bag) are errors.

"Unconfigured" is reachable only with `secrets.password.optional = true`: the processor otherwise refuses to run an op whose secret it cannot materialize. Then the rule decides what absence means through `allow_unconfigured` — true is the loopback-demo shape ("open until a password is set"); the default false keeps a route closed when the secret is missing OR misnamed. `basic_auth_configured` says which case produced the verdict, so a log or a trace can tell "let in" from "checked".

func Copy

func Copy(ctx context.Context, _ string, in, _ []byte) (event.Payload, error)

Copy is the handler for `txco://copy`. It reads a value from one envelope path and writes it to another, with optional encoding.

This is the chassis-side answer to "txcl SET RHS is literal-only" (the constraint that forces `txco://route` to exist in Go). When a rule needs to move an envelope field — e.g. "the response body for this web request lives at .text, please put it at _txc.web.res.body" — Copy is the primitive.

WITH parameters (op.Meta):

from    = ".text"                 (required: source path on input envelope)
to      = "_txc.web.res.body"     (required: destination path on response)
encode  = "base64"                (optional: "base64" | "" — default "")
fallback = "value"                (optional: literal substituted when
                                  the source path is empty/missing)

Path syntax follows gjson on read (a leading "." is optional and stripped) and sjson on write. When the source path is absent or resolves to an empty value AND `fallback` is set, the literal `fallback` value is used as the source instead — letting one rule express "use query param if present, else fall back to this." (Named `fallback`, not `default`: `default` is a reserved txcl keyword, so it can't be a WITH param name.) Without `fallback`, an empty source produces an empty destination (no failure). A missing `from` or `to` parameter at the WITH level IS an authoring error and fails loud.

func HMACSign

func HMACSign(ctx context.Context, opName string, in, _ []byte) (event.Payload, error)

HMACSign is the handler for `txco://hmac-sign`. It HMACs an envelope-path value with a named secret key and writes the digest at a declared output path.

WITH parameters (op.Meta):

secrets.key.secret  = "WEBHOOK_HMAC"   // required: NAME in op.Secrets
algorithm           = "sha256"         // sha256 (default) | sha512
input_path          = "body"           // gjson path on op.Input
output_path         = "_txc.computed.sig"  // sjson path on the response
encoding            = "hex"            // hex (default) | base64

The handler reads `op.Secrets["WEBHOOK_HMAC"]` (the splice puts it there during processor.Run), computes `HMAC-<alg>(secret, <input>)` — where <input> is the value at input_path, a JSON string taken as its literal (unquoted) value and an object/array as its raw bytes (see hmacInputBytes; matches hmac-verify so signatures round-trip) — and writes the digest into the response at output_path. The response is JSON; output_path defaults to `_txc.computed.hmac` if absent.

Cleartext is consumed inside this function and never leaves it — the digest is the safe-to-publish artifact (one-way function of input+key).

func HMACVerify

func HMACVerify(ctx context.Context, opName string, in, _ []byte) (event.Payload, error)

HMACVerify is the handler for `txco://hmac-verify`. It recomputes an HMAC over an envelope-path value with a named secret key and constant-time-compares it to an expected signature carried on the envelope, writing a boolean result at output_path.

It is the verification counterpart to txco://hmac-sign, and it lives as an op (not a txcl function) for two reasons functions can't satisfy: the keyed HMAC needs the per-op secret bag (txcl functions never see it), and the comparison must be constant-time (a txcl `==` short-circuits and leaks timing). This is the primitive behind signed-webhook verification — Stripe / GitHub / Shopify / Slack all use minor variants of HMAC-over-body.

Gate it with a WHEN like any op; it only runs when its rule fires. It adds `sig_valid` to the response so a later rule can branch, e.g.:

WHEN ._txc.computed.sig_valid != true
  EXEC "txco://..."   # reject / 401

WITH parameters (op.Meta):

secrets.key.secret = "STRIPE_WEBHOOK_SECRET"   // required: NAME in op.Secrets
algorithm          = "sha256"                  // sha256 (default) | sha512
input_path         = "verify.signed"           // gjson path to the signed data
expected_path      = "verify.v1"               // required: gjson path to the expected sig
encoding           = "hex"                     // hex (default) | base64 (of the expected sig)
output_path        = "_txc.computed.sig_valid" // sjson path for the bool result

String inputs are hashed as their literal (unquoted) value, so a scheme that signs a constructed string like Stripe's "t.body" works; object/array inputs use the raw JSON bytes (preserving whitespace and key order, which is what vendors sign).

Config problems (missing secret ref or expected_path, unmaterialized secret, bad algorithm/encoding) return an error. A missing, empty, or malformed expected signature is NOT an error — it's a failed verification, so the op emits sig_valid=false (fail closed).

The op emits ONLY the boolean — never the recomputed digest, which is itself a valid signature and must not leak into the envelope/trace.

func WebRender

func WebRender(ctx context.Context, _ string, in, _ []byte) (event.Payload, error)

WebRender is the handler for `txco://web-render`. It reads a value from a source envelope path, optionally transforms it (raw / HTML wrap / markdown-to-HTML), base64-encodes it, and writes the result into the web-response shape `_txc.web.res.*` plus `_txc.halt = true`. Designed for the "scope 200 renders the answer that scope 100 produced" pattern.

WITH parameters (op.Meta):

source        = ".text"                          (optional, default ".text")
content_type  = "text/plain; charset=utf-8"      (optional, default
                                                  matches the wrap mode)
status        = 200                              (optional, default 200)
wrap          = "raw"                            (optional: "raw" |
                                                  "html" | "markdown-to-html")

`wrap` modes:

  • "raw" — verbatim bytes. Default content-type "text/plain; charset=utf-8".
  • "html" — wrap the (HTML-escaped) source in a minimal `<pre>` document. Default content-type "text/html; charset=utf-8".
  • "markdown-to-html" — render the source as CommonMark via goldmark. Default content-type "text/html; charset=utf-8".

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL