Talmi is a small Security Token Service (STS) written in Go.
It verifies upstream identity tokens (usually OIDC JWTs), evaluates them against policy rules, and mints short-lived downstream artifacts (for example GitHub App tokens). Talmi can record audit logs and keep a registry of currently active (non-expired) tokens.
Workloads (CI jobs, automation, services) often need credentials to call APIs. Long-lived secrets in build systems are hard to protect and hard to audit.
With OIDC federation, the workload gets a short-lived identity token from an upstream issuer (e.g. GitHub Actions) that proves its identity. Talmi can then verify this identity, check it against defined policies, and mint short-lived, scoped credentials for the requested resource.
The basic flow to issue an artifact (=> token) is as follows:
- Receive an upstream token (OIDC JWT).
- Verify the token and build a principal (subject + claims).
- Evaluate rules in order. First match wins.
- Mint an artifact (=> token) using the rule’s provider configuration.
- Audit
You can install the Talmi CLI and server using go install:
go install github.com/darmiel/talmi@latestCreate a configuration file talmi.yaml - you can find an example in the
examples/ directory.
Then start the Talmi server:
talmi serve --config talmi.yaml --addr :8080To perform administrative tasks, you must first authenticate with Talmi using a token that matches a rule granting
access to the talmi provider. You can find an example how to do so in
examples/talmi-testing.yaml (see talmi-jwt provider).
An issuer is a trusted source of upstream identity tokens. In most setups this is OIDC (Talmi fetches signing keys via the issuer’s discovery endpoints).
The issuer configuration determines:
- which token sources are trusted
- which audience/client ID is expected
- how verification happens
issuers:
- name: name-of-issuer
type: oidc
issuer_url: https://issuer.example.com/
client_id: my-audienceImportant
The Talmi server requires network access to the Issuer's URL
(specifically the .well-known/openid-configuration endpoint) to fetch public keys for signature verification.
After verification Talmi represents the caller as a principal:
principal.id(usually the token subject)principal.issuer(which issuer verified the token)principal.attributes(claims from the token, e.g. sub, repository, workflow, etc.)
Rules match against these attributes.
Rules are policy entries that map identity to a grant.
Important properties:
- Rules are evaluated top to bottom
- First matching rule wins
- A rule has:
match: issuer + conditionsgrant: which provider to mint from (and its config)
A minimal rule shape:
- name: allow-something
match:
issuer: my-oidc
condition: ...
grant:
provider: github
config: ...Providers mint the downstream artifact. Talmi’s policy engine decides which provider and how it should mint, but the provider decides the concrete “artifact” format (e.g., GitHub installation token, Talmi admin JWT, etc.).
Currently supported providers: (check provider/registry.go for the full list)
stub: returns dummy tokens for testing and writes to loggithub-app: mints GitHub App installation tokens. For configuration details seeGitHubAppProviderConfigandGitHubAppGrantConfigtalmi-jwt: mints Talmi admin JWTs for accessing Talmi server admin APIs. For configuration details seeTalmiJWTProviderConfigandTalmiJWTGrantConfig
You can configure multiple providers of the same kind to enforce the Principle of Least Privilege.
For example, you might configure two GitHub providers:
github-ci-reader: A GitHub App with only "Read-Only" permissions, available to all CI pipelines.github-admin: A GitHub App with "Write" permissions, restricted to a specific list of repository owners via Policy Rules.
This means you don't need to create a single, over-privileged GitHub App. Instead, you can create multiple Apps with specific permission sets and control access via Talmi's policy engine.
Talmi supports simple matchers for common cases, plus composition for more complex logic.
match:
issuer: flower-oidc
condition:
sub: { contains: "@company.com" }Supported operators (from the existing README/code intent):
equalscontainsinexists- for a full list see condition.go
Use all (AND), any (OR), and not:
match:
issuer: flower-oidc
conditions:
all: # all following conditions must match
- user: { contains: "@company.com" }
- not: { user: "bob@company.com" } # what did you do, Bob?Tip
If you cannot use the short syntax, for example when your attribute name collides with one of the operators, you can use the long syntax:
condition:
key: all
operator: equals
value: some-valueKeep rules ordered from most specific to most general.
Before writing rules, confirm what claims your upstream tokens actually contain. Talmi
includes a helper (talmi attributes) that decodes JWT claims without validating them.
Use it to learn the available attribute names and values, then write match conditions against those.
why produces a structured trace of each rule and condition result. It’s the fastest way to see:
- which rule first matched
- exactly which condition failed and why
- whether issuer matching/selection is the issue
why can run either:
- locally against a config file (useful while editing policies) by providing
-f talmi.yaml - remotely against a server where you are authenticated as
admin(note that this does not produce an audit log entry)
Tip
You view the evaluation trace for a previous request by providing the correlation ID you can find in the audit log:
talmi why --replay-id <correlation-id>Note that this applies the current policy configuration, not the one that was active at the time of the request.
Talmi provides built-in tools to track access and security events.
The token registry tracks all currently valid tokens issued by the system. You can list them using:
talmi audit tokens
The audit log records every access request, including denied requests:
talmi audit log --limit 50It contains time, correlation-id, principal and status
╭───────────────────────────┬──────────────────────┬────────────────────────────┬───────────────╮
│ Time │ Correlation ID │ Principal │ Action │
├───────────────────────────┼──────────────────────┼────────────────────────────┼───────────────┤
│ 2025-12-17T22:55:47+01:00 │ d51idota26vcnr2h29ag │ daniel@company.com │ ✔ issue_token │
│ 2025-12-17T23:01:46+01:00 │ d51igila26vcnr2h29b0 │ my-pipeline/my-job/my-task │ ✔ issue_token │
╰───────────────────────────┴──────────────────────┴────────────────────────────┴───────────────╯
To view full details of a specific audit entry (=> request), including metadata and errors, use:
talmi audit inspect <audit-id>This shows more information about this audit entry
── Audit Entry ──
Correlation ID: d51igila26vcnr2h29b0
Time: Wed, 17 Dec 2025 23:01:46 CET
Action: issue_token
Decision: granted
── Identity ──
Subject: my-pipeline/my-job/my-task
Issuer: flower-oidc
Action: issue_token
Attributes:
aud: [talmi-dev]
exp: 1.766012497e+09
iat: 1.766008897e+09
intent: create
iss: https://flower.d2a.io/oidc
sub: my-pipeline/my-job/my-task
── Request & Policy ──
Action: issue_token
Req. Provider: (first grant)
Req. Issuer: (auto discover)
Matched Rule: allow-job-to-create-repositories
── Output ──
Provider: github
Fingerprint: SQHSEnPLrtM2agiC5M4kLD+M5Wxzb4DXXd0OobIioaQ=
Metadata:
installation: 25508
permissions: map[actions:write]
repositories: <nil>
All minting requests should contain the correlation ID by passing it as the User-Agent for requests in the following
format:
Talmi/v1.0.0 (correlation_id=%s; principal=%s; provider=%s)
Additionally, Talmi may compute a fingerprint for each minted token (depending on the provider). This allows you to track token usage in audit logs of downstream services (e.g., GitHub).
You can find more information about identifying audit log events in GitHub here
One common use-case: you see activity in a downstream system (e.g., GitHub Enterprise audit log) and want to find the exact decision that produced the token.
- Copy the token fingerprint/hash from the downstream audit log
- Search Talmi audit log entries for that fingerprint
- Use Talmi’s
audit log --fingerprint=<fingerprint>to locate the matching request(s). - Take the
Correlation IDfrom the matching audit entry The correlation ID is the stable handle you use inside Talmi to inspect a single request in detail. - Inspect the full audit entry This shows the principal attributes, requested provider/issuer, matched rule (if any), provider output, and metadata/errors.
- Run an evaluation trace (
why --replay-id=<correlation-id>) to see the rule-by-rule evaluation trace. The trace is useful when:- a rule matched unexpectedly
- a rule did not match when you thought it would
- the wrong provider was chosen due to rule order
- a condition failed due to missing/renamed claims
sequenceDiagram
participant Client as CI Pipeline / Client
participant IdP as Identity Provider (e.g., GitHub Actions)
participant Talmi as Talmi Server
participant Provider as Downstream Provider (e.g., GitHub App)
Note over Client, IdP: 1. Identity
Client->>IdP: Request OIDC Token
IdP-->>Client: Signed JWT
Note over Client, Talmi: 2. Exchange
Client->>Talmi: Request Access (send JWT)
Note over Talmi: 3. Policy Engine
Talmi->>Talmi: Verify JWT Signature
Talmi->>Talmi: Match Principal against Rules
Note over Talmi, Provider: 4. Minting
Talmi->>Provider: Request Scoped Token
Provider-->>Talmi: Short-lived Access Token
Talmi-->>Client: Return Access Token
... README is under construction ...