Skip to content

core: let a check read a value from a path with ref #63

Description

@jbpros

Every value in a check is written into the policy, and every path starts inside the subject. So a policy can't read params, and it can't look something up in another part of the input. Policies work around both in Rego before calling ergo. SDLC-CTRL-0004 copies its licence allowlist out of data.params, and it joins each SBOM component to its lockfile pin itself.

Design

$$input is the top of the document given to ergo.report. A path that starts with it reads from there and not from the subject:

path: ["$$input", lock, packages]

$$ marks names that ergo provides. Names a policy binds itself (#52's each and as, like $pr) use one $. Keeping them apart means ergo can add built-ins later without breaking a policy that already uses that name. A policy name can't start with $.

{ref: path} reads a value where a literal would go: value, values, patterns, min, max and the values in a selector's where:

values: [MIT, Apache-2.0]                              # a literal, as today
values: {ref: ["$$input", params, allowed_licenses]}   # read from the input

{literal: x} forces a literal, for a value that is itself an object with a ref or literal key. Nothing inside it is interpreted, so {literal: {literal: {ref: [main]}}} is the object {literal: {ref: [main]}}. In a path, {literal: "$$input"} is the key $$input.

Params need nothing more. The caller puts them in the document, for example {trail: ..., params: ...}, and the policy reads ["$$input", params, ...]. ergo has no defaults. A param that has a default is written into the policy or the params file.

Joins come with #52's names. The lock package with the same name as the component being checked is:

from: [sbom, components, {each: component}]
...
path: ["$$input", lock, packages, {where: {name: {ref: ["$component", name]}}}]

The report

The expression shows where a value comes from, and the row's inputs show what it was:

expression: some licences: licences[] in $$input.params.allowed_licenses
inputs:     licences[] = ["GPL-3.0"], $$input.params.allowed_licenses = ["0BSD", "Apache-2.0", ...]

Params can change between runs, so the row has to record what was actually compared.

Failing closed

  • A reference that can't be read fails the check with the usual cause (absent, null, unmatched or not_an_object). A missing param fails every row that uses it.
  • A present filter whose selector matches nothing rules the subject out, like a missing field since core: let a present filter rule out a subject whose field is missing #62. For a join, "no lock entry for this package" is an answer, not an unreadable filter. A selector that matches several items still fails.

Order

  1. $$input, ref and literal. That's enough for params, and it doesn't need core: let from iterate at any step and name what it iterates over #52.
  2. After core: let from iterate at any step and name what it iterates over #52: refs from policy names ($component), and the present change above, for joins.

What's left of SDLC-CTRL-0004's Rego after both is the text parsing and the version comparison between Snyk and uv, which belong in how the evidence is attested and not in ergo.

Activity

  1. added
    enhancementNew feature or request
    coreRelates to the ergo library or format themselves.
    impacts-ergo-policiesImpacts ergo policy schema. Careful design attention needed.
    impacts-ergo-reportsImpacts ergo report schema. Careful design attention needed.
    on Oct 1, 2026
  2. jbpros commented on Oct 5, 2026

    @jbpros
    ContributorAuthor

    From #113, DEV-0401: a vulnerability must be fixed within a number of days set by its severity. The days come from the params, keyed by a field of the subject:

    days: { ref: [$$params, sla_days, { ref: [severity] }] }

    Two things stop this today: a ref can only start with $$input or $$params, and a ref inside a ref's own path isn't followed. Step 2 above covers the first, for refs that start from a name like $component. This needs a ref step inside a ref as well, read from the subject.

    The operator that takes days is #140.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreRelates to the ergo library or format themselves.enhancementNew feature or requestimpacts-ergo-policiesImpacts ergo policy schema. Careful design attention needed.impacts-ergo-reportsImpacts ergo report schema. Careful design attention needed.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions