Skip to content

Let your own tools decide when a grant works

A Grant says what an agent may do. A condition on that Grant says when.

EKKA checks every condition at the moment it signs the token for a decision. So access tightens the instant your security tools report trouble, and it comes back by itself when they report recovery. There is no lockdown to lift and no plan to run.

ekka gate grant add \
  --type postgres --instance prod-db \
  --resource customers --capability knowledge.postgres.read \
  --agent analyst --ttl 3600 \
  --when 'org.risk level eq normal max_age 10m'

That Grant works while something has told EKKA, in the last ten minutes, that your risk level is normal. The moment your SIEM says elevated, the next decision is refused. When it says normal again, the next decision is allowed.

A condition is one shape

Six fields, and every condition uses the same six. A time window is not a different kind of rule; it is this rule pointed at the clock.

Field What it means
source Who says it: the name your tool posts under, or the built-in time
subject What it is about: the organization by default, or @<id> for one Enclave or host
field Which field of that statement, for example level
op eq, ne, in, lt, lte, gt, gte
value What the statement is compared with
max_age How fresh the statement has to be

Written on the command line, the six read in that order:

--when 'org.risk level eq normal max_age 10m'
--when 'host.posture@3a7e encrypted eq true max_age 1h'
--when 'time hour_utc in 9,10,11,12,13,14,15,16,17'

--when repeats, and all of them must hold:

ekka gate grant add \
  --type s3 --instance exports \
  --resource quarterly --capability knowledge.s3.write \
  --agent reporter --ttl 3600 \
  --when 'org.risk level eq normal max_age 10m' \
  --when 'time hour_utc in 9,10,11,12,13,14,15,16,17'

time is read live, so it takes no max_age. Every other source needs one: a statement with no expiry keeps authorizing after the world has moved on.

Where the statements come from

Your own tools. Your EDR, MDM or SIEM posts a signed statement to the signals door, and EKKA keeps the newest one about each thing:

POST /govern/signals/<your-org>/<source>

The source name is yours to choose. org.risk, crowdstrike, jamf and splunk are all just names, and the field inside each one is whatever your tool reports. EKKA does not need to understand your tool; it only compares what your tool said with what your Grant requires.

See what EKKA believes

ekka org facts list

Before any tool has spoken, that answers:

Nothing has told EKKA anything about this organization's world yet.

A grant with a condition on a source that has never spoken is REFUSED, never
assumed true. So a grant asking for `org.risk level eq normal` refuses until
something says what the level is.

Once your tools are posting, the same command lists one row per statement: its source, its subject, its field, the value, and how long ago it was observed. --verbose adds the exact instant and the signed statement each belief came from, and --json gives you the values untouched.

The age is measured against your tool's own clock, and it is the number a condition checks. A statement older than a grant's max_age refuses that grant even when it says the right thing.

What a refusal tells you

A refused condition names itself, what the world said, and how old that was. It carries the source, the subject, the field, the value your tool reported, that value's age, and the value the Grant required. A statement that has gone stale says so, and it is refused even when it says the right thing.

Every one of those answers is written to your audit chain, so the record shows which condition decided each call.

Recovery takes no action

The next decision after your tools report recovery is allowed. Nobody re-approves anything, and no Grant is re-issued.

Limits

  • A condition is checked where EKKA signs the token. A token already issued is valid for its lifetime.
  • All conditions on a Grant must hold. There is no any of these.
  • in takes a list; every other operator takes one value.
  • lt, lte, gt and gte compare numbers, and refuse a value that is not one rather than guessing.
  • A Grant with no conditions behaves exactly as it always has.

Every condition fails closed

Five states refuse, and each says which one it was: no statement, a statement older than max_age, a source that has never spoken, a value the operator cannot compare, and a condition EKKA cannot read. A source that has said nothing is refused, never assumed to be fine.

Check it on your own organization:

ekka audit list --limit 200 --all --json | grep -c condition_

That counts the condition refusals on your chain. Zero is the expected answer until a conditional Grant has actually refused something. It means no condition has turned an agent away yet, not that the record is missing. The count rises the first time one does, and the reason code on each row names which of the five states it was.