Bind real Forgejo callback to current issuer grants and retain ongoing profile gates

This commit is contained in:
codex 2026-10-18 12:31:03 +02:00
parent 7c682d9c66
commit 27737fcce0
110 changed files with 32617 additions and 0 deletions

View file

@ -0,0 +1,273 @@
{
"objective": "Finish what you found missing and finish CUST-WP-0080",
"status": "active",
"observed_at": "2026-10-18T10:25:27.278660+00:00",
"full_scope_baseline": {
"path": "docs/changes/netkingdom-target-2026-10-18/cust80-completion-r1/completion-audit.json",
"sha256": "sha256:9bdee9a790db598956fe35d5954137bb4582c9f3810d2392f7ef3236c369b065",
"all_requirements_retained": true
},
"normative_architecture": {
"path": "docs/netkingdom-target-security-architecture.md",
"sha256": "sha256:701c1e276df3d21925f6939821fb3953babfcb5bce8ff10fed582642f75ff484"
},
"acceptance_gates": [
{
"id": "TSA-01",
"required_outcome": "Every inventoried privileged PEP authenticates its decision",
"required_evidence": "PEP denominator; admitted signer/channel; forged, altered, wrong-key/pin, unsigned/downgrade, expired and rotation refusals. Start SECRETS-WP-0014.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-02",
"required_outcome": "Exact approval produces one bounded protected operation",
"required_evidence": "SECRETS-WP-0013/RPF-WP-0035-T12 native effect; Approval T01/T05 issuer/tenant provenance; wrong actor/tenant/body, expiry, replay and unavailable refusal.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-03",
"required_outcome": "Crashes preserve uncertainty and cleanup",
"required_evidence": "Actual ledger restart/concurrency, consumed-before-effect failure, target reconciliation, scoped cleanup and revocation evidence; no blind replay.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-04",
"required_outcome": "First reusable browser case is accepted",
"required_evidence": "NK-WP-0048 and named Mason/Warden/KeyCape adapters; independent native structure, sign-in/denial, delivery, revocation and rollback.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-05",
"required_outcome": "Human and browser consumers can use the delivered identity",
"required_evidence": "KEY-WP-0040/U08 real-factor recovery; User Engine journey; NK-WP-0050 and CUST-WP-0080-T05 human/agent Playwright acceptance.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-06",
"required_outcome": "Actual agent and runner authority is bounded",
"required_evidence": "CUST-WP-0073/GLAS-WP-0012 admitted runtime read-denial/tool-effect proof; Forge/Platform per-job credential and repository-workflow abuse tests.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-07",
"required_outcome": "Credential lifecycle is inventoried and simplified",
"required_evidence": "Current owner/consumer denominator, unknown/orphan findings, native-identity elimination candidates; sampled replacement/adoption/old-refusal and deletion semantics. G04/G09/G12.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-08",
"required_outcome": "Recovery is independent and measured",
"required_evidence": "NK-WP-0049 plus platform/root owners; isolated recovery with ordinary issuer unavailable, restore/reconnect/audit continuity and measured RTO/RPO. G08/G11.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-09",
"required_outcome": "Security effects have independently reconciled evidence",
"required_evidence": "Admitted source/class inventory, atomic outbox, receiver readback, lag/duplicate/boundary accounting and missing-class findings. Approval/Audit/Tenant/Secrets owners.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-10",
"required_outcome": "Source and delivery controls cover declared entry paths",
"required_evidence": "FORGE-WP-0006/RMGR-WP-0021: denominator, SSH/HTTP/UI/new repo/import/mirror coverage, required checks/provenance, private synthetic finding through resolution.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-11",
"required_outcome": "Discovery remains accurate",
"required_evidence": "All reviewed IDs exactly once, owner/index parity, usable source links, current limits/version aliases, drift checks and package-family disposition. Reuse/Repo Manager/provider owners.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-12",
"required_outcome": "Improvement operates without repeated manual polling",
"required_evidence": "CUST-WP-0079/Activity accepted schedule, unassisted firing, checkpoint and changed-state notifications; adopted cadence runtime/receiver receipts; owner change and native remeasurement.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-13",
"required_outcome": "Shared adapters reduce per-consumer implementation",
"required_evidence": "Producer-format cross-language fixtures, two real consumers, version compatibility, denied-path parity, upgrades and rollback; no second PDP.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-14",
"required_outcome": "Minimal profiles are independently reusable",
"required_evidence": "Version/provider lock, dependency and optional-provider declaration, provider adapters, second clean installation, complete positive/negative journeys and upgrade/recovery instructions.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-15",
"required_outcome": "Optimization improves measured operations",
"required_evidence": "Baseline and repeat measurements of onboarding time/touches, p50/p95 decision/effect latency, scanner throughput/failure, resources/headroom and owner effort with unchanged control coverage.",
"status": "incomplete",
"completion_evidence": []
},
{
"id": "TSA-16",
"required_outcome": "Governance and exceptions remain truthful",
"required_evidence": "Existing SEC-DSM ledger and close-down controls reconciled to exact evidence; real authority for changes, explicit residual promotions, preserved deferrals and no invented owner assent.",
"status": "incomplete",
"completion_evidence": []
}
],
"sec_dsm_requirements": {
"path": "docs/evidence/2026-10-12-netkingdom-security/sec-dsm-requirement-ledger.json",
"sha256": "sha256:ad81f7186ada04aa1cc410932fc8669d69dc2232f3c0be00657b17f3aa3a8184",
"count": 116,
"status_counts": {
"unverified": 84,
"incomplete": 12,
"reference": 20
},
"closure_status_unchanged": true
},
"gap_ids": [
"G01",
"G02",
"G03",
"G04",
"G05",
"G06",
"G07",
"G08",
"G09",
"G10",
"G11",
"G12",
"G13"
],
"separate_required_deliverables": [
"G01-G13 current closure evidence or already applicable approved exceptions",
"SEC-DSM requirement ledger reconciled with exact evidence",
"T05 real human and agent Playwright acceptance and supported discovery",
"Second real consumer and clean installation proves reuse",
"Residual handoffs before workplan finish; no required work left only in prose"
],
"source_progress": {
"packet": "docs/changes/netkingdom-target-2026-10-18/forgejo-postgres-source-r1",
"producer_source_commit": "git:ca0cb75729a63c9288b7333382d0fa3eb1e8c9a3",
"consumer_owner_commit": "git:308e83fcfbf9ec98dbe6b0286cbe8ec7be8899b3",
"consumer_paths": 40,
"patch_sha256": "sha256:feb1079399a43989742ae0a946c1ada50113d1821e6541f97f0b5592ee1fc20c",
"postgreSQL_test_subtest_passes": 37,
"database_schema_profiles": [
"default",
"explicit-public",
"named"
],
"separate_client_crash_recovery": true,
"actual_database_server_restart_recovery": true,
"controlled_missing_lock_and_private_marker_counterexamples": true,
"following_query_hook_context_panic_corrected": true,
"selected_composed_passes_including_subtests": 94,
"source_skips": 0,
"release": {
"frontend_and_static_backend": "passed",
"release_version": "11.0.3+cust80.feb10793",
"binary_sha256": "sha256:92efb26614d3fca22c400920d92c4cd022e32ab86c1f562f60699ab5f750cd28",
"embedded_assets_exact": 2234,
"actual_release_CLI_checks": 48,
"exact_local_OCI_exports": 2,
"manifest": "sha256:9d127b0a75d3d53b1598e7f380dce29f23f42c2e69056fdfc9a42122d98c41c7",
"native_admission": false
},
"independent_owner_assent": false,
"production_database": false,
"native_gates_closed": 0
},
"current_work": "Implement the selected current KeyCape checks across actual Forgejo ongoing HTTP/API/Git/SSH paths, including authoritative account/source/subject/application-tenant binding, current groups and exact resource/action policy. Finish actual current protected operation/inverse and effect/approval integration; private journal custody/retention; every-release-writer exclusion; complete release rebuild and independent/native acceptance. All sixteen gates, G01-G13 and 116 ledger entries remain in scope.",
"workplan_finished": false,
"goal_complete": false,
"preceding_audit": {
"path": "docs/changes/netkingdom-target-2026-10-18/cust80-completion-r5/completion-audit.json",
"sha256": "sha256:db0c077a8f6cc687579f6f53af48ebaa502ebccdda2533b186e71d942896d66e"
},
"issuer_source_progress": {
"packet": "docs/changes/netkingdom-target-2026-10-18/keycape-token-status-r1",
"base_revision": "git:d803e9282a47b49daf930fb905312fa5104c0ace",
"scope": "proposed opt-in extension in the existing issuer",
"source_delta_paths": 18,
"complete_race_passes_including_subtests": 659,
"existing_native_external_skips": 6,
"new_token_status_passes_including_subtests": 43,
"actual_built_server_process_checks": 26,
"actual_process_restart": true,
"actual_handler_HTTPS_PKCE_status_revocation": true,
"controlled_revocation_guard_omission_failed_checks": 7,
"independent_owner_assent": false,
"native_admission": false,
"gates_closed": 0,
"owner_identifier_reconciliation": "existing exact historical reference restored using official consistency injection helper; new canonical records assigned by normal Repo Manager; no replacement existing UUID",
"consumer_owner_commit": "git:67a0869c1ee40c9e00a60cb7c1d828734d5d3d32",
"owner_primary_projection_verified": true,
"owner_workplan": "KEY-WP-0042",
"owner_workplan_status": "proposed",
"owner_tasks": {
"T01": "progress",
"T02": "done",
"T03": "todo"
},
"source_task_complete_only": true
},
"shared_client_source_progress": {
"packet": "docs/changes/netkingdom-target-2026-10-18/keycape-current-client-r1",
"issuer_commit": "git:08c083bae9ff35fbaf028c36d72a95b57d2541b7",
"warden_commit": "git:08c098e64a9afd89e43f65436782ed2bb69cdfbd",
"complete_issuer_race_passes_including_subtests": 724,
"existing_native_external_skips": 6,
"warden_canonical_unit_passes": 796,
"warden_deselected": 4,
"built_cross_owner_fixture_assertions": 45,
"owner_processes": 2,
"generated_go_consumer_processes": 4,
"exact_source_delta_replay_paths": 23,
"single_generated_producer_export_and_drift_refusal": true,
"independent_owner_assent": false,
"native_admission": false,
"second_real_consumer": false,
"native_gates_closed": 0,
"both_owner_primary_projections_verified": true
},
"current_callback_source_progress": {
"packet": "docs/changes/netkingdom-target-2026-10-18/forgejo-current-token-r1",
"issuer_revision": "git:e2c5a947829568baf837543a6708fd0c3f6a863b",
"apps_revision": "git:ab3cf5cdc56d085d040256e2e84633b0289ab0af",
"consumer_source_postimages_exact": 49,
"issuer_source_postimages_exact": 6,
"latest_focused_results": 91,
"final_composed_results": 116,
"composed_skips": 0,
"complete_issuer_race_results": 726,
"existing_native_external_skips": 6,
"actual_issuer_consumer_processes": 5,
"original_purpose_and_issuer_failures_retained": true,
"synthetic_MFA_and_PDP_admission": true,
"independent_owner_assent": false,
"complete_release_rebuild": false,
"native_admission": false,
"native_gates_closed": 0,
"both_owner_primary_projections_verified": true
},
"ongoing_access_profile": {
"selection": "Require current KeyCape checks on every ongoing API/Git/SSH operation for linked Forgejo accounts; retain separate local break-glass path",
"selected_by": "user conversation response",
"decision": "b96ef564-c713-4a48-9ab2-849958cec681",
"implementation_status": "incomplete",
"native_rollout_authority": false
}
}

View file

@ -0,0 +1,76 @@
# Actual Forgejo callback and issuer current-grant binding
This source checkpoint consumes the existing producer-owned KeyCape client
inside actual Forgejo source. It does not introduce a token registry, PDP or
independently maintained client. The generated five-file adapter is exact from
KeyCape `08c083ba`; issuer source `e2c5a947` adds the online human ID token's
exact RS256 `at_hash` binding. Legacy disabled-profile issuance is unchanged.
The [OIDC definition](https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken)
supplies the hash rule; requiring it is the explicit proposed online-consumer
profile, not a claim that the code flow universally requires this optional field.
The callback requires independently admitted application tenant and current-token
configuration. It separates ID-token purpose from access-grant `jti`/`scope`,
checks the signed exact token-pair hash before private caller access, and joins
issuer, owning client, subject, tenant, principal type, issuance, assurance,
original authentication time and tenant provenance. Fresh authenticated status
checks precede the PDP and follow its response. No stale positive is cached;
missing, revoked, substituted, suspended, unavailable or withdrawn-trust inputs
refuse, without a group-only fallback or disclosed bearer/credential value.
## Verification
- Two new purpose counterexamples fail against the original ID verifier (three
failure events including the parent test); the corrected latest focused suite
passes 91 test/subtest results without skips.
- The complete actual issuer race suite passes 726 test/subtest results with
the same six preexisting native/external skips, listed in `issuer-full-race.json`.
New checks verify exact signed pair correspondence, distinct grants/resource
audience separation and unchanged disabled-profile output.
- A compiled actual Forgejo test binary executes in five separate consumer
processes against real fixture HTTPS token/discovery/UserInfo/status/revocation
handlers. Two current grants pass; a mixed pair, revoked original and suspended
human refuse. The provider FetchUser, guarded callback and generated current
client execute. The identical consumer fails against the preceding issuer.
- All 49 consumer source postimages and six issuer source delta postimages replay
exactly from their pinned source archives with fuzz zero. Compiled runtime and
generated client bytes match. The sole later source change adds a tested
owner-tenant negative vector.
Positive integration phases execute actual discovery and FetchUser. Negative
phases supply a completed-authentication snapshot to exercise the callback
current-status boundary directly. MFA authentication sessions and PDP
admission/signing are synthetic. Tokens and
private signing/TLS/client material remain in fixture memory/tmpfs, never public
receipts. The actual paired source fixture is not a native browser, deployed
Forgejo, production issuer/custody or second real consumer acceptance. A complete
frontend/static release and OCI rebuild for the new patch remain outstanding.
Earlier release artifacts apply to their original patch only.
The first composed run passed 114 results and skipped the existing private
file-replacement check because its reused fixture directory was exhausted. The
runner now creates new read-only/private fixture aliases per trial. The original
skip and its cause remain in the receipts; they are not PostgreSQL/native proof.
The final composed rerun passes 116 test/subtest results without skips, as
recorded in `validation-summary.json`.
## Selected ongoing profile and remaining work
The user selected **current KeyCape checks on every API/Git/SSH operation** for
NetKingdom-linked accounts, retaining the separate local break-glass path. State
Hub decision `b96ef564-c713-4a48-9ab2-849958cec681` records that conversation
choice and grants no native mutation. Actual ongoing route/resource/action/group
enforcement is still implementation work under RAPPS16-T09/NK48-T43. This callback
checkpoint cannot satisfy it.
Current protected operation/inverse identities, approval/effect correspondence,
private journal custody/retention, every release writer, independent owner review,
complete package/type/source admission and all native acceptance remain open.
The full CUST-WP-0080 scope stays sixteen gates, G01–G13 and 116 ledger entries;
no native gate closes here. Existing owner task UUIDs/statuses are preserved.
Apps publication retains the initial four-file source commit and a separate
two-file validation-document correction. `apps-owner-source` preserves initial
postimages; `apps-owner-source-correction` preserves final corrections. The
original executed correction manifest is byte-exact alongside its capsule-path
projection. Runtime and patch bytes do not change in that refinement.

View file

@ -0,0 +1,20 @@
{
"actual_discovery_and_userinfo": true,
"actual_forgejo_callback_and_generated_client": true,
"actual_https_human_PKCE": true,
"actual_introspection_and_revocation": true,
"consumer_processes": 5,
"native_acceptance": false,
"phases": [
"active",
"mixed-pair",
"revoked-after-userinfo",
"active",
"suspended-after-userinfo"
],
"private_inputs": "fixture memory/tmpfs only",
"status": "passed",
"status_calls": 7,
"synthetic_MFA_session_seed_and_PDP_admission": true,
"userinfo_calls": 2
}

View file

@ -0,0 +1,31 @@
"""Apply only frozen owner source; preserve unrelated worktree and index state."""
from pathlib import Path
import argparse
import hashlib
import json
import re
import subprocess
HERE=Path(__file__).resolve().parent
parser=argparse.ArgumentParser();parser.add_argument('owner',choices=['key-cape','railiance-apps']);args=parser.parse_args()
cfg=json.loads((HERE/(args.owner+'-application-manifest.json')).read_text());root=Path('/home/worsch')/args.owner
sha=lambda raw:'sha256:'+hashlib.sha256(raw).hexdigest()
def git(*a):return subprocess.check_output(['git','-C',str(root),*a],text=True,timeout=40).strip()
base=cfg['base_revision'].removeprefix('git:')
assert git('rev-parse','HEAD')==base==git('ls-remote','origin','refs/heads/main').split()[0]
assert set(git('diff','--name-only','HEAD').splitlines())<={'.custodian-brief.md','WORK-RECORDS.md'}
paths={r['path'] for r in cfg['files']}
protected={name:{'sha256':sha((root/name).read_bytes()),'index':git('ls-files','--stage','--',name)} for name in git('ls-files').splitlines() if name not in paths and (root/name).is_file()}
before=re.findall(r'```task\n.*?```',(root/cfg['workplan']).read_text(),re.S)
for r in cfg['files']:
p=root/r['path'];assert (not p.exists()) if r['before_sha256'] is None else sha(p.read_bytes())==r['before_sha256'],r['path']
assert sha(Path(r['source']).read_bytes())==r['sha256'],r['path']
for r in cfg['files']:
p=root/r['path'];p.parent.mkdir(parents=True,exist_ok=True);p.write_bytes(Path(r['source']).read_bytes())
assert re.findall(r'```task\n.*?```',(root/cfg['workplan']).read_text(),re.S)==before
for name,r in protected.items():assert sha((root/name).read_bytes())==r['sha256'] and git('ls-files','--stage','--',name)==r['index'],name
out=HERE/(args.owner+'-publication');out.mkdir(exist_ok=False)
(out/'protected.json').write_text(json.dumps(protected,indent=2)+'\n')
(out/'application.json').write_text(json.dumps({'owner':args.owner,'files':len(paths),'existing_task_blocks_preserved':len(before),'unrelated_worktree_and_index_preserved':True,'native_admission':False},indent=2)+'\n')
publication={'owner':args.owner,'base_revision':base,'message':cfg['message'],'files':[{'path':r['path'],'sha256':r['sha256'].removeprefix('sha256:')} for r in cfg['files']]}
(out/'publication-inputs.json').write_text(json.dumps(publication,indent=2)+'\n');print(json.dumps({'owner':args.owner,'files':len(paths),'source_applied':True}))

View file

@ -0,0 +1,172 @@
# Forgejo release preparation (build only)
The Apps Makefile targets package the digest-bound 49-path Forgejo 11.0.3
source candidate in `forgejo-security-extension.patch`. It combines the original
three-group any-of gate, private secret-file input, private source-operation DB
primitives and the producer-owned Access Engine client in the OIDC callback.
Coordinator source tests and complete release checks are recorded separately;
independent owner review and operational admission of this revision remain open. They never deploy, push, change default values or choose
an operational image. Third-party application source is expanded only into a new
`/tmp` workspace; this repository retains a release input lock and exact reviewed
patch, not a second source-code tree. Upstream source ownership and publication
remain separate owner work.
Supply explicit public input archives/cache paths to `make forgejo-release-prepare`:
`FORGEJO_SOURCE_ARCHIVE`, `FORGEJO_GO_ARCHIVE`, `FORGEJO_NODE_ARCHIVE`,
`FORGEJO_BUILD_GOMODCACHE`, optionally `FORGEJO_BUILD_GOCACHE_SEED` and
`FORGEJO_BUILD_NPM_CACHE_SEED`. Every archive, patch, source output, dependency
lock and selected host compiler tool is digest-bound by `inputs.lock.json`.
Go's official checksums and Node's official SHASUMS must be independently checked
when acquiring those public archives. The source archive is official Forgejo
v11.0.3; archive PAX commit b81aa4d0807cc1e2d6a2fbafd0b75c46cc0ae237.
The workspace must not exist. Existing trials are preserved, never restarted
because an observer timed out. The recipe's `process.json` records the exact
active child PID, phase and command, then completion; logs and receipts stay in
the workspace. Do not run two phases against the same workspace concurrently.
`make forgejo-release-acquire-npm` explicitly acquires only public registry
packages, with fresh HOME/config paths, integrity-bound lockfile and lifecycle
scripts disabled. With the cache ready, `make forgejo-release-build` reruns
`npm ci --offline --ignore-scripts`, Go module verification, upstream frontend
compilation and upstream generation/static backend targets inside a network
namespace without connectivity. It uses the exact Go/Node distributions, local
compiler inputs, `bindata timetzdata sqlite sqlite_unlock_notify` plus upstream
static target's `netgo osusergo`, explicit Make GOFLAGS for trimpath/buildvcs and
fixed file/directory timestamps. Build tags retain the upstream rootless build
profile. `make forgejo-release-verify` compares every embedded public/options/
template file to actual generated source bytes, checks the extension CLI and
checks the ELF has no dynamic interpreter. These are local artifact checks,
not application login, DB, native rollout or architecture acceptance.
For a **local container build** only, first acquire the locked public amd64
base with an empty Docker client config and the explicit local socket. Then
`make forgejo-release-container` uses that exact digest as a build input and a
minimal context containing only the verified executable and generated Dockerfile.
It directly exports a local OCI archive with `rewrite-timestamp=true`, no-cache,
no image load or push. SOURCE_DATE_EPOCH alone does not fix COPY parent-directory
timestamps. The image epoch is max(source epoch,ceil immutable base created
instant), so the original base layers remain exact while new metadata is stable.
The recipe verifies every OCI blob, original base layer prefix, rootless runtime
configuration, provenance labels and exact final executable. Two no-cache exports
must reproduce the frozen output image and archive digests before claiming
reproducibility. The actual local proof receipts are in the accompanying source
proposal packet, not an admission of the resulting artifact.
There is no deployment tag, registry publication, RUN directive, privileged
container or Docker host/secret mount. Public base acquisition and a standard
local Docker/buildx daemon supporting OCI export/layer timestamp rewriting are
explicit prerequisites. An unavailable feature is a retained blocker; do not
switch to a privileged builder or claim a successful image from a backend-only
binary. Existing contexts/output archives refuse overwrite. Reconcile an actual
failed export explicitly, retaining original metadata/logs/artifacts; observation
timeouts never justify another build. Older prototype load/save behavior and
its config-versus-manifest-ID issue are retained in the source trial history,
not shipped as a production continuation.
The static host toolchain is pinned for this Linux amd64 review estate. Broader
cross-host reproducibility or a different architecture/compiler/libc is a new
input revision, not an implicit fallback. The glibc static-linker's optional
SQLite `dlopen` diagnostic is retained; no SQLite-extension runtime compatibility
or production platform admission is inferred from ELF inspection or asset checks.
An accepted owner image/source/backend, authenticated access-engine decision,
private secret-input boundary, type/case/descriptor re-freeze, current operation/
inverse sessions, durable journal and application/Helm writer exclusion are
separate gates. Helm omission/rollback does not remove persisted OAuth source.
The existing case gate is compatibility code, not a single-PDP exception.
## KeyCape identity and callback admission
The callback independently verifies the ID token against owner-admitted public
keys, algorithms, issuer, audience and token type. Public trust is loaded from
`FORGEJO_ACCESS_ENGINE_ADMISSION_FILE`; only an absolute private file reference
is used for the PDP caller token. No admission file or operational keys are
supplied by this build. New fields `tenant_source_claim` and
`max_authentication_age` are mandatory. The KeyCape v0.3 profile uses
`assurance_claim: assurance` and `tenant_source_claim: tenant_source`. Maximum
identity age and maximum authentication age are separate positive durations
chosen by the owner, not implicit defaults.
The signed assurance object preserves `level`, `methods`, boolean `mfa`,
`source: key-cape` and integer `at`. Its authentication time must not follow
issuance or the current time, and must satisfy the admitted authentication-age
bound. Identity validity ends at the earliest token expiration, issued-at age
bound or authentication-age bound. Directory, registration and default tenant
provenance remain distinct in the request. Mutable UserInfo cannot replace
these authenticated facts. Current UserInfo subject and the Goth subject must
match the independently verified token; the three required group alternatives
remain policy input.
The parser also represents valid lower-assurance facts. Application policy
continues to decide authorization. The Check client and unchanged canonical
signature bytes are a generated source slice of Access Engine ca0cb757; Forgejo
adds no policy evaluator. Exact caller/policy/registry/signer/request/lifetime
pins, an authenticated HTTPS connection and a current ALLOW are required.
Synthetic callback fixtures test ALLOW, DENY and adverse cases, including an ID
token serialized by the actual pinned KeyCape handler. Those fixtures do not
prove an actual browser, factor, custody or native rollout.
The current integration covers this guarded OIDC callback. Ongoing application
HTTP/API/Git/SSH paths, current operation/inverse sessions, durable private
evidence and retention, deployed PostgreSQL/custody acceptance and exclusion of all release
writers remain open in RAPPS-WP-0016-T08/T09 and NK-WP-0048-T43. An old image
approval or source test result cannot admit this candidate.
## PostgreSQL operation-source verification
The source primitive has now run on a disposable PostgreSQL 16.14 instance using
the actual Forgejo engine, model schemas and OAuth configuration types. Tests
cover default, explicit-public and named schemas, exact source restoration, drift
and linked-account refusals, trigger rollback, uncooperative SQL writer exclusion
and concurrent preparation. Independent client processes and an actual server
restart verify prepared/applied/inverse journal recovery. The test-only
`source_operation_postgres` tag selects the integration tests; no runtime route,
credential, session or authority callback is added. The companion Custodian
`forgejo-postgres-source-r1` packet holds the isolated fixture, commands, source
pins, counterexamples and original failed attempts.
Actual PostgreSQL diagnostics exposed a tracing-hook composition crash. Tracing
now publishes its task context to the shared XORM hook context so later hooks
retain it. Ordinary and private SQLite traces and actual PostgreSQL failure
queries exercise this path. Controlled copies without the table lock or private
query marker fail their respective regressions. These are source/database
mechanics checks; owner admission, current authenticated operation/inverse
sessions, runtime/backup custody, retention, every release writer and native
deployed-database acceptance remain separate obligations. The previous frozen
release is historical and does not contain this context correction.
## Current-token callback source continuation
The 49-path candidate additionally vendors the unchanged, producer-generated
KeyCape current-token adapter from `08c083ba`; no consumer token registry or
handwritten copy is introduced. `current_token` (explicit HTTPS issuer, resource
audience, owning client, CA/JWKS/private-client-file references and maximum token
age) and `application_tenant` are mandatory in the independently admitted
`FORGEJO_ACCESS_ENGINE_ADMISSION_FILE` for this guarded callback profile. A signed
caller cannot select the application's tenant. No operational configuration or
credential is supplied by the build.
The callback requires the online KeyCape human ID token's exact RS256 `at_hash`
access-grant binding before private caller use. Access-only `jti`/`scope` cannot
substitute for an ID token. It joins independently verified subject/tenant/type,
issuance, assurance/authentication time and tenant provenance to a fresh issuer
observation before the PDP, then checks current status again after the decision.
Source tests refuse mixed, revoked, suspended, stale, malformed and unavailable
inputs; actual HTTPS issuer PKCE/discovery/UserInfo/status/revocation and real
Forgejo provider/callback run in five isolated consumer processes. MFA session
seeds and PDP admissions are synthetic. Complete release build/asset/OCI evidence
for the preceding patch remains historical; this revision requires its own build
and independent owner/package/type/custody admission before operational use.
The user selected current KeyCape checks on every ongoing API/Git/SSH operation
for NetKingdom-linked accounts, while retaining the separate local break-glass
path. That is an implementation requirement under RAPPS16-T09/NK48-T43, not a
claim that the callback implements those routes or a native deployment grant.
Ongoing resource/action/current-group decisions, protected operation/inverse and
all release writers/journal custody remain acceptance work.
Final composed source validation passes 116 test/subtest results without skips.
The initial 114-pass run skipped its private file-replacement check because a
reused fixture was exhausted; the runner now creates fresh private/read-only
aliases. That initial skip is retained and is not PostgreSQL acceptance.

View file

@ -0,0 +1,335 @@
---
id: RAPPS-WP-0016
type: workplan
title: "Forgejo sign-in through NetKingdom, before the forge closes"
domain: infotech
repo: railiance-apps
status: active
flavor: implementation
owner: claude-code
topic_slug: railiance
created: "2026-10-08"
updated: "2026-10-18"
depends_on:
- KEY-WP-0038
related_repos:
- prj-forgejo-org-refactor
- key-cape
- railiance-platform
state_hub_workstream_id: "f6b1d6f6-0abb-5774-87b9-bf3cfb640a3b"
---
# RAPPS-WP-0016 - Forgejo sign-in through NetKingdom, before the forge closes
## Why
`forgejo.coulomb.social` is closing: private by default, no anonymous access
(prj-forgejo-org-refactor ORGREF-WP-0002, decision
`decisions/forge-visibility-policy.md`). The founder's condition is that sign-in
runs through NetKingdom first. railiance-apps owns the Forgejo Helm release
(`docs/forgejo-on-railiance01.md`: S5 Helm release, ingress, operator Makefile),
so the Forgejo side lands here. This plan is ORGREF-WP-0002-T05. The NetKingdom
side is key-cape `KEY-WP-0038`.
## Boundaries
- **Founder guardrail:** tighten gradually; never lock anyone out; never risk
assets; never reach a state that is hard to repair. Every step keeps the
current sign-in path working until the new one is proven.
- **No visibility change** in this plan. Repositories, packages and
`REQUIRE_SIGNIN_VIEW` are ORGREF-WP-0002-T08.
- **Configuration in git.** The authentication source is declared in
`helm/forgejo-values.yaml` (`gitea.oauth`), with its secret drawn from an
ExternalSecret over OpenBao. No click-ops, and no secret value in git.
- Open registration is already disabled (`service.DISABLE_REGISTRATION: true`).
This plan keeps it that way.
## Tasks
```task
id: RAPPS-WP-0016-T01
status: done
priority: high
state_hub_task_id: "98004ed0-83e6-5f6e-9b18-15b8b2aec2e7"
```
### T01 — Make the local administrator a sealed break-glass
Before anything about sign-in changes: confirm the local administrator account
works, keep its credential only in OpenBao, and document recovery (who, how,
where it is logged) in `docs/forgejo-on-railiance01.md`. Verify one break-glass
sign-in and record it without the credential.
Acceptance: a recorded break-glass sign-in, with the recovery path written down.
This is the "never lock ourselves out" floor for every later step.
Done 2026-10-17: founder sealed `forgejo_admin` into OpenBao
`platform/workloads/forgejo/break-glass-admin` (version 1) and verified a local
sign-in (HTTP 200, site admin): `docs/evidence/2026-10-17-forgejo-breakglass-signin.json`.
Recovery path: `docs/forgejo-on-railiance01.md` § Break-glass administrator.
```task
id: RAPPS-WP-0016-T02
status: wait
depends_on: [RAPPS-WP-0016-T01, KEY-WP-0038-T02]
blocking_reason: "Break-glass done (T01); needs the NetKingdom client registered with its secret in custody (KEY-WP-0038-T02)."
priority: high
state_hub_task_id: "13cd3aec-c87d-5d3f-a421-39d8ff131edd"
```
### T02 — Add the NetKingdom authentication source
- an ExternalSecret delivering the `forgejo` client secret from OpenBao to a
Secret in the `forgejo` namespace. Custody path is
`platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, read by ESO through
Kubernetes-auth role `external-secrets-keycape-forgejo-forgejo`, which carries
only policy `workload-kv-read-keycape-forgejo-forgejo` (net-kingdom 074b3a0;
replaces the earlier `platform/workloads/forgejo/netkingdom-oidc-client` proposal). The
SA/SecretStore/ExternalSecret shape follows the NK-WP-0048
`deliver-consumer-secret` receipt contract
(`docs/evidence/nk48-t16-owner-review/owner-review-railiance-apps.json`)
- `gitea.oauth`: name `netkingdom` (must match the redirect URI registered in
key-cape), provider `openidConnect`, auto-discovery from
`https://kc.coulomb.social/.well-known/openid-configuration`, scopes
`openid profile email groups`, group-claim name `groups`
- `oauth2_client` settings: no automatic account creation for unknown subjects;
account linking to existing local accounts by username after a successful
local sign-in (so the founder and agent accounts keep their history)
- Helm upgrade with the previous revision noted for rollback
Local password sign-in stays enabled.
Acceptance: the "Sign in with netkingdom" option appears. Local sign-in still
works. Rollback is one `helm rollback` to the noted revision.
```task
id: RAPPS-WP-0016-T03
status: wait
depends_on: [RAPPS-WP-0016-T02]
blocking_reason: "Linking and team mapping follow a working authentication source."
priority: high
state_hub_task_id: "81f072aa-c316-511e-a2cd-21dd1b1d0a00"
```
### T03 — Link accounts and map groups to teams
- The founder links their account through NetKingdom (MFA), then signs in again
through NetKingdom only and sees the same repositories, organisations and
admin rights.
- Map NetKingdom groups (names agreed in KEY-WP-0038-T01) to organisation teams:
owners and readers at least. Create the read-only teams that
ORGREF-WP-0002-T06 consumers will use.
- Apply KEY-WP-0038-T03's decision on agents and services: NetKingdom sign-in, or
scoped tokens bound to service identities.
Acceptance: founder and one agent work through NetKingdom; group-to-team mapping
verified by adding and removing one test membership; break-glass still works.
```task
id: RAPPS-WP-0016-T04
status: wait
depends_on: [RAPPS-WP-0016-T03]
needs_human: true
blocking_reason: "Restricting local password sign-in is a founder decision once NetKingdom sign-in is proven."
priority: medium
state_hub_task_id: "32c54737-c40f-5fde-9b91-163f8dcb0068"
```
### T04 — Decide on local password sign-in
Once NetKingdom sign-in has been used for a while without problems, the founder
decides whether local password sign-in is turned off for everyone except
break-glass. This is not required to close the forge; it is the next tightening
step, taken separately.
```task
id: RAPPS-WP-0016-T05
status: wait
depends_on: [RAPPS-WP-0016-T02]
blocking_reason: "Waits for net-kingdom's answer to the Apps 1.3 needs-change record (the-custodian docs/changes/netkingdom-target-2026-10-17/nk48-1.3-owner-review/owner-review-railiance-apps.json: RAPPS-1.3-C1..C3) and the re-frozen 1.3 candidate."
priority: medium
state_hub_task_id: "728786c9-f1f9-5983-a6a4-dea21faf67f5"
```
### T05 — Apps owner seams for NK-WP-0048 consumer steps
Implement the real Apps capabilities behind the `deliver-consumer-secret` and
`hand-over` adapters, as owner-run paths (option C): `owner_apply` /
`owner_remove` emitting `netkingdom.consumer-secret-delivery.v1` receipts from the
T02 objects; a consumer verifier that observes Forgejo sign-in and the T03
positive/negative group probes; and a committed-evidence hand-over receipt store.
See the owner-review record for the contract.
Acceptance: receipts produced from the live T02/T03 run validate against the
adapters' checks with no value-bearing keys.
## Also in scope: stale node credential
The k3s `registries.yaml` on railiance01 still holds a login for the frozen Gitea
host `92.205.130.254` (ORGREF-WP-0002-T02). Coordinate its removal with the node
owner when ORGREF-WP-0002-T06 adds the forge registry login, in the same change.
## T06 — Package the reviewed consumer source through the Apps build path
```task
id: RAPPS-WP-0016-T06
status: done
priority: high
state_hub_task_id: "08ab8921-7801-5309-b84b-bc2a3ceea1e4"
```
Completed 2026-10-18, source build tooling only. The existing Apps Makefile now
has explicit prepare, public-dependency acquisition, offline build, artifact
verification and local OCI export targets for the pinned official Forgejo11.0.3
source plus reviewed multi-value claim patch. Source/toolchains/locks/compiler
inputs/base image are fixed. Third-party application source stays in a new /tmp
workspace; Apps holds the release input lock, patch and build recipe.
The original build profile produced identical frontend assets, bindata sources
and complete static executable in two workspaces, and identical local OCI
archives in two no-cache exports. Independent review then found host process
argv visibility through /proc (APPSBUILD-R1-F01). The revised recipe adds a
private PID namespace; exact source and synthetic canary regression are separately
reviewed. The original full builds are historical r1-profile evidence; no full
r2 build is claimed. Original packet/review and failure history remain in
Custodian apps-forgejo-release-build-r1 and apps-release-independent-review-r1;
the correction is apps-forgejo-release-build-r2. This is coordinator source
integration after independent technical review, not formal owner assent or image
admission. Helm values, deployed image, local sign-in and visibility are unchanged.
NK-WP-0048-T39 remains progress for the full release acceptance scope.
## T07 — Keep the client secret out of process arguments
```task
id: RAPPS-WP-0016-T07
status: progress
priority: high
state_hub_task_id: "786d6584-6db1-5a10-9324-d7b94db0df94"
```
Implement and independently review a real Forgejo add/update --secret-file path
and the actual chart helper/mount adaptation. Preserve the existing CLIENT_SECRET
key and custody route; use no agent-value argument or environment fallback. Test
bounded stable nofollow file reads, exact existing minted-secret encoding,
mutual exclusion/update preservation, fixed errors and actual Helm rendering.
Private mount identity/reader set and new package/type revisions need explicit
owner/runtime admission. The in-progress isolated candidate is Custodian
forgejo-private-input-r1; source completion is distinct from T02 native delivery.
## T08 — Provide a transactional application-source inverse and durable recovery
```task
id: RAPPS-WP-0016-T08
status: progress
priority: high
state_hub_task_id: "4ab8a5e7-ab32-5ee2-aaf2-6da246d1b0c8"
```
Implement actual owner database primitives that record operation intent/outcome
in the same durable database, compare complete raw persisted before/post/current
source state privately, and serialize conflicting application writers at the DB
boundary. Never emit stored credential/config bytes or delete/detach linked users
or a preexisting/shared source. SQLite and PostgreSQL transaction semantics must
be tested against the real app packages; unsupported backends refuse.
T05 and NK-WP-0048-T34 additionally require actual authenticated execution and
fresh inverse sessions, admitted evidence/recovery and exclusion of every release
writer, including Helm/runtime writers. The candidate DB primitive alone does
not establish those facilities. Helm rollback/omission does not remove persisted
OAuth sources: verify the exact app-state inverse before any image downgrade.
If links prevent removal, retain the extended image and reconcile. The earlier
T02 single-Helm-rollback statement is not sufficient recovery for this extension;
its historical text is retained and corrected here.
2026-10-18 PostgreSQL/source-context continuation for T08: actual Forgejo model
schemas, OAuth configuration types and production DB initialization run on a
disposable PostgreSQL 16.14 instance. All 37 test/subtest results pass across
default, explicit-public and named schemas. Separate client processes prove
rollback after process death immediately following INSERT, committed apply
survival through a real server restart, and subsequent reconciliation/inverse.
Exact raw restoration, private drift, linked-user/external-link refusal, trigger
rollback, table-writer exclusion and concurrent preparation are covered.
Controlled copies with the table lock or private-query context removed fail the
corresponding tests in all three schema profiles. All owned fixture containers
and volumes were removed; no deployed database or real credential was used.
The diagnostics check exposed a real tracing-hook composition panic. The new
40-path build candidate publishes the task context into XORM's shared query
context so subsequent hooks preserve it. Actual private/ordinary SQLite traces
and PostgreSQL driver-error hooks cover the correction. Six focused SQLite DB
checks and all 94 selected composed package results pass without skips. The
previous panic, fixture/compilation failures and refinement history remain in
Custodian forgejo-postgres-source-r1. Complete release evidence uses the new
feb10793 patch; the prior frozen artifact is historical.
This proves bounded source/database mechanics. T08 remains progress for current
authenticated initiating/inverse sessions, release-wide writer exclusion, private
evidence/custody/retention and native deployed-database acceptance. Independent
owner review and exact package/type/runtime admission remain open. T07/T09 and
all original task UUIDs and statuses are preserved.
## T09 — Enforce authenticated Access Engine decisions in the application path
```task
id: RAPPS-WP-0016-T09
status: progress
priority: high
state_hub_task_id: "d3804638-c73d-5b9f-a456-4262f62f9e6b"
```
Use the producer-owned decision format/canonical bytes and independently admitted
signer/policy/request pins. Forgejo must verify the caller's issuer/tenant and
assurance facts, obtain and authenticate the exact Access Engine resource/action
decision, and refuse missing, stale, wrong-pin, unavailable or malformed replies.
Keep the original three-group any-of access set as policy input; its local
compatibility gate is not a substitute PDP or an architecture exception. An
issuer allowed/denied claim cannot manufacture authorization. Source integration
needs real provider/callback/session wiring and meaningful producer-consumer
positive/adverse tests before re-freezing the case and package. Existing break-
glass/local account and linking behavior remain within the declared owner scope.
2026-10-18 source continuation for T07/T08/T09: the digest-bound build
candidate now composes all 37 paths, including private secret-file input, actual
source-state transactions/private SQL hooks, the producer-owned Access Engine
Check client from ca0cb757 and guarded OIDC callback enforcement. The parser uses
the actual KeyCape typed assurance/authentication-time/provenance contract; its
actual issuer fixture/callback suite passes 71 test/subtest results, and selected
composed packages pass 93 with no skips. The previous incompatible-string-claim
rejection is retained. All patch postimages independently replay from the pinned
official archive. Complete release build/verification is tracked in Custodian
forgejo-keycape-claims-r1; earlier release outputs do not prove this revision.
This is coordinator source integration of a tested build candidate; independent
owner review and formal operational admission remain open. T07's chart/private
mount admission, T08's fresh authenticated operation/inverse and release-wide
writer exclusion, T09's ongoing HTTP/API/Git/SSH paths, private evidence/retention,
PostgreSQL/native proof and actual issuer/factor/custody acceptance remain open.
No trust, deployed image, Helm values, source DB, user/link state or local sign-in
is changed by this build-source update. The preserved legacy patch is historical;
the input lock now explicitly selects forgejo-security-extension.patch.
2026-10-18 current-token callback continuation: the new 49-path source candidate
uses the exact generated KeyCape online client, without a consumer token registry.
Mandatory owner application-tenant/current-token admission, ID-token purpose,
exact signed RS256 at_hash, current status before/after the PDP, and signed
identity/assurance/authentication-time/tenant-provenance joins refuse substitution
and stale authority. The latest focused callback/identity suite passes 91 results
without skips; the final selected four composed packages pass 116 results without
skips. The earlier 114-pass run skipped the private file-replacement check because
its reused fixture directory was exhausted; a fresh private/read-only fixture
restores that check. The original skip is retained, not PostgreSQL evidence.
The actual HTTP PKCE/discovery/UserInfo/status/revocation and Forgejo provider /
callback pass five fresh consumer processes: two current grants accepted, mixed
pair/revoked original/suspended human refused. The original verifier and issuer
fail their new controls. MFA session seeds and PDP admission are synthetic.
The user explicitly selected current KeyCape checks on every API/Git/SSH
operation for linked accounts, keeping the separate local break-glass path.
That full ongoing route/resource/action/group profile is still implementation
work here; callback proof does not close it. Input lock now selects patch
9fe3d7ba; complete release/asset/OCI checks, independent owner assent,
software/type/package/custody admission, real factors/current sessions and native
acceptance remain open. T07/T08 and every existing task UUID/status are preserved.
Exact source/evidence: Custodian forgejo-current-token-r1.

View file

@ -0,0 +1,167 @@
# Forgejo release preparation (build only)
The Apps Makefile targets package the digest-bound 49-path Forgejo 11.0.3
source candidate in `forgejo-security-extension.patch`. It combines the original
three-group any-of gate, private secret-file input, private source-operation DB
primitives and the producer-owned Access Engine client in the OIDC callback.
Coordinator source tests and complete release checks are recorded separately;
independent owner review and operational admission of this revision remain open. They never deploy, push, change default values or choose
an operational image. Third-party application source is expanded only into a new
`/tmp` workspace; this repository retains a release input lock and exact reviewed
patch, not a second source-code tree. Upstream source ownership and publication
remain separate owner work.
Supply explicit public input archives/cache paths to `make forgejo-release-prepare`:
`FORGEJO_SOURCE_ARCHIVE`, `FORGEJO_GO_ARCHIVE`, `FORGEJO_NODE_ARCHIVE`,
`FORGEJO_BUILD_GOMODCACHE`, optionally `FORGEJO_BUILD_GOCACHE_SEED` and
`FORGEJO_BUILD_NPM_CACHE_SEED`. Every archive, patch, source output, dependency
lock and selected host compiler tool is digest-bound by `inputs.lock.json`.
Go's official checksums and Node's official SHASUMS must be independently checked
when acquiring those public archives. The source archive is official Forgejo
v11.0.3; archive PAX commit b81aa4d0807cc1e2d6a2fbafd0b75c46cc0ae237.
The workspace must not exist. Existing trials are preserved, never restarted
because an observer timed out. The recipe's `process.json` records the exact
active child PID, phase and command, then completion; logs and receipts stay in
the workspace. Do not run two phases against the same workspace concurrently.
`make forgejo-release-acquire-npm` explicitly acquires only public registry
packages, with fresh HOME/config paths, integrity-bound lockfile and lifecycle
scripts disabled. With the cache ready, `make forgejo-release-build` reruns
`npm ci --offline --ignore-scripts`, Go module verification, upstream frontend
compilation and upstream generation/static backend targets inside a network
namespace without connectivity. It uses the exact Go/Node distributions, local
compiler inputs, `bindata timetzdata sqlite sqlite_unlock_notify` plus upstream
static target's `netgo osusergo`, explicit Make GOFLAGS for trimpath/buildvcs and
fixed file/directory timestamps. Build tags retain the upstream rootless build
profile. `make forgejo-release-verify` compares every embedded public/options/
template file to actual generated source bytes, checks the extension CLI and
checks the ELF has no dynamic interpreter. These are local artifact checks,
not application login, DB, native rollout or architecture acceptance.
For a **local container build** only, first acquire the locked public amd64
base with an empty Docker client config and the explicit local socket. Then
`make forgejo-release-container` uses that exact digest as a build input and a
minimal context containing only the verified executable and generated Dockerfile.
It directly exports a local OCI archive with `rewrite-timestamp=true`, no-cache,
no image load or push. SOURCE_DATE_EPOCH alone does not fix COPY parent-directory
timestamps. The image epoch is max(source epoch,ceil immutable base created
instant), so the original base layers remain exact while new metadata is stable.
The recipe verifies every OCI blob, original base layer prefix, rootless runtime
configuration, provenance labels and exact final executable. Two no-cache exports
must reproduce the frozen output image and archive digests before claiming
reproducibility. The actual local proof receipts are in the accompanying source
proposal packet, not an admission of the resulting artifact.
There is no deployment tag, registry publication, RUN directive, privileged
container or Docker host/secret mount. Public base acquisition and a standard
local Docker/buildx daemon supporting OCI export/layer timestamp rewriting are
explicit prerequisites. An unavailable feature is a retained blocker; do not
switch to a privileged builder or claim a successful image from a backend-only
binary. Existing contexts/output archives refuse overwrite. Reconcile an actual
failed export explicitly, retaining original metadata/logs/artifacts; observation
timeouts never justify another build. Older prototype load/save behavior and
its config-versus-manifest-ID issue are retained in the source trial history,
not shipped as a production continuation.
The static host toolchain is pinned for this Linux amd64 review estate. Broader
cross-host reproducibility or a different architecture/compiler/libc is a new
input revision, not an implicit fallback. The glibc static-linker's optional
SQLite `dlopen` diagnostic is retained; no SQLite-extension runtime compatibility
or production platform admission is inferred from ELF inspection or asset checks.
An accepted owner image/source/backend, authenticated access-engine decision,
private secret-input boundary, type/case/descriptor re-freeze, current operation/
inverse sessions, durable journal and application/Helm writer exclusion are
separate gates. Helm omission/rollback does not remove persisted OAuth source.
The existing case gate is compatibility code, not a single-PDP exception.
## KeyCape identity and callback admission
The callback independently verifies the ID token against owner-admitted public
keys, algorithms, issuer, audience and token type. Public trust is loaded from
`FORGEJO_ACCESS_ENGINE_ADMISSION_FILE`; only an absolute private file reference
is used for the PDP caller token. No admission file or operational keys are
supplied by this build. New fields `tenant_source_claim` and
`max_authentication_age` are mandatory. The KeyCape v0.3 profile uses
`assurance_claim: assurance` and `tenant_source_claim: tenant_source`. Maximum
identity age and maximum authentication age are separate positive durations
chosen by the owner, not implicit defaults.
The signed assurance object preserves `level`, `methods`, boolean `mfa`,
`source: key-cape` and integer `at`. Its authentication time must not follow
issuance or the current time, and must satisfy the admitted authentication-age
bound. Identity validity ends at the earliest token expiration, issued-at age
bound or authentication-age bound. Directory, registration and default tenant
provenance remain distinct in the request. Mutable UserInfo cannot replace
these authenticated facts. Current UserInfo subject and the Goth subject must
match the independently verified token; the three required group alternatives
remain policy input.
The parser also represents valid lower-assurance facts. Application policy
continues to decide authorization. The Check client and unchanged canonical
signature bytes are a generated source slice of Access Engine ca0cb757; Forgejo
adds no policy evaluator. Exact caller/policy/registry/signer/request/lifetime
pins, an authenticated HTTPS connection and a current ALLOW are required.
Synthetic callback fixtures test ALLOW, DENY and adverse cases, including an ID
token serialized by the actual pinned KeyCape handler. Those fixtures do not
prove an actual browser, factor, custody or native rollout.
The current integration covers this guarded OIDC callback. Ongoing application
HTTP/API/Git/SSH paths, current operation/inverse sessions, durable private
evidence and retention, deployed PostgreSQL/custody acceptance and exclusion of all release
writers remain open in RAPPS-WP-0016-T08/T09 and NK-WP-0048-T43. An old image
approval or source test result cannot admit this candidate.
## PostgreSQL operation-source verification
The source primitive has now run on a disposable PostgreSQL 16.14 instance using
the actual Forgejo engine, model schemas and OAuth configuration types. Tests
cover default, explicit-public and named schemas, exact source restoration, drift
and linked-account refusals, trigger rollback, uncooperative SQL writer exclusion
and concurrent preparation. Independent client processes and an actual server
restart verify prepared/applied/inverse journal recovery. The test-only
`source_operation_postgres` tag selects the integration tests; no runtime route,
credential, session or authority callback is added. The companion Custodian
`forgejo-postgres-source-r1` packet holds the isolated fixture, commands, source
pins, counterexamples and original failed attempts.
Actual PostgreSQL diagnostics exposed a tracing-hook composition crash. Tracing
now publishes its task context to the shared XORM hook context so later hooks
retain it. Ordinary and private SQLite traces and actual PostgreSQL failure
queries exercise this path. Controlled copies without the table lock or private
query marker fail their respective regressions. These are source/database
mechanics checks; owner admission, current authenticated operation/inverse
sessions, runtime/backup custody, retention, every release writer and native
deployed-database acceptance remain separate obligations. The previous frozen
release is historical and does not contain this context correction.
## Current-token callback source continuation
The 49-path candidate additionally vendors the unchanged, producer-generated
KeyCape current-token adapter from `08c083ba`; no consumer token registry or
handwritten copy is introduced. `current_token` (explicit HTTPS issuer, resource
audience, owning client, CA/JWKS/private-client-file references and maximum token
age) and `application_tenant` are mandatory in the independently admitted
`FORGEJO_ACCESS_ENGINE_ADMISSION_FILE` for this guarded callback profile. A signed
caller cannot select the application's tenant. No operational configuration or
credential is supplied by the build.
The callback requires the online KeyCape human ID token's exact RS256 `at_hash`
access-grant binding before private caller use. Access-only `jti`/`scope` cannot
substitute for an ID token. It joins independently verified subject/tenant/type,
issuance, assurance/authentication time and tenant provenance to a fresh issuer
observation before the PDP, then checks current status again after the decision.
Source tests refuse mixed, revoked, suspended, stale, malformed and unavailable
inputs; actual HTTPS issuer PKCE/discovery/UserInfo/status/revocation and real
Forgejo provider/callback run in five isolated consumer processes. MFA session
seeds and PDP admissions are synthetic. Complete release build/asset/OCI evidence
for the preceding patch remains historical; this revision requires its own build
and independent owner/package/type/custody admission before operational use.
The user selected current KeyCape checks on every ongoing API/Git/SSH operation
for NetKingdom-linked accounts, while retaining the separate local break-glass
path. That is an implementation requirement under RAPPS16-T09/NK48-T43, not a
claim that the callback implements those routes or a native deployment grant.
Ongoing resource/action/current-group decisions, protected operation/inverse and
all release writers/journal custody remain acceptance work.

View file

@ -0,0 +1,244 @@
{
"schema": "railiance-apps.forgejo-release-inputs.v1",
"forgejo_version": "11.0.3",
"source_archive_sha256": "1ddd8c926ca07d40436897971458ace7dcf1d3496a0813ab9e0d869edef2365c",
"source_commit": "b81aa4d0807cc1e2d6a2fbafd0b75c46cc0ae237",
"patch_sha256": "9fe3d7baa648c9347964a177befab5070e00151d1ec5ddff16c41890db84e62a",
"go_archive_sha256": "3333f6ea53afa971e9078895eaa4ac7204a8c6b5c68c10e6bc9a33e8e391bdd8",
"go_version": "1.24.3",
"node_archive_sha256": "dafe2e8f82cb97de1bd10db9e2ec4c07bbf53389b0799b1e095a918951e78fd4",
"node_version": "22.15.0",
"source_date_epoch": 1752105237,
"release_version": "11.0.3+cust80.9fe3d7ba",
"tags": "bindata timetzdata sqlite sqlite_unlock_notify",
"runtime_base": "code.forgejo.org/forgejo/forgejo@sha256:41649dff5311664097d29b4d80620403f0b27e7c0eee0b8af79b132a37f3807b",
"runtime_index_digest": "sha256:691657c7e64af7730a2e885e6d022cf16ae60724931c5392bd61838afadcaeba",
"platform": "linux/amd64",
"reviewed_outputs": [
{
"path": "cmd/admin_auth_oauth.go",
"sha256": "d718d817c46f6393488a9c5a894b7535640cd0ec1a1ae3f72f8411a2bbec3b38"
},
{
"path": "cmd/admin_auth_oauth_secret_linux.go",
"sha256": "6741961f1df67c1afbf34c796e549425f0e04a0d0678c6eedb05f66d4754c64b"
},
{
"path": "cmd/admin_auth_oauth_secret_linux_test.go",
"sha256": "b28c2369dfab9bf2ca48e707e01b4394a36c8be5490cfe5bb658017bb626c4b2"
},
{
"path": "cmd/admin_auth_oauth_secret_other.go",
"sha256": "54056c989a4757e73db0252130e7c16bf16f5ec8eaaca2468f019ba2ef5c532d"
},
{
"path": "go.mod",
"sha256": "11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0"
},
{
"path": "models/auth/source_operation.go",
"sha256": "ef1b07ef6cf454015d00cde8588835f5118d829da91eb0303d37184859345a31"
},
{
"path": "models/auth/source_operation_internal_test.go",
"sha256": "af0f424d3a1fd2b29793b64baa1fa07284fee7ea0b7721c503db4efac0d871cb"
},
{
"path": "models/auth/source_operation_postgres_export_test.go",
"sha256": "b9d1553144766b15c408295b87506106935abf4ed61460598b58dc81825e808d"
},
{
"path": "models/auth/source_operation_postgres_test.go",
"sha256": "4a37504e5af00580d114ef1bed5cb32e09e4ec2de5fd3192adef043a4ea5f7fe"
},
{
"path": "models/auth/source_operation_process_test.go",
"sha256": "40d33b11a01cb659f0bf6f8b545c4d79910e8c082a789060bcbc9cadeee965bd"
},
{
"path": "models/auth/source_operation_test.go",
"sha256": "77d311bca42f48faf3fbdc15c53582e3d8a6d9108a72ed7bb6172e5b88951428"
},
{
"path": "models/db/context.go",
"sha256": "d4c4acfcb8987071aecf5cd957b3821c8d2619a2696398d37c2970f2527686f7"
},
{
"path": "models/db/engine.go",
"sha256": "d21a5b0b9ca37ac48222b79007467a232abc38edda99b3c5f54f1f4c5ab795e8"
},
{
"path": "models/db/sensitive_sql.go",
"sha256": "06a298d0d6e628e6920384d4ddeef449e40c090841ebaa3f6d175725a45ae584"
},
{
"path": "models/db/sensitive_sql_logging_test.go",
"sha256": "d924b35d30686473caf29601cf51cc363ba68158cb4ef0981c9e2fc7b03c04b7"
},
{
"path": "models/db/sensitive_sql_test.go",
"sha256": "de6353c955047c3311747e4777b9f9b2195e9efb19160b971dd758dfc94cf31b"
},
{
"path": "models/db/tracing_context_test.go",
"sha256": "612b60c67a2be7e2da9173b95d5ef5abd7b9e1e377360828e39a622610bba070"
},
{
"path": "routers/web/admin/auths.go",
"sha256": "3498229e06fb9ff52396bcb5e68c0cc2d2ad24e6494533d8717d7c833f3e2923"
},
{
"path": "routers/web/auth/oauth.go",
"sha256": "58bff778e0dc17a521706289bf45c3e0d76a759a0874f76c379b3d1ddb419b17"
},
{
"path": "services/auth/source/oauth2/access_engine.go",
"sha256": "d16330043825d0284253e65d28bda2de4edbc633e4887d8c3618a106ee251daa"
},
{
"path": "services/auth/source/oauth2/access_engine_assurance.go",
"sha256": "42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642"
},
{
"path": "services/auth/source/oauth2/access_engine_assurance_test.go",
"sha256": "44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3"
},
{
"path": "services/auth/source/oauth2/access_engine_azp_test.go",
"sha256": "5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce"
},
{
"path": "services/auth/source/oauth2/access_engine_callback_test.go",
"sha256": "1cbe378cc20149be7b354484ab3297916733564f03a2441cd276f9039d043580"
},
{
"path": "services/auth/source/oauth2/access_engine_current_token.go",
"sha256": "4a2e484ece7cf74686173d5e85c127643287570c2298af135ddfdc7c450b18d8"
},
{
"path": "services/auth/source/oauth2/access_engine_current_token_test.go",
"sha256": "58f799c08a7b4638780abc511bd1a7359bd5e691c7805ff54582d02d2f42cae1"
},
{
"path": "services/auth/source/oauth2/access_engine_purpose_test.go",
"sha256": "533dc3153fcf9bfcf7bfc08712c057092c7b3892882f217fb8f839fdbdd99939"
},
{
"path": "services/auth/source/oauth2/access_engine_test.go",
"sha256": "9112a142983c0a58c5d40344b58a20fa2c1bdbc7a79b5c586796362839a43b48"
},
{
"path": "services/auth/source/oauth2/current_issuer_fixture_test.go",
"sha256": "24fb430e72848326c5d9ef42eab5028cf915dd601f43fb06010b32b568908781"
},
{
"path": "services/auth/source/oauth2/providers_openid.go",
"sha256": "5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf"
},
{
"path": "services/auth/source/oauth2/required_claim_values.go",
"sha256": "29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda"
},
{
"path": "services/auth/source/oauth2/required_claim_values_test.go",
"sha256": "54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0"
},
{
"path": "services/auth/source/oauth2/source.go",
"sha256": "44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642"
},
{
"path": "services/auth/source/oauth2/source_callout.go",
"sha256": "6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c"
},
{
"path": "third_party/access-engine-pep/LICENSE",
"sha256": "ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6"
},
{
"path": "third_party/access-engine-pep/go.mod",
"sha256": "fea700810e47c1ba5638b86a168f8531c083a6e1222ca80bd2a8947b2ea6f2c3"
},
{
"path": "third_party/access-engine-pep/internal/sign/sign.go",
"sha256": "0df9515922e90775ebbfac798361dcafb20da8a4213796b44e246b8a92046cde"
},
{
"path": "third_party/access-engine-pep/pkg/api/canonical.go",
"sha256": "a122a2e8cd524333b13c5939c7a3685e41efbe47497a64e0c7297c92b7f10d19"
},
{
"path": "third_party/access-engine-pep/pkg/api/caring.go",
"sha256": "1907156f0f889e7b9e9c14970cf7a86a20d00478a496efaf92c7231f27fec5e3"
},
{
"path": "third_party/access-engine-pep/pkg/api/resource_manifest.go",
"sha256": "f0fa4080287d389ed48c2b1d6af96cc62e57ef72c902333f677f964420be133f"
},
{
"path": "third_party/access-engine-pep/pkg/decisionpep/client.go",
"sha256": "0d57f6ddaca5c3f81ce426c0e8b14ba14ea5962096d3be5909aa456eef19eeba"
},
{
"path": "third_party/access-engine-pep/pkg/decisionpep/client_test.go",
"sha256": "1a6be3485f580f95a1f1406fb76e525f2c4bf02fe04a424cfe0095bb728e3f62"
},
{
"path": "third_party/access-engine-pep/producer-source.json",
"sha256": "c1d83e3d79f9860ffaa61ec19a6b3415e914d3058b11b07cdd05a7d1182cdceb"
},
{
"path": "third_party/access-engine-pep/testsupport/signfixture/sign.go",
"sha256": "fe4aa86f444dc20145aefbaca35b24f046264b2c5c8b4e3b057a4496b77a4d29"
},
{
"path": "third_party/keycape-tokenstatus/LICENSE",
"sha256": "ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6"
},
{
"path": "third_party/keycape-tokenstatus/go.mod",
"sha256": "e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54"
},
{
"path": "third_party/keycape-tokenstatus/internal/jose/jose.go",
"sha256": "a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d"
},
{
"path": "third_party/keycape-tokenstatus/pkg/tokenstatus/client.go",
"sha256": "f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575"
},
{
"path": "third_party/keycape-tokenstatus/producer-source.json",
"sha256": "3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
}
],
"lockfiles": {
"go.mod": "11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
"go.sum": "19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
"package.json": "ab5c54ec7077501b50acf3ab5f1fff4f63a51abe99e720b8ecdc6cc6c78f39fc",
"package-lock.json": "fdd5c2f891311be7c01b1907b418c243ca1f0f951fd4b4ee5b5e931c43493657"
},
"host_tools": {
"/usr/bin/make": "d78b8f1d099fbcfb6f2f49ab87223b9b68fb3956642f92d6ec6de812e8afa965",
"/usr/bin/gcc": "1b99826121ae6682a634e5efe09bd3e3df58ce58e0b28f849114ab5b89139c26",
"/usr/bin/ld": "e46a122cc3a29feba3b1d0fc2a274b6e46538be016ebe381370c0801bb98b0b8",
"/usr/bin/ar": "534681ac11c18868cfc4fdf98770aa0ba8973eedc90c231e94e6ba96e1a04f27",
"/usr/bin/bwrap": "e318903862396f96de3df57264e0158682b952fd3fb53ac23d876413e7b30f71",
"/usr/lib/x86_64-linux-gnu/libc.a": "a40c7eae7581f977b65eddb81cbc799213a695749e039d966db55bdee4ac9621",
"/usr/lib/x86_64-linux-gnu/libm.a": "420cd3c406f6f8c12deb3d1291146e9c091ee015a7c39eaf2237f01e80d6f0ae"
},
"runtime_base_config_sha256": "bc5a3df111729582faadebd01d126ba769e2b65d0c62f306f6bc79f4e562452b",
"patch_file": "forgejo-security-extension.patch",
"candidate_review_status": "current-token callback source tested; complete release rebuild and independent owner review pending",
"producer_source_commit": "ca0cb75729a63c9288b7333382d0fa3eb1e8c9a3",
"optional_source_tests": {
"source_operation_postgres": {
"database_major": 16,
"synthetic_fixture_image": "postgres@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777",
"source_only": true,
"production_connection_or_credentials": false
}
},
"identity_client_source_revision": "git:08c083bae9ff35fbaf028c36d72a95b57d2541b7",
"identity_issuer_source_revision": "git:e2c5a947829568baf837543a6708fd0c3f6a863b"
}

View file

@ -0,0 +1,333 @@
---
id: RAPPS-WP-0016
type: workplan
title: "Forgejo sign-in through NetKingdom, before the forge closes"
domain: infotech
repo: railiance-apps
status: active
flavor: implementation
owner: claude-code
topic_slug: railiance
created: "2026-10-08"
updated: "2026-10-18"
depends_on:
- KEY-WP-0038
related_repos:
- prj-forgejo-org-refactor
- key-cape
- railiance-platform
state_hub_workstream_id: "f6b1d6f6-0abb-5774-87b9-bf3cfb640a3b"
---
# RAPPS-WP-0016 - Forgejo sign-in through NetKingdom, before the forge closes
## Why
`forgejo.coulomb.social` is closing: private by default, no anonymous access
(prj-forgejo-org-refactor ORGREF-WP-0002, decision
`decisions/forge-visibility-policy.md`). The founder's condition is that sign-in
runs through NetKingdom first. railiance-apps owns the Forgejo Helm release
(`docs/forgejo-on-railiance01.md`: S5 Helm release, ingress, operator Makefile),
so the Forgejo side lands here. This plan is ORGREF-WP-0002-T05. The NetKingdom
side is key-cape `KEY-WP-0038`.
## Boundaries
- **Founder guardrail:** tighten gradually; never lock anyone out; never risk
assets; never reach a state that is hard to repair. Every step keeps the
current sign-in path working until the new one is proven.
- **No visibility change** in this plan. Repositories, packages and
`REQUIRE_SIGNIN_VIEW` are ORGREF-WP-0002-T08.
- **Configuration in git.** The authentication source is declared in
`helm/forgejo-values.yaml` (`gitea.oauth`), with its secret drawn from an
ExternalSecret over OpenBao. No click-ops, and no secret value in git.
- Open registration is already disabled (`service.DISABLE_REGISTRATION: true`).
This plan keeps it that way.
## Tasks
```task
id: RAPPS-WP-0016-T01
status: done
priority: high
state_hub_task_id: "98004ed0-83e6-5f6e-9b18-15b8b2aec2e7"
```
### T01 — Make the local administrator a sealed break-glass
Before anything about sign-in changes: confirm the local administrator account
works, keep its credential only in OpenBao, and document recovery (who, how,
where it is logged) in `docs/forgejo-on-railiance01.md`. Verify one break-glass
sign-in and record it without the credential.
Acceptance: a recorded break-glass sign-in, with the recovery path written down.
This is the "never lock ourselves out" floor for every later step.
Done 2026-10-17: founder sealed `forgejo_admin` into OpenBao
`platform/workloads/forgejo/break-glass-admin` (version 1) and verified a local
sign-in (HTTP 200, site admin): `docs/evidence/2026-10-17-forgejo-breakglass-signin.json`.
Recovery path: `docs/forgejo-on-railiance01.md` § Break-glass administrator.
```task
id: RAPPS-WP-0016-T02
status: wait
depends_on: [RAPPS-WP-0016-T01, KEY-WP-0038-T02]
blocking_reason: "Break-glass done (T01); needs the NetKingdom client registered with its secret in custody (KEY-WP-0038-T02)."
priority: high
state_hub_task_id: "13cd3aec-c87d-5d3f-a421-39d8ff131edd"
```
### T02 — Add the NetKingdom authentication source
- an ExternalSecret delivering the `forgejo` client secret from OpenBao to a
Secret in the `forgejo` namespace. Custody path is
`platform/workloads/keycape/forgejo`, field `CLIENT_SECRET`, read by ESO through
Kubernetes-auth role `external-secrets-keycape-forgejo-forgejo`, which carries
only policy `workload-kv-read-keycape-forgejo-forgejo` (net-kingdom 074b3a0;
replaces the earlier `platform/workloads/forgejo/netkingdom-oidc-client` proposal). The
SA/SecretStore/ExternalSecret shape follows the NK-WP-0048
`deliver-consumer-secret` receipt contract
(`docs/evidence/nk48-t16-owner-review/owner-review-railiance-apps.json`)
- `gitea.oauth`: name `netkingdom` (must match the redirect URI registered in
key-cape), provider `openidConnect`, auto-discovery from
`https://kc.coulomb.social/.well-known/openid-configuration`, scopes
`openid profile email groups`, group-claim name `groups`
- `oauth2_client` settings: no automatic account creation for unknown subjects;
account linking to existing local accounts by username after a successful
local sign-in (so the founder and agent accounts keep their history)
- Helm upgrade with the previous revision noted for rollback
Local password sign-in stays enabled.
Acceptance: the "Sign in with netkingdom" option appears. Local sign-in still
works. Rollback is one `helm rollback` to the noted revision.
```task
id: RAPPS-WP-0016-T03
status: wait
depends_on: [RAPPS-WP-0016-T02]
blocking_reason: "Linking and team mapping follow a working authentication source."
priority: high
state_hub_task_id: "81f072aa-c316-511e-a2cd-21dd1b1d0a00"
```
### T03 — Link accounts and map groups to teams
- The founder links their account through NetKingdom (MFA), then signs in again
through NetKingdom only and sees the same repositories, organisations and
admin rights.
- Map NetKingdom groups (names agreed in KEY-WP-0038-T01) to organisation teams:
owners and readers at least. Create the read-only teams that
ORGREF-WP-0002-T06 consumers will use.
- Apply KEY-WP-0038-T03's decision on agents and services: NetKingdom sign-in, or
scoped tokens bound to service identities.
Acceptance: founder and one agent work through NetKingdom; group-to-team mapping
verified by adding and removing one test membership; break-glass still works.
```task
id: RAPPS-WP-0016-T04
status: wait
depends_on: [RAPPS-WP-0016-T03]
needs_human: true
blocking_reason: "Restricting local password sign-in is a founder decision once NetKingdom sign-in is proven."
priority: medium
state_hub_task_id: "32c54737-c40f-5fde-9b91-163f8dcb0068"
```
### T04 — Decide on local password sign-in
Once NetKingdom sign-in has been used for a while without problems, the founder
decides whether local password sign-in is turned off for everyone except
break-glass. This is not required to close the forge; it is the next tightening
step, taken separately.
```task
id: RAPPS-WP-0016-T05
status: wait
depends_on: [RAPPS-WP-0016-T02]
blocking_reason: "Waits for net-kingdom's answer to the Apps 1.3 needs-change record (the-custodian docs/changes/netkingdom-target-2026-10-17/nk48-1.3-owner-review/owner-review-railiance-apps.json: RAPPS-1.3-C1..C3) and the re-frozen 1.3 candidate."
priority: medium
state_hub_task_id: "728786c9-f1f9-5983-a6a4-dea21faf67f5"
```
### T05 — Apps owner seams for NK-WP-0048 consumer steps
Implement the real Apps capabilities behind the `deliver-consumer-secret` and
`hand-over` adapters, as owner-run paths (option C): `owner_apply` /
`owner_remove` emitting `netkingdom.consumer-secret-delivery.v1` receipts from the
T02 objects; a consumer verifier that observes Forgejo sign-in and the T03
positive/negative group probes; and a committed-evidence hand-over receipt store.
See the owner-review record for the contract.
Acceptance: receipts produced from the live T02/T03 run validate against the
adapters' checks with no value-bearing keys.
## Also in scope: stale node credential
The k3s `registries.yaml` on railiance01 still holds a login for the frozen Gitea
host `92.205.130.254` (ORGREF-WP-0002-T02). Coordinate its removal with the node
owner when ORGREF-WP-0002-T06 adds the forge registry login, in the same change.
## T06 — Package the reviewed consumer source through the Apps build path
```task
id: RAPPS-WP-0016-T06
status: done
priority: high
state_hub_task_id: "08ab8921-7801-5309-b84b-bc2a3ceea1e4"
```
Completed 2026-10-18, source build tooling only. The existing Apps Makefile now
has explicit prepare, public-dependency acquisition, offline build, artifact
verification and local OCI export targets for the pinned official Forgejo11.0.3
source plus reviewed multi-value claim patch. Source/toolchains/locks/compiler
inputs/base image are fixed. Third-party application source stays in a new /tmp
workspace; Apps holds the release input lock, patch and build recipe.
The original build profile produced identical frontend assets, bindata sources
and complete static executable in two workspaces, and identical local OCI
archives in two no-cache exports. Independent review then found host process
argv visibility through /proc (APPSBUILD-R1-F01). The revised recipe adds a
private PID namespace; exact source and synthetic canary regression are separately
reviewed. The original full builds are historical r1-profile evidence; no full
r2 build is claimed. Original packet/review and failure history remain in
Custodian apps-forgejo-release-build-r1 and apps-release-independent-review-r1;
the correction is apps-forgejo-release-build-r2. This is coordinator source
integration after independent technical review, not formal owner assent or image
admission. Helm values, deployed image, local sign-in and visibility are unchanged.
NK-WP-0048-T39 remains progress for the full release acceptance scope.
## T07 — Keep the client secret out of process arguments
```task
id: RAPPS-WP-0016-T07
status: progress
priority: high
state_hub_task_id: "786d6584-6db1-5a10-9324-d7b94db0df94"
```
Implement and independently review a real Forgejo add/update --secret-file path
and the actual chart helper/mount adaptation. Preserve the existing CLIENT_SECRET
key and custody route; use no agent-value argument or environment fallback. Test
bounded stable nofollow file reads, exact existing minted-secret encoding,
mutual exclusion/update preservation, fixed errors and actual Helm rendering.
Private mount identity/reader set and new package/type revisions need explicit
owner/runtime admission. The in-progress isolated candidate is Custodian
forgejo-private-input-r1; source completion is distinct from T02 native delivery.
## T08 — Provide a transactional application-source inverse and durable recovery
```task
id: RAPPS-WP-0016-T08
status: progress
priority: high
state_hub_task_id: "4ab8a5e7-ab32-5ee2-aaf2-6da246d1b0c8"
```
Implement actual owner database primitives that record operation intent/outcome
in the same durable database, compare complete raw persisted before/post/current
source state privately, and serialize conflicting application writers at the DB
boundary. Never emit stored credential/config bytes or delete/detach linked users
or a preexisting/shared source. SQLite and PostgreSQL transaction semantics must
be tested against the real app packages; unsupported backends refuse.
T05 and NK-WP-0048-T34 additionally require actual authenticated execution and
fresh inverse sessions, admitted evidence/recovery and exclusion of every release
writer, including Helm/runtime writers. The candidate DB primitive alone does
not establish those facilities. Helm rollback/omission does not remove persisted
OAuth sources: verify the exact app-state inverse before any image downgrade.
If links prevent removal, retain the extended image and reconcile. The earlier
T02 single-Helm-rollback statement is not sufficient recovery for this extension;
its historical text is retained and corrected here.
2026-10-18 PostgreSQL/source-context continuation for T08: actual Forgejo model
schemas, OAuth configuration types and production DB initialization run on a
disposable PostgreSQL 16.14 instance. All 37 test/subtest results pass across
default, explicit-public and named schemas. Separate client processes prove
rollback after process death immediately following INSERT, committed apply
survival through a real server restart, and subsequent reconciliation/inverse.
Exact raw restoration, private drift, linked-user/external-link refusal, trigger
rollback, table-writer exclusion and concurrent preparation are covered.
Controlled copies with the table lock or private-query context removed fail the
corresponding tests in all three schema profiles. All owned fixture containers
and volumes were removed; no deployed database or real credential was used.
The diagnostics check exposed a real tracing-hook composition panic. The new
40-path build candidate publishes the task context into XORM's shared query
context so subsequent hooks preserve it. Actual private/ordinary SQLite traces
and PostgreSQL driver-error hooks cover the correction. Six focused SQLite DB
checks and all 94 selected composed package results pass without skips. The
previous panic, fixture/compilation failures and refinement history remain in
Custodian forgejo-postgres-source-r1. Complete release evidence uses the new
feb10793 patch; the prior frozen artifact is historical.
This proves bounded source/database mechanics. T08 remains progress for current
authenticated initiating/inverse sessions, release-wide writer exclusion, private
evidence/custody/retention and native deployed-database acceptance. Independent
owner review and exact package/type/runtime admission remain open. T07/T09 and
all original task UUIDs and statuses are preserved.
## T09 — Enforce authenticated Access Engine decisions in the application path
```task
id: RAPPS-WP-0016-T09
status: progress
priority: high
state_hub_task_id: "d3804638-c73d-5b9f-a456-4262f62f9e6b"
```
Use the producer-owned decision format/canonical bytes and independently admitted
signer/policy/request pins. Forgejo must verify the caller's issuer/tenant and
assurance facts, obtain and authenticate the exact Access Engine resource/action
decision, and refuse missing, stale, wrong-pin, unavailable or malformed replies.
Keep the original three-group any-of access set as policy input; its local
compatibility gate is not a substitute PDP or an architecture exception. An
issuer allowed/denied claim cannot manufacture authorization. Source integration
needs real provider/callback/session wiring and meaningful producer-consumer
positive/adverse tests before re-freezing the case and package. Existing break-
glass/local account and linking behavior remain within the declared owner scope.
2026-10-18 source continuation for T07/T08/T09: the digest-bound build
candidate now composes all 37 paths, including private secret-file input, actual
source-state transactions/private SQL hooks, the producer-owned Access Engine
Check client from ca0cb757 and guarded OIDC callback enforcement. The parser uses
the actual KeyCape typed assurance/authentication-time/provenance contract; its
actual issuer fixture/callback suite passes 71 test/subtest results, and selected
composed packages pass 93 with no skips. The previous incompatible-string-claim
rejection is retained. All patch postimages independently replay from the pinned
official archive. Complete release build/verification is tracked in Custodian
forgejo-keycape-claims-r1; earlier release outputs do not prove this revision.
This is coordinator source integration of a tested build candidate; independent
owner review and formal operational admission remain open. T07's chart/private
mount admission, T08's fresh authenticated operation/inverse and release-wide
writer exclusion, T09's ongoing HTTP/API/Git/SSH paths, private evidence/retention,
PostgreSQL/native proof and actual issuer/factor/custody acceptance remain open.
No trust, deployed image, Helm values, source DB, user/link state or local sign-in
is changed by this build-source update. The preserved legacy patch is historical;
the input lock now explicitly selects forgejo-security-extension.patch.
2026-10-18 current-token callback continuation: the new 49-path source candidate
uses the exact generated KeyCape online client, without a consumer token registry.
Mandatory owner application-tenant/current-token admission, ID-token purpose,
exact signed RS256 at_hash, current status before/after the PDP, and signed
identity/assurance/authentication-time/tenant-provenance joins refuse substitution
and stale authority. The latest focused callback/identity suite passes 91 results
without skips; the selected four composed packages passed 114 results with one
optional PostgreSQL test skipped before the additional owner-tenant regression.
The actual HTTP PKCE/discovery/UserInfo/status/revocation and Forgejo provider /
callback pass five fresh consumer processes: two current grants accepted, mixed
pair/revoked original/suspended human refused. The original verifier and issuer
fail their new controls. MFA session seeds and PDP admission are synthetic.
The user explicitly selected current KeyCape checks on every API/Git/SSH
operation for linked accounts, keeping the separate local break-glass path.
That full ongoing route/resource/action/group profile is still implementation
work here; callback proof does not close it. Input lock now selects patch
9fe3d7ba; complete release/asset/OCI checks, independent owner assent,
software/type/package/custody admission, real factors/current sessions and native
acceptance remain open. T07/T08 and every existing task UUID/status are preserved.
Exact source/evidence: Custodian forgejo-current-token-r1.

View file

@ -0,0 +1,14 @@
{
"workplan_id": "f6b1d6f6-0abb-5774-87b9-bf3cfb640a3b",
"id": "1f779c83-05d1-465a-a0e0-e4741fd6110a",
"topic_id": null,
"task_id": "d3804638-c73d-5b9f-a456-4262f62f9e6b",
"decision_id": null,
"event_type": "note",
"summary": "Published Apps ab3cf5cd, retaining source a927ec8c and explicit validation-document refinement. New 49-path Forgejo source candidate consumes unchanged generated KeyCape adapter and requires owner application tenant/current-token admission, ID purpose, exact at_hash, signed fact joins and current checks before/after PDP. Latest focused 91 passes, final selected four-package suite 116 passes with zero skips. Initial file-replacement fixture skip/cause retained; fresh private/read-only fixtures restore check. Five actual issuer/provider/callback consumer processes pass with synthetic MFA/PDP admission; original issuer and purpose checks fail expected controls. Exact source replay and primary/remote verified; all nine task UUID/status bindings and preexisting staged brief preserved. User selected current checks on every API/Git/SSH operation, separate local break-glass retained; ongoing resource/group/action implementation, release rebuild, independent/native admission remain open in T09/NK48-T43.",
"detail": null,
"author": "codex",
"session_id": null,
"created_at": "2026-10-18T10:27:23.748772Z",
"workstream_id": "f6b1d6f6-0abb-5774-87b9-bf3cfb640a3b"
}

View file

@ -0,0 +1,7 @@
{
"summary": "Published Apps ab3cf5cd, retaining source a927ec8c and explicit validation-document refinement. New 49-path Forgejo source candidate consumes unchanged generated KeyCape adapter and requires owner application tenant/current-token admission, ID purpose, exact at_hash, signed fact joins and current checks before/after PDP. Latest focused 91 passes, final selected four-package suite 116 passes with zero skips. Initial file-replacement fixture skip/cause retained; fresh private/read-only fixtures restore check. Five actual issuer/provider/callback consumer processes pass with synthetic MFA/PDP admission; original issuer and purpose checks fail expected controls. Exact source replay and primary/remote verified; all nine task UUID/status bindings and preexisting staged brief preserved. User selected current checks on every API/Git/SSH operation, separate local break-glass retained; ongoing resource/group/action implementation, release rebuild, independent/native admission remain open in T09/NK48-T43.",
"event_type": "note",
"author": "codex",
"workplan_id": "f6b1d6f6-0abb-5774-87b9-bf3cfb640a3b",
"task_id": "d3804638-c73d-5b9f-a456-4262f62f9e6b"
}

View file

@ -0,0 +1,10 @@
{
"initial_source_revision": "git:a927ec8cc821a93688ca939e5a06eb0379f8d9c3",
"final_revision": "git:ab3cf5cdc56d085d040256e2e84633b0289ab0af",
"initial_postimages_exact": 4,
"correction_postimages_exact": 2,
"executed_correction_manifest_sha256": "sha256:b4fb2e4ed9fe46c2fa6947384a23815dcbd2e4606555bdd3dcde4093382d2e92",
"original_command_manifest_retained_byte_exact": true,
"published_correction_source_paths": "separate frozen correction capsule; same source bytes/digests",
"runtime_or_patch_changed_by_correction": false
}

View file

@ -0,0 +1,21 @@
{
"owner": "railiance-apps",
"base_revision": "git:a927ec8cc821a93688ca939e5a06eb0379f8d9c3",
"remote_base_revision": "git:308e83fcfbf9ec98dbe6b0286cbe8ec7be8899b3",
"files": [
{
"path": "build/forgejo/README.md",
"before_sha256": "sha256:3cd9ea9e0078ebe76b49337df92b00b48600524e664afe419306fe931cdb651c",
"sha256": "sha256:bce802e2d57f699da4911a53b8a1e424bbc1bbd995eff4f1fd1011d1c6527f70",
"source": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/forgejo-current-token-r1/apps-owner-source/build/forgejo/README.md"
},
{
"path": "workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md",
"before_sha256": "sha256:48eae582342a4c62ba38140ed2f4326102c5ca627636afe36432eb2fc692f74a",
"sha256": "sha256:3341dedc6b01d6b6755c55bafdd81a316d02369b209f7d760434d3bb15f70a4a",
"source": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/forgejo-current-token-r1/apps-owner-source/workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md"
}
],
"reason": "correct initial skip classification and record final 116-pass zero-skip rerun; original source commit retained",
"runtime_source_changed": false
}

View file

@ -0,0 +1,21 @@
{
"owner": "railiance-apps",
"base_revision": "git:a927ec8cc821a93688ca939e5a06eb0379f8d9c3",
"remote_base_revision": "git:308e83fcfbf9ec98dbe6b0286cbe8ec7be8899b3",
"files": [
{
"path": "build/forgejo/README.md",
"before_sha256": "sha256:3cd9ea9e0078ebe76b49337df92b00b48600524e664afe419306fe931cdb651c",
"sha256": "sha256:bce802e2d57f699da4911a53b8a1e424bbc1bbd995eff4f1fd1011d1c6527f70",
"source": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/forgejo-current-token-r1/apps-owner-source-correction/build/forgejo/README.md"
},
{
"path": "workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md",
"before_sha256": "sha256:48eae582342a4c62ba38140ed2f4326102c5ca627636afe36432eb2fc692f74a",
"sha256": "sha256:3341dedc6b01d6b6755c55bafdd81a316d02369b209f7d760434d3bb15f70a4a",
"source": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-18/forgejo-current-token-r1/apps-owner-source-correction/workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md"
}
],
"reason": "correct initial skip classification and record final 116-pass zero-skip rerun; original source commit retained",
"runtime_source_changed": false
}

View file

@ -0,0 +1,11 @@
{
"workspace": "/tmp/custodian-forgejo-current-token-r1",
"apps_base": "git:308e83fcfbf9ec98dbe6b0286cbe8ec7be8899b3",
"official_source": "git:b81aa4d0807cc1e2d6a2fbafd0b75c46cc0ae237",
"base_patch_sha256": "sha256:feb1079399a43989742ae0a946c1ada50113d1821e6541f97f0b5592ee1fc20c",
"all_base_source_postimages_exact": 40,
"keycape_producer": "git:08c083bae9ff35fbaf028c36d72a95b57d2541b7",
"generated_adapter_files_exact": 5,
"real_credentials": false,
"native_admission": false
}

View file

@ -0,0 +1,8 @@
{
"compiled_test_binary": "sha256:8052b5598371a595388c32c46ed9815349b3285f3ed864ec3dece75fafa8852e",
"exact_pinned_runtime_and_export_files": 26,
"latest_focused_results": 91,
"newest_change": "one owner-tenant negative test; production and generated code unchanged",
"complete_release_build": false,
"native_admission": false
}

View file

@ -0,0 +1,261 @@
{
"official_archive_sha256": "sha256:1ddd8c926ca07d40436897971458ace7dcf1d3496a0813ab9e0d869edef2365c",
"base_patch_sha256": "sha256:feb1079399a43989742ae0a946c1ada50113d1821e6541f97f0b5592ee1fc20c",
"patch_sha256": "sha256:9fe3d7baa648c9347964a177befab5070e00151d1ec5ddff16c41890db84e62a",
"files": [
{
"path": "cmd/admin_auth_oauth.go",
"before_sha256": "sha256:d5faee30ffbb9a560256c92a0e05e652b79e95e016448cbac7dbb6ad0fdad937",
"after_sha256": "sha256:d718d817c46f6393488a9c5a894b7535640cd0ec1a1ae3f72f8411a2bbec3b38"
},
{
"path": "cmd/admin_auth_oauth_secret_linux.go",
"before_sha256": null,
"after_sha256": "sha256:6741961f1df67c1afbf34c796e549425f0e04a0d0678c6eedb05f66d4754c64b"
},
{
"path": "cmd/admin_auth_oauth_secret_linux_test.go",
"before_sha256": null,
"after_sha256": "sha256:b28c2369dfab9bf2ca48e707e01b4394a36c8be5490cfe5bb658017bb626c4b2"
},
{
"path": "cmd/admin_auth_oauth_secret_other.go",
"before_sha256": null,
"after_sha256": "sha256:54056c989a4757e73db0252130e7c16bf16f5ec8eaaca2468f019ba2ef5c532d"
},
{
"path": "go.mod",
"before_sha256": "sha256:652162a4a95c6d1c349d2a8c0b514def5abd837f001137b77b7b3f4a0f162d00",
"after_sha256": "sha256:11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0"
},
{
"path": "models/auth/source_operation.go",
"before_sha256": null,
"after_sha256": "sha256:ef1b07ef6cf454015d00cde8588835f5118d829da91eb0303d37184859345a31"
},
{
"path": "models/auth/source_operation_internal_test.go",
"before_sha256": null,
"after_sha256": "sha256:af0f424d3a1fd2b29793b64baa1fa07284fee7ea0b7721c503db4efac0d871cb"
},
{
"path": "models/auth/source_operation_postgres_export_test.go",
"before_sha256": null,
"after_sha256": "sha256:b9d1553144766b15c408295b87506106935abf4ed61460598b58dc81825e808d"
},
{
"path": "models/auth/source_operation_postgres_test.go",
"before_sha256": null,
"after_sha256": "sha256:4a37504e5af00580d114ef1bed5cb32e09e4ec2de5fd3192adef043a4ea5f7fe"
},
{
"path": "models/auth/source_operation_process_test.go",
"before_sha256": null,
"after_sha256": "sha256:40d33b11a01cb659f0bf6f8b545c4d79910e8c082a789060bcbc9cadeee965bd"
},
{
"path": "models/auth/source_operation_test.go",
"before_sha256": null,
"after_sha256": "sha256:77d311bca42f48faf3fbdc15c53582e3d8a6d9108a72ed7bb6172e5b88951428"
},
{
"path": "models/db/context.go",
"before_sha256": "sha256:10774e272206c7a8d6ebfb106b37d535de77f7e96ba57657b4dad960639f8519",
"after_sha256": "sha256:d4c4acfcb8987071aecf5cd957b3821c8d2619a2696398d37c2970f2527686f7"
},
{
"path": "models/db/engine.go",
"before_sha256": "sha256:774cedc74e1a8d9ae6f9e5f106c6cbb66f12c11b42e99ef07a7affd03269c3d2",
"after_sha256": "sha256:d21a5b0b9ca37ac48222b79007467a232abc38edda99b3c5f54f1f4c5ab795e8"
},
{
"path": "models/db/sensitive_sql.go",
"before_sha256": null,
"after_sha256": "sha256:06a298d0d6e628e6920384d4ddeef449e40c090841ebaa3f6d175725a45ae584"
},
{
"path": "models/db/sensitive_sql_logging_test.go",
"before_sha256": null,
"after_sha256": "sha256:d924b35d30686473caf29601cf51cc363ba68158cb4ef0981c9e2fc7b03c04b7"
},
{
"path": "models/db/sensitive_sql_test.go",
"before_sha256": null,
"after_sha256": "sha256:de6353c955047c3311747e4777b9f9b2195e9efb19160b971dd758dfc94cf31b"
},
{
"path": "models/db/tracing_context_test.go",
"before_sha256": null,
"after_sha256": "sha256:612b60c67a2be7e2da9173b95d5ef5abd7b9e1e377360828e39a622610bba070"
},
{
"path": "routers/web/admin/auths.go",
"before_sha256": "sha256:eeed6999e4655993b55873bb51efb4fcd946532255b9473d16e086e04a1ec911",
"after_sha256": "sha256:3498229e06fb9ff52396bcb5e68c0cc2d2ad24e6494533d8717d7c833f3e2923"
},
{
"path": "routers/web/auth/oauth.go",
"before_sha256": "sha256:6f454b3550b150178a8597a9437adb0c626556f34c15a13778c15d675b05659b",
"after_sha256": "sha256:58bff778e0dc17a521706289bf45c3e0d76a759a0874f76c379b3d1ddb419b17"
},
{
"path": "services/auth/source/oauth2/access_engine.go",
"before_sha256": null,
"after_sha256": "sha256:d16330043825d0284253e65d28bda2de4edbc633e4887d8c3618a106ee251daa"
},
{
"path": "services/auth/source/oauth2/access_engine_assurance.go",
"before_sha256": null,
"after_sha256": "sha256:42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642"
},
{
"path": "services/auth/source/oauth2/access_engine_assurance_test.go",
"before_sha256": null,
"after_sha256": "sha256:44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3"
},
{
"path": "services/auth/source/oauth2/access_engine_azp_test.go",
"before_sha256": null,
"after_sha256": "sha256:5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce"
},
{
"path": "services/auth/source/oauth2/access_engine_callback_test.go",
"before_sha256": null,
"after_sha256": "sha256:1cbe378cc20149be7b354484ab3297916733564f03a2441cd276f9039d043580"
},
{
"path": "services/auth/source/oauth2/access_engine_current_token.go",
"before_sha256": null,
"after_sha256": "sha256:4a2e484ece7cf74686173d5e85c127643287570c2298af135ddfdc7c450b18d8"
},
{
"path": "services/auth/source/oauth2/access_engine_current_token_test.go",
"before_sha256": null,
"after_sha256": "sha256:58f799c08a7b4638780abc511bd1a7359bd5e691c7805ff54582d02d2f42cae1"
},
{
"path": "services/auth/source/oauth2/access_engine_purpose_test.go",
"before_sha256": null,
"after_sha256": "sha256:533dc3153fcf9bfcf7bfc08712c057092c7b3892882f217fb8f839fdbdd99939"
},
{
"path": "services/auth/source/oauth2/access_engine_test.go",
"before_sha256": null,
"after_sha256": "sha256:9112a142983c0a58c5d40344b58a20fa2c1bdbc7a79b5c586796362839a43b48"
},
{
"path": "services/auth/source/oauth2/current_issuer_fixture_test.go",
"before_sha256": null,
"after_sha256": "sha256:24fb430e72848326c5d9ef42eab5028cf915dd601f43fb06010b32b568908781"
},
{
"path": "services/auth/source/oauth2/providers_openid.go",
"before_sha256": "sha256:6ddca7fb4ab8b16e6b096481cfb40ca5abdf3a327f38c895b95b3846cc7c2248",
"after_sha256": "sha256:5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf"
},
{
"path": "services/auth/source/oauth2/required_claim_values.go",
"before_sha256": null,
"after_sha256": "sha256:29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda"
},
{
"path": "services/auth/source/oauth2/required_claim_values_test.go",
"before_sha256": null,
"after_sha256": "sha256:54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0"
},
{
"path": "services/auth/source/oauth2/source.go",
"before_sha256": "sha256:a6ee5146c07d8efb1837ef91f1ab5f3139f69a1ff66f814d5e9c6053bf9b0d8b",
"after_sha256": "sha256:44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642"
},
{
"path": "services/auth/source/oauth2/source_callout.go",
"before_sha256": "sha256:c30c418182d4cb98b1e448f621c93322accfbbf92d6a59e6df1ae45a7aa8fc7b",
"after_sha256": "sha256:6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c"
},
{
"path": "third_party/access-engine-pep/LICENSE",
"before_sha256": null,
"after_sha256": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6"
},
{
"path": "third_party/access-engine-pep/go.mod",
"before_sha256": null,
"after_sha256": "sha256:fea700810e47c1ba5638b86a168f8531c083a6e1222ca80bd2a8947b2ea6f2c3"
},
{
"path": "third_party/access-engine-pep/internal/sign/sign.go",
"before_sha256": null,
"after_sha256": "sha256:0df9515922e90775ebbfac798361dcafb20da8a4213796b44e246b8a92046cde"
},
{
"path": "third_party/access-engine-pep/pkg/api/canonical.go",
"before_sha256": null,
"after_sha256": "sha256:a122a2e8cd524333b13c5939c7a3685e41efbe47497a64e0c7297c92b7f10d19"
},
{
"path": "third_party/access-engine-pep/pkg/api/caring.go",
"before_sha256": null,
"after_sha256": "sha256:1907156f0f889e7b9e9c14970cf7a86a20d00478a496efaf92c7231f27fec5e3"
},
{
"path": "third_party/access-engine-pep/pkg/api/resource_manifest.go",
"before_sha256": null,
"after_sha256": "sha256:f0fa4080287d389ed48c2b1d6af96cc62e57ef72c902333f677f964420be133f"
},
{
"path": "third_party/access-engine-pep/pkg/decisionpep/client.go",
"before_sha256": null,
"after_sha256": "sha256:0d57f6ddaca5c3f81ce426c0e8b14ba14ea5962096d3be5909aa456eef19eeba"
},
{
"path": "third_party/access-engine-pep/pkg/decisionpep/client_test.go",
"before_sha256": null,
"after_sha256": "sha256:1a6be3485f580f95a1f1406fb76e525f2c4bf02fe04a424cfe0095bb728e3f62"
},
{
"path": "third_party/access-engine-pep/producer-source.json",
"before_sha256": null,
"after_sha256": "sha256:c1d83e3d79f9860ffaa61ec19a6b3415e914d3058b11b07cdd05a7d1182cdceb"
},
{
"path": "third_party/access-engine-pep/testsupport/signfixture/sign.go",
"before_sha256": null,
"after_sha256": "sha256:fe4aa86f444dc20145aefbaca35b24f046264b2c5c8b4e3b057a4496b77a4d29"
},
{
"path": "third_party/keycape-tokenstatus/LICENSE",
"before_sha256": null,
"after_sha256": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6"
},
{
"path": "third_party/keycape-tokenstatus/go.mod",
"before_sha256": null,
"after_sha256": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54"
},
{
"path": "third_party/keycape-tokenstatus/internal/jose/jose.go",
"before_sha256": null,
"after_sha256": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d"
},
{
"path": "third_party/keycape-tokenstatus/pkg/tokenstatus/client.go",
"before_sha256": null,
"after_sha256": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575"
},
{
"path": "third_party/keycape-tokenstatus/producer-source.json",
"before_sha256": null,
"after_sha256": "sha256:3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
}
],
"exact_replay": true,
"fuzz": 0,
"native_admission": false,
"open_gates": [
"Ongoing current identity checks on HTTP/API/Git/SSH selected by user; integration remains required.",
"Actual resource/action PDP coverage and current group checks.",
"Fresh current protected operation/inverse, exact effects/approvals, journal custody and every-release-writer exclusion.",
"Complete release build, independent owner review, profile/package/type/custody admission and native acceptance."
]
}

View file

@ -0,0 +1,171 @@
{
"returncode": 1,
"seconds": 12.1,
"argv": [
"bwrap",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--unshare-net",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-forgejo-current-token-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod/cache/download",
"/cached",
"--ro-bind",
"/etc/ssl/certs",
"/etc/ssl/certs",
"--ro-bind",
"/etc/resolv.conf",
"/etc/resolv.conf",
"--chdir",
"/work/source",
"--clearenv",
"--ro-bind",
"/tmp/custodian-forgejo-successor-attempt2/modules",
"/work/modules",
"--bind",
"/tmp/custodian-forgejo-successor-attempt2/gocache",
"/work/gocache",
"--dir",
"/run",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--setenv",
"PATH",
"/work/go/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOPATH",
"/work/gopath",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOVCS",
"*:off",
"--setenv",
"GOMODCACHE",
"/work/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"GIT_CONFIG_NOSYSTEM",
"1",
"--setenv",
"GIT_CONFIG_GLOBAL",
"/work/home/test.gitconfig",
"--setenv",
"GIT_TERMINAL_PROMPT",
"0",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/go",
"/work/go",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--",
"go",
"test",
"-p",
"1",
"-json",
"-tags",
"sqlite sqlite_unlock_notify",
"./services/auth/source/oauth2",
"-run",
"^TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims$",
"-count=1"
],
"source_sha256_at_start": {
"go.mod": "sha256:11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
"go.sum": "sha256:19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
"services/auth/source/oauth2/access_engine.go": "sha256:d5f07f9491ac2212410e1fa8c0248d68ba3a723e7ca99e363627c5292d187aa8",
"services/auth/source/oauth2/access_engine_assurance.go": "sha256:42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642",
"services/auth/source/oauth2/access_engine_assurance_test.go": "sha256:44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3",
"services/auth/source/oauth2/access_engine_azp_test.go": "sha256:5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce",
"services/auth/source/oauth2/access_engine_callback_test.go": "sha256:5cea36b630c895a9473dc876b7128526ddef65d9395b2fddc56343e162452472",
"services/auth/source/oauth2/access_engine_purpose_test.go": "sha256:bf492f6384d7da0f84321f138d4efab5c77508c667c288c2807ad3cccc10383b",
"services/auth/source/oauth2/access_engine_test.go": "sha256:99edd6cfe3e834d73e0447a7915f52cc4ab7b82644669c5ae61963f850b447e8",
"services/auth/source/oauth2/assert_interface_test.go": "sha256:a0db85e38b7ea61e21b0291e55c8b451c89a877bd3db229e35dbb4418458f54f",
"services/auth/source/oauth2/init.go": "sha256:98a8030ed005be67b237d54c3b197a629f8c289e3d74c8edfcf4d740831f87d1",
"services/auth/source/oauth2/jwtsigningkey.go": "sha256:0c6e8ac131fd41ad299073ca748dd95e5fd9c044219c4d7f36d50c4ffcd5b266",
"services/auth/source/oauth2/jwtsigningkey_test.go": "sha256:3e414a541479741773e48de84be7d5a3b794bcc7289627fbd2e8996832628321",
"services/auth/source/oauth2/providers.go": "sha256:7df0051f19932a68d16a33a5d41898e06e9612f839195e27c86802eeebb2e3e9",
"services/auth/source/oauth2/providers_base.go": "sha256:643dbc0561ec7aa7a975978ff2c21d256a5ab619b125e8077d2395281f628542",
"services/auth/source/oauth2/providers_custom.go": "sha256:265a526d209d86c24cbcfdde4c2d73957aaeaccfd329438c11ce99c5951ebb39",
"services/auth/source/oauth2/providers_openid.go": "sha256:5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf",
"services/auth/source/oauth2/providers_simple.go": "sha256:53927b6bfc621dd1b68a074b6763abe5b27795d5a50ca6f0555f3e7a04fa1613",
"services/auth/source/oauth2/required_claim_values.go": "sha256:29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda",
"services/auth/source/oauth2/required_claim_values_test.go": "sha256:54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0",
"services/auth/source/oauth2/source.go": "sha256:44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642",
"services/auth/source/oauth2/source_authenticate.go": "sha256:c6551f00ef4349ccd6afdbb2cba847067528485ff621c3e507b9aab588bb96b5",
"services/auth/source/oauth2/source_callout.go": "sha256:6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c",
"services/auth/source/oauth2/source_name.go": "sha256:712943826e01f718ab4ca717df6c5f2171535cb2229dbf8460f542d757ef8d7f",
"services/auth/source/oauth2/source_register.go": "sha256:5ad9ccf213881734dc7fe7619cbe6c47cb6a4afdbbfc3e674ab4493857ebbf91",
"services/auth/source/oauth2/store.go": "sha256:35b244081569215384de07f8cdc1e83b5545a2e673ca1ac5a2a6ee686cb917a3",
"services/auth/source/oauth2/token.go": "sha256:c0a8868f904bb31842202b4e635a76403902adff9e638d5cb581e7e2d4954933",
"services/auth/source/oauth2/urlmapping.go": "sha256:f2132408d4a447e766e67495b1ca7b8cc2b47d4eb56b0c44b50ba01fe41ca83e",
"third_party/keycape-tokenstatus/LICENSE": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"third_party/keycape-tokenstatus/go.mod": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54",
"third_party/keycape-tokenstatus/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"third_party/keycape-tokenstatus/pkg/tokenstatus/client.go": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575",
"third_party/keycape-tokenstatus/producer-source.json": "sha256:3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
},
"counts_including_subtests": {
"pass": 0,
"fail": 3,
"skip": 0
},
"failures": [
"forgejo.org/services/auth/source/oauth2:TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti",
"forgejo.org/services/auth/source/oauth2:TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope",
"forgejo.org/services/auth/source/oauth2:TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims"
],
"real_credentials": false,
"native_admission": false,
"network": "private empty namespace",
"pid_namespace": "private",
"home": "empty allowlisted fixture HOME"
}

View file

@ -0,0 +1,18 @@
{"Time":"2026-10-18T09:51:33.456606536Z","Action":"start","Package":"forgejo.org/services/auth/source/oauth2"}
{"Time":"2026-10-18T09:51:33.53889052Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims"}
{"Time":"2026-10-18T09:51:33.538988645Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims\n"}
{"Time":"2026-10-18T09:51:33.539004281Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti"}
{"Time":"2026-10-18T09:51:33.539010693Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti\n"}
{"Time":"2026-10-18T09:51:33.541219549Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":" access_engine_purpose_test.go:11: access-token grant fields were accepted as an ID token\n"}
{"Time":"2026-10-18T09:51:33.541272137Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"--- FAIL: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti (0.00s)\n"}
{"Time":"2026-10-18T09:51:33.5412801Z","Action":"fail","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Elapsed":0}
{"Time":"2026-10-18T09:51:33.541296159Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope"}
{"Time":"2026-10-18T09:51:33.541304176Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope\n"}
{"Time":"2026-10-18T09:51:33.541541645Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":" access_engine_purpose_test.go:11: access-token grant fields were accepted as an ID token\n"}
{"Time":"2026-10-18T09:51:33.541568742Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"--- FAIL: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope (0.00s)\n"}
{"Time":"2026-10-18T09:51:33.541578426Z","Action":"fail","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Elapsed":0}
{"Time":"2026-10-18T09:51:33.541588012Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"--- FAIL: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims (0.00s)\n"}
{"Time":"2026-10-18T09:51:33.541595794Z","Action":"fail","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Elapsed":0}
{"Time":"2026-10-18T09:51:33.541602575Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"FAIL\n"}
{"Time":"2026-10-18T09:51:33.547443935Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"FAIL\tforgejo.org/services/auth/source/oauth2\t0.090s\n"}
{"Time":"2026-10-18T09:51:33.54749646Z","Action":"fail","Package":"forgejo.org/services/auth/source/oauth2","Elapsed":0.091}

View file

@ -0,0 +1,169 @@
{
"returncode": 0,
"seconds": 16.16,
"argv": [
"bwrap",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--unshare-net",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-forgejo-current-token-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod/cache/download",
"/cached",
"--ro-bind",
"/etc/ssl/certs",
"/etc/ssl/certs",
"--ro-bind",
"/etc/resolv.conf",
"/etc/resolv.conf",
"--chdir",
"/work/source",
"--clearenv",
"--ro-bind",
"/tmp/custodian-forgejo-successor-attempt2/modules",
"/work/modules",
"--bind",
"/tmp/custodian-forgejo-successor-attempt2/gocache",
"/work/gocache",
"--dir",
"/run",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--setenv",
"PATH",
"/work/go/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOPATH",
"/work/gopath",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOVCS",
"*:off",
"--setenv",
"GOMODCACHE",
"/work/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"GIT_CONFIG_NOSYSTEM",
"1",
"--setenv",
"GIT_CONFIG_GLOBAL",
"/work/home/test.gitconfig",
"--setenv",
"GIT_TERMINAL_PROMPT",
"0",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/go",
"/work/go",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--",
"go",
"test",
"-p",
"1",
"-json",
"-tags",
"sqlite sqlite_unlock_notify",
"./services/auth/source/oauth2",
"-run",
"TestAccessEngine|TestVerifiedApplicationIdentity|TestRequired|TestCurrent",
"-count=1"
],
"source_sha256_at_start": {
"go.mod": "sha256:11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
"go.sum": "sha256:19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
"services/auth/source/oauth2/access_engine.go": "sha256:896b5be0a865fad41aaa92dc68f36e62fca70029da2f0d693855292ab0415e3e",
"services/auth/source/oauth2/access_engine_assurance.go": "sha256:42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642",
"services/auth/source/oauth2/access_engine_assurance_test.go": "sha256:44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3",
"services/auth/source/oauth2/access_engine_azp_test.go": "sha256:5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce",
"services/auth/source/oauth2/access_engine_callback_test.go": "sha256:0dc1471854dab6f659adb662863a9bd241cd50655f0f0d0ee17b53a04992a939",
"services/auth/source/oauth2/access_engine_current_token.go": "sha256:6e0052ee3d090f68136221ce77cd3c236790ff9dd2e4c082f6fc035660d09df1",
"services/auth/source/oauth2/access_engine_current_token_test.go": "sha256:d3dd4c99178fc31e867c58aadc2afe9ccb3976001e9a26b352288c6148e1d91c",
"services/auth/source/oauth2/access_engine_purpose_test.go": "sha256:bf492f6384d7da0f84321f138d4efab5c77508c667c288c2807ad3cccc10383b",
"services/auth/source/oauth2/access_engine_test.go": "sha256:99edd6cfe3e834d73e0447a7915f52cc4ab7b82644669c5ae61963f850b447e8",
"services/auth/source/oauth2/assert_interface_test.go": "sha256:a0db85e38b7ea61e21b0291e55c8b451c89a877bd3db229e35dbb4418458f54f",
"services/auth/source/oauth2/init.go": "sha256:98a8030ed005be67b237d54c3b197a629f8c289e3d74c8edfcf4d740831f87d1",
"services/auth/source/oauth2/jwtsigningkey.go": "sha256:0c6e8ac131fd41ad299073ca748dd95e5fd9c044219c4d7f36d50c4ffcd5b266",
"services/auth/source/oauth2/jwtsigningkey_test.go": "sha256:3e414a541479741773e48de84be7d5a3b794bcc7289627fbd2e8996832628321",
"services/auth/source/oauth2/providers.go": "sha256:7df0051f19932a68d16a33a5d41898e06e9612f839195e27c86802eeebb2e3e9",
"services/auth/source/oauth2/providers_base.go": "sha256:643dbc0561ec7aa7a975978ff2c21d256a5ab619b125e8077d2395281f628542",
"services/auth/source/oauth2/providers_custom.go": "sha256:265a526d209d86c24cbcfdde4c2d73957aaeaccfd329438c11ce99c5951ebb39",
"services/auth/source/oauth2/providers_openid.go": "sha256:5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf",
"services/auth/source/oauth2/providers_simple.go": "sha256:53927b6bfc621dd1b68a074b6763abe5b27795d5a50ca6f0555f3e7a04fa1613",
"services/auth/source/oauth2/required_claim_values.go": "sha256:29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda",
"services/auth/source/oauth2/required_claim_values_test.go": "sha256:54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0",
"services/auth/source/oauth2/source.go": "sha256:44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642",
"services/auth/source/oauth2/source_authenticate.go": "sha256:c6551f00ef4349ccd6afdbb2cba847067528485ff621c3e507b9aab588bb96b5",
"services/auth/source/oauth2/source_callout.go": "sha256:6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c",
"services/auth/source/oauth2/source_name.go": "sha256:712943826e01f718ab4ca717df6c5f2171535cb2229dbf8460f542d757ef8d7f",
"services/auth/source/oauth2/source_register.go": "sha256:5ad9ccf213881734dc7fe7619cbe6c47cb6a4afdbbfc3e674ab4493857ebbf91",
"services/auth/source/oauth2/store.go": "sha256:35b244081569215384de07f8cdc1e83b5545a2e673ca1ac5a2a6ee686cb917a3",
"services/auth/source/oauth2/token.go": "sha256:c0a8868f904bb31842202b4e635a76403902adff9e638d5cb581e7e2d4954933",
"services/auth/source/oauth2/urlmapping.go": "sha256:f2132408d4a447e766e67495b1ca7b8cc2b47d4eb56b0c44b50ba01fe41ca83e",
"third_party/keycape-tokenstatus/LICENSE": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"third_party/keycape-tokenstatus/go.mod": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54",
"third_party/keycape-tokenstatus/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"third_party/keycape-tokenstatus/pkg/tokenstatus/client.go": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575",
"third_party/keycape-tokenstatus/producer-source.json": "sha256:3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
},
"counts_including_subtests": {
"pass": 88,
"fail": 0,
"skip": 0
},
"failures": [],
"real_credentials": false,
"native_admission": false,
"network": "private empty namespace",
"pid_namespace": "private",
"home": "empty allowlisted fixture HOME"
}

View file

@ -0,0 +1,356 @@
{"Time":"2026-10-18T09:53:43.654562185Z","Action":"start","Package":"forgejo.org/services/auth/source/oauth2"}
{"Time":"2026-10-18T09:53:43.73834898Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals"}
{"Time":"2026-10-18T09:53:43.738399486Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals\n"}
{"Time":"2026-10-18T09:53:43.738501994Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods"}
{"Time":"2026-10-18T09:53:43.738512057Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods\n"}
{"Time":"2026-10-18T09:53:43.740608304Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.740661343Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Elapsed":0}
{"Time":"2026-10-18T09:53:43.740678503Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field"}
{"Time":"2026-10-18T09:53:43.740686014Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field\n"}
{"Time":"2026-10-18T09:53:43.740988185Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.741028519Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Elapsed":0}
{"Time":"2026-10-18T09:53:43.741045913Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at"}
{"Time":"2026-10-18T09:53:43.74106769Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/missing-at\n"}
{"Time":"2026-10-18T09:53:43.741196718Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/missing-at (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.741226889Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Elapsed":0}
{"Time":"2026-10-18T09:53:43.741241624Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at"}
{"Time":"2026-10-18T09:53:43.741250208Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/string-at\n"}
{"Time":"2026-10-18T09:53:43.741571123Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/string-at (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.741593612Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Elapsed":0}
{"Time":"2026-10-18T09:53:43.741601547Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at"}
{"Time":"2026-10-18T09:53:43.741628202Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/fractional-at\n"}
{"Time":"2026-10-18T09:53:43.741910559Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/fractional-at (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.74193367Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Elapsed":0}
{"Time":"2026-10-18T09:53:43.741940686Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa"}
{"Time":"2026-10-18T09:53:43.741946006Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa\n"}
{"Time":"2026-10-18T09:53:43.74227941Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.742307039Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Elapsed":0}
{"Time":"2026-10-18T09:53:43.742317742Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level"}
{"Time":"2026-10-18T09:53:43.742325082Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-level\n"}
{"Time":"2026-10-18T09:53:43.742563435Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-level (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.742581461Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Elapsed":0}
{"Time":"2026-10-18T09:53:43.742588314Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods"}
{"Time":"2026-10-18T09:53:43.742593598Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/object-methods\n"}
{"Time":"2026-10-18T09:53:43.742918102Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/object-methods (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.742940582Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Elapsed":0}
{"Time":"2026-10-18T09:53:43.742947922Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods"}
{"Time":"2026-10-18T09:53:43.742953224Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/empty-methods\n"}
{"Time":"2026-10-18T09:53:43.743239575Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/empty-methods (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.743257637Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Elapsed":0}
{"Time":"2026-10-18T09:53:43.743284418Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods"}
{"Time":"2026-10-18T09:53:43.743291551Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods\n"}
{"Time":"2026-10-18T09:53:43.743523087Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.743548948Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Elapsed":0}
{"Time":"2026-10-18T09:53:43.743556315Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication"}
{"Time":"2026-10-18T09:53:43.743561617Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication\n"}
{"Time":"2026-10-18T09:53:43.743872348Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.743899544Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Elapsed":0}
{"Time":"2026-10-18T09:53:43.743907785Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication"}
{"Time":"2026-10-18T09:53:43.743913006Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/future-authentication\n"}
{"Time":"2026-10-18T09:53:43.744198635Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/future-authentication (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.744231584Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Elapsed":0}
{"Time":"2026-10-18T09:53:43.744240214Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa"}
{"Time":"2026-10-18T09:53:43.744246328Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa\n"}
{"Time":"2026-10-18T09:53:43.744483391Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.744500452Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Elapsed":0}
{"Time":"2026-10-18T09:53:43.744507918Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source"}
{"Time":"2026-10-18T09:53:43.744513437Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-source\n"}
{"Time":"2026-10-18T09:53:43.744786659Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-source (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.744803945Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Elapsed":0}
{"Time":"2026-10-18T09:53:43.745320733Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals (0.01s)\n"}
{"Time":"2026-10-18T09:53:43.745341978Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Elapsed":0.01}
{"Time":"2026-10-18T09:53:43.745357983Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds"}
{"Time":"2026-10-18T09:53:43.745364043Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"=== RUN TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds\n"}
{"Time":"2026-10-18T09:53:43.745776474Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"--- PASS: TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.745805068Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Elapsed":0}
{"Time":"2026-10-18T09:53:43.745812182Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact"}
{"Time":"2026-10-18T09:53:43.745816628Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"=== RUN TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact\n"}
{"Time":"2026-10-18T09:53:43.746149081Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"--- PASS: TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.746170091Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Elapsed":0}
{"Time":"2026-10-18T09:53:43.746177296Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient"}
{"Time":"2026-10-18T09:53:43.746182571Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient\n"}
{"Time":"2026-10-18T09:53:43.746189208Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client"}
{"Time":"2026-10-18T09:53:43.746194303Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client\n"}
{"Time":"2026-10-18T09:53:43.74656826Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.746589424Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Elapsed":0}
{"Time":"2026-10-18T09:53:43.746597097Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number"}
{"Time":"2026-10-18T09:53:43.746621083Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/number\n"}
{"Time":"2026-10-18T09:53:43.746834314Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/number (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.74685133Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Elapsed":0}
{"Time":"2026-10-18T09:53:43.746858616Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array"}
{"Time":"2026-10-18T09:53:43.746864603Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/array\n"}
{"Time":"2026-10-18T09:53:43.747141902Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/array (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.747157204Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Elapsed":0}
{"Time":"2026-10-18T09:53:43.747163949Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty"}
{"Time":"2026-10-18T09:53:43.747169233Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty\n"}
{"Time":"2026-10-18T09:53:43.747411635Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.74743223Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Elapsed":0}
{"Time":"2026-10-18T09:53:43.747440346Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.747446874Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Elapsed":0}
{"Time":"2026-10-18T09:53:43.747452726Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient"}
{"Time":"2026-10-18T09:53:43.747457812Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"=== RUN TestAccessEngineAuthorizedPartyAcceptsAdmittedClient\n"}
{"Time":"2026-10-18T09:53:43.747955925Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyAcceptsAdmittedClient (0.00s)\n"}
{"Time":"2026-10-18T09:53:43.747975609Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Elapsed":0}
{"Time":"2026-10-18T09:53:43.747982436Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision"}
{"Time":"2026-10-18T09:53:43.748001173Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision\n"}
{"Time":"2026-10-18T09:53:43.748007774Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins"}
{"Time":"2026-10-18T09:53:43.748013103Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins\n"}
{"Time":"2026-10-18T09:53:44.006435868Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins (0.26s)\n"}
{"Time":"2026-10-18T09:53:44.006488555Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Elapsed":0.26}
{"Time":"2026-10-18T09:53:44.006837234Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners"}
{"Time":"2026-10-18T09:53:44.00686049Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners\n"}
{"Time":"2026-10-18T09:53:44.395447072Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners (0.39s)\n"}
{"Time":"2026-10-18T09:53:44.395507966Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Elapsed":0.39}
{"Time":"2026-10-18T09:53:44.395529905Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers"}
{"Time":"2026-10-18T09:53:44.395534918Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers\n"}
{"Time":"2026-10-18T09:53:44.687427704Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers (0.29s)\n"}
{"Time":"2026-10-18T09:53:44.687472664Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Elapsed":0.29}
{"Time":"2026-10-18T09:53:44.687482493Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision (0.94s)\n"}
{"Time":"2026-10-18T09:53:44.687487822Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Elapsed":0.94}
{"Time":"2026-10-18T09:53:44.687492989Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP"}
{"Time":"2026-10-18T09:53:44.687497182Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP\n"}
{"Time":"2026-10-18T09:53:44.687516876Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny"}
{"Time":"2026-10-18T09:53:44.687521159Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny\n"}
{"Time":"2026-10-18T09:53:45.100747655Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny (0.41s)\n"}
{"Time":"2026-10-18T09:53:45.100792561Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Elapsed":0.41}
{"Time":"2026-10-18T09:53:45.10080064Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned"}
{"Time":"2026-10-18T09:53:45.100806141Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned\n"}
{"Time":"2026-10-18T09:53:45.584505856Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned (0.48s)\n"}
{"Time":"2026-10-18T09:53:45.584555911Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Elapsed":0.48}
{"Time":"2026-10-18T09:53:45.584565027Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key"}
{"Time":"2026-10-18T09:53:45.584569969Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key\n"}
{"Time":"2026-10-18T09:53:45.756640557Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key (0.17s)\n"}
{"Time":"2026-10-18T09:53:45.756703524Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Elapsed":0.17}
{"Time":"2026-10-18T09:53:45.756719521Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin"}
{"Time":"2026-10-18T09:53:45.756723732Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin\n"}
{"Time":"2026-10-18T09:53:46.09718194Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin (0.34s)\n"}
{"Time":"2026-10-18T09:53:46.097251066Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Elapsed":0.34}
{"Time":"2026-10-18T09:53:46.097272067Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding"}
{"Time":"2026-10-18T09:53:46.097280129Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding\n"}
{"Time":"2026-10-18T09:53:46.453092909Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding (0.36s)\n"}
{"Time":"2026-10-18T09:53:46.453197446Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Elapsed":0.36}
{"Time":"2026-10-18T09:53:46.453216761Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired"}
{"Time":"2026-10-18T09:53:46.453224885Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired\n"}
{"Time":"2026-10-18T09:53:46.716353049Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired (0.26s)\n"}
{"Time":"2026-10-18T09:53:46.716405972Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Elapsed":0.26}
{"Time":"2026-10-18T09:53:46.716417938Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP (2.03s)\n"}
{"Time":"2026-10-18T09:53:46.716428028Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Elapsed":2.03}
{"Time":"2026-10-18T09:53:46.716434556Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP"}
{"Time":"2026-10-18T09:53:46.716440048Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP\n"}
{"Time":"2026-10-18T09:53:46.716545757Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source"}
{"Time":"2026-10-18T09:53:46.716558643Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source\n"}
{"Time":"2026-10-18T09:53:46.786931237Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source (0.07s)\n"}
{"Time":"2026-10-18T09:53:46.786985611Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Elapsed":0.07}
{"Time":"2026-10-18T09:53:46.786997181Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience"}
{"Time":"2026-10-18T09:53:46.787004025Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience\n"}
{"Time":"2026-10-18T09:53:46.905975097Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience (0.12s)\n"}
{"Time":"2026-10-18T09:53:46.906028136Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Elapsed":0.12}
{"Time":"2026-10-18T09:53:46.90605599Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token"}
{"Time":"2026-10-18T09:53:46.906063781Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token\n"}
{"Time":"2026-10-18T09:53:47.115155772Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token (0.21s)\n"}
{"Time":"2026-10-18T09:53:47.115211508Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Elapsed":0.21}
{"Time":"2026-10-18T09:53:47.115224312Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token"}
{"Time":"2026-10-18T09:53:47.115232563Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token\n"}
{"Time":"2026-10-18T09:53:47.386174997Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token (0.27s)\n"}
{"Time":"2026-10-18T09:53:47.386227433Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Elapsed":0.27}
{"Time":"2026-10-18T09:53:47.386238145Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user"}
{"Time":"2026-10-18T09:53:47.38624774Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user\n"}
{"Time":"2026-10-18T09:53:47.591265206Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user (0.21s)\n"}
{"Time":"2026-10-18T09:53:47.591314954Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Elapsed":0.21}
{"Time":"2026-10-18T09:53:47.591325432Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject"}
{"Time":"2026-10-18T09:53:47.591331978Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject\n"}
{"Time":"2026-10-18T09:53:47.972378643Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject (0.38s)\n"}
{"Time":"2026-10-18T09:53:47.972437625Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Elapsed":0.38}
{"Time":"2026-10-18T09:53:47.972451133Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups"}
{"Time":"2026-10-18T09:53:47.972474569Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups\n"}
{"Time":"2026-10-18T09:53:48.116564299Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups (0.14s)\n"}
{"Time":"2026-10-18T09:53:48.116619827Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Elapsed":0.14}
{"Time":"2026-10-18T09:53:48.116632848Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider"}
{"Time":"2026-10-18T09:53:48.116640324Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider\n"}
{"Time":"2026-10-18T09:53:48.451621047Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider (0.33s)\n"}
{"Time":"2026-10-18T09:53:48.451699065Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Elapsed":0.33}
{"Time":"2026-10-18T09:53:48.451735359Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission"}
{"Time":"2026-10-18T09:53:48.451749011Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission\n"}
{"Time":"2026-10-18T09:53:48.487589584Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission (0.04s)\n"}
{"Time":"2026-10-18T09:53:48.487642398Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Elapsed":0.04}
{"Time":"2026-10-18T09:53:48.487654382Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity"}
{"Time":"2026-10-18T09:53:48.487661289Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity\n"}
{"Time":"2026-10-18T09:53:48.803153699Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity (0.32s)\n"}
{"Time":"2026-10-18T09:53:48.803208317Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Elapsed":0.32}
{"Time":"2026-10-18T09:53:48.803221644Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP (2.09s)\n"}
{"Time":"2026-10-18T09:53:48.803229958Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Elapsed":2.09}
{"Time":"2026-10-18T09:53:48.803289562Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision"}
{"Time":"2026-10-18T09:53:48.803298246Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision\n"}
{"Time":"2026-10-18T09:53:48.803306587Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing"}
{"Time":"2026-10-18T09:53:48.803312737Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing\n"}
{"Time":"2026-10-18T09:53:49.027724667Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing (0.22s)\n"}
{"Time":"2026-10-18T09:53:49.027806976Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Elapsed":0.22}
{"Time":"2026-10-18T09:53:49.027825218Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access"}
{"Time":"2026-10-18T09:53:49.027835886Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access\n"}
{"Time":"2026-10-18T09:53:49.704792262Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access (0.68s)\n"}
{"Time":"2026-10-18T09:53:49.704845257Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Elapsed":0.68}
{"Time":"2026-10-18T09:53:49.704896736Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject"}
{"Time":"2026-10-18T09:53:49.704912489Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/subject\n"}
{"Time":"2026-10-18T09:53:50.106158612Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/subject (0.40s)\n"}
{"Time":"2026-10-18T09:53:50.106212761Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Elapsed":0.4}
{"Time":"2026-10-18T09:53:50.10622525Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant"}
{"Time":"2026-10-18T09:53:50.106232382Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant\n"}
{"Time":"2026-10-18T09:53:50.388799147Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant (0.28s)\n"}
{"Time":"2026-10-18T09:53:50.38885124Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Elapsed":0.28}
{"Time":"2026-10-18T09:53:50.388876235Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal"}
{"Time":"2026-10-18T09:53:50.388882674Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/principal\n"}
{"Time":"2026-10-18T09:53:50.600904666Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/principal (0.21s)\n"}
{"Time":"2026-10-18T09:53:50.600962413Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Elapsed":0.21}
{"Time":"2026-10-18T09:53:50.600975443Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance"}
{"Time":"2026-10-18T09:53:50.600983495Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance\n"}
{"Time":"2026-10-18T09:53:50.865187766Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance (0.26s)\n"}
{"Time":"2026-10-18T09:53:50.865235738Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Elapsed":0.26}
{"Time":"2026-10-18T09:53:50.865244899Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time"}
{"Time":"2026-10-18T09:53:50.865250138Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time\n"}
{"Time":"2026-10-18T09:53:51.275873984Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time (0.41s)\n"}
{"Time":"2026-10-18T09:53:51.275919061Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Elapsed":0.41}
{"Time":"2026-10-18T09:53:51.275926617Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source"}
{"Time":"2026-10-18T09:53:51.275930549Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source\n"}
{"Time":"2026-10-18T09:53:51.457577292Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source (0.18s)\n"}
{"Time":"2026-10-18T09:53:51.457639187Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Elapsed":0.18}
{"Time":"2026-10-18T09:53:51.457652316Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at"}
{"Time":"2026-10-18T09:53:51.457660125Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at\n"}
{"Time":"2026-10-18T09:53:51.800451766Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at (0.34s)\n"}
{"Time":"2026-10-18T09:53:51.800557169Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Elapsed":0.34}
{"Time":"2026-10-18T09:53:51.800576989Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive"}
{"Time":"2026-10-18T09:53:51.800585537Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive\n"}
{"Time":"2026-10-18T09:53:52.052231669Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive (0.25s)\n"}
{"Time":"2026-10-18T09:53:52.052279659Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Elapsed":0.25}
{"Time":"2026-10-18T09:53:52.05229002Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable"}
{"Time":"2026-10-18T09:53:52.05229342Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable\n"}
{"Time":"2026-10-18T09:53:52.464961499Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable (0.41s)\n"}
{"Time":"2026-10-18T09:53:52.465011202Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Elapsed":0.41}
{"Time":"2026-10-18T09:53:52.465021626Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status"}
{"Time":"2026-10-18T09:53:52.46502746Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status\n"}
{"Time":"2026-10-18T09:53:52.912239967Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status (0.45s)\n"}
{"Time":"2026-10-18T09:53:52.912291311Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Elapsed":0.45}
{"Time":"2026-10-18T09:53:52.912301537Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission"}
{"Time":"2026-10-18T09:53:52.912307109Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission\n"}
{"Time":"2026-10-18T09:53:53.532045087Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission (0.62s)\n"}
{"Time":"2026-10-18T09:53:53.532170553Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Elapsed":0.62}
{"Time":"2026-10-18T09:53:53.53219104Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client"}
{"Time":"2026-10-18T09:53:53.532198308Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client\n"}
{"Time":"2026-10-18T09:53:53.733177444Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client (0.20s)\n"}
{"Time":"2026-10-18T09:53:53.733230367Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Elapsed":0.2}
{"Time":"2026-10-18T09:53:53.73324235Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key"}
{"Time":"2026-10-18T09:53:53.733249095Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key\n"}
{"Time":"2026-10-18T09:53:53.91638578Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key (0.18s)\n"}
{"Time":"2026-10-18T09:53:53.916443139Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Elapsed":0.18}
{"Time":"2026-10-18T09:53:53.916457161Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision (5.11s)\n"}
{"Time":"2026-10-18T09:53:53.916466566Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Elapsed":5.11}
{"Time":"2026-10-18T09:53:53.916475367Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn"}
{"Time":"2026-10-18T09:53:53.916482364Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"=== RUN TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn\n"}
{"Time":"2026-10-18T09:53:54.640660044Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"--- PASS: TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn (0.72s)\n"}
{"Time":"2026-10-18T09:53:54.640714021Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Elapsed":0.72}
{"Time":"2026-10-18T09:53:54.640724724Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims"}
{"Time":"2026-10-18T09:53:54.640730468Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims\n"}
{"Time":"2026-10-18T09:53:54.64073651Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti"}
{"Time":"2026-10-18T09:53:54.640741451Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti\n"}
{"Time":"2026-10-18T09:53:54.641134495Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.641153783Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Elapsed":0}
{"Time":"2026-10-18T09:53:54.641163287Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope"}
{"Time":"2026-10-18T09:53:54.641168409Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope\n"}
{"Time":"2026-10-18T09:53:54.641607982Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.641622833Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Elapsed":0}
{"Time":"2026-10-18T09:53:54.641629876Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.641635476Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Elapsed":0}
{"Time":"2026-10-18T09:53:54.641643032Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE"}
{"Time":"2026-10-18T09:53:54.641648533Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE\n"}
{"Time":"2026-10-18T09:53:54.642092524Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience"}
{"Time":"2026-10-18T09:53:54.642106257Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/audience\n"}
{"Time":"2026-10-18T09:53:54.642377027Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/audience (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.642390661Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Elapsed":0}
{"Time":"2026-10-18T09:53:54.642398118Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired"}
{"Time":"2026-10-18T09:53:54.642402961Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/expired\n"}
{"Time":"2026-10-18T09:53:54.642673271Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/expired (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.642685597Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Elapsed":0}
{"Time":"2026-10-18T09:53:54.642704281Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued"}
{"Time":"2026-10-18T09:53:54.642708934Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/future-issued\n"}
{"Time":"2026-10-18T09:53:54.642991508Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/future-issued (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.64300369Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Elapsed":0}
{"Time":"2026-10-18T09:53:54.64301075Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued"}
{"Time":"2026-10-18T09:53:54.643018316Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/old-issued\n"}
{"Time":"2026-10-18T09:53:54.643287796Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/old-issued (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.643300168Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Elapsed":0}
{"Time":"2026-10-18T09:53:54.643307219Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp"}
{"Time":"2026-10-18T09:53:54.643311376Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-exp\n"}
{"Time":"2026-10-18T09:53:54.64358927Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-exp (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.643623761Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Elapsed":0}
{"Time":"2026-10-18T09:53:54.643632535Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant"}
{"Time":"2026-10-18T09:53:54.643636791Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-tenant\n"}
{"Time":"2026-10-18T09:53:54.643904811Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-tenant (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.643930861Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Elapsed":0}
{"Time":"2026-10-18T09:53:54.643936867Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance"}
{"Time":"2026-10-18T09:53:54.643940943Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-assurance\n"}
{"Time":"2026-10-18T09:53:54.644211866Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-assurance (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.644245554Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Elapsed":0}
{"Time":"2026-10-18T09:53:54.644254058Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject"}
{"Time":"2026-10-18T09:53:54.644258476Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/empty-subject\n"}
{"Time":"2026-10-18T09:53:54.644507109Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/empty-subject (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.644519507Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Elapsed":0}
{"Time":"2026-10-18T09:53:54.644526631Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer"}
{"Time":"2026-10-18T09:53:54.644530761Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/issuer\n"}
{"Time":"2026-10-18T09:53:54.644799601Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/issuer (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.644812279Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Elapsed":0}
{"Time":"2026-10-18T09:53:54.644819313Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat"}
{"Time":"2026-10-18T09:53:54.644823452Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-iat\n"}
{"Time":"2026-10-18T09:53:54.64512507Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-iat (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.645137514Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Elapsed":0}
{"Time":"2026-10-18T09:53:54.645144673Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal"}
{"Time":"2026-10-18T09:53:54.64514883Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-principal\n"}
{"Time":"2026-10-18T09:53:54.645475398Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-principal (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.645504127Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Elapsed":0}
{"Time":"2026-10-18T09:53:54.64551446Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp"}
{"Time":"2026-10-18T09:53:54.645521097Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp\n"}
{"Time":"2026-10-18T09:53:54.645808631Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.645828758Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Elapsed":0}
{"Time":"2026-10-18T09:53:54.645836855Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key"}
{"Time":"2026-10-18T09:53:54.645841229Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-key\n"}
{"Time":"2026-10-18T09:53:54.646196048Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-key (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.646216878Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Elapsed":0}
{"Time":"2026-10-18T09:53:54.646288728Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key"}
{"Time":"2026-10-18T09:53:54.646299521Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/unknown-key\n"}
{"Time":"2026-10-18T09:53:54.646532527Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/unknown-key (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.646550769Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Elapsed":0}
{"Time":"2026-10-18T09:53:54.646559408Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type"}
{"Time":"2026-10-18T09:53:54.646563835Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-type\n"}
{"Time":"2026-10-18T09:53:54.646815335Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-type (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.64683979Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Elapsed":0}
{"Time":"2026-10-18T09:53:54.646848275Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion"}
{"Time":"2026-10-18T09:53:54.646852495Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion\n"}
{"Time":"2026-10-18T09:53:54.647011235Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.647026573Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Elapsed":0}
{"Time":"2026-10-18T09:53:54.647053544Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none"}
{"Time":"2026-10-18T09:53:54.647058593Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/none\n"}
{"Time":"2026-10-18T09:53:54.647073472Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/none (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.647174709Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Elapsed":0}
{"Time":"2026-10-18T09:53:54.647191796Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE (0.01s)\n"}
{"Time":"2026-10-18T09:53:54.647199488Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Elapsed":0.01}
{"Time":"2026-10-18T09:53:54.647287292Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation"}
{"Time":"2026-10-18T09:53:54.647336868Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"=== RUN TestRequiredClaimExactAnyOfAndNoMutation\n"}
{"Time":"2026-10-18T09:53:54.647349997Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"--- PASS: TestRequiredClaimExactAnyOfAndNoMutation (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.647364335Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Elapsed":0}
{"Time":"2026-10-18T09:53:54.64743567Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation"}
{"Time":"2026-10-18T09:53:54.647448826Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"=== RUN TestRequiredClaimConfigurationRefusalsAndLegacyIsolation\n"}
{"Time":"2026-10-18T09:53:54.647458114Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"--- PASS: TestRequiredClaimConfigurationRefusalsAndLegacyIsolation (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.647463046Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Elapsed":0}
{"Time":"2026-10-18T09:53:54.647514733Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable"}
{"Time":"2026-10-18T09:53:54.647524896Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"=== RUN TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable\n"}
{"Time":"2026-10-18T09:53:54.650207859Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"--- PASS: TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.65026804Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Elapsed":0}
{"Time":"2026-10-18T09:53:54.650279393Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval"}
{"Time":"2026-10-18T09:53:54.650285903Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"=== RUN TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval\n"}
{"Time":"2026-10-18T09:53:54.650317049Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"--- PASS: TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.650325669Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Elapsed":0}
{"Time":"2026-10-18T09:53:54.650342423Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse"}
{"Time":"2026-10-18T09:53:54.650348771Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"=== RUN TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse\n"}
{"Time":"2026-10-18T09:53:54.65039273Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"--- PASS: TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse (0.00s)\n"}
{"Time":"2026-10-18T09:53:54.650429584Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Elapsed":0}
{"Time":"2026-10-18T09:53:54.65045421Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"PASS\n"}
{"Time":"2026-10-18T09:53:54.658262068Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"ok \tforgejo.org/services/auth/source/oauth2\t11.003s\n"}
{"Time":"2026-10-18T09:53:54.658318119Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Elapsed":11.004}

View file

@ -0,0 +1,169 @@
{
"returncode": 0,
"seconds": 16.66,
"argv": [
"bwrap",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--unshare-net",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-forgejo-current-token-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod/cache/download",
"/cached",
"--ro-bind",
"/etc/ssl/certs",
"/etc/ssl/certs",
"--ro-bind",
"/etc/resolv.conf",
"/etc/resolv.conf",
"--chdir",
"/work/source",
"--clearenv",
"--ro-bind",
"/tmp/custodian-forgejo-successor-attempt2/modules",
"/work/modules",
"--bind",
"/tmp/custodian-forgejo-successor-attempt2/gocache",
"/work/gocache",
"--dir",
"/run",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--setenv",
"PATH",
"/work/go/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOPATH",
"/work/gopath",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOVCS",
"*:off",
"--setenv",
"GOMODCACHE",
"/work/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"GIT_CONFIG_NOSYSTEM",
"1",
"--setenv",
"GIT_CONFIG_GLOBAL",
"/work/home/test.gitconfig",
"--setenv",
"GIT_TERMINAL_PROMPT",
"0",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/go",
"/work/go",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--",
"go",
"test",
"-p",
"1",
"-json",
"-tags",
"sqlite sqlite_unlock_notify",
"./services/auth/source/oauth2",
"-run",
"TestAccessEngine|TestVerifiedApplicationIdentity|TestRequired|TestCurrent",
"-count=1"
],
"source_sha256_at_start": {
"go.mod": "sha256:11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
"go.sum": "sha256:19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
"services/auth/source/oauth2/access_engine.go": "sha256:ac845a1373cd7017dc475ca9d2b53885c2283df67336021bd51dfe02da24179c",
"services/auth/source/oauth2/access_engine_assurance.go": "sha256:42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642",
"services/auth/source/oauth2/access_engine_assurance_test.go": "sha256:44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3",
"services/auth/source/oauth2/access_engine_azp_test.go": "sha256:5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce",
"services/auth/source/oauth2/access_engine_callback_test.go": "sha256:1cbe378cc20149be7b354484ab3297916733564f03a2441cd276f9039d043580",
"services/auth/source/oauth2/access_engine_current_token.go": "sha256:4a2e484ece7cf74686173d5e85c127643287570c2298af135ddfdc7c450b18d8",
"services/auth/source/oauth2/access_engine_current_token_test.go": "sha256:58f799c08a7b4638780abc511bd1a7359bd5e691c7805ff54582d02d2f42cae1",
"services/auth/source/oauth2/access_engine_purpose_test.go": "sha256:bf492f6384d7da0f84321f138d4efab5c77508c667c288c2807ad3cccc10383b",
"services/auth/source/oauth2/access_engine_test.go": "sha256:99edd6cfe3e834d73e0447a7915f52cc4ab7b82644669c5ae61963f850b447e8",
"services/auth/source/oauth2/assert_interface_test.go": "sha256:a0db85e38b7ea61e21b0291e55c8b451c89a877bd3db229e35dbb4418458f54f",
"services/auth/source/oauth2/init.go": "sha256:98a8030ed005be67b237d54c3b197a629f8c289e3d74c8edfcf4d740831f87d1",
"services/auth/source/oauth2/jwtsigningkey.go": "sha256:0c6e8ac131fd41ad299073ca748dd95e5fd9c044219c4d7f36d50c4ffcd5b266",
"services/auth/source/oauth2/jwtsigningkey_test.go": "sha256:3e414a541479741773e48de84be7d5a3b794bcc7289627fbd2e8996832628321",
"services/auth/source/oauth2/providers.go": "sha256:7df0051f19932a68d16a33a5d41898e06e9612f839195e27c86802eeebb2e3e9",
"services/auth/source/oauth2/providers_base.go": "sha256:643dbc0561ec7aa7a975978ff2c21d256a5ab619b125e8077d2395281f628542",
"services/auth/source/oauth2/providers_custom.go": "sha256:265a526d209d86c24cbcfdde4c2d73957aaeaccfd329438c11ce99c5951ebb39",
"services/auth/source/oauth2/providers_openid.go": "sha256:5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf",
"services/auth/source/oauth2/providers_simple.go": "sha256:53927b6bfc621dd1b68a074b6763abe5b27795d5a50ca6f0555f3e7a04fa1613",
"services/auth/source/oauth2/required_claim_values.go": "sha256:29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda",
"services/auth/source/oauth2/required_claim_values_test.go": "sha256:54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0",
"services/auth/source/oauth2/source.go": "sha256:44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642",
"services/auth/source/oauth2/source_authenticate.go": "sha256:c6551f00ef4349ccd6afdbb2cba847067528485ff621c3e507b9aab588bb96b5",
"services/auth/source/oauth2/source_callout.go": "sha256:6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c",
"services/auth/source/oauth2/source_name.go": "sha256:712943826e01f718ab4ca717df6c5f2171535cb2229dbf8460f542d757ef8d7f",
"services/auth/source/oauth2/source_register.go": "sha256:5ad9ccf213881734dc7fe7619cbe6c47cb6a4afdbbfc3e674ab4493857ebbf91",
"services/auth/source/oauth2/store.go": "sha256:35b244081569215384de07f8cdc1e83b5545a2e673ca1ac5a2a6ee686cb917a3",
"services/auth/source/oauth2/token.go": "sha256:c0a8868f904bb31842202b4e635a76403902adff9e638d5cb581e7e2d4954933",
"services/auth/source/oauth2/urlmapping.go": "sha256:f2132408d4a447e766e67495b1ca7b8cc2b47d4eb56b0c44b50ba01fe41ca83e",
"third_party/keycape-tokenstatus/LICENSE": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"third_party/keycape-tokenstatus/go.mod": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54",
"third_party/keycape-tokenstatus/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"third_party/keycape-tokenstatus/pkg/tokenstatus/client.go": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575",
"third_party/keycape-tokenstatus/producer-source.json": "sha256:3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
},
"counts_including_subtests": {
"pass": 90,
"fail": 0,
"skip": 0
},
"failures": [],
"real_credentials": false,
"native_admission": false,
"network": "private empty namespace",
"pid_namespace": "private",
"home": "empty allowlisted fixture HOME"
}

View file

@ -0,0 +1,364 @@
{"Time":"2026-10-18T09:58:37.45062086Z","Action":"start","Package":"forgejo.org/services/auth/source/oauth2"}
{"Time":"2026-10-18T09:58:37.536556468Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals"}
{"Time":"2026-10-18T09:58:37.536627317Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals\n"}
{"Time":"2026-10-18T09:58:37.536767229Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source"}
{"Time":"2026-10-18T09:58:37.536785344Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-source\n"}
{"Time":"2026-10-18T09:58:37.538905524Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-source (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.538941286Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Elapsed":0}
{"Time":"2026-10-18T09:58:37.538954154Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level"}
{"Time":"2026-10-18T09:58:37.538961692Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-level\n"}
{"Time":"2026-10-18T09:58:37.539208377Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-level (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.539230524Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Elapsed":0}
{"Time":"2026-10-18T09:58:37.539240758Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods"}
{"Time":"2026-10-18T09:58:37.539247061Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/object-methods\n"}
{"Time":"2026-10-18T09:58:37.539565614Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/object-methods (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.539583206Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Elapsed":0}
{"Time":"2026-10-18T09:58:37.539596705Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods"}
{"Time":"2026-10-18T09:58:37.539603522Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/empty-methods\n"}
{"Time":"2026-10-18T09:58:37.53988551Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/empty-methods (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.539903733Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Elapsed":0}
{"Time":"2026-10-18T09:58:37.539917061Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods"}
{"Time":"2026-10-18T09:58:37.539942968Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods\n"}
{"Time":"2026-10-18T09:58:37.540199256Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.54021922Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Elapsed":0}
{"Time":"2026-10-18T09:58:37.54023381Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication"}
{"Time":"2026-10-18T09:58:37.5402528Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication\n"}
{"Time":"2026-10-18T09:58:37.540567484Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.540584319Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Elapsed":0}
{"Time":"2026-10-18T09:58:37.540594734Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication"}
{"Time":"2026-10-18T09:58:37.540609369Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/future-authentication\n"}
{"Time":"2026-10-18T09:58:37.540966948Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/future-authentication (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.540994018Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Elapsed":0}
{"Time":"2026-10-18T09:58:37.541006137Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at"}
{"Time":"2026-10-18T09:58:37.541011953Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/missing-at\n"}
{"Time":"2026-10-18T09:58:37.541309991Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/missing-at (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.541326592Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Elapsed":0}
{"Time":"2026-10-18T09:58:37.541333499Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at"}
{"Time":"2026-10-18T09:58:37.541338937Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/string-at\n"}
{"Time":"2026-10-18T09:58:37.541601735Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/string-at (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.541634459Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Elapsed":0}
{"Time":"2026-10-18T09:58:37.541643684Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at"}
{"Time":"2026-10-18T09:58:37.541649085Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/fractional-at\n"}
{"Time":"2026-10-18T09:58:37.541949324Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/fractional-at (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.541977079Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Elapsed":0}
{"Time":"2026-10-18T09:58:37.541984987Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods"}
{"Time":"2026-10-18T09:58:37.541995177Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods\n"}
{"Time":"2026-10-18T09:58:37.542246478Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.542281897Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Elapsed":0}
{"Time":"2026-10-18T09:58:37.542293025Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field"}
{"Time":"2026-10-18T09:58:37.542298336Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field\n"}
{"Time":"2026-10-18T09:58:37.542554804Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.542569899Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Elapsed":0}
{"Time":"2026-10-18T09:58:37.542578096Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa"}
{"Time":"2026-10-18T09:58:37.542583524Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa\n"}
{"Time":"2026-10-18T09:58:37.542870056Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.54288553Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Elapsed":0}
{"Time":"2026-10-18T09:58:37.542892113Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa"}
{"Time":"2026-10-18T09:58:37.542897577Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa\n"}
{"Time":"2026-10-18T09:58:37.543190259Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.543205318Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Elapsed":0}
{"Time":"2026-10-18T09:58:37.543659922Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals (0.01s)\n"}
{"Time":"2026-10-18T09:58:37.543676812Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Elapsed":0.01}
{"Time":"2026-10-18T09:58:37.54368802Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds"}
{"Time":"2026-10-18T09:58:37.543695108Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"=== RUN TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds\n"}
{"Time":"2026-10-18T09:58:37.544141949Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"--- PASS: TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.544163672Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Elapsed":0}
{"Time":"2026-10-18T09:58:37.544171255Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact"}
{"Time":"2026-10-18T09:58:37.544176891Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"=== RUN TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact\n"}
{"Time":"2026-10-18T09:58:37.544598312Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"--- PASS: TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.544656419Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Elapsed":0}
{"Time":"2026-10-18T09:58:37.544678539Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient"}
{"Time":"2026-10-18T09:58:37.54468724Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient\n"}
{"Time":"2026-10-18T09:58:37.544698052Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client"}
{"Time":"2026-10-18T09:58:37.544704021Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client\n"}
{"Time":"2026-10-18T09:58:37.545309169Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.54534421Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Elapsed":0}
{"Time":"2026-10-18T09:58:37.545356076Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number"}
{"Time":"2026-10-18T09:58:37.545391415Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/number\n"}
{"Time":"2026-10-18T09:58:37.545977166Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/number (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.546018294Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Elapsed":0}
{"Time":"2026-10-18T09:58:37.546027257Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array"}
{"Time":"2026-10-18T09:58:37.546033587Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/array\n"}
{"Time":"2026-10-18T09:58:37.546411816Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/array (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.546435459Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Elapsed":0}
{"Time":"2026-10-18T09:58:37.546442646Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty"}
{"Time":"2026-10-18T09:58:37.546448092Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty\n"}
{"Time":"2026-10-18T09:58:37.546763624Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.546785031Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Elapsed":0}
{"Time":"2026-10-18T09:58:37.546793074Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.546799585Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Elapsed":0}
{"Time":"2026-10-18T09:58:37.546805743Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient"}
{"Time":"2026-10-18T09:58:37.546811009Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"=== RUN TestAccessEngineAuthorizedPartyAcceptsAdmittedClient\n"}
{"Time":"2026-10-18T09:58:37.547333343Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyAcceptsAdmittedClient (0.00s)\n"}
{"Time":"2026-10-18T09:58:37.547360584Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Elapsed":0}
{"Time":"2026-10-18T09:58:37.547369366Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision"}
{"Time":"2026-10-18T09:58:37.547392153Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision\n"}
{"Time":"2026-10-18T09:58:37.547406238Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins"}
{"Time":"2026-10-18T09:58:37.547412045Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins\n"}
{"Time":"2026-10-18T09:58:38.025832897Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins (0.48s)\n"}
{"Time":"2026-10-18T09:58:38.02588012Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Elapsed":0.48}
{"Time":"2026-10-18T09:58:38.02589086Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners"}
{"Time":"2026-10-18T09:58:38.025895242Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners\n"}
{"Time":"2026-10-18T09:58:38.617849139Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners (0.59s)\n"}
{"Time":"2026-10-18T09:58:38.617891998Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Elapsed":0.59}
{"Time":"2026-10-18T09:58:38.61790382Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers"}
{"Time":"2026-10-18T09:58:38.617907463Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers\n"}
{"Time":"2026-10-18T09:58:38.753330459Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers (0.14s)\n"}
{"Time":"2026-10-18T09:58:38.753381154Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Elapsed":0.14}
{"Time":"2026-10-18T09:58:38.753392291Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision (1.21s)\n"}
{"Time":"2026-10-18T09:58:38.753399838Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Elapsed":1.21}
{"Time":"2026-10-18T09:58:38.753407124Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP"}
{"Time":"2026-10-18T09:58:38.753412697Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP\n"}
{"Time":"2026-10-18T09:58:38.753438522Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny"}
{"Time":"2026-10-18T09:58:38.753444104Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny\n"}
{"Time":"2026-10-18T09:58:38.966863993Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny (0.21s)\n"}
{"Time":"2026-10-18T09:58:38.966906383Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Elapsed":0.21}
{"Time":"2026-10-18T09:58:38.966913904Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned"}
{"Time":"2026-10-18T09:58:38.966917862Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned\n"}
{"Time":"2026-10-18T09:58:39.087701691Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned (0.12s)\n"}
{"Time":"2026-10-18T09:58:39.087768112Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Elapsed":0.12}
{"Time":"2026-10-18T09:58:39.087788374Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key"}
{"Time":"2026-10-18T09:58:39.087795633Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key\n"}
{"Time":"2026-10-18T09:58:39.347725726Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key (0.26s)\n"}
{"Time":"2026-10-18T09:58:39.347771326Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Elapsed":0.26}
{"Time":"2026-10-18T09:58:39.347781534Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin"}
{"Time":"2026-10-18T09:58:39.347785853Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin\n"}
{"Time":"2026-10-18T09:58:39.635769444Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin (0.29s)\n"}
{"Time":"2026-10-18T09:58:39.635832132Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Elapsed":0.29}
{"Time":"2026-10-18T09:58:39.635846677Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding"}
{"Time":"2026-10-18T09:58:39.635853828Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding\n"}
{"Time":"2026-10-18T09:58:40.276369484Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding (0.64s)\n"}
{"Time":"2026-10-18T09:58:40.276414246Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Elapsed":0.64}
{"Time":"2026-10-18T09:58:40.276422488Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired"}
{"Time":"2026-10-18T09:58:40.276430964Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired\n"}
{"Time":"2026-10-18T09:58:40.74500589Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired (0.47s)\n"}
{"Time":"2026-10-18T09:58:40.745058605Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Elapsed":0.47}
{"Time":"2026-10-18T09:58:40.745072158Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP (1.99s)\n"}
{"Time":"2026-10-18T09:58:40.745080833Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Elapsed":1.99}
{"Time":"2026-10-18T09:58:40.745093078Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP"}
{"Time":"2026-10-18T09:58:40.745100806Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP\n"}
{"Time":"2026-10-18T09:58:40.745108759Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user"}
{"Time":"2026-10-18T09:58:40.745115414Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user\n"}
{"Time":"2026-10-18T09:58:40.92587352Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user (0.18s)\n"}
{"Time":"2026-10-18T09:58:40.925939572Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Elapsed":0.18}
{"Time":"2026-10-18T09:58:40.925956686Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups"}
{"Time":"2026-10-18T09:58:40.925965442Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups\n"}
{"Time":"2026-10-18T09:58:41.162049521Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups (0.24s)\n"}
{"Time":"2026-10-18T09:58:41.162099323Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Elapsed":0.24}
{"Time":"2026-10-18T09:58:41.16212724Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source"}
{"Time":"2026-10-18T09:58:41.162134752Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source\n"}
{"Time":"2026-10-18T09:58:41.275350242Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source (0.11s)\n"}
{"Time":"2026-10-18T09:58:41.275401928Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Elapsed":0.11}
{"Time":"2026-10-18T09:58:41.275413371Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience"}
{"Time":"2026-10-18T09:58:41.275420441Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience\n"}
{"Time":"2026-10-18T09:58:41.373726607Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience (0.10s)\n"}
{"Time":"2026-10-18T09:58:41.373779674Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Elapsed":0.1}
{"Time":"2026-10-18T09:58:41.373790287Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token"}
{"Time":"2026-10-18T09:58:41.373797239Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token\n"}
{"Time":"2026-10-18T09:58:41.400130968Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token (0.03s)\n"}
{"Time":"2026-10-18T09:58:41.400198183Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Elapsed":0.03}
{"Time":"2026-10-18T09:58:41.400211375Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity"}
{"Time":"2026-10-18T09:58:41.400222926Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity\n"}
{"Time":"2026-10-18T09:58:41.926269875Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity (0.53s)\n"}
{"Time":"2026-10-18T09:58:41.92632241Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Elapsed":0.53}
{"Time":"2026-10-18T09:58:41.926332347Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject"}
{"Time":"2026-10-18T09:58:41.926355557Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject\n"}
{"Time":"2026-10-18T09:58:42.411601029Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject (0.49s)\n"}
{"Time":"2026-10-18T09:58:42.41166057Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Elapsed":0.49}
{"Time":"2026-10-18T09:58:42.411672085Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider"}
{"Time":"2026-10-18T09:58:42.411679037Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider\n"}
{"Time":"2026-10-18T09:58:42.891560595Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider (0.48s)\n"}
{"Time":"2026-10-18T09:58:42.891610307Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Elapsed":0.48}
{"Time":"2026-10-18T09:58:42.891620695Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission"}
{"Time":"2026-10-18T09:58:42.891636917Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission\n"}
{"Time":"2026-10-18T09:58:43.053469024Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission (0.16s)\n"}
{"Time":"2026-10-18T09:58:43.053521838Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Elapsed":0.16}
{"Time":"2026-10-18T09:58:43.053536897Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token"}
{"Time":"2026-10-18T09:58:43.053544192Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token\n"}
{"Time":"2026-10-18T09:58:43.507997548Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token (0.45s)\n"}
{"Time":"2026-10-18T09:58:43.508045917Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Elapsed":0.45}
{"Time":"2026-10-18T09:58:43.508058234Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP (2.76s)\n"}
{"Time":"2026-10-18T09:58:43.50807268Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Elapsed":2.76}
{"Time":"2026-10-18T09:58:43.508093888Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision"}
{"Time":"2026-10-18T09:58:43.508100354Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision\n"}
{"Time":"2026-10-18T09:58:43.508110868Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing"}
{"Time":"2026-10-18T09:58:43.508116413Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing\n"}
{"Time":"2026-10-18T09:58:43.706699832Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing (0.20s)\n"}
{"Time":"2026-10-18T09:58:43.706757299Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Elapsed":0.2}
{"Time":"2026-10-18T09:58:43.706771077Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access"}
{"Time":"2026-10-18T09:58:43.706777317Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access\n"}
{"Time":"2026-10-18T09:58:44.713937979Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access (1.01s)\n"}
{"Time":"2026-10-18T09:58:44.713982678Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Elapsed":1.01}
{"Time":"2026-10-18T09:58:44.713993408Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject"}
{"Time":"2026-10-18T09:58:44.713998314Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/subject\n"}
{"Time":"2026-10-18T09:58:44.980513474Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/subject (0.27s)\n"}
{"Time":"2026-10-18T09:58:44.980557036Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Elapsed":0.27}
{"Time":"2026-10-18T09:58:44.980565269Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant"}
{"Time":"2026-10-18T09:58:44.980569859Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant\n"}
{"Time":"2026-10-18T09:58:45.392707481Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant (0.41s)\n"}
{"Time":"2026-10-18T09:58:45.39275043Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Elapsed":0.41}
{"Time":"2026-10-18T09:58:45.392770818Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal"}
{"Time":"2026-10-18T09:58:45.392775453Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/principal\n"}
{"Time":"2026-10-18T09:58:45.484306902Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/principal (0.09s)\n"}
{"Time":"2026-10-18T09:58:45.484499321Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Elapsed":0.09}
{"Time":"2026-10-18T09:58:45.484509645Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance"}
{"Time":"2026-10-18T09:58:45.484514839Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance\n"}
{"Time":"2026-10-18T09:58:45.725947057Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance (0.24s)\n"}
{"Time":"2026-10-18T09:58:45.72599383Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Elapsed":0.24}
{"Time":"2026-10-18T09:58:45.726003451Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time"}
{"Time":"2026-10-18T09:58:45.72601209Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time\n"}
{"Time":"2026-10-18T09:58:45.971587025Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time (0.25s)\n"}
{"Time":"2026-10-18T09:58:45.971639605Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Elapsed":0.25}
{"Time":"2026-10-18T09:58:45.971651913Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source"}
{"Time":"2026-10-18T09:58:45.971658379Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source\n"}
{"Time":"2026-10-18T09:58:46.193451086Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source (0.22s)\n"}
{"Time":"2026-10-18T09:58:46.193492962Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Elapsed":0.22}
{"Time":"2026-10-18T09:58:46.193503368Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at"}
{"Time":"2026-10-18T09:58:46.193508292Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at\n"}
{"Time":"2026-10-18T09:58:46.534895389Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at (0.34s)\n"}
{"Time":"2026-10-18T09:58:46.534949619Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Elapsed":0.34}
{"Time":"2026-10-18T09:58:46.534960385Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive"}
{"Time":"2026-10-18T09:58:46.534967184Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive\n"}
{"Time":"2026-10-18T09:58:46.697529474Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive (0.16s)\n"}
{"Time":"2026-10-18T09:58:46.697637627Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Elapsed":0.16}
{"Time":"2026-10-18T09:58:46.697688267Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable"}
{"Time":"2026-10-18T09:58:46.697728656Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable\n"}
{"Time":"2026-10-18T09:58:46.897859053Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable (0.20s)\n"}
{"Time":"2026-10-18T09:58:46.897914374Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Elapsed":0.2}
{"Time":"2026-10-18T09:58:46.897925789Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status"}
{"Time":"2026-10-18T09:58:46.89793267Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status\n"}
{"Time":"2026-10-18T09:58:47.133344699Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status (0.24s)\n"}
{"Time":"2026-10-18T09:58:47.133394357Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Elapsed":0.24}
{"Time":"2026-10-18T09:58:47.13340992Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission"}
{"Time":"2026-10-18T09:58:47.133416629Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission\n"}
{"Time":"2026-10-18T09:58:47.395932851Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission (0.26s)\n"}
{"Time":"2026-10-18T09:58:47.395990976Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Elapsed":0.26}
{"Time":"2026-10-18T09:58:47.39600039Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client"}
{"Time":"2026-10-18T09:58:47.39600626Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client\n"}
{"Time":"2026-10-18T09:58:47.551597522Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client (0.16s)\n"}
{"Time":"2026-10-18T09:58:47.551686179Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Elapsed":0.16}
{"Time":"2026-10-18T09:58:47.551699741Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key"}
{"Time":"2026-10-18T09:58:47.551704575Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key\n"}
{"Time":"2026-10-18T09:58:47.726988817Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key (0.18s)\n"}
{"Time":"2026-10-18T09:58:47.72703833Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Elapsed":0.18}
{"Time":"2026-10-18T09:58:47.727048051Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant"}
{"Time":"2026-10-18T09:58:47.727054174Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant\n"}
{"Time":"2026-10-18T09:58:48.064860713Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant (0.34s)\n"}
{"Time":"2026-10-18T09:58:48.064901462Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Elapsed":0.34}
{"Time":"2026-10-18T09:58:48.064911327Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash"}
{"Time":"2026-10-18T09:58:48.064915394Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash\n"}
{"Time":"2026-10-18T09:58:48.243746078Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash (0.18s)\n"}
{"Time":"2026-10-18T09:58:48.243796917Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Elapsed":0.18}
{"Time":"2026-10-18T09:58:48.243811894Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision (4.74s)\n"}
{"Time":"2026-10-18T09:58:48.243834897Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Elapsed":4.74}
{"Time":"2026-10-18T09:58:48.243843365Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn"}
{"Time":"2026-10-18T09:58:48.243849217Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"=== RUN TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn\n"}
{"Time":"2026-10-18T09:58:48.998762292Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"--- PASS: TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn (0.75s)\n"}
{"Time":"2026-10-18T09:58:48.998818226Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Elapsed":0.75}
{"Time":"2026-10-18T09:58:48.998829127Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims"}
{"Time":"2026-10-18T09:58:48.998833474Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims\n"}
{"Time":"2026-10-18T09:58:48.998937126Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti"}
{"Time":"2026-10-18T09:58:48.998955846Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti\n"}
{"Time":"2026-10-18T09:58:48.99944807Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti (0.00s)\n"}
{"Time":"2026-10-18T09:58:48.99946477Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Elapsed":0}
{"Time":"2026-10-18T09:58:48.999474085Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope"}
{"Time":"2026-10-18T09:58:48.999479072Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope\n"}
{"Time":"2026-10-18T09:58:48.999951999Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope (0.00s)\n"}
{"Time":"2026-10-18T09:58:48.999969331Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Elapsed":0}
{"Time":"2026-10-18T09:58:48.999977158Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims (0.00s)\n"}
{"Time":"2026-10-18T09:58:48.999982911Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Elapsed":0}
{"Time":"2026-10-18T09:58:48.999992388Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE"}
{"Time":"2026-10-18T09:58:49.000015616Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE\n"}
{"Time":"2026-10-18T09:58:49.000441862Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer"}
{"Time":"2026-10-18T09:58:49.000461492Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/issuer\n"}
{"Time":"2026-10-18T09:58:49.000842526Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/issuer (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.000886647Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Elapsed":0}
{"Time":"2026-10-18T09:58:49.00089561Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience"}
{"Time":"2026-10-18T09:58:49.000899686Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/audience\n"}
{"Time":"2026-10-18T09:58:49.001272494Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/audience (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.001290881Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Elapsed":0}
{"Time":"2026-10-18T09:58:49.001299213Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired"}
{"Time":"2026-10-18T09:58:49.001307427Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/expired\n"}
{"Time":"2026-10-18T09:58:49.001671841Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/expired (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.001691093Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Elapsed":0}
{"Time":"2026-10-18T09:58:49.001699921Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued"}
{"Time":"2026-10-18T09:58:49.001704691Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/future-issued\n"}
{"Time":"2026-10-18T09:58:49.002152984Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/future-issued (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.002172127Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Elapsed":0}
{"Time":"2026-10-18T09:58:49.002187466Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp"}
{"Time":"2026-10-18T09:58:49.002192317Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-exp\n"}
{"Time":"2026-10-18T09:58:49.002621078Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-exp (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.002639518Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Elapsed":0}
{"Time":"2026-10-18T09:58:49.002650114Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat"}
{"Time":"2026-10-18T09:58:49.002655245Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-iat\n"}
{"Time":"2026-10-18T09:58:49.003012301Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-iat (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.003025556Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Elapsed":0}
{"Time":"2026-10-18T09:58:49.003033599Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal"}
{"Time":"2026-10-18T09:58:49.003038153Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-principal\n"}
{"Time":"2026-10-18T09:58:49.003403442Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-principal (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.003416237Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Elapsed":0}
{"Time":"2026-10-18T09:58:49.003495625Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance"}
{"Time":"2026-10-18T09:58:49.003506581Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-assurance\n"}
{"Time":"2026-10-18T09:58:49.003856378Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-assurance (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.003869444Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Elapsed":0}
{"Time":"2026-10-18T09:58:49.003877298Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued"}
{"Time":"2026-10-18T09:58:49.003881951Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/old-issued\n"}
{"Time":"2026-10-18T09:58:49.004269783Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/old-issued (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.004287258Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Elapsed":0}
{"Time":"2026-10-18T09:58:49.00429688Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant"}
{"Time":"2026-10-18T09:58:49.004316141Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-tenant\n"}
{"Time":"2026-10-18T09:58:49.004847635Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-tenant (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.004889656Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Elapsed":0}
{"Time":"2026-10-18T09:58:49.004899503Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp"}
{"Time":"2026-10-18T09:58:49.00490421Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp\n"}
{"Time":"2026-10-18T09:58:49.005633795Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.005659756Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Elapsed":0}
{"Time":"2026-10-18T09:58:49.005761227Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject"}
{"Time":"2026-10-18T09:58:49.00577707Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/empty-subject\n"}
{"Time":"2026-10-18T09:58:49.006380045Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/empty-subject (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.00642184Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Elapsed":0}
{"Time":"2026-10-18T09:58:49.006436664Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key"}
{"Time":"2026-10-18T09:58:49.006443301Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-key\n"}
{"Time":"2026-10-18T09:58:49.007106546Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-key (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.007138449Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Elapsed":0}
{"Time":"2026-10-18T09:58:49.007233185Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key"}
{"Time":"2026-10-18T09:58:49.007244817Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/unknown-key\n"}
{"Time":"2026-10-18T09:58:49.007592279Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/unknown-key (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.007606877Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Elapsed":0}
{"Time":"2026-10-18T09:58:49.0076409Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type"}
{"Time":"2026-10-18T09:58:49.007646851Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-type\n"}
{"Time":"2026-10-18T09:58:49.007988722Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-type (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.008003718Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Elapsed":0}
{"Time":"2026-10-18T09:58:49.008091933Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion"}
{"Time":"2026-10-18T09:58:49.008109526Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion\n"}
{"Time":"2026-10-18T09:58:49.008333839Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.00834961Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Elapsed":0}
{"Time":"2026-10-18T09:58:49.008443453Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none"}
{"Time":"2026-10-18T09:58:49.008454724Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/none\n"}
{"Time":"2026-10-18T09:58:49.008737262Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/none (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.008762808Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Elapsed":0}
{"Time":"2026-10-18T09:58:49.008770103Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE (0.01s)\n"}
{"Time":"2026-10-18T09:58:49.008775369Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Elapsed":0.01}
{"Time":"2026-10-18T09:58:49.009000809Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation"}
{"Time":"2026-10-18T09:58:49.009015516Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"=== RUN TestRequiredClaimExactAnyOfAndNoMutation\n"}
{"Time":"2026-10-18T09:58:49.009194148Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"--- PASS: TestRequiredClaimExactAnyOfAndNoMutation (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.009207385Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Elapsed":0}
{"Time":"2026-10-18T09:58:49.009304753Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation"}
{"Time":"2026-10-18T09:58:49.009329884Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"=== RUN TestRequiredClaimConfigurationRefusalsAndLegacyIsolation\n"}
{"Time":"2026-10-18T09:58:49.00944493Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"--- PASS: TestRequiredClaimConfigurationRefusalsAndLegacyIsolation (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.00948576Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Elapsed":0}
{"Time":"2026-10-18T09:58:49.009621457Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable"}
{"Time":"2026-10-18T09:58:49.009639897Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"=== RUN TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable\n"}
{"Time":"2026-10-18T09:58:49.013367051Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"--- PASS: TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.013414951Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Elapsed":0}
{"Time":"2026-10-18T09:58:49.013425961Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval"}
{"Time":"2026-10-18T09:58:49.013431488Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"=== RUN TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval\n"}
{"Time":"2026-10-18T09:58:49.013592455Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"--- PASS: TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.013605395Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Elapsed":0}
{"Time":"2026-10-18T09:58:49.013617388Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse"}
{"Time":"2026-10-18T09:58:49.013622221Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"=== RUN TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse\n"}
{"Time":"2026-10-18T09:58:49.013774414Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"--- PASS: TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse (0.00s)\n"}
{"Time":"2026-10-18T09:58:49.013787363Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Elapsed":0}
{"Time":"2026-10-18T09:58:49.01379491Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"PASS\n"}
{"Time":"2026-10-18T09:58:49.022903528Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"ok \tforgejo.org/services/auth/source/oauth2\t11.572s\n"}
{"Time":"2026-10-18T09:58:49.022961085Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Elapsed":11.572}

View file

@ -0,0 +1,168 @@
{
"returncode": 0,
"seconds": 11.5,
"argv": [
"bwrap",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--unshare-net",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-forgejo-current-token-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod/cache/download",
"/cached",
"--ro-bind",
"/etc/ssl/certs",
"/etc/ssl/certs",
"--ro-bind",
"/etc/resolv.conf",
"/etc/resolv.conf",
"--chdir",
"/work/source",
"--clearenv",
"--ro-bind",
"/tmp/custodian-forgejo-successor-attempt2/modules",
"/work/modules",
"--bind",
"/tmp/custodian-forgejo-successor-attempt2/gocache",
"/work/gocache",
"--dir",
"/run",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--setenv",
"PATH",
"/work/go/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOPATH",
"/work/gopath",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOVCS",
"*:off",
"--setenv",
"GOMODCACHE",
"/work/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"GIT_CONFIG_NOSYSTEM",
"1",
"--setenv",
"GIT_CONFIG_GLOBAL",
"/work/home/test.gitconfig",
"--setenv",
"GIT_TERMINAL_PROMPT",
"0",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/go",
"/work/go",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--",
"go",
"test",
"-c",
"-tags",
"sqlite sqlite_unlock_notify current_issuer_fixture",
"-o",
"/work/forgejo-oauth2.test",
"./services/auth/source/oauth2"
],
"source_sha256_at_start": {
"go.mod": "sha256:11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
"go.sum": "sha256:19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
"services/auth/source/oauth2/access_engine.go": "sha256:d16330043825d0284253e65d28bda2de4edbc633e4887d8c3618a106ee251daa",
"services/auth/source/oauth2/access_engine_assurance.go": "sha256:42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642",
"services/auth/source/oauth2/access_engine_assurance_test.go": "sha256:44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3",
"services/auth/source/oauth2/access_engine_azp_test.go": "sha256:5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce",
"services/auth/source/oauth2/access_engine_callback_test.go": "sha256:1cbe378cc20149be7b354484ab3297916733564f03a2441cd276f9039d043580",
"services/auth/source/oauth2/access_engine_current_token.go": "sha256:4a2e484ece7cf74686173d5e85c127643287570c2298af135ddfdc7c450b18d8",
"services/auth/source/oauth2/access_engine_current_token_test.go": "sha256:58f799c08a7b4638780abc511bd1a7359bd5e691c7805ff54582d02d2f42cae1",
"services/auth/source/oauth2/access_engine_purpose_test.go": "sha256:bf492f6384d7da0f84321f138d4efab5c77508c667c288c2807ad3cccc10383b",
"services/auth/source/oauth2/access_engine_test.go": "sha256:9112a142983c0a58c5d40344b58a20fa2c1bdbc7a79b5c586796362839a43b48",
"services/auth/source/oauth2/assert_interface_test.go": "sha256:a0db85e38b7ea61e21b0291e55c8b451c89a877bd3db229e35dbb4418458f54f",
"services/auth/source/oauth2/current_issuer_fixture_test.go": "sha256:24fb430e72848326c5d9ef42eab5028cf915dd601f43fb06010b32b568908781",
"services/auth/source/oauth2/init.go": "sha256:98a8030ed005be67b237d54c3b197a629f8c289e3d74c8edfcf4d740831f87d1",
"services/auth/source/oauth2/jwtsigningkey.go": "sha256:0c6e8ac131fd41ad299073ca748dd95e5fd9c044219c4d7f36d50c4ffcd5b266",
"services/auth/source/oauth2/jwtsigningkey_test.go": "sha256:3e414a541479741773e48de84be7d5a3b794bcc7289627fbd2e8996832628321",
"services/auth/source/oauth2/providers.go": "sha256:7df0051f19932a68d16a33a5d41898e06e9612f839195e27c86802eeebb2e3e9",
"services/auth/source/oauth2/providers_base.go": "sha256:643dbc0561ec7aa7a975978ff2c21d256a5ab619b125e8077d2395281f628542",
"services/auth/source/oauth2/providers_custom.go": "sha256:265a526d209d86c24cbcfdde4c2d73957aaeaccfd329438c11ce99c5951ebb39",
"services/auth/source/oauth2/providers_openid.go": "sha256:5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf",
"services/auth/source/oauth2/providers_simple.go": "sha256:53927b6bfc621dd1b68a074b6763abe5b27795d5a50ca6f0555f3e7a04fa1613",
"services/auth/source/oauth2/required_claim_values.go": "sha256:29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda",
"services/auth/source/oauth2/required_claim_values_test.go": "sha256:54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0",
"services/auth/source/oauth2/source.go": "sha256:44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642",
"services/auth/source/oauth2/source_authenticate.go": "sha256:c6551f00ef4349ccd6afdbb2cba847067528485ff621c3e507b9aab588bb96b5",
"services/auth/source/oauth2/source_callout.go": "sha256:6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c",
"services/auth/source/oauth2/source_name.go": "sha256:712943826e01f718ab4ca717df6c5f2171535cb2229dbf8460f542d757ef8d7f",
"services/auth/source/oauth2/source_register.go": "sha256:5ad9ccf213881734dc7fe7619cbe6c47cb6a4afdbbfc3e674ab4493857ebbf91",
"services/auth/source/oauth2/store.go": "sha256:35b244081569215384de07f8cdc1e83b5545a2e673ca1ac5a2a6ee686cb917a3",
"services/auth/source/oauth2/token.go": "sha256:c0a8868f904bb31842202b4e635a76403902adff9e638d5cb581e7e2d4954933",
"services/auth/source/oauth2/urlmapping.go": "sha256:f2132408d4a447e766e67495b1ca7b8cc2b47d4eb56b0c44b50ba01fe41ca83e",
"third_party/keycape-tokenstatus/LICENSE": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"third_party/keycape-tokenstatus/go.mod": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54",
"third_party/keycape-tokenstatus/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"third_party/keycape-tokenstatus/pkg/tokenstatus/client.go": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575",
"third_party/keycape-tokenstatus/producer-source.json": "sha256:3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
},
"counts_including_subtests": {
"pass": 0,
"fail": 0,
"skip": 0
},
"failures": [],
"real_credentials": false,
"native_admission": false,
"network": "private empty namespace",
"pid_namespace": "private",
"home": "empty allowlisted fixture HOME",
"compiled_fixture_only": true
}

View file

@ -0,0 +1,174 @@
{
"returncode": 0,
"seconds": 114.15,
"argv": [
"bwrap",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--unshare-net",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-forgejo-current-token-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod/cache/download",
"/cached",
"--ro-bind",
"/etc/ssl/certs",
"/etc/ssl/certs",
"--ro-bind",
"/etc/resolv.conf",
"/etc/resolv.conf",
"--chdir",
"/work/source",
"--clearenv",
"--ro-bind",
"/tmp/custodian-forgejo-successor-attempt2/modules",
"/work/modules",
"--bind",
"/tmp/custodian-forgejo-successor-attempt2/gocache",
"/work/gocache",
"--dir",
"/run",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--setenv",
"PATH",
"/work/go/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOPATH",
"/work/gopath",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOVCS",
"*:off",
"--setenv",
"GOMODCACHE",
"/work/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"GIT_CONFIG_NOSYSTEM",
"1",
"--setenv",
"GIT_CONFIG_GLOBAL",
"/work/home/test.gitconfig",
"--setenv",
"GIT_TERMINAL_PROMPT",
"0",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/go",
"/work/go",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--",
"go",
"test",
"-p",
"1",
"-json",
"-tags",
"sqlite sqlite_unlock_notify",
"./services/auth/source/oauth2",
"./cmd",
"./models/auth",
"./models/db",
"-run",
"TestAccessEngine|TestVerifiedApplicationIdentity|TestRequired|TestCurrent|TestPrivateSecret|TestSourceOperation|TestSensitiveSQL",
"-count=1"
],
"source_sha256_at_start": {
"go.mod": "sha256:11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
"go.sum": "sha256:19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
"services/auth/source/oauth2/access_engine.go": "sha256:d16330043825d0284253e65d28bda2de4edbc633e4887d8c3618a106ee251daa",
"services/auth/source/oauth2/access_engine_assurance.go": "sha256:42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642",
"services/auth/source/oauth2/access_engine_assurance_test.go": "sha256:44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3",
"services/auth/source/oauth2/access_engine_azp_test.go": "sha256:5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce",
"services/auth/source/oauth2/access_engine_callback_test.go": "sha256:1cbe378cc20149be7b354484ab3297916733564f03a2441cd276f9039d043580",
"services/auth/source/oauth2/access_engine_current_token.go": "sha256:4a2e484ece7cf74686173d5e85c127643287570c2298af135ddfdc7c450b18d8",
"services/auth/source/oauth2/access_engine_current_token_test.go": "sha256:58f799c08a7b4638780abc511bd1a7359bd5e691c7805ff54582d02d2f42cae1",
"services/auth/source/oauth2/access_engine_purpose_test.go": "sha256:bf492f6384d7da0f84321f138d4efab5c77508c667c288c2807ad3cccc10383b",
"services/auth/source/oauth2/access_engine_test.go": "sha256:9112a142983c0a58c5d40344b58a20fa2c1bdbc7a79b5c586796362839a43b48",
"services/auth/source/oauth2/assert_interface_test.go": "sha256:a0db85e38b7ea61e21b0291e55c8b451c89a877bd3db229e35dbb4418458f54f",
"services/auth/source/oauth2/current_issuer_fixture_test.go": "sha256:24fb430e72848326c5d9ef42eab5028cf915dd601f43fb06010b32b568908781",
"services/auth/source/oauth2/init.go": "sha256:98a8030ed005be67b237d54c3b197a629f8c289e3d74c8edfcf4d740831f87d1",
"services/auth/source/oauth2/jwtsigningkey.go": "sha256:0c6e8ac131fd41ad299073ca748dd95e5fd9c044219c4d7f36d50c4ffcd5b266",
"services/auth/source/oauth2/jwtsigningkey_test.go": "sha256:3e414a541479741773e48de84be7d5a3b794bcc7289627fbd2e8996832628321",
"services/auth/source/oauth2/providers.go": "sha256:7df0051f19932a68d16a33a5d41898e06e9612f839195e27c86802eeebb2e3e9",
"services/auth/source/oauth2/providers_base.go": "sha256:643dbc0561ec7aa7a975978ff2c21d256a5ab619b125e8077d2395281f628542",
"services/auth/source/oauth2/providers_custom.go": "sha256:265a526d209d86c24cbcfdde4c2d73957aaeaccfd329438c11ce99c5951ebb39",
"services/auth/source/oauth2/providers_openid.go": "sha256:5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf",
"services/auth/source/oauth2/providers_simple.go": "sha256:53927b6bfc621dd1b68a074b6763abe5b27795d5a50ca6f0555f3e7a04fa1613",
"services/auth/source/oauth2/required_claim_values.go": "sha256:29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda",
"services/auth/source/oauth2/required_claim_values_test.go": "sha256:54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0",
"services/auth/source/oauth2/source.go": "sha256:44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642",
"services/auth/source/oauth2/source_authenticate.go": "sha256:c6551f00ef4349ccd6afdbb2cba847067528485ff621c3e507b9aab588bb96b5",
"services/auth/source/oauth2/source_callout.go": "sha256:6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c",
"services/auth/source/oauth2/source_name.go": "sha256:712943826e01f718ab4ca717df6c5f2171535cb2229dbf8460f542d757ef8d7f",
"services/auth/source/oauth2/source_register.go": "sha256:5ad9ccf213881734dc7fe7619cbe6c47cb6a4afdbbfc3e674ab4493857ebbf91",
"services/auth/source/oauth2/store.go": "sha256:35b244081569215384de07f8cdc1e83b5545a2e673ca1ac5a2a6ee686cb917a3",
"services/auth/source/oauth2/token.go": "sha256:c0a8868f904bb31842202b4e635a76403902adff9e638d5cb581e7e2d4954933",
"services/auth/source/oauth2/urlmapping.go": "sha256:f2132408d4a447e766e67495b1ca7b8cc2b47d4eb56b0c44b50ba01fe41ca83e",
"third_party/keycape-tokenstatus/LICENSE": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"third_party/keycape-tokenstatus/go.mod": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54",
"third_party/keycape-tokenstatus/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"third_party/keycape-tokenstatus/pkg/tokenstatus/client.go": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575",
"third_party/keycape-tokenstatus/producer-source.json": "sha256:3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
},
"counts_including_subtests": {
"pass": 114,
"fail": 0,
"skip": 1
},
"failures": [],
"real_credentials": false,
"native_admission": false,
"network": "private empty namespace",
"pid_namespace": "private",
"home": "empty allowlisted fixture HOME",
"compiled_fixture_only": false
}

View file

@ -0,0 +1,521 @@
{"Time":"2026-10-18T10:02:36.286761203Z","Action":"start","Package":"forgejo.org/services/auth/source/oauth2"}
{"Time":"2026-10-18T10:02:36.377445354Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals"}
{"Time":"2026-10-18T10:02:36.37749613Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals\n"}
{"Time":"2026-10-18T10:02:36.377567952Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication"}
{"Time":"2026-10-18T10:02:36.377576095Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/future-authentication\n"}
{"Time":"2026-10-18T10:02:36.380110564Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/future-authentication (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.380152233Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Elapsed":0}
{"Time":"2026-10-18T10:02:36.380177508Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at"}
{"Time":"2026-10-18T10:02:36.380183892Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/missing-at\n"}
{"Time":"2026-10-18T10:02:36.380437238Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/missing-at (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.380488294Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Elapsed":0}
{"Time":"2026-10-18T10:02:36.380503172Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source"}
{"Time":"2026-10-18T10:02:36.380509503Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-source\n"}
{"Time":"2026-10-18T10:02:36.380779768Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-source (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.380814565Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Elapsed":0}
{"Time":"2026-10-18T10:02:36.380841851Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level"}
{"Time":"2026-10-18T10:02:36.380850697Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-level\n"}
{"Time":"2026-10-18T10:02:36.381211448Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-level (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.38125675Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Elapsed":0}
{"Time":"2026-10-18T10:02:36.381267039Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods"}
{"Time":"2026-10-18T10:02:36.381294587Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/object-methods\n"}
{"Time":"2026-10-18T10:02:36.3815904Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/object-methods (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.381636262Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Elapsed":0}
{"Time":"2026-10-18T10:02:36.381659021Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods"}
{"Time":"2026-10-18T10:02:36.381667362Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/empty-methods\n"}
{"Time":"2026-10-18T10:02:36.382058251Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/empty-methods (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.382129253Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Elapsed":0}
{"Time":"2026-10-18T10:02:36.382141219Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods"}
{"Time":"2026-10-18T10:02:36.382147125Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods\n"}
{"Time":"2026-10-18T10:02:36.382462404Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.382489438Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Elapsed":0}
{"Time":"2026-10-18T10:02:36.382497175Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field"}
{"Time":"2026-10-18T10:02:36.382501963Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field\n"}
{"Time":"2026-10-18T10:02:36.382838588Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.382883512Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Elapsed":0}
{"Time":"2026-10-18T10:02:36.382903548Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication"}
{"Time":"2026-10-18T10:02:36.382913359Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication\n"}
{"Time":"2026-10-18T10:02:36.383350324Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.383402912Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Elapsed":0}
{"Time":"2026-10-18T10:02:36.3834178Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at"}
{"Time":"2026-10-18T10:02:36.383423111Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/string-at\n"}
{"Time":"2026-10-18T10:02:36.383844108Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/string-at (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.383877616Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Elapsed":0}
{"Time":"2026-10-18T10:02:36.383884451Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at"}
{"Time":"2026-10-18T10:02:36.383889763Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/fractional-at\n"}
{"Time":"2026-10-18T10:02:36.384426397Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/fractional-at (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.384460897Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Elapsed":0}
{"Time":"2026-10-18T10:02:36.384469175Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa"}
{"Time":"2026-10-18T10:02:36.384474107Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa\n"}
{"Time":"2026-10-18T10:02:36.384897181Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.384926451Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Elapsed":0}
{"Time":"2026-10-18T10:02:36.384933223Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa"}
{"Time":"2026-10-18T10:02:36.384937813Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa\n"}
{"Time":"2026-10-18T10:02:36.385189115Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.385201405Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Elapsed":0}
{"Time":"2026-10-18T10:02:36.385208628Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods"}
{"Time":"2026-10-18T10:02:36.385212884Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods\n"}
{"Time":"2026-10-18T10:02:36.385602186Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.385612943Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Elapsed":0}
{"Time":"2026-10-18T10:02:36.386242293Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals (0.01s)\n"}
{"Time":"2026-10-18T10:02:36.386258209Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Elapsed":0.01}
{"Time":"2026-10-18T10:02:36.386265215Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds"}
{"Time":"2026-10-18T10:02:36.386268669Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"=== RUN TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds\n"}
{"Time":"2026-10-18T10:02:36.386801345Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"--- PASS: TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.386821904Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Elapsed":0}
{"Time":"2026-10-18T10:02:36.386833789Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact"}
{"Time":"2026-10-18T10:02:36.386839127Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"=== RUN TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact\n"}
{"Time":"2026-10-18T10:02:36.387402493Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"--- PASS: TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.387453251Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Elapsed":0}
{"Time":"2026-10-18T10:02:36.387477814Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient"}
{"Time":"2026-10-18T10:02:36.387487651Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient\n"}
{"Time":"2026-10-18T10:02:36.387502223Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client"}
{"Time":"2026-10-18T10:02:36.387511015Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client\n"}
{"Time":"2026-10-18T10:02:36.388612403Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.388670555Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Elapsed":0}
{"Time":"2026-10-18T10:02:36.388679284Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number"}
{"Time":"2026-10-18T10:02:36.38870271Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/number\n"}
{"Time":"2026-10-18T10:02:36.389148399Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/number (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.38919179Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Elapsed":0}
{"Time":"2026-10-18T10:02:36.38919986Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array"}
{"Time":"2026-10-18T10:02:36.389204441Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/array\n"}
{"Time":"2026-10-18T10:02:36.389841582Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/array (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.389872169Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Elapsed":0}
{"Time":"2026-10-18T10:02:36.389881394Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty"}
{"Time":"2026-10-18T10:02:36.389884992Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty\n"}
{"Time":"2026-10-18T10:02:36.390402311Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.390442609Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Elapsed":0}
{"Time":"2026-10-18T10:02:36.39045159Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.390456162Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Elapsed":0}
{"Time":"2026-10-18T10:02:36.390462492Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient"}
{"Time":"2026-10-18T10:02:36.390466162Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"=== RUN TestAccessEngineAuthorizedPartyAcceptsAdmittedClient\n"}
{"Time":"2026-10-18T10:02:36.391172134Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyAcceptsAdmittedClient (0.00s)\n"}
{"Time":"2026-10-18T10:02:36.391221765Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Elapsed":0}
{"Time":"2026-10-18T10:02:36.391236021Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision"}
{"Time":"2026-10-18T10:02:36.391261369Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision\n"}
{"Time":"2026-10-18T10:02:36.391279007Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins"}
{"Time":"2026-10-18T10:02:36.391285463Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins\n"}
{"Time":"2026-10-18T10:02:36.844977507Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins (0.45s)\n"}
{"Time":"2026-10-18T10:02:36.84502923Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Elapsed":0.45}
{"Time":"2026-10-18T10:02:36.845039906Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners"}
{"Time":"2026-10-18T10:02:36.845045461Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners\n"}
{"Time":"2026-10-18T10:02:37.376071266Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners (0.53s)\n"}
{"Time":"2026-10-18T10:02:37.376116578Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Elapsed":0.53}
{"Time":"2026-10-18T10:02:37.37613215Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers"}
{"Time":"2026-10-18T10:02:37.37613647Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers\n"}
{"Time":"2026-10-18T10:02:37.469947459Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers (0.09s)\n"}
{"Time":"2026-10-18T10:02:37.470014746Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Elapsed":0.09}
{"Time":"2026-10-18T10:02:37.470038714Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision (1.08s)\n"}
{"Time":"2026-10-18T10:02:37.470050373Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Elapsed":1.08}
{"Time":"2026-10-18T10:02:37.470059444Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP"}
{"Time":"2026-10-18T10:02:37.470066613Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP\n"}
{"Time":"2026-10-18T10:02:37.470102475Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny"}
{"Time":"2026-10-18T10:02:37.470110257Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny\n"}
{"Time":"2026-10-18T10:02:37.665831862Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny (0.20s)\n"}
{"Time":"2026-10-18T10:02:37.665877498Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Elapsed":0.2}
{"Time":"2026-10-18T10:02:37.665887913Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned"}
{"Time":"2026-10-18T10:02:37.665891592Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned\n"}
{"Time":"2026-10-18T10:02:38.122760652Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned (0.46s)\n"}
{"Time":"2026-10-18T10:02:38.122812664Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Elapsed":0.46}
{"Time":"2026-10-18T10:02:38.122822889Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key"}
{"Time":"2026-10-18T10:02:38.122829679Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key\n"}
{"Time":"2026-10-18T10:02:38.672838887Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key (0.55s)\n"}
{"Time":"2026-10-18T10:02:38.672886335Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Elapsed":0.55}
{"Time":"2026-10-18T10:02:38.672894334Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin"}
{"Time":"2026-10-18T10:02:38.672899465Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin\n"}
{"Time":"2026-10-18T10:02:38.73110489Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin (0.06s)\n"}
{"Time":"2026-10-18T10:02:38.731187948Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Elapsed":0.06}
{"Time":"2026-10-18T10:02:38.731204873Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding"}
{"Time":"2026-10-18T10:02:38.731210834Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding\n"}
{"Time":"2026-10-18T10:02:39.033993684Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding (0.30s)\n"}
{"Time":"2026-10-18T10:02:39.034046633Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Elapsed":0.3}
{"Time":"2026-10-18T10:02:39.03405593Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired"}
{"Time":"2026-10-18T10:02:39.034061701Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired\n"}
{"Time":"2026-10-18T10:02:39.342124377Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired (0.31s)\n"}
{"Time":"2026-10-18T10:02:39.342180239Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Elapsed":0.31}
{"Time":"2026-10-18T10:02:39.342194748Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP (1.87s)\n"}
{"Time":"2026-10-18T10:02:39.342200384Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Elapsed":1.87}
{"Time":"2026-10-18T10:02:39.34220602Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP"}
{"Time":"2026-10-18T10:02:39.342211646Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP\n"}
{"Time":"2026-10-18T10:02:39.342229464Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user"}
{"Time":"2026-10-18T10:02:39.342238346Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user\n"}
{"Time":"2026-10-18T10:02:39.894189509Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user (0.55s)\n"}
{"Time":"2026-10-18T10:02:39.894243793Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Elapsed":0.55}
{"Time":"2026-10-18T10:02:39.894254947Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups"}
{"Time":"2026-10-18T10:02:39.894262702Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups\n"}
{"Time":"2026-10-18T10:02:40.677135971Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups (0.78s)\n"}
{"Time":"2026-10-18T10:02:40.677190191Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Elapsed":0.78}
{"Time":"2026-10-18T10:02:40.677235774Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source"}
{"Time":"2026-10-18T10:02:40.677245503Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source\n"}
{"Time":"2026-10-18T10:02:41.300213165Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source (0.62s)\n"}
{"Time":"2026-10-18T10:02:41.300263625Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Elapsed":0.62}
{"Time":"2026-10-18T10:02:41.300274022Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission"}
{"Time":"2026-10-18T10:02:41.300280568Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission\n"}
{"Time":"2026-10-18T10:02:41.456629808Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission (0.16s)\n"}
{"Time":"2026-10-18T10:02:41.456692199Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Elapsed":0.16}
{"Time":"2026-10-18T10:02:41.456704642Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token"}
{"Time":"2026-10-18T10:02:41.456710404Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token\n"}
{"Time":"2026-10-18T10:02:41.754661946Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token (0.30s)\n"}
{"Time":"2026-10-18T10:02:41.754734246Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Elapsed":0.3}
{"Time":"2026-10-18T10:02:41.754746113Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity"}
{"Time":"2026-10-18T10:02:41.754751361Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity\n"}
{"Time":"2026-10-18T10:02:41.884522785Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity (0.13s)\n"}
{"Time":"2026-10-18T10:02:41.884580333Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Elapsed":0.13}
{"Time":"2026-10-18T10:02:41.884592335Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token"}
{"Time":"2026-10-18T10:02:41.884616105Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token\n"}
{"Time":"2026-10-18T10:02:41.992453903Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token (0.11s)\n"}
{"Time":"2026-10-18T10:02:41.992503462Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Elapsed":0.11}
{"Time":"2026-10-18T10:02:41.99252036Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject"}
{"Time":"2026-10-18T10:02:41.99252779Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject\n"}
{"Time":"2026-10-18T10:02:42.50912818Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject (0.52s)\n"}
{"Time":"2026-10-18T10:02:42.509187342Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Elapsed":0.52}
{"Time":"2026-10-18T10:02:42.509199651Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider"}
{"Time":"2026-10-18T10:02:42.509208542Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider\n"}
{"Time":"2026-10-18T10:02:42.777293385Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider (0.27s)\n"}
{"Time":"2026-10-18T10:02:42.777347641Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Elapsed":0.27}
{"Time":"2026-10-18T10:02:42.777359319Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience"}
{"Time":"2026-10-18T10:02:42.777366271Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience\n"}
{"Time":"2026-10-18T10:02:43.344358491Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience (0.57s)\n"}
{"Time":"2026-10-18T10:02:43.344413054Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Elapsed":0.57}
{"Time":"2026-10-18T10:02:43.344429204Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP (4.00s)\n"}
{"Time":"2026-10-18T10:02:43.3444375Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Elapsed":4}
{"Time":"2026-10-18T10:02:43.344465093Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision"}
{"Time":"2026-10-18T10:02:43.344472631Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision\n"}
{"Time":"2026-10-18T10:02:43.344483704Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing"}
{"Time":"2026-10-18T10:02:43.344490007Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing\n"}
{"Time":"2026-10-18T10:02:43.409503243Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing (0.06s)\n"}
{"Time":"2026-10-18T10:02:43.40957943Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Elapsed":0.06}
{"Time":"2026-10-18T10:02:43.409592829Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access"}
{"Time":"2026-10-18T10:02:43.409598772Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access\n"}
{"Time":"2026-10-18T10:02:43.76422413Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access (0.35s)\n"}
{"Time":"2026-10-18T10:02:43.764276971Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Elapsed":0.35}
{"Time":"2026-10-18T10:02:43.764288332Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject"}
{"Time":"2026-10-18T10:02:43.76429514Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/subject\n"}
{"Time":"2026-10-18T10:02:44.154085873Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/subject (0.39s)\n"}
{"Time":"2026-10-18T10:02:44.154136811Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Elapsed":0.39}
{"Time":"2026-10-18T10:02:44.154146081Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant"}
{"Time":"2026-10-18T10:02:44.154151293Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant\n"}
{"Time":"2026-10-18T10:02:44.563624408Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant (0.41s)\n"}
{"Time":"2026-10-18T10:02:44.563696411Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Elapsed":0.41}
{"Time":"2026-10-18T10:02:44.563734743Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal"}
{"Time":"2026-10-18T10:02:44.563744906Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/principal\n"}
{"Time":"2026-10-18T10:02:44.723009997Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/principal (0.16s)\n"}
{"Time":"2026-10-18T10:02:44.723065174Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Elapsed":0.16}
{"Time":"2026-10-18T10:02:44.723077672Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance"}
{"Time":"2026-10-18T10:02:44.723093154Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance\n"}
{"Time":"2026-10-18T10:02:44.965427968Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance (0.24s)\n"}
{"Time":"2026-10-18T10:02:44.965471368Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Elapsed":0.24}
{"Time":"2026-10-18T10:02:44.965478519Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time"}
{"Time":"2026-10-18T10:02:44.965481901Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time\n"}
{"Time":"2026-10-18T10:02:45.104444944Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time (0.14s)\n"}
{"Time":"2026-10-18T10:02:45.104493844Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Elapsed":0.14}
{"Time":"2026-10-18T10:02:45.104502889Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source"}
{"Time":"2026-10-18T10:02:45.10450774Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source\n"}
{"Time":"2026-10-18T10:02:45.181514471Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source (0.08s)\n"}
{"Time":"2026-10-18T10:02:45.181563191Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Elapsed":0.08}
{"Time":"2026-10-18T10:02:45.181572587Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at"}
{"Time":"2026-10-18T10:02:45.181577835Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at\n"}
{"Time":"2026-10-18T10:02:45.584085762Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at (0.40s)\n"}
{"Time":"2026-10-18T10:02:45.584141867Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Elapsed":0.4}
{"Time":"2026-10-18T10:02:45.584152796Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive"}
{"Time":"2026-10-18T10:02:45.584158747Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive\n"}
{"Time":"2026-10-18T10:02:45.890012871Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive (0.31s)\n"}
{"Time":"2026-10-18T10:02:45.890066316Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Elapsed":0.31}
{"Time":"2026-10-18T10:02:45.890079401Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable"}
{"Time":"2026-10-18T10:02:45.890084504Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable\n"}
{"Time":"2026-10-18T10:02:45.982451047Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable (0.09s)\n"}
{"Time":"2026-10-18T10:02:45.982502562Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Elapsed":0.09}
{"Time":"2026-10-18T10:02:45.982512752Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status"}
{"Time":"2026-10-18T10:02:45.982518514Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status\n"}
{"Time":"2026-10-18T10:02:46.383704879Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status (0.40s)\n"}
{"Time":"2026-10-18T10:02:46.383757531Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Elapsed":0.4}
{"Time":"2026-10-18T10:02:46.383769226Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission"}
{"Time":"2026-10-18T10:02:46.383775655Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission\n"}
{"Time":"2026-10-18T10:02:46.805394646Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission (0.42s)\n"}
{"Time":"2026-10-18T10:02:46.805457848Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Elapsed":0.42}
{"Time":"2026-10-18T10:02:46.805468714Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client"}
{"Time":"2026-10-18T10:02:46.805474764Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client\n"}
{"Time":"2026-10-18T10:02:47.11370227Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client (0.31s)\n"}
{"Time":"2026-10-18T10:02:47.113793272Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Elapsed":0.31}
{"Time":"2026-10-18T10:02:47.11380981Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key"}
{"Time":"2026-10-18T10:02:47.113815987Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key\n"}
{"Time":"2026-10-18T10:02:47.159427206Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key (0.05s)\n"}
{"Time":"2026-10-18T10:02:47.15948076Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Elapsed":0.05}
{"Time":"2026-10-18T10:02:47.159490985Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant"}
{"Time":"2026-10-18T10:02:47.15949681Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant\n"}
{"Time":"2026-10-18T10:02:47.357792416Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant (0.20s)\n"}
{"Time":"2026-10-18T10:02:47.357840144Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Elapsed":0.2}
{"Time":"2026-10-18T10:02:47.357850397Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash"}
{"Time":"2026-10-18T10:02:47.357856177Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash\n"}
{"Time":"2026-10-18T10:02:47.539451646Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash (0.18s)\n"}
{"Time":"2026-10-18T10:02:47.539504478Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Elapsed":0.18}
{"Time":"2026-10-18T10:02:47.53952832Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision (4.20s)\n"}
{"Time":"2026-10-18T10:02:47.539584398Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Elapsed":4.2}
{"Time":"2026-10-18T10:02:47.539599475Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn"}
{"Time":"2026-10-18T10:02:47.539607338Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"=== RUN TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn\n"}
{"Time":"2026-10-18T10:02:47.649544749Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"--- PASS: TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn (0.11s)\n"}
{"Time":"2026-10-18T10:02:47.649588771Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Elapsed":0.11}
{"Time":"2026-10-18T10:02:47.64959749Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims"}
{"Time":"2026-10-18T10:02:47.649601837Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims\n"}
{"Time":"2026-10-18T10:02:47.649717672Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti"}
{"Time":"2026-10-18T10:02:47.649728015Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti\n"}
{"Time":"2026-10-18T10:02:47.650301593Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.650430831Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Elapsed":0}
{"Time":"2026-10-18T10:02:47.650459046Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope"}
{"Time":"2026-10-18T10:02:47.650465809Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope\n"}
{"Time":"2026-10-18T10:02:47.650997418Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.651016553Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Elapsed":0}
{"Time":"2026-10-18T10:02:47.651026012Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.651031161Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Elapsed":0}
{"Time":"2026-10-18T10:02:47.651122803Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE"}
{"Time":"2026-10-18T10:02:47.651186086Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE\n"}
{"Time":"2026-10-18T10:02:47.651866582Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject"}
{"Time":"2026-10-18T10:02:47.651890162Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/empty-subject\n"}
{"Time":"2026-10-18T10:02:47.65231406Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/empty-subject (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.652356865Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Elapsed":0}
{"Time":"2026-10-18T10:02:47.652379589Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience"}
{"Time":"2026-10-18T10:02:47.652386397Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/audience\n"}
{"Time":"2026-10-18T10:02:47.652862985Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/audience (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.652885537Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Elapsed":0}
{"Time":"2026-10-18T10:02:47.652896493Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp"}
{"Time":"2026-10-18T10:02:47.652903653Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-exp\n"}
{"Time":"2026-10-18T10:02:47.653352038Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-exp (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.653377269Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Elapsed":0}
{"Time":"2026-10-18T10:02:47.653385601Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat"}
{"Time":"2026-10-18T10:02:47.65338937Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-iat\n"}
{"Time":"2026-10-18T10:02:47.653837464Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-iat (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.653867681Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Elapsed":0}
{"Time":"2026-10-18T10:02:47.653879818Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal"}
{"Time":"2026-10-18T10:02:47.653904209Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-principal\n"}
{"Time":"2026-10-18T10:02:47.654293322Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-principal (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.654307885Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Elapsed":0}
{"Time":"2026-10-18T10:02:47.654394904Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance"}
{"Time":"2026-10-18T10:02:47.65441769Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-assurance\n"}
{"Time":"2026-10-18T10:02:47.65472716Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-assurance (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.654740208Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Elapsed":0}
{"Time":"2026-10-18T10:02:47.654754211Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer"}
{"Time":"2026-10-18T10:02:47.654757719Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/issuer\n"}
{"Time":"2026-10-18T10:02:47.655093061Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/issuer (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.655104135Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Elapsed":0}
{"Time":"2026-10-18T10:02:47.655195723Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired"}
{"Time":"2026-10-18T10:02:47.655206544Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/expired\n"}
{"Time":"2026-10-18T10:02:47.65573527Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/expired (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.655764549Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Elapsed":0}
{"Time":"2026-10-18T10:02:47.65577326Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued"}
{"Time":"2026-10-18T10:02:47.655777624Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/future-issued\n"}
{"Time":"2026-10-18T10:02:47.656264877Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/future-issued (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.656294643Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Elapsed":0}
{"Time":"2026-10-18T10:02:47.656303399Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued"}
{"Time":"2026-10-18T10:02:47.656328945Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/old-issued\n"}
{"Time":"2026-10-18T10:02:47.656705517Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/old-issued (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.656726942Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Elapsed":0}
{"Time":"2026-10-18T10:02:47.656735544Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant"}
{"Time":"2026-10-18T10:02:47.656740594Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-tenant\n"}
{"Time":"2026-10-18T10:02:47.657116135Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-tenant (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.657127145Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Elapsed":0}
{"Time":"2026-10-18T10:02:47.6572392Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp"}
{"Time":"2026-10-18T10:02:47.657258542Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp\n"}
{"Time":"2026-10-18T10:02:47.657552516Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.657565654Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Elapsed":0}
{"Time":"2026-10-18T10:02:47.657572751Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key"}
{"Time":"2026-10-18T10:02:47.657576637Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-key\n"}
{"Time":"2026-10-18T10:02:47.658047671Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-key (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.658062333Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Elapsed":0}
{"Time":"2026-10-18T10:02:47.658070927Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key"}
{"Time":"2026-10-18T10:02:47.658079755Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/unknown-key\n"}
{"Time":"2026-10-18T10:02:47.658335313Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/unknown-key (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.658347729Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Elapsed":0}
{"Time":"2026-10-18T10:02:47.658367306Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type"}
{"Time":"2026-10-18T10:02:47.658372229Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-type\n"}
{"Time":"2026-10-18T10:02:47.658553296Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-type (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.658565884Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Elapsed":0}
{"Time":"2026-10-18T10:02:47.658571294Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion"}
{"Time":"2026-10-18T10:02:47.658575478Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion\n"}
{"Time":"2026-10-18T10:02:47.658699411Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.658789773Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Elapsed":0}
{"Time":"2026-10-18T10:02:47.658808195Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none"}
{"Time":"2026-10-18T10:02:47.658814868Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/none\n"}
{"Time":"2026-10-18T10:02:47.659024645Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/none (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.659036611Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Elapsed":0}
{"Time":"2026-10-18T10:02:47.659042598Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE (0.01s)\n"}
{"Time":"2026-10-18T10:02:47.659075457Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Elapsed":0.01}
{"Time":"2026-10-18T10:02:47.659260302Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation"}
{"Time":"2026-10-18T10:02:47.659277958Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"=== RUN TestRequiredClaimExactAnyOfAndNoMutation\n"}
{"Time":"2026-10-18T10:02:47.659288156Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"--- PASS: TestRequiredClaimExactAnyOfAndNoMutation (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.659325984Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Elapsed":0}
{"Time":"2026-10-18T10:02:47.659352197Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation"}
{"Time":"2026-10-18T10:02:47.659369176Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"=== RUN TestRequiredClaimConfigurationRefusalsAndLegacyIsolation\n"}
{"Time":"2026-10-18T10:02:47.659377039Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"--- PASS: TestRequiredClaimConfigurationRefusalsAndLegacyIsolation (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.659452297Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Elapsed":0}
{"Time":"2026-10-18T10:02:47.659465282Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable"}
{"Time":"2026-10-18T10:02:47.659469421Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"=== RUN TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable\n"}
{"Time":"2026-10-18T10:02:47.666116781Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"--- PASS: TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable (0.01s)\n"}
{"Time":"2026-10-18T10:02:47.666394522Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Elapsed":0.01}
{"Time":"2026-10-18T10:02:47.66642335Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval"}
{"Time":"2026-10-18T10:02:47.666445695Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"=== RUN TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval\n"}
{"Time":"2026-10-18T10:02:47.666669096Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"--- PASS: TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.666684362Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Elapsed":0}
{"Time":"2026-10-18T10:02:47.666692234Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse"}
{"Time":"2026-10-18T10:02:47.66669668Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"=== RUN TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse\n"}
{"Time":"2026-10-18T10:02:47.666829775Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"--- PASS: TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse (0.00s)\n"}
{"Time":"2026-10-18T10:02:47.666841749Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Elapsed":0}
{"Time":"2026-10-18T10:02:47.666927089Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"PASS\n"}
{"Time":"2026-10-18T10:02:47.674386398Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"ok \tforgejo.org/services/auth/source/oauth2\t11.387s\n"}
{"Time":"2026-10-18T10:02:47.674438184Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Elapsed":11.388}
{"Time":"2026-10-18T10:03:26.827401418Z","Action":"start","Package":"forgejo.org/cmd"}
{"Time":"2026-10-18T10:03:27.069297722Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...s/storage/storage.go:157:initAttachments() [I] Initialising Attachment storage with type: local\n"}
{"Time":"2026-10-18T10:03:27.069480465Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata4152700923/attachments\n"}
{"Time":"2026-10-18T10:03:27.069490673Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...s/storage/storage.go:147:initAvatars() [I] Initialising Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:03:27.069496155Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata4152700923/avatars\n"}
{"Time":"2026-10-18T10:03:27.06950116Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...s/storage/storage.go:173:initRepoAvatars() [I] Initialising Repository Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:03:27.069505948Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata4152700923/repo-avatars\n"}
{"Time":"2026-10-18T10:03:27.069510637Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...s/storage/storage.go:179:initRepoArchives() [I] Initialising Repository Archive storage with type: local\n"}
{"Time":"2026-10-18T10:03:27.069518076Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata4152700923/repo-archive\n"}
{"Time":"2026-10-18T10:03:27.06952474Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...s/storage/storage.go:189:initPackages() [I] Initialising Packages storage with type: local\n"}
{"Time":"2026-10-18T10:03:27.069529384Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata4152700923/packages\n"}
{"Time":"2026-10-18T10:03:27.069544244Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...s/storage/storage.go:200:initActions() [I] Initialising Actions storage with type: local\n"}
{"Time":"2026-10-18T10:03:27.069549276Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata4152700923/actions_log\n"}
{"Time":"2026-10-18T10:03:27.069553992Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...s/storage/storage.go:204:initActions() [I] Initialising ActionsArtifacts storage with type: local\n"}
{"Time":"2026-10-18T10:03:27.069558591Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:03:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/go-build3627027042/b1036/data/actions_artifacts\n"}
{"Time":"2026-10-18T10:03:27.246060561Z","Action":"run","Package":"forgejo.org/cmd","Test":"TestPrivateSecretMetadata"}
{"Time":"2026-10-18T10:03:27.246110976Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretMetadata","Output":"=== RUN TestPrivateSecretMetadata\n"}
{"Time":"2026-10-18T10:03:27.246126657Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretMetadata","Output":"--- PASS: TestPrivateSecretMetadata (0.00s)\n"}
{"Time":"2026-10-18T10:03:27.246140408Z","Action":"pass","Package":"forgejo.org/cmd","Test":"TestPrivateSecretMetadata","Elapsed":0}
{"Time":"2026-10-18T10:03:27.246295791Z","Action":"run","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement"}
{"Time":"2026-10-18T10:03:27.246314853Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement","Output":"=== RUN TestPrivateSecretOpenedFileReplacement\n"}
{"Time":"2026-10-18T10:03:27.247006389Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement","Output":" admin_auth_oauth_secret_linux_test.go:57: requires isolated read-only bind fixture\n"}
{"Time":"2026-10-18T10:03:27.247092116Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement","Output":"--- SKIP: TestPrivateSecretOpenedFileReplacement (0.00s)\n"}
{"Time":"2026-10-18T10:03:27.247110069Z","Action":"skip","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement","Elapsed":0}
{"Time":"2026-10-18T10:03:27.247482923Z","Action":"output","Package":"forgejo.org/cmd","Output":"PASS\n"}
{"Time":"2026-10-18T10:03:27.27933977Z","Action":"output","Package":"forgejo.org/cmd","Output":"ok \tforgejo.org/cmd\t0.452s\n"}
{"Time":"2026-10-18T10:03:27.279403044Z","Action":"pass","Package":"forgejo.org/cmd","Elapsed":0.452}
{"Time":"2026-10-18T10:03:33.209698168Z","Action":"start","Package":"forgejo.org/models/auth"}
{"Time":"2026-10-18T10:03:33.396487319Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...s/storage/storage.go:157:initAttachments() [I] Initialising Attachment storage with type: local\n"}
{"Time":"2026-10-18T10:03:33.396686844Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata920743698/attachments\n"}
{"Time":"2026-10-18T10:03:33.396699721Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...s/storage/storage.go:147:initAvatars() [I] Initialising Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:03:33.396706916Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata920743698/avatars\n"}
{"Time":"2026-10-18T10:03:33.396712128Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...s/storage/storage.go:173:initRepoAvatars() [I] Initialising Repository Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:03:33.396717232Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata920743698/repo-avatars\n"}
{"Time":"2026-10-18T10:03:33.396722246Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...s/storage/storage.go:179:initRepoArchives() [I] Initialising Repository Archive storage with type: local\n"}
{"Time":"2026-10-18T10:03:33.396727052Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata920743698/repo-archive\n"}
{"Time":"2026-10-18T10:03:33.39673193Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...s/storage/storage.go:189:initPackages() [I] Initialising Packages storage with type: local\n"}
{"Time":"2026-10-18T10:03:33.396736682Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata920743698/packages\n"}
{"Time":"2026-10-18T10:03:33.396748495Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...s/storage/storage.go:200:initActions() [I] Initialising Actions storage with type: local\n"}
{"Time":"2026-10-18T10:03:33.396753599Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata920743698/actions_log\n"}
{"Time":"2026-10-18T10:03:33.396758504Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...s/storage/storage.go:204:initActions() [I] Initialising ActionsArtifacts storage with type: local\n"}
{"Time":"2026-10-18T10:03:33.396763292Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:03:33 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/go-build3627027042/b1316/data/actions_artifacts\n"}
{"Time":"2026-10-18T10:03:33.537899252Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationDatabaseWriterExclusion"}
{"Time":"2026-10-18T10:03:33.538041787Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationDatabaseWriterExclusion","Output":"=== RUN TestSourceOperationDatabaseWriterExclusion\n"}
{"Time":"2026-10-18T10:03:33.639093097Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationDatabaseWriterExclusion","Output":"--- PASS: TestSourceOperationDatabaseWriterExclusion (0.10s)\n"}
{"Time":"2026-10-18T10:03:33.639145596Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationDatabaseWriterExclusion","Elapsed":0.1}
{"Time":"2026-10-18T10:03:33.639161105Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging"}
{"Time":"2026-10-18T10:03:33.63916547Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Output":"=== RUN TestSourceOperationRefusesActualXORMArgumentLogging\n"}
{"Time":"2026-10-18T10:03:33.639173297Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Output":"[xorm] [info] 2026/10/18 10:03:33.639134 [SQL] BEGIN TRANSACTION [] - 50.659µs\n"}
{"Time":"2026-10-18T10:03:33.63917877Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Output":"[xorm] [info] 2026/10/18 10:03:33.639160 [SQL] ROLLBACK [] - 13.354µs\n"}
{"Time":"2026-10-18T10:03:33.639264695Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Output":"--- PASS: TestSourceOperationRefusesActualXORMArgumentLogging (0.00s)\n"}
{"Time":"2026-10-18T10:03:33.639286427Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Elapsed":0}
{"Time":"2026-10-18T10:03:33.64030111Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationProcessRecovery"}
{"Time":"2026-10-18T10:03:33.640336881Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationProcessRecovery","Output":"=== RUN TestSourceOperationProcessRecovery\n"}
{"Time":"2026-10-18T10:03:35.896451485Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationProcessRecovery","Output":"--- PASS: TestSourceOperationProcessRecovery (2.26s)\n"}
{"Time":"2026-10-18T10:03:35.89650374Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationProcessRecovery","Elapsed":2.26}
{"Time":"2026-10-18T10:03:35.896514723Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationCreateAndInverse"}
{"Time":"2026-10-18T10:03:35.896519079Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationCreateAndInverse","Output":"=== RUN TestSourceOperationCreateAndInverse\n"}
{"Time":"2026-10-18T10:03:35.926108994Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationCreateAndInverse","Output":"--- PASS: TestSourceOperationCreateAndInverse (0.03s)\n"}
{"Time":"2026-10-18T10:03:35.926165208Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationCreateAndInverse","Elapsed":0.03}
{"Time":"2026-10-18T10:03:35.926178905Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRestoresExactRawPreexisting"}
{"Time":"2026-10-18T10:03:35.92618427Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRestoresExactRawPreexisting","Output":"=== RUN TestSourceOperationRestoresExactRawPreexisting\n"}
{"Time":"2026-10-18T10:03:35.945407969Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRestoresExactRawPreexisting","Output":"--- PASS: TestSourceOperationRestoresExactRawPreexisting (0.02s)\n"}
{"Time":"2026-10-18T10:03:35.945456852Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRestoresExactRawPreexisting","Elapsed":0.02}
{"Time":"2026-10-18T10:03:35.945482542Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationUnchangedNeverDeletesPreexisting"}
{"Time":"2026-10-18T10:03:35.945488547Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationUnchangedNeverDeletesPreexisting","Output":"=== RUN TestSourceOperationUnchangedNeverDeletesPreexisting\n"}
{"Time":"2026-10-18T10:03:35.974070972Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationUnchangedNeverDeletesPreexisting","Output":"--- PASS: TestSourceOperationUnchangedNeverDeletesPreexisting (0.03s)\n"}
{"Time":"2026-10-18T10:03:35.974123101Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationUnchangedNeverDeletesPreexisting","Elapsed":0.03}
{"Time":"2026-10-18T10:03:35.974140486Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses"}
{"Time":"2026-10-18T10:03:35.974146185Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses","Output":"=== RUN TestSourceOperationPrivateDriftRefuses\n"}
{"Time":"2026-10-18T10:03:35.974374853Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-apply"}
{"Time":"2026-10-18T10:03:35.974391923Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-apply","Output":"=== RUN TestSourceOperationPrivateDriftRefuses/before-apply\n"}
{"Time":"2026-10-18T10:03:35.996858429Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-apply","Output":"--- PASS: TestSourceOperationPrivateDriftRefuses/before-apply (0.02s)\n"}
{"Time":"2026-10-18T10:03:35.996908303Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-apply","Elapsed":0.02}
{"Time":"2026-10-18T10:03:35.996923822Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-inverse"}
{"Time":"2026-10-18T10:03:35.996929782Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-inverse","Output":"=== RUN TestSourceOperationPrivateDriftRefuses/before-inverse\n"}
{"Time":"2026-10-18T10:03:36.01523478Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-inverse","Output":"--- PASS: TestSourceOperationPrivateDriftRefuses/before-inverse (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.015283482Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-inverse","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.015293933Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses","Output":"--- PASS: TestSourceOperationPrivateDriftRefuses (0.04s)\n"}
{"Time":"2026-10-18T10:03:36.015300128Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses","Elapsed":0.04}
{"Time":"2026-10-18T10:03:36.01530937Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationStockCRUDDriftRefuses"}
{"Time":"2026-10-18T10:03:36.015314438Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationStockCRUDDriftRefuses","Output":"=== RUN TestSourceOperationStockCRUDDriftRefuses\n"}
{"Time":"2026-10-18T10:03:36.034994343Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationStockCRUDDriftRefuses","Output":"--- PASS: TestSourceOperationStockCRUDDriftRefuses (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.0350675Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationStockCRUDDriftRefuses","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.035103605Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion"}
{"Time":"2026-10-18T10:03:36.035147528Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion","Output":"=== RUN TestSourceOperationLinkedSourceRefusesDeletion\n"}
{"Time":"2026-10-18T10:03:36.035187998Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/user"}
{"Time":"2026-10-18T10:03:36.035212696Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/user","Output":"=== RUN TestSourceOperationLinkedSourceRefusesDeletion/user\n"}
{"Time":"2026-10-18T10:03:36.053512886Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/user","Output":"--- PASS: TestSourceOperationLinkedSourceRefusesDeletion/user (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.053565583Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/user","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.053577865Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/external"}
{"Time":"2026-10-18T10:03:36.053583464Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/external","Output":"=== RUN TestSourceOperationLinkedSourceRefusesDeletion/external\n"}
{"Time":"2026-10-18T10:03:36.072400155Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/external","Output":"--- PASS: TestSourceOperationLinkedSourceRefusesDeletion/external (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.072451382Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/external","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.072461643Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion","Output":"--- PASS: TestSourceOperationLinkedSourceRefusesDeletion (0.04s)\n"}
{"Time":"2026-10-18T10:03:36.072468388Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion","Elapsed":0.04}
{"Time":"2026-10-18T10:03:36.07247369Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPreparedCancellationAndIDReuse"}
{"Time":"2026-10-18T10:03:36.072478415Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPreparedCancellationAndIDReuse","Output":"=== RUN TestSourceOperationPreparedCancellationAndIDReuse\n"}
{"Time":"2026-10-18T10:03:36.091777715Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPreparedCancellationAndIDReuse","Output":"--- PASS: TestSourceOperationPreparedCancellationAndIDReuse (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.091833793Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPreparedCancellationAndIDReuse","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.091845642Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRejectsUnsafeRuntime"}
{"Time":"2026-10-18T10:03:36.091852702Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRejectsUnsafeRuntime","Output":"=== RUN TestSourceOperationRejectsUnsafeRuntime\n"}
{"Time":"2026-10-18T10:03:36.109374155Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRejectsUnsafeRuntime","Output":"--- PASS: TestSourceOperationRejectsUnsafeRuntime (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.109427438Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRejectsUnsafeRuntime","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.109438096Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesSharedManagedSource"}
{"Time":"2026-10-18T10:03:36.109443984Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesSharedManagedSource","Output":"=== RUN TestSourceOperationRefusesSharedManagedSource\n"}
{"Time":"2026-10-18T10:03:36.129206721Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesSharedManagedSource","Output":"--- PASS: TestSourceOperationRefusesSharedManagedSource (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.129260365Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesSharedManagedSource","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.129270437Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationBoundsPrivateJournalCustody"}
{"Time":"2026-10-18T10:03:36.129276398Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationBoundsPrivateJournalCustody","Output":"=== RUN TestSourceOperationBoundsPrivateJournalCustody\n"}
{"Time":"2026-10-18T10:03:36.150126051Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationBoundsPrivateJournalCustody","Output":"--- PASS: TestSourceOperationBoundsPrivateJournalCustody (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.150180975Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationBoundsPrivateJournalCustody","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.15019772Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRequiresExactPostState"}
{"Time":"2026-10-18T10:03:36.150204708Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRequiresExactPostState","Output":"=== RUN TestSourceOperationRequiresExactPostState\n"}
{"Time":"2026-10-18T10:03:36.170095586Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRequiresExactPostState","Output":"--- PASS: TestSourceOperationRequiresExactPostState (0.02s)\n"}
{"Time":"2026-10-18T10:03:36.170150014Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRequiresExactPostState","Elapsed":0.02}
{"Time":"2026-10-18T10:03:36.170459161Z","Action":"output","Package":"forgejo.org/models/auth","Output":"PASS\n"}
{"Time":"2026-10-18T10:03:36.204795981Z","Action":"output","Package":"forgejo.org/models/auth","Output":"ok \tforgejo.org/models/auth\t2.995s\n"}
{"Time":"2026-10-18T10:03:36.204885053Z","Action":"pass","Package":"forgejo.org/models/auth","Elapsed":2.995}
{"Time":"2026-10-18T10:04:34.442600416Z","Action":"start","Package":"forgejo.org/models/db"}
{"Time":"2026-10-18T10:04:34.65276927Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...s/storage/storage.go:157:initAttachments() [I] Initialising Attachment storage with type: local\n"}
{"Time":"2026-10-18T10:04:34.652859993Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata838365758/attachments\n"}
{"Time":"2026-10-18T10:04:34.652870462Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...s/storage/storage.go:147:initAvatars() [I] Initialising Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:04:34.652880426Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata838365758/avatars\n"}
{"Time":"2026-10-18T10:04:34.652888244Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...s/storage/storage.go:173:initRepoAvatars() [I] Initialising Repository Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:04:34.652895999Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata838365758/repo-avatars\n"}
{"Time":"2026-10-18T10:04:34.652904917Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...s/storage/storage.go:179:initRepoArchives() [I] Initialising Repository Archive storage with type: local\n"}
{"Time":"2026-10-18T10:04:34.652912356Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata838365758/repo-archive\n"}
{"Time":"2026-10-18T10:04:34.652935774Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...s/storage/storage.go:189:initPackages() [I] Initialising Packages storage with type: local\n"}
{"Time":"2026-10-18T10:04:34.652942266Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata838365758/packages\n"}
{"Time":"2026-10-18T10:04:34.653046127Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...s/storage/storage.go:200:initActions() [I] Initialising Actions storage with type: local\n"}
{"Time":"2026-10-18T10:04:34.653072637Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata838365758/actions_log\n"}
{"Time":"2026-10-18T10:04:34.653090194Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...s/storage/storage.go:204:initActions() [I] Initialising ActionsArtifacts storage with type: local\n"}
{"Time":"2026-10-18T10:04:34.653148139Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:04:34 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/go-build3627027042/b1337/data/actions_artifacts\n"}
{"Time":"2026-10-18T10:04:34.76957674Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine"}
{"Time":"2026-10-18T10:04:34.769624865Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine","Output":"=== RUN TestSensitiveSQLLoggingChecksUnderlyingEngine\n"}
{"Time":"2026-10-18T10:04:34.769780476Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/engine"}
{"Time":"2026-10-18T10:04:34.769798285Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/engine","Output":"=== RUN TestSensitiveSQLLoggingChecksUnderlyingEngine/engine\n"}
{"Time":"2026-10-18T10:04:34.769807825Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/engine","Output":"--- PASS: TestSensitiveSQLLoggingChecksUnderlyingEngine/engine (0.00s)\n"}
{"Time":"2026-10-18T10:04:34.769812686Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/engine","Elapsed":0}
{"Time":"2026-10-18T10:04:34.770585777Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/session"}
{"Time":"2026-10-18T10:04:34.770631098Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/session","Output":"=== RUN TestSensitiveSQLLoggingChecksUnderlyingEngine/session\n"}
{"Time":"2026-10-18T10:04:34.77064484Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/session","Output":"--- PASS: TestSensitiveSQLLoggingChecksUnderlyingEngine/session (0.00s)\n"}
{"Time":"2026-10-18T10:04:34.77065173Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/session","Elapsed":0}
{"Time":"2026-10-18T10:04:34.770666716Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine","Output":"--- PASS: TestSensitiveSQLLoggingChecksUnderlyingEngine (0.00s)\n"}
{"Time":"2026-10-18T10:04:34.770672974Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine","Elapsed":0}
{"Time":"2026-10-18T10:04:34.770682442Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLHooks"}
{"Time":"2026-10-18T10:04:34.770688213Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLHooks","Output":"=== RUN TestSensitiveSQLHooks\n"}
{"Time":"2026-10-18T10:04:34.776085941Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLHooks","Output":"--- PASS: TestSensitiveSQLHooks (0.01s)\n"}
{"Time":"2026-10-18T10:04:34.776132479Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLHooks","Elapsed":0.01}
{"Time":"2026-10-18T10:04:34.776145013Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLActualContextPropagation"}
{"Time":"2026-10-18T10:04:34.776149684Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLActualContextPropagation","Output":"=== RUN TestSensitiveSQLActualContextPropagation\n"}
{"Time":"2026-10-18T10:04:34.778748834Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLActualContextPropagation","Output":"--- PASS: TestSensitiveSQLActualContextPropagation (0.00s)\n"}
{"Time":"2026-10-18T10:04:34.778786039Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLActualContextPropagation","Elapsed":0}
{"Time":"2026-10-18T10:04:34.779167757Z","Action":"output","Package":"forgejo.org/models/db","Output":"PASS\n"}
{"Time":"2026-10-18T10:04:34.811424991Z","Action":"output","Package":"forgejo.org/models/db","Output":"ok \tforgejo.org/models/db\t0.369s\n"}
{"Time":"2026-10-18T10:04:34.811472836Z","Action":"pass","Package":"forgejo.org/models/db","Elapsed":0.369}

View file

@ -0,0 +1,171 @@
{
"returncode": 0,
"seconds": 15.36,
"argv": [
"bwrap",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--unshare-net",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-forgejo-current-token-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod/cache/download",
"/cached",
"--ro-bind",
"/etc/ssl/certs",
"/etc/ssl/certs",
"--ro-bind",
"/etc/resolv.conf",
"/etc/resolv.conf",
"--chdir",
"/work/source",
"--clearenv",
"--ro-bind",
"/tmp/custodian-forgejo-successor-attempt2/modules",
"/work/modules",
"--bind",
"/tmp/custodian-forgejo-successor-attempt2/gocache",
"/work/gocache",
"--dir",
"/run",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--setenv",
"PATH",
"/work/go/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOPATH",
"/work/gopath",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOVCS",
"*:off",
"--setenv",
"GOMODCACHE",
"/work/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"GIT_CONFIG_NOSYSTEM",
"1",
"--setenv",
"GIT_CONFIG_GLOBAL",
"/work/home/test.gitconfig",
"--setenv",
"GIT_TERMINAL_PROMPT",
"0",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/go",
"/work/go",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-successor-r3/descriptor-fixtures",
"/run/writable-input",
"--",
"go",
"test",
"-p",
"1",
"-json",
"-tags",
"sqlite sqlite_unlock_notify",
"./services/auth/source/oauth2",
"-run",
"TestAccessEngine|TestVerifiedApplicationIdentity|TestRequired|TestCurrent",
"-count=1"
],
"source_sha256_at_start": {
"go.mod": "sha256:11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
"go.sum": "sha256:19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
"services/auth/source/oauth2/access_engine.go": "sha256:d16330043825d0284253e65d28bda2de4edbc633e4887d8c3618a106ee251daa",
"services/auth/source/oauth2/access_engine_assurance.go": "sha256:42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642",
"services/auth/source/oauth2/access_engine_assurance_test.go": "sha256:44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3",
"services/auth/source/oauth2/access_engine_azp_test.go": "sha256:5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce",
"services/auth/source/oauth2/access_engine_callback_test.go": "sha256:1cbe378cc20149be7b354484ab3297916733564f03a2441cd276f9039d043580",
"services/auth/source/oauth2/access_engine_current_token.go": "sha256:4a2e484ece7cf74686173d5e85c127643287570c2298af135ddfdc7c450b18d8",
"services/auth/source/oauth2/access_engine_current_token_test.go": "sha256:58f799c08a7b4638780abc511bd1a7359bd5e691c7805ff54582d02d2f42cae1",
"services/auth/source/oauth2/access_engine_purpose_test.go": "sha256:533dc3153fcf9bfcf7bfc08712c057092c7b3892882f217fb8f839fdbdd99939",
"services/auth/source/oauth2/access_engine_test.go": "sha256:9112a142983c0a58c5d40344b58a20fa2c1bdbc7a79b5c586796362839a43b48",
"services/auth/source/oauth2/assert_interface_test.go": "sha256:a0db85e38b7ea61e21b0291e55c8b451c89a877bd3db229e35dbb4418458f54f",
"services/auth/source/oauth2/current_issuer_fixture_test.go": "sha256:24fb430e72848326c5d9ef42eab5028cf915dd601f43fb06010b32b568908781",
"services/auth/source/oauth2/init.go": "sha256:98a8030ed005be67b237d54c3b197a629f8c289e3d74c8edfcf4d740831f87d1",
"services/auth/source/oauth2/jwtsigningkey.go": "sha256:0c6e8ac131fd41ad299073ca748dd95e5fd9c044219c4d7f36d50c4ffcd5b266",
"services/auth/source/oauth2/jwtsigningkey_test.go": "sha256:3e414a541479741773e48de84be7d5a3b794bcc7289627fbd2e8996832628321",
"services/auth/source/oauth2/providers.go": "sha256:7df0051f19932a68d16a33a5d41898e06e9612f839195e27c86802eeebb2e3e9",
"services/auth/source/oauth2/providers_base.go": "sha256:643dbc0561ec7aa7a975978ff2c21d256a5ab619b125e8077d2395281f628542",
"services/auth/source/oauth2/providers_custom.go": "sha256:265a526d209d86c24cbcfdde4c2d73957aaeaccfd329438c11ce99c5951ebb39",
"services/auth/source/oauth2/providers_openid.go": "sha256:5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf",
"services/auth/source/oauth2/providers_simple.go": "sha256:53927b6bfc621dd1b68a074b6763abe5b27795d5a50ca6f0555f3e7a04fa1613",
"services/auth/source/oauth2/required_claim_values.go": "sha256:29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda",
"services/auth/source/oauth2/required_claim_values_test.go": "sha256:54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0",
"services/auth/source/oauth2/source.go": "sha256:44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642",
"services/auth/source/oauth2/source_authenticate.go": "sha256:c6551f00ef4349ccd6afdbb2cba847067528485ff621c3e507b9aab588bb96b5",
"services/auth/source/oauth2/source_callout.go": "sha256:6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c",
"services/auth/source/oauth2/source_name.go": "sha256:712943826e01f718ab4ca717df6c5f2171535cb2229dbf8460f542d757ef8d7f",
"services/auth/source/oauth2/source_register.go": "sha256:5ad9ccf213881734dc7fe7619cbe6c47cb6a4afdbbfc3e674ab4493857ebbf91",
"services/auth/source/oauth2/store.go": "sha256:35b244081569215384de07f8cdc1e83b5545a2e673ca1ac5a2a6ee686cb917a3",
"services/auth/source/oauth2/token.go": "sha256:c0a8868f904bb31842202b4e635a76403902adff9e638d5cb581e7e2d4954933",
"services/auth/source/oauth2/urlmapping.go": "sha256:f2132408d4a447e766e67495b1ca7b8cc2b47d4eb56b0c44b50ba01fe41ca83e",
"third_party/keycape-tokenstatus/LICENSE": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"third_party/keycape-tokenstatus/go.mod": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54",
"third_party/keycape-tokenstatus/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"third_party/keycape-tokenstatus/pkg/tokenstatus/client.go": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575",
"third_party/keycape-tokenstatus/producer-source.json": "sha256:3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
},
"counts_including_subtests": {
"pass": 91,
"fail": 0,
"skip": 0
},
"failures": [],
"real_credentials": false,
"native_admission": false,
"network": "private empty namespace",
"pid_namespace": "private",
"home": "empty allowlisted fixture HOME",
"compiled_fixture_only": false
}

View file

@ -0,0 +1,368 @@
{"Time":"2026-10-18T10:09:21.652984262Z","Action":"start","Package":"forgejo.org/services/auth/source/oauth2"}
{"Time":"2026-10-18T10:09:21.739313615Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals"}
{"Time":"2026-10-18T10:09:21.739371325Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals\n"}
{"Time":"2026-10-18T10:09:21.739438711Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods"}
{"Time":"2026-10-18T10:09:21.73958782Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods\n"}
{"Time":"2026-10-18T10:09:21.741715043Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.741764647Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Elapsed":0}
{"Time":"2026-10-18T10:09:21.741777307Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field"}
{"Time":"2026-10-18T10:09:21.74178288Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field\n"}
{"Time":"2026-10-18T10:09:21.742049899Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.742065868Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Elapsed":0}
{"Time":"2026-10-18T10:09:21.742077699Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication"}
{"Time":"2026-10-18T10:09:21.742083443Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication\n"}
{"Time":"2026-10-18T10:09:21.742358226Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.742373546Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Elapsed":0}
{"Time":"2026-10-18T10:09:21.742380679Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication"}
{"Time":"2026-10-18T10:09:21.742385854Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/future-authentication\n"}
{"Time":"2026-10-18T10:09:21.742648437Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/future-authentication (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.742666354Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Elapsed":0}
{"Time":"2026-10-18T10:09:21.742692657Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at"}
{"Time":"2026-10-18T10:09:21.742699258Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/missing-at\n"}
{"Time":"2026-10-18T10:09:21.742933319Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/missing-at (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.742948107Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Elapsed":0}
{"Time":"2026-10-18T10:09:21.742954897Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at"}
{"Time":"2026-10-18T10:09:21.742960109Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/string-at\n"}
{"Time":"2026-10-18T10:09:21.743202069Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/string-at (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.74321656Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Elapsed":0}
{"Time":"2026-10-18T10:09:21.743223413Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at"}
{"Time":"2026-10-18T10:09:21.743228543Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/fractional-at\n"}
{"Time":"2026-10-18T10:09:21.743526239Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/fractional-at (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.743540874Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Elapsed":0}
{"Time":"2026-10-18T10:09:21.743547538Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa"}
{"Time":"2026-10-18T10:09:21.743552632Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa\n"}
{"Time":"2026-10-18T10:09:21.744006958Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.744039302Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Elapsed":0}
{"Time":"2026-10-18T10:09:21.744047652Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level"}
{"Time":"2026-10-18T10:09:21.744053018Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-level\n"}
{"Time":"2026-10-18T10:09:21.744149863Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-level (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.744178835Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Elapsed":0}
{"Time":"2026-10-18T10:09:21.744188817Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods"}
{"Time":"2026-10-18T10:09:21.744194426Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods\n"}
{"Time":"2026-10-18T10:09:21.74449764Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.744513312Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Elapsed":0}
{"Time":"2026-10-18T10:09:21.744520481Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa"}
{"Time":"2026-10-18T10:09:21.744525729Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa\n"}
{"Time":"2026-10-18T10:09:21.744781647Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.744796796Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Elapsed":0}
{"Time":"2026-10-18T10:09:21.744803514Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source"}
{"Time":"2026-10-18T10:09:21.74480878Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-source\n"}
{"Time":"2026-10-18T10:09:21.745064861Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-source (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.745080001Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Elapsed":0}
{"Time":"2026-10-18T10:09:21.74508662Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods"}
{"Time":"2026-10-18T10:09:21.745091886Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/object-methods\n"}
{"Time":"2026-10-18T10:09:21.745345189Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/object-methods (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.745360347Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Elapsed":0}
{"Time":"2026-10-18T10:09:21.745367011Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods"}
{"Time":"2026-10-18T10:09:21.745372223Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/empty-methods\n"}
{"Time":"2026-10-18T10:09:21.745616609Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/empty-methods (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.745631749Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Elapsed":0}
{"Time":"2026-10-18T10:09:21.746096245Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals (0.01s)\n"}
{"Time":"2026-10-18T10:09:21.74611162Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Elapsed":0.01}
{"Time":"2026-10-18T10:09:21.746119582Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds"}
{"Time":"2026-10-18T10:09:21.746124776Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"=== RUN TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds\n"}
{"Time":"2026-10-18T10:09:21.746535169Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"--- PASS: TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.746550074Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Elapsed":0}
{"Time":"2026-10-18T10:09:21.746556801Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact"}
{"Time":"2026-10-18T10:09:21.746562022Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"=== RUN TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact\n"}
{"Time":"2026-10-18T10:09:21.746889384Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"--- PASS: TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.746913803Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Elapsed":0}
{"Time":"2026-10-18T10:09:21.746920963Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient"}
{"Time":"2026-10-18T10:09:21.746926427Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient\n"}
{"Time":"2026-10-18T10:09:21.746932793Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client"}
{"Time":"2026-10-18T10:09:21.746937924Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client\n"}
{"Time":"2026-10-18T10:09:21.747143572Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.747159469Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Elapsed":0}
{"Time":"2026-10-18T10:09:21.74716689Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number"}
{"Time":"2026-10-18T10:09:21.747185339Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/number\n"}
{"Time":"2026-10-18T10:09:21.747471574Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/number (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.747487138Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Elapsed":0}
{"Time":"2026-10-18T10:09:21.747493675Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array"}
{"Time":"2026-10-18T10:09:21.74749886Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/array\n"}
{"Time":"2026-10-18T10:09:21.747779365Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/array (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.747832224Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Elapsed":0}
{"Time":"2026-10-18T10:09:21.747844001Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty"}
{"Time":"2026-10-18T10:09:21.747853387Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty\n"}
{"Time":"2026-10-18T10:09:21.748101997Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.748122448Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Elapsed":0}
{"Time":"2026-10-18T10:09:21.748132701Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.748139662Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Elapsed":0}
{"Time":"2026-10-18T10:09:21.748145631Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient"}
{"Time":"2026-10-18T10:09:21.748151006Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"=== RUN TestAccessEngineAuthorizedPartyAcceptsAdmittedClient\n"}
{"Time":"2026-10-18T10:09:21.748570758Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyAcceptsAdmittedClient (0.00s)\n"}
{"Time":"2026-10-18T10:09:21.748586557Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Elapsed":0}
{"Time":"2026-10-18T10:09:21.748593356Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision"}
{"Time":"2026-10-18T10:09:21.748617558Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision\n"}
{"Time":"2026-10-18T10:09:21.748649082Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins"}
{"Time":"2026-10-18T10:09:21.748655106Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins\n"}
{"Time":"2026-10-18T10:09:22.227315514Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins (0.48s)\n"}
{"Time":"2026-10-18T10:09:22.227380195Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Elapsed":0.48}
{"Time":"2026-10-18T10:09:22.22739281Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners"}
{"Time":"2026-10-18T10:09:22.227397175Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners\n"}
{"Time":"2026-10-18T10:09:22.399228426Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners (0.17s)\n"}
{"Time":"2026-10-18T10:09:22.399284631Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Elapsed":0.17}
{"Time":"2026-10-18T10:09:22.399302693Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers"}
{"Time":"2026-10-18T10:09:22.399309329Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers\n"}
{"Time":"2026-10-18T10:09:22.649647613Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers (0.25s)\n"}
{"Time":"2026-10-18T10:09:22.649692816Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Elapsed":0.25}
{"Time":"2026-10-18T10:09:22.649701888Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision (0.90s)\n"}
{"Time":"2026-10-18T10:09:22.649707244Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Elapsed":0.9}
{"Time":"2026-10-18T10:09:22.6497126Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP"}
{"Time":"2026-10-18T10:09:22.649717091Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP\n"}
{"Time":"2026-10-18T10:09:22.649736469Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny"}
{"Time":"2026-10-18T10:09:22.649741356Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny\n"}
{"Time":"2026-10-18T10:09:23.055988774Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny (0.41s)\n"}
{"Time":"2026-10-18T10:09:23.056034834Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Elapsed":0.41}
{"Time":"2026-10-18T10:09:23.0560435Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned"}
{"Time":"2026-10-18T10:09:23.05604808Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned\n"}
{"Time":"2026-10-18T10:09:23.697794424Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned (0.64s)\n"}
{"Time":"2026-10-18T10:09:23.697846571Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Elapsed":0.64}
{"Time":"2026-10-18T10:09:23.697856156Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key"}
{"Time":"2026-10-18T10:09:23.697861792Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key\n"}
{"Time":"2026-10-18T10:09:23.991183953Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key (0.29s)\n"}
{"Time":"2026-10-18T10:09:23.991250473Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Elapsed":0.29}
{"Time":"2026-10-18T10:09:23.991266668Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin"}
{"Time":"2026-10-18T10:09:23.991275713Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin\n"}
{"Time":"2026-10-18T10:09:24.321292756Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin (0.33s)\n"}
{"Time":"2026-10-18T10:09:24.321336093Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Elapsed":0.33}
{"Time":"2026-10-18T10:09:24.32134474Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding"}
{"Time":"2026-10-18T10:09:24.321348536Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding\n"}
{"Time":"2026-10-18T10:09:24.61751747Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding (0.30s)\n"}
{"Time":"2026-10-18T10:09:24.617563557Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Elapsed":0.3}
{"Time":"2026-10-18T10:09:24.617576326Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired"}
{"Time":"2026-10-18T10:09:24.617582782Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired\n"}
{"Time":"2026-10-18T10:09:25.26096403Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired (0.64s)\n"}
{"Time":"2026-10-18T10:09:25.261022272Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Elapsed":0.64}
{"Time":"2026-10-18T10:09:25.26103918Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP (2.61s)\n"}
{"Time":"2026-10-18T10:09:25.261045357Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Elapsed":2.61}
{"Time":"2026-10-18T10:09:25.261050758Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP"}
{"Time":"2026-10-18T10:09:25.26105533Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP\n"}
{"Time":"2026-10-18T10:09:25.261063869Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject"}
{"Time":"2026-10-18T10:09:25.261068188Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject\n"}
{"Time":"2026-10-18T10:09:25.344178469Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject (0.08s)\n"}
{"Time":"2026-10-18T10:09:25.344231662Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Elapsed":0.08}
{"Time":"2026-10-18T10:09:25.34424425Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups"}
{"Time":"2026-10-18T10:09:25.344251346Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups\n"}
{"Time":"2026-10-18T10:09:25.376462508Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups (0.03s)\n"}
{"Time":"2026-10-18T10:09:25.376515232Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Elapsed":0.03}
{"Time":"2026-10-18T10:09:25.3765436Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider"}
{"Time":"2026-10-18T10:09:25.376551247Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider\n"}
{"Time":"2026-10-18T10:09:25.616257893Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider (0.24s)\n"}
{"Time":"2026-10-18T10:09:25.616310716Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Elapsed":0.24}
{"Time":"2026-10-18T10:09:25.616321338Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission"}
{"Time":"2026-10-18T10:09:25.616328038Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission\n"}
{"Time":"2026-10-18T10:09:25.760690699Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission (0.14s)\n"}
{"Time":"2026-10-18T10:09:25.760743441Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Elapsed":0.14}
{"Time":"2026-10-18T10:09:25.760755434Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token"}
{"Time":"2026-10-18T10:09:25.760761936Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token\n"}
{"Time":"2026-10-18T10:09:26.084506958Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token (0.32s)\n"}
{"Time":"2026-10-18T10:09:26.084558447Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Elapsed":0.32}
{"Time":"2026-10-18T10:09:26.084570241Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user"}
{"Time":"2026-10-18T10:09:26.084577293Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user\n"}
{"Time":"2026-10-18T10:09:26.198029843Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user (0.11s)\n"}
{"Time":"2026-10-18T10:09:26.198080655Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Elapsed":0.11}
{"Time":"2026-10-18T10:09:26.198091052Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source"}
{"Time":"2026-10-18T10:09:26.198110096Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source\n"}
{"Time":"2026-10-18T10:09:26.622334558Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source (0.42s)\n"}
{"Time":"2026-10-18T10:09:26.622401448Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Elapsed":0.42}
{"Time":"2026-10-18T10:09:26.622416922Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience"}
{"Time":"2026-10-18T10:09:26.622425253Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience\n"}
{"Time":"2026-10-18T10:09:26.704846966Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience (0.08s)\n"}
{"Time":"2026-10-18T10:09:26.704899393Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Elapsed":0.08}
{"Time":"2026-10-18T10:09:26.704910321Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token"}
{"Time":"2026-10-18T10:09:26.704917283Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token\n"}
{"Time":"2026-10-18T10:09:26.977061118Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token (0.27s)\n"}
{"Time":"2026-10-18T10:09:26.977127421Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Elapsed":0.27}
{"Time":"2026-10-18T10:09:26.977144482Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity"}
{"Time":"2026-10-18T10:09:26.977173157Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity\n"}
{"Time":"2026-10-18T10:09:27.381016906Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity (0.40s)\n"}
{"Time":"2026-10-18T10:09:27.381127412Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Elapsed":0.4}
{"Time":"2026-10-18T10:09:27.381153382Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP (2.12s)\n"}
{"Time":"2026-10-18T10:09:27.381163156Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Elapsed":2.12}
{"Time":"2026-10-18T10:09:27.381188378Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision"}
{"Time":"2026-10-18T10:09:27.381196331Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision\n"}
{"Time":"2026-10-18T10:09:27.381204419Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing"}
{"Time":"2026-10-18T10:09:27.38121056Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing\n"}
{"Time":"2026-10-18T10:09:27.771854866Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing (0.39s)\n"}
{"Time":"2026-10-18T10:09:27.771908798Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Elapsed":0.39}
{"Time":"2026-10-18T10:09:27.771919745Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access"}
{"Time":"2026-10-18T10:09:27.7719263Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access\n"}
{"Time":"2026-10-18T10:09:27.900679683Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access (0.13s)\n"}
{"Time":"2026-10-18T10:09:27.900731668Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Elapsed":0.13}
{"Time":"2026-10-18T10:09:27.900743499Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject"}
{"Time":"2026-10-18T10:09:27.900751479Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/subject\n"}
{"Time":"2026-10-18T10:09:28.34277205Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/subject (0.44s)\n"}
{"Time":"2026-10-18T10:09:28.342840978Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Elapsed":0.44}
{"Time":"2026-10-18T10:09:28.342850581Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant"}
{"Time":"2026-10-18T10:09:28.342856343Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant\n"}
{"Time":"2026-10-18T10:09:28.650368344Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant (0.31s)\n"}
{"Time":"2026-10-18T10:09:28.650413133Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Elapsed":0.31}
{"Time":"2026-10-18T10:09:28.650431979Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal"}
{"Time":"2026-10-18T10:09:28.650436713Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/principal\n"}
{"Time":"2026-10-18T10:09:28.849675466Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/principal (0.20s)\n"}
{"Time":"2026-10-18T10:09:28.849751536Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Elapsed":0.2}
{"Time":"2026-10-18T10:09:28.849768732Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance"}
{"Time":"2026-10-18T10:09:28.849777695Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance\n"}
{"Time":"2026-10-18T10:09:29.118605137Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance (0.27s)\n"}
{"Time":"2026-10-18T10:09:29.11865548Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Elapsed":0.27}
{"Time":"2026-10-18T10:09:29.118666265Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time"}
{"Time":"2026-10-18T10:09:29.118672171Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time\n"}
{"Time":"2026-10-18T10:09:29.271436552Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time (0.15s)\n"}
{"Time":"2026-10-18T10:09:29.27149511Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Elapsed":0.15}
{"Time":"2026-10-18T10:09:29.271516391Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source"}
{"Time":"2026-10-18T10:09:29.271520674Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source\n"}
{"Time":"2026-10-18T10:09:29.481276211Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source (0.21s)\n"}
{"Time":"2026-10-18T10:09:29.481406411Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Elapsed":0.21}
{"Time":"2026-10-18T10:09:29.48142807Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at"}
{"Time":"2026-10-18T10:09:29.481453517Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at\n"}
{"Time":"2026-10-18T10:09:29.545882857Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at (0.06s)\n"}
{"Time":"2026-10-18T10:09:29.545953228Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Elapsed":0.06}
{"Time":"2026-10-18T10:09:29.545968927Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive"}
{"Time":"2026-10-18T10:09:29.54599707Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive\n"}
{"Time":"2026-10-18T10:09:29.630397662Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive (0.08s)\n"}
{"Time":"2026-10-18T10:09:29.630466121Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Elapsed":0.08}
{"Time":"2026-10-18T10:09:29.630484074Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable"}
{"Time":"2026-10-18T10:09:29.630493091Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable\n"}
{"Time":"2026-10-18T10:09:29.899917373Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable (0.27s)\n"}
{"Time":"2026-10-18T10:09:29.899978185Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Elapsed":0.27}
{"Time":"2026-10-18T10:09:29.899995733Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status"}
{"Time":"2026-10-18T10:09:29.900001306Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status\n"}
{"Time":"2026-10-18T10:09:30.468436594Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status (0.57s)\n"}
{"Time":"2026-10-18T10:09:30.468505053Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Elapsed":0.57}
{"Time":"2026-10-18T10:09:30.468545207Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission"}
{"Time":"2026-10-18T10:09:30.468553764Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission\n"}
{"Time":"2026-10-18T10:09:30.591377637Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission (0.12s)\n"}
{"Time":"2026-10-18T10:09:30.591448693Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Elapsed":0.12}
{"Time":"2026-10-18T10:09:30.591460334Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client"}
{"Time":"2026-10-18T10:09:30.591467187Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client\n"}
{"Time":"2026-10-18T10:09:30.654186655Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client (0.06s)\n"}
{"Time":"2026-10-18T10:09:30.654249442Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Elapsed":0.06}
{"Time":"2026-10-18T10:09:30.654264339Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key"}
{"Time":"2026-10-18T10:09:30.654272878Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key\n"}
{"Time":"2026-10-18T10:09:30.930781669Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key (0.28s)\n"}
{"Time":"2026-10-18T10:09:30.930837134Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Elapsed":0.28}
{"Time":"2026-10-18T10:09:30.9308502Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant"}
{"Time":"2026-10-18T10:09:30.930858Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant\n"}
{"Time":"2026-10-18T10:09:31.009144574Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant (0.08s)\n"}
{"Time":"2026-10-18T10:09:31.009224088Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Elapsed":0.08}
{"Time":"2026-10-18T10:09:31.009238173Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash"}
{"Time":"2026-10-18T10:09:31.009244863Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash\n"}
{"Time":"2026-10-18T10:09:31.26552281Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash (0.26s)\n"}
{"Time":"2026-10-18T10:09:31.265582829Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Elapsed":0.26}
{"Time":"2026-10-18T10:09:31.265594335Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision (3.88s)\n"}
{"Time":"2026-10-18T10:09:31.265617166Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Elapsed":3.88}
{"Time":"2026-10-18T10:09:31.265624786Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn"}
{"Time":"2026-10-18T10:09:31.265630575Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"=== RUN TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn\n"}
{"Time":"2026-10-18T10:09:31.830001811Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"--- PASS: TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn (0.56s)\n"}
{"Time":"2026-10-18T10:09:31.830063877Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Elapsed":0.56}
{"Time":"2026-10-18T10:09:31.830079955Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims"}
{"Time":"2026-10-18T10:09:31.830086772Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims\n"}
{"Time":"2026-10-18T10:09:31.830291475Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti"}
{"Time":"2026-10-18T10:09:31.830311493Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti\n"}
{"Time":"2026-10-18T10:09:31.830886983Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.830911844Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Elapsed":0}
{"Time":"2026-10-18T10:09:31.831040715Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope"}
{"Time":"2026-10-18T10:09:31.831065612Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope\n"}
{"Time":"2026-10-18T10:09:31.83159451Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.831625213Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Elapsed":0}
{"Time":"2026-10-18T10:09:31.831634312Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.831642599Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Elapsed":0}
{"Time":"2026-10-18T10:09:31.831652815Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityCannotChooseApplicationTenant"}
{"Time":"2026-10-18T10:09:31.831676467Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityCannotChooseApplicationTenant","Output":"=== RUN TestVerifiedApplicationIdentityCannotChooseApplicationTenant\n"}
{"Time":"2026-10-18T10:09:31.832327892Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityCannotChooseApplicationTenant","Output":"--- PASS: TestVerifiedApplicationIdentityCannotChooseApplicationTenant (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.832349308Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityCannotChooseApplicationTenant","Elapsed":0}
{"Time":"2026-10-18T10:09:31.832355584Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE"}
{"Time":"2026-10-18T10:09:31.832359226Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE\n"}
{"Time":"2026-10-18T10:09:31.8328725Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject"}
{"Time":"2026-10-18T10:09:31.832894845Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/empty-subject\n"}
{"Time":"2026-10-18T10:09:31.833212965Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/empty-subject (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.833225435Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Elapsed":0}
{"Time":"2026-10-18T10:09:31.833237969Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer"}
{"Time":"2026-10-18T10:09:31.833241757Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/issuer\n"}
{"Time":"2026-10-18T10:09:31.833643818Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/issuer (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.833663944Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Elapsed":0}
{"Time":"2026-10-18T10:09:31.833672213Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience"}
{"Time":"2026-10-18T10:09:31.833683043Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/audience\n"}
{"Time":"2026-10-18T10:09:31.833959629Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/audience (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.83397659Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Elapsed":0}
{"Time":"2026-10-18T10:09:31.83398825Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued"}
{"Time":"2026-10-18T10:09:31.833993119Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/old-issued\n"}
{"Time":"2026-10-18T10:09:31.834352021Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/old-issued (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.834382887Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Elapsed":0}
{"Time":"2026-10-18T10:09:31.834389127Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp"}
{"Time":"2026-10-18T10:09:31.83439332Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-exp\n"}
{"Time":"2026-10-18T10:09:31.834639223Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-exp (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.834662361Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Elapsed":0}
{"Time":"2026-10-18T10:09:31.834670287Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat"}
{"Time":"2026-10-18T10:09:31.834674516Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-iat\n"}
{"Time":"2026-10-18T10:09:31.835035498Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-iat (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.835060106Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Elapsed":0}
{"Time":"2026-10-18T10:09:31.835066464Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant"}
{"Time":"2026-10-18T10:09:31.835070882Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-tenant\n"}
{"Time":"2026-10-18T10:09:31.835356365Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-tenant (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.835378286Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Elapsed":0}
{"Time":"2026-10-18T10:09:31.835384129Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal"}
{"Time":"2026-10-18T10:09:31.835388313Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-principal\n"}
{"Time":"2026-10-18T10:09:31.835612404Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-principal (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.835629771Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Elapsed":0}
{"Time":"2026-10-18T10:09:31.835637292Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance"}
{"Time":"2026-10-18T10:09:31.835658437Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-assurance\n"}
{"Time":"2026-10-18T10:09:31.835989321Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-assurance (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.836018384Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Elapsed":0}
{"Time":"2026-10-18T10:09:31.8360264Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired"}
{"Time":"2026-10-18T10:09:31.836030593Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/expired\n"}
{"Time":"2026-10-18T10:09:31.83635345Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/expired (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.836374614Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Elapsed":0}
{"Time":"2026-10-18T10:09:31.836382459Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued"}
{"Time":"2026-10-18T10:09:31.836386778Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/future-issued\n"}
{"Time":"2026-10-18T10:09:31.836906503Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/future-issued (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.836955665Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Elapsed":0}
{"Time":"2026-10-18T10:09:31.836970129Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp"}
{"Time":"2026-10-18T10:09:31.83697617Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp\n"}
{"Time":"2026-10-18T10:09:31.837492273Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.837526078Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Elapsed":0}
{"Time":"2026-10-18T10:09:31.83753615Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key"}
{"Time":"2026-10-18T10:09:31.83754129Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-key\n"}
{"Time":"2026-10-18T10:09:31.83795749Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-key (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.837970565Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Elapsed":0}
{"Time":"2026-10-18T10:09:31.837998014Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key"}
{"Time":"2026-10-18T10:09:31.838004524Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/unknown-key\n"}
{"Time":"2026-10-18T10:09:31.838216042Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/unknown-key (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.83822826Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Elapsed":0}
{"Time":"2026-10-18T10:09:31.838235537Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type"}
{"Time":"2026-10-18T10:09:31.83824001Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-type\n"}
{"Time":"2026-10-18T10:09:31.838487353Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-type (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.838500175Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Elapsed":0}
{"Time":"2026-10-18T10:09:31.838512394Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion"}
{"Time":"2026-10-18T10:09:31.838518796Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion\n"}
{"Time":"2026-10-18T10:09:31.83868245Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.83869484Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Elapsed":0}
{"Time":"2026-10-18T10:09:31.83870218Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none"}
{"Time":"2026-10-18T10:09:31.838706553Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/none\n"}
{"Time":"2026-10-18T10:09:31.838876068Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/none (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.838890676Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Elapsed":0}
{"Time":"2026-10-18T10:09:31.838898278Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE (0.01s)\n"}
{"Time":"2026-10-18T10:09:31.838903517Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Elapsed":0.01}
{"Time":"2026-10-18T10:09:31.838915501Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation"}
{"Time":"2026-10-18T10:09:31.838930884Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"=== RUN TestRequiredClaimExactAnyOfAndNoMutation\n"}
{"Time":"2026-10-18T10:09:31.839071292Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"--- PASS: TestRequiredClaimExactAnyOfAndNoMutation (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.839083213Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Elapsed":0}
{"Time":"2026-10-18T10:09:31.839088785Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation"}
{"Time":"2026-10-18T10:09:31.839093132Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"=== RUN TestRequiredClaimConfigurationRefusalsAndLegacyIsolation\n"}
{"Time":"2026-10-18T10:09:31.839134566Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"--- PASS: TestRequiredClaimConfigurationRefusalsAndLegacyIsolation (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.839143854Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Elapsed":0}
{"Time":"2026-10-18T10:09:31.839169697Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable"}
{"Time":"2026-10-18T10:09:31.839178227Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"=== RUN TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable\n"}
{"Time":"2026-10-18T10:09:31.845802439Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"--- PASS: TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable (0.01s)\n"}
{"Time":"2026-10-18T10:09:31.845846786Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Elapsed":0.01}
{"Time":"2026-10-18T10:09:31.845856254Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval"}
{"Time":"2026-10-18T10:09:31.845860267Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"=== RUN TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval\n"}
{"Time":"2026-10-18T10:09:31.845991979Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"--- PASS: TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.846005956Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Elapsed":0}
{"Time":"2026-10-18T10:09:31.846012655Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse"}
{"Time":"2026-10-18T10:09:31.846016325Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"=== RUN TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse\n"}
{"Time":"2026-10-18T10:09:31.846108761Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"--- PASS: TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse (0.00s)\n"}
{"Time":"2026-10-18T10:09:31.846129861Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Elapsed":0}
{"Time":"2026-10-18T10:09:31.846155461Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"PASS\n"}
{"Time":"2026-10-18T10:09:31.854854546Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"ok \tforgejo.org/services/auth/source/oauth2\t10.201s\n"}
{"Time":"2026-10-18T10:09:31.854922455Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Elapsed":10.202}

View file

@ -0,0 +1,168 @@
{
"returncode": 0,
"seconds": 36.83,
"argv": [
"bwrap",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--unshare-net",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-forgejo-current-token-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod/cache/download",
"/cached",
"--ro-bind",
"/etc/ssl/certs",
"/etc/ssl/certs",
"--ro-bind",
"/etc/resolv.conf",
"/etc/resolv.conf",
"--chdir",
"/work/source",
"--clearenv",
"--ro-bind",
"/tmp/custodian-forgejo-successor-attempt2/modules",
"/work/modules",
"--bind",
"/tmp/custodian-forgejo-successor-attempt2/gocache",
"/work/gocache",
"--dir",
"/run",
"--setenv",
"PATH",
"/work/go/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOPATH",
"/work/gopath",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOVCS",
"*:off",
"--setenv",
"GOMODCACHE",
"/work/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"GIT_CONFIG_NOSYSTEM",
"1",
"--setenv",
"GIT_CONFIG_GLOBAL",
"/work/home/test.gitconfig",
"--setenv",
"GIT_TERMINAL_PROMPT",
"0",
"--ro-bind",
"/tmp/custodian-forgejo-successor-r3/go",
"/work/go",
"--ro-bind",
"/tmp/custodian-forgejo-current-token-r1/descriptor-fixtures-attempt7",
"/run/forgejo-oidc",
"--bind",
"/tmp/custodian-forgejo-current-token-r1/descriptor-fixtures-attempt7",
"/run/writable-input",
"--",
"go",
"test",
"-p",
"1",
"-json",
"-tags",
"sqlite sqlite_unlock_notify",
"./services/auth/source/oauth2",
"./cmd",
"./models/auth",
"./models/db",
"-run",
"TestAccessEngine|TestVerifiedApplicationIdentity|TestRequired|TestCurrent|TestPrivateSecret|TestSourceOperation|TestSensitiveSQL",
"-count=1"
],
"source_sha256_at_start": {
"go.mod": "sha256:11fe525ccb3f099aa281738b86097bbd587a99b4640ddae8dd04dd7d046c49f0",
"go.sum": "sha256:19c305c7e68de9d3f1bc0dda2de09c5d38597f1ee7fa411c7b36b92467ae1eaf",
"services/auth/source/oauth2/access_engine.go": "sha256:d16330043825d0284253e65d28bda2de4edbc633e4887d8c3618a106ee251daa",
"services/auth/source/oauth2/access_engine_assurance.go": "sha256:42de751b1e9a243301d566260954813b770558ecb449639acaeab307fe788642",
"services/auth/source/oauth2/access_engine_assurance_test.go": "sha256:44e7459f6c1106a264093c19d61d01a8365f6ad22682906552d832ba39d61dd3",
"services/auth/source/oauth2/access_engine_azp_test.go": "sha256:5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce",
"services/auth/source/oauth2/access_engine_callback_test.go": "sha256:1cbe378cc20149be7b354484ab3297916733564f03a2441cd276f9039d043580",
"services/auth/source/oauth2/access_engine_current_token.go": "sha256:4a2e484ece7cf74686173d5e85c127643287570c2298af135ddfdc7c450b18d8",
"services/auth/source/oauth2/access_engine_current_token_test.go": "sha256:58f799c08a7b4638780abc511bd1a7359bd5e691c7805ff54582d02d2f42cae1",
"services/auth/source/oauth2/access_engine_purpose_test.go": "sha256:533dc3153fcf9bfcf7bfc08712c057092c7b3892882f217fb8f839fdbdd99939",
"services/auth/source/oauth2/access_engine_test.go": "sha256:9112a142983c0a58c5d40344b58a20fa2c1bdbc7a79b5c586796362839a43b48",
"services/auth/source/oauth2/assert_interface_test.go": "sha256:a0db85e38b7ea61e21b0291e55c8b451c89a877bd3db229e35dbb4418458f54f",
"services/auth/source/oauth2/current_issuer_fixture_test.go": "sha256:24fb430e72848326c5d9ef42eab5028cf915dd601f43fb06010b32b568908781",
"services/auth/source/oauth2/init.go": "sha256:98a8030ed005be67b237d54c3b197a629f8c289e3d74c8edfcf4d740831f87d1",
"services/auth/source/oauth2/jwtsigningkey.go": "sha256:0c6e8ac131fd41ad299073ca748dd95e5fd9c044219c4d7f36d50c4ffcd5b266",
"services/auth/source/oauth2/jwtsigningkey_test.go": "sha256:3e414a541479741773e48de84be7d5a3b794bcc7289627fbd2e8996832628321",
"services/auth/source/oauth2/providers.go": "sha256:7df0051f19932a68d16a33a5d41898e06e9612f839195e27c86802eeebb2e3e9",
"services/auth/source/oauth2/providers_base.go": "sha256:643dbc0561ec7aa7a975978ff2c21d256a5ab619b125e8077d2395281f628542",
"services/auth/source/oauth2/providers_custom.go": "sha256:265a526d209d86c24cbcfdde4c2d73957aaeaccfd329438c11ce99c5951ebb39",
"services/auth/source/oauth2/providers_openid.go": "sha256:5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf",
"services/auth/source/oauth2/providers_simple.go": "sha256:53927b6bfc621dd1b68a074b6763abe5b27795d5a50ca6f0555f3e7a04fa1613",
"services/auth/source/oauth2/required_claim_values.go": "sha256:29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda",
"services/auth/source/oauth2/required_claim_values_test.go": "sha256:54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0",
"services/auth/source/oauth2/source.go": "sha256:44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642",
"services/auth/source/oauth2/source_authenticate.go": "sha256:c6551f00ef4349ccd6afdbb2cba847067528485ff621c3e507b9aab588bb96b5",
"services/auth/source/oauth2/source_callout.go": "sha256:6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c",
"services/auth/source/oauth2/source_name.go": "sha256:712943826e01f718ab4ca717df6c5f2171535cb2229dbf8460f542d757ef8d7f",
"services/auth/source/oauth2/source_register.go": "sha256:5ad9ccf213881734dc7fe7619cbe6c47cb6a4afdbbfc3e674ab4493857ebbf91",
"services/auth/source/oauth2/store.go": "sha256:35b244081569215384de07f8cdc1e83b5545a2e673ca1ac5a2a6ee686cb917a3",
"services/auth/source/oauth2/token.go": "sha256:c0a8868f904bb31842202b4e635a76403902adff9e638d5cb581e7e2d4954933",
"services/auth/source/oauth2/urlmapping.go": "sha256:f2132408d4a447e766e67495b1ca7b8cc2b47d4eb56b0c44b50ba01fe41ca83e",
"third_party/keycape-tokenstatus/LICENSE": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"third_party/keycape-tokenstatus/go.mod": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54",
"third_party/keycape-tokenstatus/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"third_party/keycape-tokenstatus/pkg/tokenstatus/client.go": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575",
"third_party/keycape-tokenstatus/producer-source.json": "sha256:3bd9b25b912e7c17b331997ca5fee9da8a7c314f336e09e3c116885954799b73"
},
"counts_including_subtests": {
"pass": 116,
"fail": 0,
"skip": 0
},
"failures": [],
"real_credentials": false,
"native_admission": false,
"network": "private empty namespace",
"pid_namespace": "private",
"home": "empty allowlisted fixture HOME",
"compiled_fixture_only": false
}

View file

@ -0,0 +1,524 @@
{"Time":"2026-10-18T10:18:51.989800388Z","Action":"start","Package":"forgejo.org/services/auth/source/oauth2"}
{"Time":"2026-10-18T10:18:52.078314108Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals"}
{"Time":"2026-10-18T10:18:52.078368671Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals\n"}
{"Time":"2026-10-18T10:18:52.078428248Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level"}
{"Time":"2026-10-18T10:18:52.07843502Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-level\n"}
{"Time":"2026-10-18T10:18:52.080814438Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-level (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.080866549Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-level","Elapsed":0}
{"Time":"2026-10-18T10:18:52.080937506Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods"}
{"Time":"2026-10-18T10:18:52.080944386Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/empty-methods\n"}
{"Time":"2026-10-18T10:18:52.081152369Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/empty-methods (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.081167022Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/empty-methods","Elapsed":0}
{"Time":"2026-10-18T10:18:52.081246103Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at"}
{"Time":"2026-10-18T10:18:52.081257393Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/missing-at\n"}
{"Time":"2026-10-18T10:18:52.08160407Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/missing-at (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.081622717Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/missing-at","Elapsed":0}
{"Time":"2026-10-18T10:18:52.081628146Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa"}
{"Time":"2026-10-18T10:18:52.08163224Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa\n"}
{"Time":"2026-10-18T10:18:52.081969782Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.081988574Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/integer-mfa","Elapsed":0}
{"Time":"2026-10-18T10:18:52.081994021Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods"}
{"Time":"2026-10-18T10:18:52.08213377Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/object-methods\n"}
{"Time":"2026-10-18T10:18:52.082323696Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/object-methods (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.082345158Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/object-methods","Elapsed":0}
{"Time":"2026-10-18T10:18:52.082352146Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods"}
{"Time":"2026-10-18T10:18:52.082356763Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods\n"}
{"Time":"2026-10-18T10:18:52.082638456Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.082653434Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/mixed-methods","Elapsed":0}
{"Time":"2026-10-18T10:18:52.082658899Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods"}
{"Time":"2026-10-18T10:18:52.082663443Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods\n"}
{"Time":"2026-10-18T10:18:52.08295553Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.082968443Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/duplicate-methods","Elapsed":0}
{"Time":"2026-10-18T10:18:52.082980814Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field"}
{"Time":"2026-10-18T10:18:52.082985152Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field\n"}
{"Time":"2026-10-18T10:18:52.083309953Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.083322063Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/unknown-object-field","Elapsed":0}
{"Time":"2026-10-18T10:18:52.083331504Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication"}
{"Time":"2026-10-18T10:18:52.083335715Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication\n"}
{"Time":"2026-10-18T10:18:52.083665395Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.083690562Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/stale-authentication","Elapsed":0}
{"Time":"2026-10-18T10:18:52.083696189Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication"}
{"Time":"2026-10-18T10:18:52.083700472Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/future-authentication\n"}
{"Time":"2026-10-18T10:18:52.083970944Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/future-authentication (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.083984227Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/future-authentication","Elapsed":0}
{"Time":"2026-10-18T10:18:52.083992414Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at"}
{"Time":"2026-10-18T10:18:52.08399704Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/string-at\n"}
{"Time":"2026-10-18T10:18:52.084267531Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/string-at (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.084279551Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/string-at","Elapsed":0}
{"Time":"2026-10-18T10:18:52.084284853Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at"}
{"Time":"2026-10-18T10:18:52.0842891Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/fractional-at\n"}
{"Time":"2026-10-18T10:18:52.084632666Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/fractional-at (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.084646417Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/fractional-at","Elapsed":0}
{"Time":"2026-10-18T10:18:52.084652279Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa"}
{"Time":"2026-10-18T10:18:52.084656535Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa\n"}
{"Time":"2026-10-18T10:18:52.085089939Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.085143159Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/inconsistent-mfa","Elapsed":0}
{"Time":"2026-10-18T10:18:52.085156992Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source"}
{"Time":"2026-10-18T10:18:52.085164593Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"=== RUN TestAccessEngineKeyCapeAssuranceRefusals/wrong-source\n"}
{"Time":"2026-10-18T10:18:52.085467134Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals/wrong-source (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.085497116Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals/wrong-source","Elapsed":0}
{"Time":"2026-10-18T10:18:52.086130296Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Output":"--- PASS: TestAccessEngineKeyCapeAssuranceRefusals (0.01s)\n"}
{"Time":"2026-10-18T10:18:52.086163885Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeAssuranceRefusals","Elapsed":0.01}
{"Time":"2026-10-18T10:18:52.086171433Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds"}
{"Time":"2026-10-18T10:18:52.086175617Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"=== RUN TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds\n"}
{"Time":"2026-10-18T10:18:52.086638129Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Output":"--- PASS: TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.086650943Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds","Elapsed":0}
{"Time":"2026-10-18T10:18:52.086656903Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact"}
{"Time":"2026-10-18T10:18:52.086660934Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"=== RUN TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact\n"}
{"Time":"2026-10-18T10:18:52.086945122Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Output":"--- PASS: TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.086956655Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact","Elapsed":0}
{"Time":"2026-10-18T10:18:52.086961813Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient"}
{"Time":"2026-10-18T10:18:52.086969089Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient\n"}
{"Time":"2026-10-18T10:18:52.087092878Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client"}
{"Time":"2026-10-18T10:18:52.08710479Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client\n"}
{"Time":"2026-10-18T10:18:52.08727234Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.08728481Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/other-client","Elapsed":0}
{"Time":"2026-10-18T10:18:52.087291014Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number"}
{"Time":"2026-10-18T10:18:52.087309878Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/number\n"}
{"Time":"2026-10-18T10:18:52.087596221Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/number (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.087607736Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/number","Elapsed":0}
{"Time":"2026-10-18T10:18:52.087613011Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array"}
{"Time":"2026-10-18T10:18:52.08761697Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/array\n"}
{"Time":"2026-10-18T10:18:52.087911482Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/array (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.087927335Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/array","Elapsed":0}
{"Time":"2026-10-18T10:18:52.087938976Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty"}
{"Time":"2026-10-18T10:18:52.087943656Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"=== RUN TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty\n"}
{"Time":"2026-10-18T10:18:52.088260278Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.088277168Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient/empty","Elapsed":0}
{"Time":"2026-10-18T10:18:52.088283435Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyRefusesAnotherClient (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.088288376Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyRefusesAnotherClient","Elapsed":0}
{"Time":"2026-10-18T10:18:52.088292822Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient"}
{"Time":"2026-10-18T10:18:52.088296888Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"=== RUN TestAccessEngineAuthorizedPartyAcceptsAdmittedClient\n"}
{"Time":"2026-10-18T10:18:52.088972Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Output":"--- PASS: TestAccessEngineAuthorizedPartyAcceptsAdmittedClient (0.00s)\n"}
{"Time":"2026-10-18T10:18:52.089012452Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineAuthorizedPartyAcceptsAdmittedClient","Elapsed":0}
{"Time":"2026-10-18T10:18:52.089019702Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision"}
{"Time":"2026-10-18T10:18:52.089038133Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision\n"}
{"Time":"2026-10-18T10:18:52.08904761Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins"}
{"Time":"2026-10-18T10:18:52.089052443Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins\n"}
{"Time":"2026-10-18T10:18:52.34556438Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins (0.26s)\n"}
{"Time":"2026-10-18T10:18:52.345612667Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-admins","Elapsed":0.26}
{"Time":"2026-10-18T10:18:52.345622153Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners"}
{"Time":"2026-10-18T10:18:52.345626878Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners\n"}
{"Time":"2026-10-18T10:18:52.583273278Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners (0.24s)\n"}
{"Time":"2026-10-18T10:18:52.583327761Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-owners","Elapsed":0.24}
{"Time":"2026-10-18T10:18:52.583344803Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers"}
{"Time":"2026-10-18T10:18:52.583352044Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"=== RUN TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers\n"}
{"Time":"2026-10-18T10:18:52.870643054Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers (0.29s)\n"}
{"Time":"2026-10-18T10:18:52.870695029Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision/forge-coulomb-readers","Elapsed":0.29}
{"Time":"2026-10-18T10:18:52.870708041Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Output":"--- PASS: TestAccessEngineActualCallbackAllowsAuthenticatedDecision (0.78s)\n"}
{"Time":"2026-10-18T10:18:52.870716878Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackAllowsAuthenticatedDecision","Elapsed":0.78}
{"Time":"2026-10-18T10:18:52.870725129Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP"}
{"Time":"2026-10-18T10:18:52.870731694Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP\n"}
{"Time":"2026-10-18T10:18:52.870755481Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny"}
{"Time":"2026-10-18T10:18:52.870762271Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny\n"}
{"Time":"2026-10-18T10:18:53.194695978Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny (0.32s)\n"}
{"Time":"2026-10-18T10:18:53.194752778Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/deny","Elapsed":0.32}
{"Time":"2026-10-18T10:18:53.194761191Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned"}
{"Time":"2026-10-18T10:18:53.194765538Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned\n"}
{"Time":"2026-10-18T10:18:53.276909407Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned (0.08s)\n"}
{"Time":"2026-10-18T10:18:53.276959291Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/unsigned","Elapsed":0.08}
{"Time":"2026-10-18T10:18:53.276984801Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key"}
{"Time":"2026-10-18T10:18:53.276989643Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key\n"}
{"Time":"2026-10-18T10:18:53.801961933Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key (0.52s)\n"}
{"Time":"2026-10-18T10:18:53.802010816Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-key","Elapsed":0.52}
{"Time":"2026-10-18T10:18:53.802019842Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin"}
{"Time":"2026-10-18T10:18:53.802025108Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin\n"}
{"Time":"2026-10-18T10:18:54.086698191Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin (0.28s)\n"}
{"Time":"2026-10-18T10:18:54.086747578Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-pin","Elapsed":0.28}
{"Time":"2026-10-18T10:18:54.086757461Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding"}
{"Time":"2026-10-18T10:18:54.086762448Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding\n"}
{"Time":"2026-10-18T10:18:54.41909618Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding (0.33s)\n"}
{"Time":"2026-10-18T10:18:54.419142754Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/wrong-binding","Elapsed":0.33}
{"Time":"2026-10-18T10:18:54.419150184Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired"}
{"Time":"2026-10-18T10:18:54.41915986Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"=== RUN TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired\n"}
{"Time":"2026-10-18T10:18:54.628540634Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired (0.21s)\n"}
{"Time":"2026-10-18T10:18:54.628591816Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP/expired","Elapsed":0.21}
{"Time":"2026-10-18T10:18:54.628609778Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Output":"--- PASS: TestAccessEngineActualCallbackRefusesUnverifiedPDP (1.76s)\n"}
{"Time":"2026-10-18T10:18:54.628617857Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackRefusesUnverifiedPDP","Elapsed":1.76}
{"Time":"2026-10-18T10:18:54.628625098Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP"}
{"Time":"2026-10-18T10:18:54.62867215Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP\n"}
{"Time":"2026-10-18T10:18:54.628686488Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity"}
{"Time":"2026-10-18T10:18:54.628692484Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity\n"}
{"Time":"2026-10-18T10:18:55.497486439Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity (0.87s)\n"}
{"Time":"2026-10-18T10:18:55.497539767Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/untrusted-identity","Elapsed":0.87}
{"Time":"2026-10-18T10:18:55.497550362Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token"}
{"Time":"2026-10-18T10:18:55.497555258Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token\n"}
{"Time":"2026-10-18T10:18:55.819513064Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token (0.32s)\n"}
{"Time":"2026-10-18T10:18:55.819563236Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-token","Elapsed":0.32}
{"Time":"2026-10-18T10:18:55.819588394Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user"}
{"Time":"2026-10-18T10:18:55.819594039Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user\n"}
{"Time":"2026-10-18T10:18:55.981381644Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user (0.16s)\n"}
{"Time":"2026-10-18T10:18:55.981437894Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-user","Elapsed":0.16}
{"Time":"2026-10-18T10:18:55.981450464Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject"}
{"Time":"2026-10-18T10:18:55.981457993Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject\n"}
{"Time":"2026-10-18T10:18:56.171524198Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject (0.19s)\n"}
{"Time":"2026-10-18T10:18:56.1715774Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/mismatched-subject","Elapsed":0.19}
{"Time":"2026-10-18T10:18:56.17158924Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups"}
{"Time":"2026-10-18T10:18:56.171596498Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups\n"}
{"Time":"2026-10-18T10:18:56.75293653Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups (0.58s)\n"}
{"Time":"2026-10-18T10:18:56.753000553Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-groups","Elapsed":0.58}
{"Time":"2026-10-18T10:18:56.753014052Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source"}
{"Time":"2026-10-18T10:18:56.753022735Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source\n"}
{"Time":"2026-10-18T10:18:57.218656261Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source (0.47s)\n"}
{"Time":"2026-10-18T10:18:57.218708218Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-source","Elapsed":0.47}
{"Time":"2026-10-18T10:18:57.218718543Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider"}
{"Time":"2026-10-18T10:18:57.218737497Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider\n"}
{"Time":"2026-10-18T10:18:57.319133141Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider (0.10s)\n"}
{"Time":"2026-10-18T10:18:57.319184458Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-provider","Elapsed":0.1}
{"Time":"2026-10-18T10:18:57.319194422Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission"}
{"Time":"2026-10-18T10:18:57.319200356Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission\n"}
{"Time":"2026-10-18T10:18:57.76671532Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission (0.45s)\n"}
{"Time":"2026-10-18T10:18:57.766758125Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/missing-admission","Elapsed":0.45}
{"Time":"2026-10-18T10:18:57.766766232Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token"}
{"Time":"2026-10-18T10:18:57.76677074Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token\n"}
{"Time":"2026-10-18T10:18:58.299106878Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token (0.53s)\n"}
{"Time":"2026-10-18T10:18:58.299209323Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/expired-token","Elapsed":0.53}
{"Time":"2026-10-18T10:18:58.299222533Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience"}
{"Time":"2026-10-18T10:18:58.299227854Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"=== RUN TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience\n"}
{"Time":"2026-10-18T10:18:58.504027985Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience (0.20s)\n"}
{"Time":"2026-10-18T10:18:58.504085894Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP/wrong-audience","Elapsed":0.2}
{"Time":"2026-10-18T10:18:58.50409813Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Output":"--- PASS: TestAccessEngineActualCallbackChecksIdentityBeforePDP (3.88s)\n"}
{"Time":"2026-10-18T10:18:58.504109979Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineActualCallbackChecksIdentityBeforePDP","Elapsed":3.88}
{"Time":"2026-10-18T10:18:58.504135029Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision"}
{"Time":"2026-10-18T10:18:58.5041419Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision\n"}
{"Time":"2026-10-18T10:18:58.504148897Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing"}
{"Time":"2026-10-18T10:18:58.504154723Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing\n"}
{"Time":"2026-10-18T10:18:58.746895605Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing (0.24s)\n"}
{"Time":"2026-10-18T10:18:58.746953694Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing","Elapsed":0.24}
{"Time":"2026-10-18T10:18:58.746966445Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access"}
{"Time":"2026-10-18T10:18:58.746974028Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access\n"}
{"Time":"2026-10-18T10:18:59.493844845Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access (0.75s)\n"}
{"Time":"2026-10-18T10:18:59.494402237Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/id-as-access","Elapsed":0.75}
{"Time":"2026-10-18T10:18:59.494421209Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject"}
{"Time":"2026-10-18T10:18:59.494427467Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/subject\n"}
{"Time":"2026-10-18T10:18:59.649238391Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/subject (0.16s)\n"}
{"Time":"2026-10-18T10:18:59.64929621Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/subject","Elapsed":0.16}
{"Time":"2026-10-18T10:18:59.649309555Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant"}
{"Time":"2026-10-18T10:18:59.649317202Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant\n"}
{"Time":"2026-10-18T10:19:00.366996003Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant (0.72s)\n"}
{"Time":"2026-10-18T10:19:00.367067906Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant","Elapsed":0.72}
{"Time":"2026-10-18T10:19:00.367172047Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal"}
{"Time":"2026-10-18T10:19:00.367184022Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/principal\n"}
{"Time":"2026-10-18T10:19:00.664993465Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/principal (0.30s)\n"}
{"Time":"2026-10-18T10:19:00.665053926Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/principal","Elapsed":0.3}
{"Time":"2026-10-18T10:19:00.665067236Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance"}
{"Time":"2026-10-18T10:19:00.665072357Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance\n"}
{"Time":"2026-10-18T10:19:00.966157108Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance (0.30s)\n"}
{"Time":"2026-10-18T10:19:00.966199634Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/assurance","Elapsed":0.3}
{"Time":"2026-10-18T10:19:00.966209814Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time"}
{"Time":"2026-10-18T10:19:00.966213385Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time\n"}
{"Time":"2026-10-18T10:19:01.356327497Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time (0.39s)\n"}
{"Time":"2026-10-18T10:19:01.356375739Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/authentication-time","Elapsed":0.39}
{"Time":"2026-10-18T10:19:01.356392223Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source"}
{"Time":"2026-10-18T10:19:01.356398247Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source\n"}
{"Time":"2026-10-18T10:19:01.478532824Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source (0.12s)\n"}
{"Time":"2026-10-18T10:19:01.478586954Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/tenant-source","Elapsed":0.12}
{"Time":"2026-10-18T10:19:01.478598325Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at"}
{"Time":"2026-10-18T10:19:01.478605557Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at\n"}
{"Time":"2026-10-18T10:19:02.093913306Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at (0.62s)\n"}
{"Time":"2026-10-18T10:19:02.093957752Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/issued-at","Elapsed":0.62}
{"Time":"2026-10-18T10:19:02.093968149Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive"}
{"Time":"2026-10-18T10:19:02.094025562Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive\n"}
{"Time":"2026-10-18T10:19:02.606703212Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive (0.51s)\n"}
{"Time":"2026-10-18T10:19:02.606755539Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/inactive","Elapsed":0.51}
{"Time":"2026-10-18T10:19:02.606766017Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable"}
{"Time":"2026-10-18T10:19:02.606771418Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable\n"}
{"Time":"2026-10-18T10:19:02.71368809Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable (0.11s)\n"}
{"Time":"2026-10-18T10:19:02.713736766Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/unavailable","Elapsed":0.11}
{"Time":"2026-10-18T10:19:02.713745711Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status"}
{"Time":"2026-10-18T10:19:02.713754385Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status\n"}
{"Time":"2026-10-18T10:19:02.94057398Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status (0.23s)\n"}
{"Time":"2026-10-18T10:19:02.940616407Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-status","Elapsed":0.23}
{"Time":"2026-10-18T10:19:02.940626317Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission"}
{"Time":"2026-10-18T10:19:02.940629815Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission\n"}
{"Time":"2026-10-18T10:19:03.060902113Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission (0.12s)\n"}
{"Time":"2026-10-18T10:19:03.060977533Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-current-admission","Elapsed":0.12}
{"Time":"2026-10-18T10:19:03.060995234Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client"}
{"Time":"2026-10-18T10:19:03.061002394Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client\n"}
{"Time":"2026-10-18T10:19:03.143078475Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client (0.08s)\n"}
{"Time":"2026-10-18T10:19:03.143126708Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/wrong-client","Elapsed":0.08}
{"Time":"2026-10-18T10:19:03.143134923Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key"}
{"Time":"2026-10-18T10:19:03.143139557Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key\n"}
{"Time":"2026-10-18T10:19:03.574148238Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key (0.43s)\n"}
{"Time":"2026-10-18T10:19:03.574202278Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/withdrawn-key","Elapsed":0.43}
{"Time":"2026-10-18T10:19:03.57421382Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant"}
{"Time":"2026-10-18T10:19:03.574221016Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant\n"}
{"Time":"2026-10-18T10:19:03.705613684Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant (0.13s)\n"}
{"Time":"2026-10-18T10:19:03.705662116Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/mismatched-grant","Elapsed":0.13}
{"Time":"2026-10-18T10:19:03.705676733Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash"}
{"Time":"2026-10-18T10:19:03.70568245Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Output":"=== RUN TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash\n"}
{"Time":"2026-10-18T10:19:04.169381464Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash (0.46s)\n"}
{"Time":"2026-10-18T10:19:04.169434594Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision/missing-at-hash","Elapsed":0.46}
{"Time":"2026-10-18T10:19:04.169446893Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Output":"--- PASS: TestAccessEngineCurrentTokenRejectsBeforeDecision (5.67s)\n"}
{"Time":"2026-10-18T10:19:04.169472484Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRejectsBeforeDecision","Elapsed":5.67}
{"Time":"2026-10-18T10:19:04.169481646Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn"}
{"Time":"2026-10-18T10:19:04.169487976Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"=== RUN TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn\n"}
{"Time":"2026-10-18T10:19:04.487920753Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Output":"--- PASS: TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn (0.32s)\n"}
{"Time":"2026-10-18T10:19:04.487971764Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn","Elapsed":0.32}
{"Time":"2026-10-18T10:19:04.487981683Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims"}
{"Time":"2026-10-18T10:19:04.487987336Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims\n"}
{"Time":"2026-10-18T10:19:04.487996759Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti"}
{"Time":"2026-10-18T10:19:04.488001656Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti\n"}
{"Time":"2026-10-18T10:19:04.48837029Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.488398766Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/jti","Elapsed":0}
{"Time":"2026-10-18T10:19:04.48840525Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope"}
{"Time":"2026-10-18T10:19:04.488409903Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"=== RUN TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope\n"}
{"Time":"2026-10-18T10:19:04.488661529Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.488679933Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims/scope","Elapsed":0}
{"Time":"2026-10-18T10:19:04.488686588Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Output":"--- PASS: TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.488691683Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims","Elapsed":0}
{"Time":"2026-10-18T10:19:04.488699086Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityCannotChooseApplicationTenant"}
{"Time":"2026-10-18T10:19:04.488722549Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityCannotChooseApplicationTenant","Output":"=== RUN TestVerifiedApplicationIdentityCannotChooseApplicationTenant\n"}
{"Time":"2026-10-18T10:19:04.48923985Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityCannotChooseApplicationTenant","Output":"--- PASS: TestVerifiedApplicationIdentityCannotChooseApplicationTenant (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.489263475Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityCannotChooseApplicationTenant","Elapsed":0}
{"Time":"2026-10-18T10:19:04.48927004Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE"}
{"Time":"2026-10-18T10:19:04.489277606Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE\n"}
{"Time":"2026-10-18T10:19:04.489558105Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired"}
{"Time":"2026-10-18T10:19:04.489570414Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/expired\n"}
{"Time":"2026-10-18T10:19:04.489805115Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/expired (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.489817865Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/expired","Elapsed":0}
{"Time":"2026-10-18T10:19:04.489823681Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued"}
{"Time":"2026-10-18T10:19:04.489828208Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/future-issued\n"}
{"Time":"2026-10-18T10:19:04.490059455Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/future-issued (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.490072602Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/future-issued","Elapsed":0}
{"Time":"2026-10-18T10:19:04.490078274Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued"}
{"Time":"2026-10-18T10:19:04.49008281Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/old-issued\n"}
{"Time":"2026-10-18T10:19:04.490305139Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/old-issued (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.490323381Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/old-issued","Elapsed":0}
{"Time":"2026-10-18T10:19:04.490329711Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp"}
{"Time":"2026-10-18T10:19:04.49033431Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-exp\n"}
{"Time":"2026-10-18T10:19:04.49055287Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-exp (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.490565602Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-exp","Elapsed":0}
{"Time":"2026-10-18T10:19:04.490571292Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance"}
{"Time":"2026-10-18T10:19:04.490576657Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-assurance\n"}
{"Time":"2026-10-18T10:19:04.490792539Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-assurance (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.490805768Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-assurance","Elapsed":0}
{"Time":"2026-10-18T10:19:04.490811512Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp"}
{"Time":"2026-10-18T10:19:04.490815948Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp\n"}
{"Time":"2026-10-18T10:19:04.49109957Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.491129706Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/multi-audience-no-azp","Elapsed":0}
{"Time":"2026-10-18T10:19:04.491139003Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat"}
{"Time":"2026-10-18T10:19:04.491147506Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-iat\n"}
{"Time":"2026-10-18T10:19:04.49140787Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-iat (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.491434796Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-iat","Elapsed":0}
{"Time":"2026-10-18T10:19:04.491443236Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant"}
{"Time":"2026-10-18T10:19:04.491449395Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-tenant\n"}
{"Time":"2026-10-18T10:19:04.491765251Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-tenant (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.491790409Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-tenant","Elapsed":0}
{"Time":"2026-10-18T10:19:04.491798218Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal"}
{"Time":"2026-10-18T10:19:04.491823962Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/no-principal\n"}
{"Time":"2026-10-18T10:19:04.492085156Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/no-principal (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.492102766Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/no-principal","Elapsed":0}
{"Time":"2026-10-18T10:19:04.49211035Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject"}
{"Time":"2026-10-18T10:19:04.49211576Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/empty-subject\n"}
{"Time":"2026-10-18T10:19:04.492455984Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/empty-subject (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.492500664Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/empty-subject","Elapsed":0}
{"Time":"2026-10-18T10:19:04.492512423Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer"}
{"Time":"2026-10-18T10:19:04.492518699Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/issuer\n"}
{"Time":"2026-10-18T10:19:04.492793526Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/issuer (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.492825447Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/issuer","Elapsed":0}
{"Time":"2026-10-18T10:19:04.492850795Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience"}
{"Time":"2026-10-18T10:19:04.492860155Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/audience\n"}
{"Time":"2026-10-18T10:19:04.493240761Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/audience (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.493270446Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/audience","Elapsed":0}
{"Time":"2026-10-18T10:19:04.493277488Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key"}
{"Time":"2026-10-18T10:19:04.493282141Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-key\n"}
{"Time":"2026-10-18T10:19:04.493573524Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-key (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.493586798Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-key","Elapsed":0}
{"Time":"2026-10-18T10:19:04.493610107Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key"}
{"Time":"2026-10-18T10:19:04.49361522Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/unknown-key\n"}
{"Time":"2026-10-18T10:19:04.493712958Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/unknown-key (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.493727458Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/unknown-key","Elapsed":0}
{"Time":"2026-10-18T10:19:04.493734771Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type"}
{"Time":"2026-10-18T10:19:04.49373927Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/wrong-type\n"}
{"Time":"2026-10-18T10:19:04.493900481Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/wrong-type (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.493913123Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/wrong-type","Elapsed":0}
{"Time":"2026-10-18T10:19:04.493918984Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion"}
{"Time":"2026-10-18T10:19:04.493924043Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion\n"}
{"Time":"2026-10-18T10:19:04.493955152Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.494027182Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/algorithm-confusion","Elapsed":0}
{"Time":"2026-10-18T10:19:04.494039833Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none"}
{"Time":"2026-10-18T10:19:04.494044441Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"=== RUN TestVerifiedApplicationIdentityActualJOSE/none\n"}
{"Time":"2026-10-18T10:19:04.494142513Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE/none (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.494154794Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE/none","Elapsed":0}
{"Time":"2026-10-18T10:19:04.494162738Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Output":"--- PASS: TestVerifiedApplicationIdentityActualJOSE (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.494168095Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestVerifiedApplicationIdentityActualJOSE","Elapsed":0}
{"Time":"2026-10-18T10:19:04.494244985Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation"}
{"Time":"2026-10-18T10:19:04.49426659Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"=== RUN TestRequiredClaimExactAnyOfAndNoMutation\n"}
{"Time":"2026-10-18T10:19:04.494305193Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Output":"--- PASS: TestRequiredClaimExactAnyOfAndNoMutation (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.494378197Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimExactAnyOfAndNoMutation","Elapsed":0}
{"Time":"2026-10-18T10:19:04.494390676Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation"}
{"Time":"2026-10-18T10:19:04.494395257Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"=== RUN TestRequiredClaimConfigurationRefusalsAndLegacyIsolation\n"}
{"Time":"2026-10-18T10:19:04.494470867Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Output":"--- PASS: TestRequiredClaimConfigurationRefusalsAndLegacyIsolation (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.494485772Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestRequiredClaimConfigurationRefusalsAndLegacyIsolation","Elapsed":0}
{"Time":"2026-10-18T10:19:04.494493599Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable"}
{"Time":"2026-10-18T10:19:04.494498324Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"=== RUN TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable\n"}
{"Time":"2026-10-18T10:19:04.497538617Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Output":"--- PASS: TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.497593892Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable","Elapsed":0}
{"Time":"2026-10-18T10:19:04.497634443Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval"}
{"Time":"2026-10-18T10:19:04.497649502Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"=== RUN TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval\n"}
{"Time":"2026-10-18T10:19:04.497664164Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Output":"--- PASS: TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.49767099Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval","Elapsed":0}
{"Time":"2026-10-18T10:19:04.497677554Z","Action":"run","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse"}
{"Time":"2026-10-18T10:19:04.497683091Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"=== RUN TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse\n"}
{"Time":"2026-10-18T10:19:04.497690052Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Output":"--- PASS: TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse (0.00s)\n"}
{"Time":"2026-10-18T10:19:04.497696094Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Test":"TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse","Elapsed":0}
{"Time":"2026-10-18T10:19:04.497732893Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"PASS\n"}
{"Time":"2026-10-18T10:19:04.505037302Z","Action":"output","Package":"forgejo.org/services/auth/source/oauth2","Output":"ok \tforgejo.org/services/auth/source/oauth2\t12.515s\n"}
{"Time":"2026-10-18T10:19:04.505080937Z","Action":"pass","Package":"forgejo.org/services/auth/source/oauth2","Elapsed":12.515}
{"Time":"2026-10-18T10:19:15.231969207Z","Action":"start","Package":"forgejo.org/cmd"}
{"Time":"2026-10-18T10:19:15.411140265Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...s/storage/storage.go:157:initAttachments() [I] Initialising Attachment storage with type: local\n"}
{"Time":"2026-10-18T10:19:15.41127546Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata246684370/attachments\n"}
{"Time":"2026-10-18T10:19:15.411285983Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...s/storage/storage.go:147:initAvatars() [I] Initialising Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:19:15.411298409Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata246684370/avatars\n"}
{"Time":"2026-10-18T10:19:15.411305578Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...s/storage/storage.go:173:initRepoAvatars() [I] Initialising Repository Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:19:15.41131244Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata246684370/repo-avatars\n"}
{"Time":"2026-10-18T10:19:15.411319185Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...s/storage/storage.go:179:initRepoArchives() [I] Initialising Repository Archive storage with type: local\n"}
{"Time":"2026-10-18T10:19:15.411325632Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata246684370/repo-archive\n"}
{"Time":"2026-10-18T10:19:15.411334Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...s/storage/storage.go:189:initPackages() [I] Initialising Packages storage with type: local\n"}
{"Time":"2026-10-18T10:19:15.411340556Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata246684370/packages\n"}
{"Time":"2026-10-18T10:19:15.411347102Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...s/storage/storage.go:200:initActions() [I] Initialising Actions storage with type: local\n"}
{"Time":"2026-10-18T10:19:15.411353532Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata246684370/actions_log\n"}
{"Time":"2026-10-18T10:19:15.41136006Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...s/storage/storage.go:204:initActions() [I] Initialising ActionsArtifacts storage with type: local\n"}
{"Time":"2026-10-18T10:19:15.411369339Z","Action":"output","Package":"forgejo.org/cmd","Output":"2026/10/18 10:19:15 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/go-build2948449686/b1036/data/actions_artifacts\n"}
{"Time":"2026-10-18T10:19:15.53127327Z","Action":"run","Package":"forgejo.org/cmd","Test":"TestPrivateSecretMetadata"}
{"Time":"2026-10-18T10:19:15.531317337Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretMetadata","Output":"=== RUN TestPrivateSecretMetadata\n"}
{"Time":"2026-10-18T10:19:15.531364488Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretMetadata","Output":"--- PASS: TestPrivateSecretMetadata (0.00s)\n"}
{"Time":"2026-10-18T10:19:15.531381224Z","Action":"pass","Package":"forgejo.org/cmd","Test":"TestPrivateSecretMetadata","Elapsed":0}
{"Time":"2026-10-18T10:19:15.531389412Z","Action":"run","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement"}
{"Time":"2026-10-18T10:19:15.531394299Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement","Output":"=== RUN TestPrivateSecretOpenedFileReplacement\n"}
{"Time":"2026-10-18T10:19:15.531806144Z","Action":"output","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement","Output":"--- PASS: TestPrivateSecretOpenedFileReplacement (0.00s)\n"}
{"Time":"2026-10-18T10:19:15.531834386Z","Action":"pass","Package":"forgejo.org/cmd","Test":"TestPrivateSecretOpenedFileReplacement","Elapsed":0}
{"Time":"2026-10-18T10:19:15.532110034Z","Action":"output","Package":"forgejo.org/cmd","Output":"PASS\n"}
{"Time":"2026-10-18T10:19:15.561953003Z","Action":"output","Package":"forgejo.org/cmd","Output":"ok \tforgejo.org/cmd\t0.330s\n"}
{"Time":"2026-10-18T10:19:15.56200964Z","Action":"pass","Package":"forgejo.org/cmd","Elapsed":0.33}
{"Time":"2026-10-18T10:19:18.5199517Z","Action":"start","Package":"forgejo.org/models/auth"}
{"Time":"2026-10-18T10:19:18.674772195Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...s/storage/storage.go:157:initAttachments() [I] Initialising Attachment storage with type: local\n"}
{"Time":"2026-10-18T10:19:18.674867092Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata2133799640/attachments\n"}
{"Time":"2026-10-18T10:19:18.674875082Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...s/storage/storage.go:147:initAvatars() [I] Initialising Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:19:18.674882692Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata2133799640/avatars\n"}
{"Time":"2026-10-18T10:19:18.674889059Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...s/storage/storage.go:173:initRepoAvatars() [I] Initialising Repository Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:19:18.674895271Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata2133799640/repo-avatars\n"}
{"Time":"2026-10-18T10:19:18.674901502Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...s/storage/storage.go:179:initRepoArchives() [I] Initialising Repository Archive storage with type: local\n"}
{"Time":"2026-10-18T10:19:18.674907634Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata2133799640/repo-archive\n"}
{"Time":"2026-10-18T10:19:18.674913775Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...s/storage/storage.go:189:initPackages() [I] Initialising Packages storage with type: local\n"}
{"Time":"2026-10-18T10:19:18.674919907Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata2133799640/packages\n"}
{"Time":"2026-10-18T10:19:18.674926237Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...s/storage/storage.go:200:initActions() [I] Initialising Actions storage with type: local\n"}
{"Time":"2026-10-18T10:19:18.674932287Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata2133799640/actions_log\n"}
{"Time":"2026-10-18T10:19:18.674982757Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...s/storage/storage.go:204:initActions() [I] Initialising ActionsArtifacts storage with type: local\n"}
{"Time":"2026-10-18T10:19:18.674991233Z","Action":"output","Package":"forgejo.org/models/auth","Output":"2026/10/18 10:19:18 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/go-build2948449686/b1316/data/actions_artifacts\n"}
{"Time":"2026-10-18T10:19:18.781028584Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationDatabaseWriterExclusion"}
{"Time":"2026-10-18T10:19:18.781094933Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationDatabaseWriterExclusion","Output":"=== RUN TestSourceOperationDatabaseWriterExclusion\n"}
{"Time":"2026-10-18T10:19:18.882373172Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationDatabaseWriterExclusion","Output":"--- PASS: TestSourceOperationDatabaseWriterExclusion (0.10s)\n"}
{"Time":"2026-10-18T10:19:18.882449377Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationDatabaseWriterExclusion","Elapsed":0.1}
{"Time":"2026-10-18T10:19:18.882473714Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging"}
{"Time":"2026-10-18T10:19:18.882483976Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Output":"=== RUN TestSourceOperationRefusesActualXORMArgumentLogging\n"}
{"Time":"2026-10-18T10:19:18.882498286Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Output":"[xorm] [info] 2026/10/18 10:19:18.882449 [SQL] BEGIN TRANSACTION [] - 58.387µs\n"}
{"Time":"2026-10-18T10:19:18.882507322Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Output":"[xorm] [info] 2026/10/18 10:19:18.882480 [SQL] ROLLBACK [] - 16.041µs\n"}
{"Time":"2026-10-18T10:19:18.88251789Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Output":"--- PASS: TestSourceOperationRefusesActualXORMArgumentLogging (0.00s)\n"}
{"Time":"2026-10-18T10:19:18.88252853Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesActualXORMArgumentLogging","Elapsed":0}
{"Time":"2026-10-18T10:19:18.883928788Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationProcessRecovery"}
{"Time":"2026-10-18T10:19:18.883977472Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationProcessRecovery","Output":"=== RUN TestSourceOperationProcessRecovery\n"}
{"Time":"2026-10-18T10:19:21.178981559Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationProcessRecovery","Output":"--- PASS: TestSourceOperationProcessRecovery (2.29s)\n"}
{"Time":"2026-10-18T10:19:21.179041632Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationProcessRecovery","Elapsed":2.29}
{"Time":"2026-10-18T10:19:21.179110389Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationCreateAndInverse"}
{"Time":"2026-10-18T10:19:21.17912286Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationCreateAndInverse","Output":"=== RUN TestSourceOperationCreateAndInverse\n"}
{"Time":"2026-10-18T10:19:21.208394173Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationCreateAndInverse","Output":"--- PASS: TestSourceOperationCreateAndInverse (0.03s)\n"}
{"Time":"2026-10-18T10:19:21.208463534Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationCreateAndInverse","Elapsed":0.03}
{"Time":"2026-10-18T10:19:21.208492903Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRestoresExactRawPreexisting"}
{"Time":"2026-10-18T10:19:21.208497935Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRestoresExactRawPreexisting","Output":"=== RUN TestSourceOperationRestoresExactRawPreexisting\n"}
{"Time":"2026-10-18T10:19:21.228044834Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRestoresExactRawPreexisting","Output":"--- PASS: TestSourceOperationRestoresExactRawPreexisting (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.228098487Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRestoresExactRawPreexisting","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.228110083Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationUnchangedNeverDeletesPreexisting"}
{"Time":"2026-10-18T10:19:21.228114871Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationUnchangedNeverDeletesPreexisting","Output":"=== RUN TestSourceOperationUnchangedNeverDeletesPreexisting\n"}
{"Time":"2026-10-18T10:19:21.251268443Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationUnchangedNeverDeletesPreexisting","Output":"--- PASS: TestSourceOperationUnchangedNeverDeletesPreexisting (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.251343737Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationUnchangedNeverDeletesPreexisting","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.251357633Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses"}
{"Time":"2026-10-18T10:19:21.251362196Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses","Output":"=== RUN TestSourceOperationPrivateDriftRefuses\n"}
{"Time":"2026-10-18T10:19:21.251378715Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-apply"}
{"Time":"2026-10-18T10:19:21.251384748Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-apply","Output":"=== RUN TestSourceOperationPrivateDriftRefuses/before-apply\n"}
{"Time":"2026-10-18T10:19:21.275801314Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-apply","Output":"--- PASS: TestSourceOperationPrivateDriftRefuses/before-apply (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.275848041Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-apply","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.275858096Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-inverse"}
{"Time":"2026-10-18T10:19:21.275861982Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-inverse","Output":"=== RUN TestSourceOperationPrivateDriftRefuses/before-inverse\n"}
{"Time":"2026-10-18T10:19:21.296072315Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-inverse","Output":"--- PASS: TestSourceOperationPrivateDriftRefuses/before-inverse (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.2961376Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses/before-inverse","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.296154165Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses","Output":"--- PASS: TestSourceOperationPrivateDriftRefuses (0.04s)\n"}
{"Time":"2026-10-18T10:19:21.296162975Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPrivateDriftRefuses","Elapsed":0.04}
{"Time":"2026-10-18T10:19:21.296180288Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationStockCRUDDriftRefuses"}
{"Time":"2026-10-18T10:19:21.296192777Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationStockCRUDDriftRefuses","Output":"=== RUN TestSourceOperationStockCRUDDriftRefuses\n"}
{"Time":"2026-10-18T10:19:21.314059542Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationStockCRUDDriftRefuses","Output":"--- PASS: TestSourceOperationStockCRUDDriftRefuses (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.314109579Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationStockCRUDDriftRefuses","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.314117965Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion"}
{"Time":"2026-10-18T10:19:21.314122194Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion","Output":"=== RUN TestSourceOperationLinkedSourceRefusesDeletion\n"}
{"Time":"2026-10-18T10:19:21.314132573Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/user"}
{"Time":"2026-10-18T10:19:21.314137388Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/user","Output":"=== RUN TestSourceOperationLinkedSourceRefusesDeletion/user\n"}
{"Time":"2026-10-18T10:19:21.335619279Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/user","Output":"--- PASS: TestSourceOperationLinkedSourceRefusesDeletion/user (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.335674807Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/user","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.335689397Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/external"}
{"Time":"2026-10-18T10:19:21.335695439Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/external","Output":"=== RUN TestSourceOperationLinkedSourceRefusesDeletion/external\n"}
{"Time":"2026-10-18T10:19:21.356168119Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/external","Output":"--- PASS: TestSourceOperationLinkedSourceRefusesDeletion/external (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.35624665Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion/external","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.356269563Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion","Output":"--- PASS: TestSourceOperationLinkedSourceRefusesDeletion (0.04s)\n"}
{"Time":"2026-10-18T10:19:21.356278796Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationLinkedSourceRefusesDeletion","Elapsed":0.04}
{"Time":"2026-10-18T10:19:21.35629207Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPreparedCancellationAndIDReuse"}
{"Time":"2026-10-18T10:19:21.356299239Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPreparedCancellationAndIDReuse","Output":"=== RUN TestSourceOperationPreparedCancellationAndIDReuse\n"}
{"Time":"2026-10-18T10:19:21.377314655Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPreparedCancellationAndIDReuse","Output":"--- PASS: TestSourceOperationPreparedCancellationAndIDReuse (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.377380977Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationPreparedCancellationAndIDReuse","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.377395026Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRejectsUnsafeRuntime"}
{"Time":"2026-10-18T10:19:21.377400364Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRejectsUnsafeRuntime","Output":"=== RUN TestSourceOperationRejectsUnsafeRuntime\n"}
{"Time":"2026-10-18T10:19:21.395148303Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRejectsUnsafeRuntime","Output":"--- PASS: TestSourceOperationRejectsUnsafeRuntime (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.3952365Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRejectsUnsafeRuntime","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.395251712Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesSharedManagedSource"}
{"Time":"2026-10-18T10:19:21.39525651Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesSharedManagedSource","Output":"=== RUN TestSourceOperationRefusesSharedManagedSource\n"}
{"Time":"2026-10-18T10:19:21.415651921Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesSharedManagedSource","Output":"--- PASS: TestSourceOperationRefusesSharedManagedSource (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.415709478Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRefusesSharedManagedSource","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.415721534Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationBoundsPrivateJournalCustody"}
{"Time":"2026-10-18T10:19:21.415726845Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationBoundsPrivateJournalCustody","Output":"=== RUN TestSourceOperationBoundsPrivateJournalCustody\n"}
{"Time":"2026-10-18T10:19:21.436854212Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationBoundsPrivateJournalCustody","Output":"--- PASS: TestSourceOperationBoundsPrivateJournalCustody (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.436914023Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationBoundsPrivateJournalCustody","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.436927197Z","Action":"run","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRequiresExactPostState"}
{"Time":"2026-10-18T10:19:21.436933951Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRequiresExactPostState","Output":"=== RUN TestSourceOperationRequiresExactPostState\n"}
{"Time":"2026-10-18T10:19:21.454743707Z","Action":"output","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRequiresExactPostState","Output":"--- PASS: TestSourceOperationRequiresExactPostState (0.02s)\n"}
{"Time":"2026-10-18T10:19:21.454797098Z","Action":"pass","Package":"forgejo.org/models/auth","Test":"TestSourceOperationRequiresExactPostState","Elapsed":0.02}
{"Time":"2026-10-18T10:19:21.455056263Z","Action":"output","Package":"forgejo.org/models/auth","Output":"PASS\n"}
{"Time":"2026-10-18T10:19:21.482025516Z","Action":"output","Package":"forgejo.org/models/auth","Output":"ok \tforgejo.org/models/auth\t2.962s\n"}
{"Time":"2026-10-18T10:19:21.48209045Z","Action":"pass","Package":"forgejo.org/models/auth","Elapsed":2.9619999999999997}
{"Time":"2026-10-18T10:19:27.132546853Z","Action":"start","Package":"forgejo.org/models/db"}
{"Time":"2026-10-18T10:19:27.331217623Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...s/storage/storage.go:157:initAttachments() [I] Initialising Attachment storage with type: local\n"}
{"Time":"2026-10-18T10:19:27.331320329Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata275439258/attachments\n"}
{"Time":"2026-10-18T10:19:27.331334712Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...s/storage/storage.go:147:initAvatars() [I] Initialising Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:19:27.331347318Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata275439258/avatars\n"}
{"Time":"2026-10-18T10:19:27.331359157Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...s/storage/storage.go:173:initRepoAvatars() [I] Initialising Repository Avatar storage with type: local\n"}
{"Time":"2026-10-18T10:19:27.331389203Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata275439258/repo-avatars\n"}
{"Time":"2026-10-18T10:19:27.331405993Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...s/storage/storage.go:179:initRepoArchives() [I] Initialising Repository Archive storage with type: local\n"}
{"Time":"2026-10-18T10:19:27.331589142Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata275439258/repo-archive\n"}
{"Time":"2026-10-18T10:19:27.331632533Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...s/storage/storage.go:189:initPackages() [I] Initialising Packages storage with type: local\n"}
{"Time":"2026-10-18T10:19:27.331650252Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata275439258/packages\n"}
{"Time":"2026-10-18T10:19:27.331662534Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...s/storage/storage.go:200:initActions() [I] Initialising Actions storage with type: local\n"}
{"Time":"2026-10-18T10:19:27.331674896Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/appdata275439258/actions_log\n"}
{"Time":"2026-10-18T10:19:27.331697034Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...s/storage/storage.go:204:initActions() [I] Initialising ActionsArtifacts storage with type: local\n"}
{"Time":"2026-10-18T10:19:27.331708125Z","Action":"output","Package":"forgejo.org/models/db","Output":"2026/10/18 10:19:27 ...les/storage/local.go:33:NewLocalStorage() [I] Creating new Local Storage at /tmp/go-build2948449686/b1337/data/actions_artifacts\n"}
{"Time":"2026-10-18T10:19:27.450304092Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine"}
{"Time":"2026-10-18T10:19:27.450364625Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine","Output":"=== RUN TestSensitiveSQLLoggingChecksUnderlyingEngine\n"}
{"Time":"2026-10-18T10:19:27.450392254Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/engine"}
{"Time":"2026-10-18T10:19:27.450407764Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/engine","Output":"=== RUN TestSensitiveSQLLoggingChecksUnderlyingEngine/engine\n"}
{"Time":"2026-10-18T10:19:27.450420694Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/engine","Output":"--- PASS: TestSensitiveSQLLoggingChecksUnderlyingEngine/engine (0.00s)\n"}
{"Time":"2026-10-18T10:19:27.450426258Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/engine","Elapsed":0}
{"Time":"2026-10-18T10:19:27.450435131Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/session"}
{"Time":"2026-10-18T10:19:27.450439378Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/session","Output":"=== RUN TestSensitiveSQLLoggingChecksUnderlyingEngine/session\n"}
{"Time":"2026-10-18T10:19:27.450444734Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/session","Output":"--- PASS: TestSensitiveSQLLoggingChecksUnderlyingEngine/session (0.00s)\n"}
{"Time":"2026-10-18T10:19:27.450449982Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine/session","Elapsed":0}
{"Time":"2026-10-18T10:19:27.450470659Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine","Output":"--- PASS: TestSensitiveSQLLoggingChecksUnderlyingEngine (0.00s)\n"}
{"Time":"2026-10-18T10:19:27.450483527Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLLoggingChecksUnderlyingEngine","Elapsed":0}
{"Time":"2026-10-18T10:19:27.450498324Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLHooks"}
{"Time":"2026-10-18T10:19:27.450504861Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLHooks","Output":"=== RUN TestSensitiveSQLHooks\n"}
{"Time":"2026-10-18T10:19:27.454616311Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLHooks","Output":"--- PASS: TestSensitiveSQLHooks (0.00s)\n"}
{"Time":"2026-10-18T10:19:27.454674274Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLHooks","Elapsed":0}
{"Time":"2026-10-18T10:19:27.454689955Z","Action":"run","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLActualContextPropagation"}
{"Time":"2026-10-18T10:19:27.454702931Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLActualContextPropagation","Output":"=== RUN TestSensitiveSQLActualContextPropagation\n"}
{"Time":"2026-10-18T10:19:27.458389623Z","Action":"output","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLActualContextPropagation","Output":"--- PASS: TestSensitiveSQLActualContextPropagation (0.00s)\n"}
{"Time":"2026-10-18T10:19:27.458442915Z","Action":"pass","Package":"forgejo.org/models/db","Test":"TestSensitiveSQLActualContextPropagation","Elapsed":0}
{"Time":"2026-10-18T10:19:27.458786012Z","Action":"output","Package":"forgejo.org/models/db","Output":"PASS\n"}
{"Time":"2026-10-18T10:19:27.485385964Z","Action":"output","Package":"forgejo.org/models/db","Output":"ok \tforgejo.org/models/db\t0.353s\n"}
{"Time":"2026-10-18T10:19:27.485434865Z","Action":"pass","Package":"forgejo.org/models/db","Elapsed":0.353}

View file

@ -0,0 +1,368 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"errors"
"fmt"
"net/url"
auth_model "forgejo.org/models/auth"
"forgejo.org/models/db"
"forgejo.org/services/auth/source/oauth2"
"github.com/urfave/cli/v2"
)
var (
oauthCLIFlags = []cli.Flag{
&cli.StringFlag{
Name: "name",
Value: "",
Usage: "Application Name",
},
&cli.StringFlag{
Name: "provider",
Value: "",
Usage: "OAuth2 Provider",
},
&cli.StringFlag{
Name: "key",
Value: "",
Usage: "Client ID (Key)",
},
&cli.StringFlag{
Name: "secret",
Value: "",
Usage: "Client Secret",
},
&cli.StringFlag{
Name: "secret-file",
Usage: "Read a 64-byte base64url client secret from an admitted read-only regular file",
},
&cli.StringFlag{
Name: "auto-discover-url",
Value: "",
Usage: "OpenID Connect Auto Discovery URL (only required when using OpenID Connect as provider)",
},
&cli.StringFlag{
Name: "use-custom-urls",
Value: "false",
Usage: "Use custom URLs for GitLab/GitHub OAuth endpoints",
},
&cli.StringFlag{
Name: "custom-tenant-id",
Value: "",
Usage: "Use custom Tenant ID for OAuth endpoints",
},
&cli.StringFlag{
Name: "custom-auth-url",
Value: "",
Usage: "Use a custom Authorization URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-token-url",
Value: "",
Usage: "Use a custom Token URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-profile-url",
Value: "",
Usage: "Use a custom Profile URL (option for GitLab/GitHub)",
},
&cli.StringFlag{
Name: "custom-email-url",
Value: "",
Usage: "Use a custom Email URL (option for GitHub)",
},
&cli.StringFlag{
Name: "icon-url",
Value: "",
Usage: "Custom icon URL for OAuth2 login source",
},
&cli.BoolFlag{
Name: "skip-local-2fa",
Usage: "Set to true to skip local 2fa for users authenticated by this source",
},
&cli.StringSliceFlag{
Name: "scopes",
Value: nil,
Usage: "Scopes to request when to authenticate against this OAuth2 source",
},
&cli.StringFlag{
Name: "required-claim-name",
Value: "",
Usage: "Claim name that has to be set to allow users to login with this source",
},
&cli.StringFlag{
Name: "required-claim-value",
Value: "",
Usage: "Claim value that has to be set to allow users to login with this source",
},
&cli.StringSliceFlag{
Name: "required-claim-values",
Usage: "Require any one exact array value from current OpenID Connect UserInfo (cannot combine with required-claim-value)",
},
&cli.StringFlag{
Name: "group-claim-name",
Value: "",
Usage: "Claim name providing group names for this source",
},
&cli.StringFlag{
Name: "admin-group",
Value: "",
Usage: "Group Claim value for administrator users",
},
&cli.StringFlag{
Name: "restricted-group",
Value: "",
Usage: "Group Claim value for restricted users",
},
&cli.StringFlag{
Name: "group-team-map",
Value: "",
Usage: "JSON mapping between groups and org teams",
},
&cli.BoolFlag{
Name: "group-team-map-removal",
Usage: "Activate automatic team membership removal depending on groups",
},
}
microcmdAuthAddOauth = &cli.Command{
Name: "add-oauth",
Usage: "Add new Oauth authentication source",
Action: runAddOauth,
Flags: oauthCLIFlags,
}
microcmdAuthUpdateOauth = &cli.Command{
Name: "update-oauth",
Usage: "Update existing Oauth authentication source",
Action: runUpdateOauth,
Flags: append(oauthCLIFlags[:1], append([]cli.Flag{idFlag}, oauthCLIFlags[1:]...)...),
}
)
func parseOAuth2Config(c *cli.Context) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if c.IsSet("use-custom-urls") {
customURLMapping = &oauth2.CustomURLMapping{
TokenURL: c.String("custom-token-url"),
AuthURL: c.String("custom-auth-url"),
ProfileURL: c.String("custom-profile-url"),
EmailURL: c.String("custom-email-url"),
Tenant: c.String("custom-tenant-id"),
}
} else {
customURLMapping = nil
}
return &oauth2.Source{
Provider: c.String("provider"),
ClientID: c.String("key"),
ClientSecret: c.String("secret"),
OpenIDConnectAutoDiscoveryURL: c.String("auto-discover-url"),
CustomURLMapping: customURLMapping,
IconURL: c.String("icon-url"),
SkipLocalTwoFA: c.Bool("skip-local-2fa"),
Scopes: c.StringSlice("scopes"),
RequiredClaimName: c.String("required-claim-name"),
RequiredClaimValue: c.String("required-claim-value"),
RequiredClaimValues: c.StringSlice("required-claim-values"),
GroupClaimName: c.String("group-claim-name"),
AdminGroup: c.String("admin-group"),
RestrictedGroup: c.String("restricted-group"),
GroupTeamMap: c.String("group-team-map"),
GroupTeamMapRemoval: c.Bool("group-team-map-removal"),
}
}
func runAddOauth(c *cli.Context) error {
secret, selected, err := oauthSecretInput(c)
if err != nil {
return err
}
ctx, cancel := installSignals()
defer cancel()
ctx = db.WithSensitiveSQL(ctx)
if err := initDB(ctx); err != nil {
return err
}
if db.SQLLoggingEnabled(ctx) {
return errors.New("OAuth configuration requires SQL logging disabled")
}
config := parseOAuth2Config(c)
if selected {
config.ClientSecret = secret
}
if err := oauth2.ValidateRequiredClaimValues(config.RequiredClaimName, config.RequiredClaimValue, config.RequiredClaimValues); err != nil {
return err
}
if config.RequiredClaimValues != nil && config.Provider != "openidConnect" {
return errors.New("required-claim-values requires openidConnect")
}
if config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
return fmt.Errorf("invalid Auto Discovery URL: %s (this must be a valid URL starting with http:// or https://)", config.OpenIDConnectAutoDiscoveryURL)
}
}
err = auth_model.CreateSource(ctx, &auth_model.Source{
Type: auth_model.OAuth2,
Name: c.String("name"),
IsActive: true,
Cfg: config,
})
if err != nil && c.IsSet("secret-file") {
return errors.New("OAuth source creation failed")
}
return err
}
func runUpdateOauth(c *cli.Context) error {
secret, selected, err := oauthSecretInput(c)
if err != nil {
return err
}
if !c.IsSet("id") {
return errors.New("--id flag is missing")
}
ctx, cancel := installSignals()
defer cancel()
ctx = db.WithSensitiveSQL(ctx)
if err := initDB(ctx); err != nil {
return err
}
if db.SQLLoggingEnabled(ctx) {
return errors.New("OAuth configuration requires SQL logging disabled")
}
source, err := auth_model.GetSourceByID(ctx, c.Int64("id"))
if err != nil {
return err
}
oAuth2Config, ok := source.Cfg.(*oauth2.Source)
if !ok {
return errors.New("authentication source is not OAuth2")
}
if c.IsSet("name") {
source.Name = c.String("name")
}
if c.IsSet("provider") {
oAuth2Config.Provider = c.String("provider")
}
if c.IsSet("key") {
oAuth2Config.ClientID = c.String("key")
}
if selected {
oAuth2Config.ClientSecret = secret
}
if c.IsSet("auto-discover-url") {
oAuth2Config.OpenIDConnectAutoDiscoveryURL = c.String("auto-discover-url")
}
if c.IsSet("icon-url") {
oAuth2Config.IconURL = c.String("icon-url")
}
if c.IsSet("scopes") {
oAuth2Config.Scopes = c.StringSlice("scopes")
}
if c.IsSet("required-claim-name") {
oAuth2Config.RequiredClaimName = c.String("required-claim-name")
}
if c.IsSet("required-claim-value") {
oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
}
if c.IsSet("required-claim-values") {
oAuth2Config.RequiredClaimValues = c.StringSlice("required-claim-values")
}
if c.IsSet("group-claim-name") {
oAuth2Config.GroupClaimName = c.String("group-claim-name")
}
if c.IsSet("admin-group") {
oAuth2Config.AdminGroup = c.String("admin-group")
}
if c.IsSet("restricted-group") {
oAuth2Config.RestrictedGroup = c.String("restricted-group")
}
if c.IsSet("group-team-map") {
oAuth2Config.GroupTeamMap = c.String("group-team-map")
}
if c.IsSet("group-team-map-removal") {
oAuth2Config.GroupTeamMapRemoval = c.Bool("group-team-map-removal")
}
// update custom URL mapping
customURLMapping := &oauth2.CustomURLMapping{}
if oAuth2Config.CustomURLMapping != nil {
customURLMapping.TokenURL = oAuth2Config.CustomURLMapping.TokenURL
customURLMapping.AuthURL = oAuth2Config.CustomURLMapping.AuthURL
customURLMapping.ProfileURL = oAuth2Config.CustomURLMapping.ProfileURL
customURLMapping.EmailURL = oAuth2Config.CustomURLMapping.EmailURL
customURLMapping.Tenant = oAuth2Config.CustomURLMapping.Tenant
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-token-url") {
customURLMapping.TokenURL = c.String("custom-token-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-auth-url") {
customURLMapping.AuthURL = c.String("custom-auth-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-profile-url") {
customURLMapping.ProfileURL = c.String("custom-profile-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-email-url") {
customURLMapping.EmailURL = c.String("custom-email-url")
}
if c.IsSet("use-custom-urls") && c.IsSet("custom-tenant-id") {
customURLMapping.Tenant = c.String("custom-tenant-id")
}
oAuth2Config.CustomURLMapping = customURLMapping
if err := oauth2.ValidateRequiredClaimValues(oAuth2Config.RequiredClaimName, oAuth2Config.RequiredClaimValue, oAuth2Config.RequiredClaimValues); err != nil {
return err
}
if oAuth2Config.RequiredClaimValues != nil && oAuth2Config.Provider != "openidConnect" {
return errors.New("required-claim-values requires openidConnect")
}
source.Cfg = oAuth2Config
err = auth_model.UpdateSource(ctx, source)
if err != nil && c.IsSet("secret-file") {
return errors.New("OAuth source update failed")
}
return err
}
// Resolve before opening the DB or mutating a loaded source. Never store the
// resolved bytes in the CLI context: they must not become flag/argv diagnostics.
func oauthSecretInput(c *cli.Context) (string, bool, error) {
if c.IsSet("secret-file") {
if c.IsSet("secret") {
return "", false, errors.New("secret and secret-file are mutually exclusive")
}
value, err := readOAuthSecretFile(c.String("secret-file"))
return value, true, err
}
return c.String("secret"), c.IsSet("secret"), nil
}

View file

@ -0,0 +1,114 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build linux
package cmd
import (
"errors"
"io"
"os"
"path/filepath"
"strings"
"golang.org/x/sys/unix"
)
var errOAuthSecretFile = errors.New("secret-file input refused")
// This checks DAC and mount properties, not namespace, Secret provenance or
// operation authority. Admission must bind the exact workload/mount/reader set.
// Kubernetes Secret subPath leaves are root-owned; fsGroup can provide 0440
// access. Requiring euid ownership would incorrectly refuse that supported case.
func readOAuthSecretFile(path string) (string, error) {
if len(path) > 4096 || !filepath.IsAbs(path) || filepath.Clean(path) != path || strings.ContainsRune(path, 0) {
return "", errOAuthSecretFile
}
parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
if len(parts) < 2 {
return "", errOAuthSecretFile
}
dir, err := unix.Open("/", unix.O_PATH|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
if err != nil {
return "", errOAuthSecretFile
}
defer func() { unix.Close(dir) }()
for _, part := range parts[:len(parts)-1] {
var st unix.Stat_t
if unix.Fstat(dir, &st) != nil || !safeOAuthSecretParent(st) {
return "", errOAuthSecretFile
}
next, err := unix.Openat(dir, part, unix.O_PATH|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
if err != nil {
return "", errOAuthSecretFile
}
unix.Close(dir)
dir = next
}
var parent unix.Stat_t
if unix.Fstat(dir, &parent) != nil || !safeOAuthSecretParent(parent) {
return "", errOAuthSecretFile
}
// Nonblocking is necessary: reject a FIFO without waiting for a writer.
fd, err := unix.Openat(dir, parts[len(parts)-1], unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
if err != nil {
return "", errOAuthSecretFile
}
f := os.NewFile(uintptr(fd), "private OAuth input")
defer f.Close()
return readOAuthSecretFD(f, dir, parts[len(parts)-1])
}
func readOAuthSecretFD(f *os.File, dir int, leaf string) (string, error) {
fd := int(f.Fd())
var before, after, named unix.Stat_t
var mount unix.Statfs_t
if unix.Fstat(fd, &before) != nil || !safeOAuthSecretLeaf(before) || unix.Fstatfs(fd, &mount) != nil || mount.Flags&unix.ST_RDONLY == 0 {
return "", errOAuthSecretFile
}
// mint-secret uses secrets.token_urlsafe(48): exactly 64 ASCII characters,
// no padding/newline. Bounded read still detects a concurrent size increase.
data, err := io.ReadAll(io.LimitReader(f, 65))
defer clear(data)
if err != nil || len(data) != 64 {
return "", errOAuthSecretFile
}
for _, b := range data {
if !(b >= 'A' && b <= 'Z' || b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b == '_') {
return "", errOAuthSecretFile
}
}
if unix.Fstat(fd, &after) != nil || !sameOAuthSecretFile(before, after) || unix.Fstatat(dir, leaf, &named, unix.AT_SYMLINK_NOFOLLOW) != nil || !sameOAuthSecretFile(before, named) {
return "", errOAuthSecretFile
}
return string(data), nil
}
func safeOAuthSecretParent(st unix.Stat_t) bool {
return st.Mode&unix.S_IFMT == unix.S_IFDIR && (st.Uid == 0 || st.Uid == uint32(os.Geteuid())) && st.Mode&0022 == 0
}
func safeOAuthSecretLeaf(st unix.Stat_t) bool {
if st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 || st.Size != 64 || st.Mode&07337 != 0 || (st.Uid != 0 && st.Uid != uint32(os.Geteuid())) {
return false
}
if st.Mode&0040 != 0 {
groups, err := os.Getgroups()
if err != nil {
return false
}
member := st.Gid == uint32(os.Getegid())
for _, gid := range groups {
member = member || st.Gid == uint32(gid)
}
if !member {
return false
}
}
return true
}
func sameOAuthSecretFile(a, b unix.Stat_t) bool {
return a.Dev == b.Dev && a.Ino == b.Ino && a.Mode == b.Mode && a.Nlink == b.Nlink && a.Uid == b.Uid && a.Gid == b.Gid && a.Size == b.Size && a.Mtim == b.Mtim && a.Ctim == b.Ctim
}

View file

@ -0,0 +1,79 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build linux
package cmd
import (
"golang.org/x/sys/unix"
"os"
"testing"
)
func TestPrivateSecretMetadata(t *testing.T) {
st := unix.Stat_t{Mode: unix.S_IFREG | 0440, Uid: 0, Gid: uint32(os.Getegid()), Nlink: 1, Size: 64}
if !safeOAuthSecretLeaf(st) {
t.Fatal("root-owned fsGroup-readable Secret subPath refused")
}
for _, mode := range []uint32{0444, 0640, 0540, 0460, 0441, 0440 | unix.S_ISUID, 0440 | unix.S_ISGID, 0440 | unix.S_ISVTX} {
x := st
x.Mode = unix.S_IFREG | mode
if safeOAuthSecretLeaf(x) {
t.Fatalf("unsafe mode accepted: %o", mode)
}
}
for _, mutate := range []func(*unix.Stat_t){func(x *unix.Stat_t) { x.Uid = 123456 }, func(x *unix.Stat_t) { x.Nlink = 2 }, func(x *unix.Stat_t) { x.Size = 63 }, func(x *unix.Stat_t) { x.Mode = unix.S_IFIFO | 0400 }, func(x *unix.Stat_t) { x.Gid = 123456 }} {
x := st
mutate(&x)
if safeOAuthSecretLeaf(x) {
t.Fatal("unsafe metadata accepted")
}
}
if !safeOAuthSecretParent(unix.Stat_t{Mode: unix.S_IFDIR | 0755, Uid: 0}) {
t.Fatal("root parent refused")
}
if safeOAuthSecretParent(unix.Stat_t{Mode: unix.S_IFDIR | 0775, Uid: 0}) {
t.Fatal("group writable parent accepted")
}
for _, mutate := range []func(*unix.Stat_t){func(x *unix.Stat_t) { x.Dev++ }, func(x *unix.Stat_t) { x.Ino++ }, func(x *unix.Stat_t) { x.Ctim.Nsec++ }, func(x *unix.Stat_t) { x.Mtim.Nsec++ }, func(x *unix.Stat_t) { x.Size++ }, func(x *unix.Stat_t) { x.Uid++ }, func(x *unix.Stat_t) { x.Gid++ }, func(x *unix.Stat_t) { x.Nlink++ }, func(x *unix.Stat_t) { x.Mode++ }} {
x := st
mutate(&x)
if sameOAuthSecretFile(st, x) {
t.Fatal("changed file considered stable")
}
}
x := st
x.Atim.Nsec++
if !sameOAuthSecretFile(st, x) {
t.Fatal("read-induced atime change refused")
}
}
// Actual Linux descriptors and namespace read-only bind mounts, not mocked stat
// metadata. A writable alias lets the fixture simulate replacement after open.
func TestPrivateSecretOpenedFileReplacement(t *testing.T) {
if _, err := os.Stat("/run/forgejo-oidc/race"); os.IsNotExist(err) {
t.Skip("requires isolated read-only bind fixture")
}
dir, err := unix.Open("/run/forgejo-oidc", unix.O_PATH|unix.O_DIRECTORY, 0)
if err != nil {
t.Fatal(err)
}
defer unix.Close(dir)
f, err := os.Open("/run/forgejo-oidc/race")
if err != nil {
t.Fatal(err)
}
defer f.Close()
// Preserve the opened inode with one link; replace only the named leaf.
if err := os.Rename("/run/writable-input/race", "/run/writable-input/race-backup"); err != nil {
t.Fatal(err)
}
if err := os.Rename("/run/writable-input/race-replacement", "/run/writable-input/race"); err != nil {
t.Fatal(err)
}
if value, err := readOAuthSecretFD(f, dir, "race"); err != errOAuthSecretFile || value != "" {
t.Fatal("replaced leaf accepted")
}
}

View file

@ -0,0 +1,12 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
//go:build !linux
package cmd
import "errors"
func readOAuthSecretFile(string) (string, error) {
return "", errors.New("secret-file input refused")
}

View file

@ -0,0 +1,265 @@
module forgejo.org
go 1.24
toolchain go1.24.3
require (
code.forgejo.org/f3/gof3/v3 v3.10.6
code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251
code.forgejo.org/forgejo/levelqueue v1.0.0
code.forgejo.org/forgejo/reply v1.0.2
code.forgejo.org/go-chi/binding v1.0.0
code.forgejo.org/go-chi/cache v1.0.0
code.forgejo.org/go-chi/captcha v1.0.1
code.forgejo.org/go-chi/session v1.0.1
code.gitea.io/actions-proto-go v0.4.0
code.gitea.io/sdk/gitea v0.20.0
codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570
connectrpc.com/connect v1.17.0
github.com/42wim/httpsig v1.2.2
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
github.com/ProtonMail/go-crypto v1.1.6
github.com/PuerkitoBio/goquery v1.10.2
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.2
github.com/alecthomas/chroma/v2 v2.15.0
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
github.com/blevesearch/bleve/v2 v2.5.2
github.com/buildkite/terminal-to-html/v3 v3.16.8
github.com/caddyserver/certmagic v0.22.2
github.com/chi-middleware/proxy v1.1.1
github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707
github.com/dustin/go-humanize v1.0.1
github.com/editorconfig/editorconfig-core-go/v2 v2.6.3
github.com/emersion/go-imap v1.2.1
github.com/felixge/fgprof v0.9.5
github.com/fsnotify/fsnotify v1.8.0
github.com/gliderlabs/ssh v0.3.8
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.2.0
github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.9.2
github.com/go-git/go-git/v5 v5.13.2
github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-openapi/spec v0.20.14
github.com/go-sql-driver/mysql v1.9.1
github.com/go-webauthn/webauthn v0.12.2
github.com/gobwas/glob v0.2.3
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f
github.com/gogs/go-gogs-client v0.0.0-20210131175652-1d7215cd8d85
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/go-github/v64 v64.0.0
github.com/google/pprof v0.0.0-20241017200806-017d972448fc
github.com/google/uuid v1.6.0
github.com/gorilla/feeds v1.2.0
github.com/gorilla/sessions v1.4.0
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/huandu/xstrings v1.5.0
github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056
github.com/jhillyerd/enmime/v2 v2.1.0
github.com/json-iterator/go v1.1.12
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.17.11
github.com/klauspost/cpuid/v2 v2.2.10
github.com/lib/pq v1.10.9
github.com/markbates/goth v1.80.0
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-sqlite3 v1.14.28
github.com/meilisearch/meilisearch-go v0.31.0
github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.27
github.com/minio/minio-go/v7 v7.0.88
github.com/msteinert/pam/v2 v2.1.0
github.com/nektos/act v0.2.52
github.com/niklasfasching/go-org v1.7.0
github.com/olivere/elastic/v7 v7.0.32
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.21.1
github.com/redis/go-redis/v9 v9.7.3
github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
github.com/sassoftware/go-rpmutils v0.4.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
github.com/stretchr/testify v1.10.0
github.com/syndtr/goleveldb v1.0.0
github.com/ulikunitz/xz v0.5.12
github.com/urfave/cli/v2 v2.27.6
github.com/valyala/fastjson v1.6.4
github.com/yohcop/openid-go v1.0.1
github.com/yuin/goldmark v1.7.8
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
gitlab.com/gitlab-org/api/client-go v0.126.0
go.uber.org/mock v0.5.0
golang.org/x/crypto v0.36.0
golang.org/x/image v0.25.0
golang.org/x/net v0.38.0
golang.org/x/oauth2 v0.28.0
golang.org/x/sync v0.12.0
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0
google.golang.org/protobuf v1.36.4
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.5.0
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.9
)
require (
cloud.google.com/go/compute/metadata v0.6.0 // indirect
dario.cat/mergo v1.0.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/RoaringBitmap/roaring/v2 v2.4.5 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/blevesearch/bleve_index_api v1.2.8 // indirect
github.com/blevesearch/geo v0.2.3 // indirect
github.com/blevesearch/go-faiss v1.0.25 // indirect
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
github.com/blevesearch/gtreap v0.1.1 // indirect
github.com/blevesearch/mmap-go v1.0.4 // indirect
github.com/blevesearch/scorch_segment_api/v2 v2.3.10 // indirect
github.com/blevesearch/segment v0.9.1 // indirect
github.com/blevesearch/snowballstem v0.9.0 // indirect
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
github.com/blevesearch/vellum v1.1.0 // indirect
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
github.com/blevesearch/zapx/v16 v16.2.4 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-enry/go-oniguruma v1.2.1 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/swag v0.22.7 // indirect
github.com/go-webauthn/x v0.1.20 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.3 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/libdns/libdns v0.2.3 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/markbates/going v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mholt/acmez/v3 v3.1.1 // indirect
github.com/miekg/dns v1.1.63 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
github.com/mschoch/smat v0.2.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nwaples/rardecode v1.1.3 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rhysd/actionlint v1.6.27 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/zeebo/assert v1.3.0 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.etcd.io/bbolt v1.4.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/time v0.10.0 // indirect
golang.org/x/tools v0.31.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.25.1
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1
replace github.com/gliderlabs/ssh => code.forgejo.org/forgejo/ssh v0.0.0-20241211213324-5fc306ca0616
replace git.sr.ht/~mariusor/go-xsd-duration => code.forgejo.org/forgejo/go-xsd-duration v0.0.0-20220703122237-02e73435a078
require github.com/netkingdom/access-engine v0.0.0
replace github.com/netkingdom/access-engine => ./third_party/access-engine-pep
require keycape v0.0.0
replace keycape => ./third_party/keycape-tokenstatus

View file

@ -0,0 +1,430 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package auth
// This owner-confined database primitive is NOT an authorization boundary.
// No CLI/web/API route exposes it. The admitted runtime must separately obtain
// fresh authenticated apply/inverse sessions and retain signed public evidence.
// Configurations and private journal rows must never leave database custody.
import (
"context"
"encoding/json"
"errors"
"reflect"
"regexp"
"sort"
"forgejo.org/models/db"
"forgejo.org/modules/setting"
"forgejo.org/modules/timeutil"
"xorm.io/xorm"
)
var (
ErrSourceOperationConflict = errors.New("source operation requires owner reconciliation")
ErrSourceOperationStorage = errors.New("source operation storage failure; reconcile before retry")
ErrSourceOperationUnsupported = errors.New("source operation requires standalone SQLite/PostgreSQL transaction with SQL logging disabled")
operationIDPattern = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._:-]{7,127}$`)
)
// Private source rows deliberately bypass Config decoding: decoding and
// reserializing can erase unknown fields, including a gate on an older image.
type sourceOperationRow struct {
ID int64 `xorm:"pk autoincr"`
Type Type
Name string
IsActive bool
IsSyncEnabled bool
Cfg string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp
UpdatedUnix timeutil.TimeStamp
}
func (sourceOperationRow) TableName() string { return "login_source" }
// The whole inventory, including private configs, stays in the owner's database.
// Neither journal payload nor its digest is a public result.
type sourceOperationJournal struct {
OperationID string `xorm:"pk VARCHAR(128)"`
CaseID string `xorm:"VARCHAR(128)"`
TargetName string
TargetID int64
Phase string
BeforeState string `xorm:"TEXT"`
DesiredState string `xorm:"TEXT"`
PostState string `xorm:"TEXT"`
}
func (sourceOperationJournal) TableName() string { return "login_source_operation" }
func init() { db.RegisterModel(new(sourceOperationJournal)) }
// SourceOperationResult contains only public outcome metadata. An authenticated
// session/evidence producer must bind this result to its actual principal,
// admitted source revision, exact parameters and current operation session.
type SourceOperationResult struct {
OperationID string
Phase string
SourceID int64
}
// sourceOperationTx acquires a database-enforced lock before any source read.
// SQLite's first write serializes every database writer, including stock admin
// CRUD. PostgreSQL table locks exclude ordinary SQL source and user-link writes.
// Privileged DDL, process/provider refresh and release writers are outside this
// bounded primitive; they still require owner runtime/release exclusion.
func sourceOperationTx(ctx context.Context, f func(context.Context) error) error {
if db.InTransaction(ctx) || setting.Database.LogSQL || (!setting.Database.Type.IsSQLite3() && !setting.Database.Type.IsPostgreSQL()) {
return ErrSourceOperationUnsupported
}
err := db.WithTx(db.WithSensitiveSQL(ctx), func(tx context.Context) error {
session, ok := db.GetEngine(tx).(*xorm.Session)
if !ok || session.Engine().Logger().IsShowSQL() {
return ErrSourceOperationUnsupported
}
if setting.Database.Type.IsPostgreSQL() {
if _, err := db.GetEngine(tx).Exec(`LOCK TABLE login_source_operation, login_source, "user", external_login_user IN EXCLUSIVE MODE`); err != nil {
return err
}
} else {
// Even with no matching row, UPDATE starts SQLite's write transaction.
if _, err := db.GetEngine(tx).Exec(`UPDATE login_source_operation SET phase = phase WHERE operation_id = ''`); err != nil {
return err
}
}
return f(tx)
})
if err == nil || errors.Is(err, ErrSourceOperationConflict) || errors.Is(err, ErrSourceOperationUnsupported) || IsErrSourceInUse(err) {
return err
}
return ErrSourceOperationStorage
}
func sourceInventory(ctx context.Context) ([]sourceOperationRow, error) {
rows := make([]sourceOperationRow, 0)
err := db.GetEngine(ctx).OrderBy("id").Find(&rows)
return rows, err
}
func encodeSourceInventory(rows []sourceOperationRow) string {
b, _ := json.Marshal(rows)
return string(b)
}
func decodeSourceInventory(s string) ([]sourceOperationRow, error) {
var rows []sourceOperationRow
if err := json.Unmarshal([]byte(s), &rows); err != nil {
return nil, ErrSourceOperationConflict
}
return rows, nil
}
func sourceJournal(ctx context.Context, operationID string) (*sourceOperationJournal, error) {
row := new(sourceOperationJournal)
found, err := db.GetEngine(ctx).ID(operationID).Get(row)
if err != nil {
return nil, err
}
if !found {
return nil, ErrSourceOperationConflict
}
return row, nil
}
func journalResult(j *sourceOperationJournal) SourceOperationResult {
return SourceOperationResult{OperationID: j.OperationID, Phase: j.Phase, SourceID: j.TargetID}
}
// PrepareSourceOperation durably captures exact source identity and complete
// original inventory before mutation. desired is private owner input, not an
// authorization token. targetID=0 requires authoritative name absence. Existing
// sources require both exact ID and name. Reuse of an operation ID is refused.
func PrepareSourceOperation(ctx context.Context, operationID, caseID string, targetID int64, desired *Source) (SourceOperationResult, error) {
var result SourceOperationResult
if !operationIDPattern.MatchString(operationID) || !operationIDPattern.MatchString(caseID) || desired == nil || desired.Type != OAuth2 || desired.Name == "" || desired.Cfg == nil || desired.IsSyncEnabled || desired.ID != targetID {
return result, ErrSourceOperationConflict
}
cfg, err := desired.Cfg.ToDB()
if err != nil || len(cfg) > 64*1024 || !json.Valid(cfg) {
return result, ErrSourceOperationConflict
}
candidate := sourceOperationRow{ID: targetID, Type: OAuth2, Name: desired.Name, IsActive: desired.IsActive, Cfg: string(cfg)}
candidateBytes, _ := json.Marshal(candidate)
err = sourceOperationTx(ctx, func(tx context.Context) error {
// Bound confidential duplicate custody. Refuse rather than erase journals
// with pending recovery or silently expire terminal private snapshots.
count, err := db.GetEngine(tx).Count(new(sourceOperationJournal))
if err != nil {
return err
}
if count >= 256 {
return ErrSourceOperationConflict
}
active, err := db.GetEngine(tx).Where("phase IN (?, ?, ?)", "prepared", "applied", "unchanged").Count(new(sourceOperationJournal))
if err != nil {
return err
}
if active >= 64 {
return ErrSourceOperationConflict
}
found, err := db.GetEngine(tx).ID(operationID).Exist(new(sourceOperationJournal))
if err != nil {
return err
}
if found {
return ErrSourceOperationConflict
}
shared, err := db.GetEngine(tx).Where("target_name = ? AND phase IN (?, ?, ?)", desired.Name, "prepared", "applied", "unchanged").Exist(new(sourceOperationJournal))
if err != nil {
return err
}
if shared {
return ErrSourceOperationConflict
}
rows, err := sourceInventory(tx)
if err != nil {
return err
}
if len(encodeSourceInventory(rows)) > 1024*1024 {
return ErrSourceOperationConflict
}
matched := false
for _, row := range rows {
if row.Name == desired.Name || row.ID == targetID {
if targetID == 0 || row.ID != targetID || row.Name != desired.Name || row.Type != OAuth2 {
return ErrSourceOperationConflict
}
matched = true
}
}
if targetID != 0 && !matched {
return ErrSourceOperationConflict
}
j := &sourceOperationJournal{OperationID: operationID, CaseID: caseID, TargetName: desired.Name, TargetID: targetID, Phase: "prepared", BeforeState: encodeSourceInventory(rows), DesiredState: string(candidateBytes)}
if _, err := db.GetEngine(tx).Insert(j); err != nil {
return err
}
result = journalResult(j)
return nil
})
if err != nil {
return SourceOperationResult{}, err
}
return result, nil
}
// ApplySourceOperation atomically compares every original raw source row,
// mutates exactly the target and persists confirmed post-state. A lost commit
// acknowledgement is reconciled using the same operation ID, never a fresh ID.
// No source registration/network call occurs inside the database transaction.
func ApplySourceOperation(ctx context.Context, operationID, caseID string) (SourceOperationResult, error) {
var result SourceOperationResult
err := sourceOperationTx(ctx, func(tx context.Context) error {
j, err := sourceJournal(tx, operationID)
if err != nil {
return err
}
if j.CaseID != caseID {
return ErrSourceOperationConflict
}
rows, err := sourceInventory(tx)
if err != nil {
return err
}
if j.Phase == "applied" || j.Phase == "unchanged" {
if encodeSourceInventory(rows) != j.PostState {
return ErrSourceOperationConflict
}
result = journalResult(j)
return nil
}
if j.Phase != "prepared" || encodeSourceInventory(rows) != j.BeforeState {
return ErrSourceOperationConflict
}
var desired sourceOperationRow
if json.Unmarshal([]byte(j.DesiredState), &desired) != nil {
return ErrSourceOperationConflict
}
unchanged := false
for _, row := range rows {
if row.ID == j.TargetID {
desired.CreatedUnix = row.CreatedUnix
desired.UpdatedUnix = row.UpdatedUnix
unchanged = reflect.DeepEqual(row, desired)
}
}
if !unchanged {
desired.UpdatedUnix = timeutil.TimeStampNow()
if j.TargetID == 0 {
desired.CreatedUnix = desired.UpdatedUnix
if _, err := db.GetEngine(tx).NoAutoTime().Insert(&desired); err != nil {
return err
}
j.TargetID = desired.ID
} else {
n, err := db.GetEngine(tx).ID(j.TargetID).NoAutoTime().AllCols().Update(&desired)
if err != nil {
return err
}
if n != 1 {
return ErrSourceOperationConflict
}
}
}
post, err := sourceInventory(tx)
if err != nil {
return err
}
expected := append([]sourceOperationRow{}, rows...)
if !unchanged {
replaced := false
for i := range expected {
if expected[i].ID == desired.ID {
expected[i] = desired
replaced = true
}
}
if !replaced {
expected = append(expected, desired)
}
sort.Slice(expected, func(i, k int) bool { return expected[i].ID < expected[k].ID })
}
if !reflect.DeepEqual(post, expected) {
return ErrSourceOperationConflict
}
j.PostState = encodeSourceInventory(post)
j.Phase = "applied"
if unchanged {
j.Phase = "unchanged"
}
if _, err := db.GetEngine(tx).ID(j.OperationID).AllCols().Update(j); err != nil {
return err
}
result = journalResult(j)
return nil
})
if err != nil {
return SourceOperationResult{}, err
}
return result, nil
}
// InverseSourceOperation requires fresh external inverse authority on EACH call.
// It restores a preexisting source in place; only confirmed case creation can
// delete. Any linked local/external account refuses deletion without alteration.
func InverseSourceOperation(ctx context.Context, operationID, caseID string) (SourceOperationResult, error) {
var result SourceOperationResult
err := sourceOperationTx(ctx, func(tx context.Context) error {
j, err := sourceJournal(tx, operationID)
if err != nil {
return err
}
if j.CaseID != caseID {
return ErrSourceOperationConflict
}
rows, err := sourceInventory(tx)
if err != nil {
return err
}
if j.Phase == "reverted" || j.Phase == "cancelled" {
if encodeSourceInventory(rows) != j.BeforeState {
return ErrSourceOperationConflict
}
result = journalResult(j)
return nil
}
if j.Phase == "prepared" {
if encodeSourceInventory(rows) != j.BeforeState {
return ErrSourceOperationConflict
}
j.Phase = "cancelled"
} else {
if (j.Phase != "applied" && j.Phase != "unchanged") || encodeSourceInventory(rows) != j.PostState {
return ErrSourceOperationConflict
}
before, err := decodeSourceInventory(j.BeforeState)
if err != nil {
return err
}
var original *sourceOperationRow
for i := range before {
if before[i].ID == j.TargetID {
original = &before[i]
}
}
if original != nil {
if _, err := db.GetEngine(tx).ID(j.TargetID).NoAutoTime().AllCols().Update(original); err != nil {
return err
}
} else {
for _, table := range []struct{ name, column string }{{"user", "login_source"}, {"external_login_user", "login_source_id"}} {
count, err := db.GetEngine(tx).Table(table.name).Where(table.column+" = ?", j.TargetID).Count()
if err != nil {
return err
}
if count != 0 {
return ErrSourceInUse{ID: j.TargetID}
}
}
n, err := db.GetEngine(tx).ID(j.TargetID).Delete(new(sourceOperationRow))
if err != nil {
return err
}
if n != 1 {
return ErrSourceOperationConflict
}
}
restored, err := sourceInventory(tx)
if err != nil {
return err
}
if encodeSourceInventory(restored) != j.BeforeState {
return ErrSourceOperationConflict
}
j.Phase = "reverted"
}
if _, err := db.GetEngine(tx).ID(j.OperationID).AllCols().Update(j); err != nil {
return err
}
result = journalResult(j)
return nil
})
if err != nil {
return SourceOperationResult{}, err
}
return result, nil
}
// ReconcileSourceOperation only checks committed journal/current equality. It
// never infers that a prepared intent ran and never retries the mutation. The
// separately authenticated runtime must call this after an ambiguous result.
func ReconcileSourceOperation(ctx context.Context, operationID, caseID string) (SourceOperationResult, error) {
var result SourceOperationResult
err := sourceOperationTx(ctx, func(tx context.Context) error {
j, err := sourceJournal(tx, operationID)
if err != nil {
return err
}
if j.CaseID != caseID {
return ErrSourceOperationConflict
}
rows, err := sourceInventory(tx)
if err != nil {
return err
}
expected := j.BeforeState
switch j.Phase {
case "prepared", "reverted", "cancelled":
case "applied", "unchanged":
expected = j.PostState
default:
return ErrSourceOperationConflict
}
if encodeSourceInventory(rows) != expected {
return ErrSourceOperationConflict
}
result = journalResult(j)
return nil
})
if err != nil {
return SourceOperationResult{}, err
}
return result, nil
}

View file

@ -0,0 +1,87 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package auth
import (
"context"
"testing"
"time"
"forgejo.org/models/db"
"xorm.io/xorm"
)
func TestSourceOperationDatabaseWriterExclusion(t *testing.T) {
// Use DEFERRED transaction mode in the managed engine as well, so this
// test detects removal of the primitive's explicit first write.
previousContext := db.DefaultContext
previousEngine, ok := db.GetEngine(previousContext).(*xorm.Engine)
if !ok {
t.Fatal("missing actual fixture engine")
}
managed, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=deferred")
if err != nil {
t.Fatal(err)
}
db.SetDefaultEngine(context.Background(), managed)
defer func() { db.SetDefaultEngine(previousContext, previousEngine); managed.Close() }()
// A second actual connection executes uncooperative raw SQL, so this checks
// database exclusion rather than an advisory-lock convention.
raw, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=deferred")
if err != nil {
t.Fatal(err)
}
defer raw.Close()
locked := make(chan struct{})
release := make(chan struct{})
done := make(chan error, 1)
go func() {
done <- sourceOperationTx(db.DefaultContext, func(ctx context.Context) error { close(locked); <-release; return nil })
}()
select {
case <-locked:
case err := <-done:
t.Fatalf("lock failed: %v", err)
case <-time.After(5 * time.Second):
t.Fatal("lock timed out")
}
writeDone := make(chan error, 1)
go func() { _, err := raw.Exec("UPDATE login_source SET name = name WHERE id = -123"); writeDone <- err }()
select {
case err := <-writeDone:
close(release)
<-done
t.Fatalf("uncooperative write escaped exclusion: %v", err)
case <-time.After(100 * time.Millisecond):
}
close(release)
select {
case err := <-done:
if err != nil {
t.Fatal(err)
}
case <-time.After(5 * time.Second):
t.Fatal("transaction did not finish")
}
select {
case err := <-writeDone:
if err != nil {
t.Fatal(err)
}
case <-time.After(5 * time.Second):
t.Fatal("writer did not resume")
}
}
func TestSourceOperationRefusesActualXORMArgumentLogging(t *testing.T) {
engine, ok := db.GetEngine(db.DefaultContext).(*xorm.Engine)
if !ok {
t.Fatal("missing actual engine")
}
engine.ShowSQL(true)
defer engine.ShowSQL(false)
called := false
err := sourceOperationTx(db.DefaultContext, func(context.Context) error { called = true; return nil })
if err != ErrSourceOperationUnsupported || called {
t.Fatal("actual SQL argument logger did not fail closed")
}
}

View file

@ -0,0 +1,7 @@
//go:build source_operation_postgres
package auth
// Test-only seam: the production transaction body still obtains its real locks
// and private query context. No runtime API or authority callback is added.
var SourceOperationPostgresTransactionForTest = sourceOperationTx

View file

@ -0,0 +1,369 @@
//go:build source_operation_postgres
package auth_test
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"runtime/trace"
"strings"
"sync"
"testing"
"time"
auth "forgejo.org/models/auth"
"forgejo.org/models/db"
user_model "forgejo.org/models/user"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
oauth2 "forgejo.org/services/auth/source/oauth2"
"github.com/lib/pq"
"github.com/stretchr/testify/require"
"xorm.io/xorm"
)
type postgresOperationLog struct {
log.Logger
mu sync.Mutex
entries []string
}
func (l *postgresOperationLog) Log(_ int, _ log.Level, format string, values ...any) {
l.mu.Lock()
defer l.mu.Unlock()
l.entries = append(l.entries, fmt.Sprintf(format, values...))
}
func (l *postgresOperationLog) take() string {
l.mu.Lock()
defer l.mu.Unlock()
result := strings.Join(l.entries, "\n")
l.entries = nil
return result
}
// The real Forgejo engine/driver, registered model schemas and public operation
// functions run. Only the database, application configuration and actors are
// synthetic. No deployed owner database or authenticated operation is claimed.
func postgresOperationDB(t *testing.T, schema string) (context.Context, *xorm.Engine, *xorm.Engine) {
t.Helper()
require.Equal(t, "/run/source-operation-postgres", os.Getenv("CUSTODIAN_PG_SOCKET"), "explicit isolated socket required")
oldSettings, oldContext := setting.Database, db.DefaultContext
oldEngine, ok := db.GetEngine(oldContext).(*xorm.Engine)
require.True(t, ok)
expectedSchema := schema
if expectedSchema == "" {
expectedSchema = "public"
}
raw, err := xorm.NewEngine("postgres", "host=/run/source-operation-postgres user=custodian_test dbname=custodian_source sslmode=disable search_path="+expectedSchema)
require.NoError(t, err)
_, err = raw.Exec(`CREATE SCHEMA IF NOT EXISTS "` + expectedSchema + `"`)
require.NoError(t, err)
setting.Database.Type = "postgres"
setting.Database.Host = "/run/source-operation-postgres"
setting.Database.Name = "custodian_source"
setting.Database.User = "custodian_test"
setting.Database.Passwd = ""
setting.Database.Schema = schema
setting.Database.SSLMode = "disable"
setting.Database.LogSQL = false
setting.Database.MaxOpenConns, setting.Database.MaxIdleConns = 8, 2
setting.Database.SlowQueryThreshold = 0
require.NoError(t, db.InitEngine(context.Background()))
engine, ok := db.GetEngine(db.DefaultContext).(*xorm.Engine)
require.True(t, ok)
t.Cleanup(func() {
_ = engine.Close()
_ = raw.Close()
setting.Database = oldSettings
db.SetDefaultEngine(oldContext, oldEngine)
})
version, err := engine.Query("SELECT current_setting('server_version_num')::integer / 10000 AS major, current_schema() AS schema")
require.NoError(t, err)
require.Equal(t, "16", string(version[0]["major"]))
require.Equal(t, expectedSchema, string(version[0]["schema"]))
require.NoError(t, db.SyncAllTables())
auth.RegisterTypeConfig(auth.OAuth2, new(oauth2.Source))
return db.DefaultContext, engine, raw
}
func resetPostgresOperations(t *testing.T, engine *xorm.Engine) {
t.Helper()
_, err := engine.Exec(`DROP TRIGGER IF EXISTS custodian_source_rewrite ON login_source`)
require.NoError(t, err)
_, err = engine.Exec(`TRUNCATE login_source_operation, login_source, "user", external_login_user RESTART IDENTITY CASCADE`)
require.NoError(t, err)
}
func TestSourceOperationPostgres(t *testing.T) {
for _, profile := range []struct{ name, schema string }{{"default", ""}, {"public", "public"}, {"named", "custodian_named"}} {
t.Run(profile.name, func(t *testing.T) {
ctx, engine, raw := postgresOperationDB(t, profile.schema)
run := func(name string, f func(*testing.T)) {
t.Run(name, func(t *testing.T) { resetPostgresOperations(t, engine); f(t) })
}
run("create-reconcile-inverse", func(t *testing.T) {
before := operationRows(t, ctx)
_, err := auth.PrepareSourceOperation(ctx, "postgres-create-operation", "postgres-create-case", 0, operationSource())
require.NoError(t, err)
require.Equal(t, before, operationRows(t, ctx))
r, err := auth.ApplySourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
require.NoError(t, err)
require.Equal(t, "applied", r.Phase)
require.Positive(t, r.SourceID)
post := operationRows(t, ctx)
again, err := auth.ApplySourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
require.NoError(t, err)
require.Equal(t, r, again)
require.Equal(t, post, operationRows(t, ctx))
_, err = auth.InverseSourceOperation(ctx, "postgres-create-operation", "postgres-wrong-case")
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
r, err = auth.ReconcileSourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
require.NoError(t, err)
require.Equal(t, "applied", r.Phase)
r, err = auth.InverseSourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
require.NoError(t, err)
require.Equal(t, "reverted", r.Phase)
require.Equal(t, before, operationRows(t, ctx))
_, err = auth.ApplySourceOperation(ctx, "postgres-create-operation", "postgres-create-case")
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
})
run("exact-raw-preexisting-restoration", func(t *testing.T) {
s := operationSource()
require.NoError(t, auth.CreateSource(ctx, s))
_, err := engine.Exec(`UPDATE login_source SET cfg = ? WHERE id = ?`, `{"Provider":"openidConnect","ClientID":"synthetic-client","ClientSecret":"synthetic-private-original","FutureGuard":{"x":true}}`, s.ID)
require.NoError(t, err)
before := operationRows(t, ctx)
s.Cfg.(*oauth2.Source).ClientSecret = "synthetic-private-new"
_, err = auth.PrepareSourceOperation(ctx, "postgres-restore-operation", "postgres-restore-case", s.ID, s)
require.NoError(t, err)
_, err = auth.ApplySourceOperation(ctx, "postgres-restore-operation", "postgres-restore-case")
require.NoError(t, err)
_, err = auth.InverseSourceOperation(ctx, "postgres-restore-operation", "postgres-restore-case")
require.NoError(t, err)
require.Equal(t, before, operationRows(t, ctx))
})
run("unchanged-preexisting-survives", func(t *testing.T) {
s := operationSource()
require.NoError(t, auth.CreateSource(ctx, s))
before := operationRows(t, ctx)
_, err := auth.PrepareSourceOperation(ctx, "postgres-unchanged-operation", "postgres-unchanged-case", s.ID, s)
require.NoError(t, err)
r, err := auth.ApplySourceOperation(ctx, "postgres-unchanged-operation", "postgres-unchanged-case")
require.NoError(t, err)
require.Equal(t, "unchanged", r.Phase)
_, err = auth.InverseSourceOperation(ctx, "postgres-unchanged-operation", "postgres-unchanged-case")
require.NoError(t, err)
require.Equal(t, before, operationRows(t, ctx))
})
for _, stage := range []string{"before-apply", "before-inverse"} {
run("private-drift-"+stage, func(t *testing.T) {
s := operationSource()
require.NoError(t, auth.CreateSource(ctx, s))
_, err := auth.PrepareSourceOperation(ctx, "postgres-drift-operation", "postgres-drift-case", s.ID, s)
require.NoError(t, err)
if stage == "before-inverse" {
_, err = auth.ApplySourceOperation(ctx, "postgres-drift-operation", "postgres-drift-case")
require.NoError(t, err)
}
_, err = engine.Exec(`UPDATE login_source SET cfg = cfg || ' ' WHERE id = ?`, s.ID)
require.NoError(t, err)
before := operationRows(t, ctx)
if stage == "before-apply" {
_, err = auth.ApplySourceOperation(ctx, "postgres-drift-operation", "postgres-drift-case")
} else {
_, err = auth.InverseSourceOperation(ctx, "postgres-drift-operation", "postgres-drift-case")
}
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
require.Equal(t, before, operationRows(t, ctx))
})
}
for _, kind := range []string{"user", "external"} {
run("linked-"+kind+"-refuses-deletion", func(t *testing.T) {
_, err := auth.PrepareSourceOperation(ctx, "postgres-linked-operation", "postgres-linked-case", 0, operationSource())
require.NoError(t, err)
r, err := auth.ApplySourceOperation(ctx, "postgres-linked-operation", "postgres-linked-case")
require.NoError(t, err)
if kind == "user" {
_, err = engine.Insert(&user_model.User{ID: 1, LowerName: "synthetic", Name: "synthetic", Email: "synthetic@invalid.test", LoginSource: r.SourceID})
} else {
_, err = engine.Exec(`INSERT INTO external_login_user (external_id, user_id, login_source_id) VALUES ('synthetic-external', 1, ?)`, r.SourceID)
}
require.NoError(t, err)
before := operationRows(t, ctx)
_, err = auth.InverseSourceOperation(ctx, "postgres-linked-operation", "postgres-linked-case")
require.True(t, auth.IsErrSourceInUse(err))
require.Equal(t, before, operationRows(t, ctx))
var count int64
if kind == "user" {
count, err = engine.Table("user").Where("login_source = ?", r.SourceID).Count()
} else {
count, err = engine.Table("external_login_user").Where("login_source_id = ?", r.SourceID).Count()
}
require.NoError(t, err)
require.EqualValues(t, 1, count)
})
}
run("unexpected-trigger-post-state-rolls-back", func(t *testing.T) {
before := operationRows(t, ctx)
_, err := auth.PrepareSourceOperation(ctx, "postgres-trigger-operation", "postgres-trigger-case", 0, operationSource())
require.NoError(t, err)
_, err = engine.Exec(`CREATE OR REPLACE FUNCTION custodian_rewrite_source() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.name := 'unexpected-rewrite'; RETURN NEW; END $$`)
require.NoError(t, err)
_, err = engine.Exec(`CREATE TRIGGER custodian_source_rewrite BEFORE INSERT ON login_source FOR EACH ROW EXECUTE FUNCTION custodian_rewrite_source()`)
require.NoError(t, err)
_, err = auth.ApplySourceOperation(ctx, "postgres-trigger-operation", "postgres-trigger-case")
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
require.Equal(t, before, operationRows(t, ctx))
r, err := auth.ReconcileSourceOperation(ctx, "postgres-trigger-operation", "postgres-trigger-case")
require.NoError(t, err)
require.Equal(t, "prepared", r.Phase)
})
run("driver-error-rollback-and-private-diagnostics", func(t *testing.T) {
captured := new(postgresOperationLog)
engine.AddHook(&db.SlowQueryHook{Logger: captured})
engine.AddHook(&db.ErrorQueryHook{Logger: captured})
const control = "synthetic-postgres-unmarked-control"
_, err := db.GetEngine(ctx).Exec("SELECT missing_synthetic_function(?)", control)
require.Error(t, err)
require.Contains(t, captured.take(), control, "actual unmarked diagnostic control must be observable")
_, err = engine.Exec(`CREATE OR REPLACE FUNCTION custodian_fail_source() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION '%', NEW.cfg; END $$`)
require.NoError(t, err)
_, err = engine.Exec(`CREATE TRIGGER custodian_source_rewrite BEFORE INSERT ON login_source FOR EACH ROW EXECUTE FUNCTION custodian_fail_source()`)
require.NoError(t, err)
before := operationRows(t, ctx)
_, err = auth.PrepareSourceOperation(ctx, "postgres-error-operation", "postgres-error-case", 0, operationSource())
require.NoError(t, err)
captured.take()
var tracing bytes.Buffer
require.NoError(t, trace.Start(&tracing))
_, err = auth.ApplySourceOperation(ctx, "postgres-error-operation", "postgres-error-case")
trace.Stop()
require.ErrorIs(t, err, auth.ErrSourceOperationStorage)
messages := captured.take()
require.Contains(t, messages, "[Error SQL Query] [owner-confined query]")
require.Contains(t, messages, "[Slow SQL Query] [owner-confined query]")
require.NotContains(t, messages, "synthetic-private-original")
require.False(t, bytes.Contains(tracing.Bytes(), []byte("synthetic-private-original")), "actual PostgreSQL trace disclosed private cfg")
require.Equal(t, before, operationRows(t, ctx))
r, err := auth.ReconcileSourceOperation(ctx, "postgres-error-operation", "postgres-error-case")
require.NoError(t, err)
require.Equal(t, "prepared", r.Phase)
})
run("uncooperative-writers-excluded", func(t *testing.T) {
locked, release, done := make(chan struct{}), make(chan struct{}), make(chan error, 1)
var once sync.Once
releaseLock := func() { once.Do(func() { close(release) }) }
defer releaseLock()
go func() {
done <- auth.SourceOperationPostgresTransactionForTest(ctx, func(context.Context) error { close(locked); <-release; return nil })
}()
select {
case <-locked:
case err := <-done:
t.Fatalf("lock acquisition refused: %v", err)
case <-time.After(5 * time.Second):
t.Fatal("lock acquisition timed out")
}
statements := []string{`UPDATE login_source_operation SET phase=phase WHERE operation_id='absent'`, `UPDATE login_source SET name=name WHERE id=-1`, `UPDATE "user" SET login_source=login_source WHERE id=-1`, `UPDATE external_login_user SET login_source_id=login_source_id WHERE user_id=-1`}
for _, statement := range statements {
session := raw.NewSession()
require.NoError(t, session.Begin())
_, err := session.Exec(`SET LOCAL lock_timeout='150ms'`)
require.NoError(t, err)
_, err = session.Exec(statement)
var pgerr *pq.Error
blocked := errors.As(err, &pgerr) && pgerr.Code == "55P03"
_ = session.Rollback()
_ = session.Close()
require.True(t, blocked, "an uncooperative writer escaped the actual table lock")
}
releaseLock()
select {
case err := <-done:
require.NoError(t, err)
case <-time.After(5 * time.Second):
t.Fatal("transaction did not finish")
}
for _, statement := range statements {
_, err := raw.Exec(statement)
require.NoError(t, err)
}
})
run("concurrent-preparation-has-one-owner", func(t *testing.T) {
start, done := make(chan struct{}), make(chan error, 2)
for _, id := range []string{"postgres-concurrent-first", "postgres-concurrent-second"} {
go func(id string) {
<-start
_, err := auth.PrepareSourceOperation(ctx, id, "postgres-concurrent-case", 0, operationSource())
done <- err
}(id)
}
close(start)
ok, conflict := 0, 0
for range 2 {
select {
case err := <-done:
if err == nil {
ok++
} else if errors.Is(err, auth.ErrSourceOperationConflict) {
conflict++
} else {
t.Fatal("unexpected concurrent preparation error")
}
case <-time.After(5 * time.Second):
t.Fatal("concurrent preparation timed out")
}
}
require.Equal(t, 1, ok)
require.Equal(t, 1, conflict)
})
})
}
}
func TestSourceOperationPostgresProcessRecovery(t *testing.T) {
step := os.Getenv("CUSTODIAN_PG_STEP")
require.NotEmpty(t, step, "explicit synthetic recovery phase required")
ctx, engine, _ := postgresOperationDB(t, "custodian_recovery")
switch step {
case "prepare":
resetPostgresOperations(t, engine)
_, err := auth.PrepareSourceOperation(ctx, "postgres-process-operation", "postgres-process-case", 0, operationSource())
require.NoError(t, err)
case "crash":
engine.AddHook(operationCrashAfterSourceInsert{})
_, _ = auth.ApplySourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
t.Fatal("actual source insert did not reach the process-death hook")
case "recover-prepared":
r, err := auth.ReconcileSourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
require.NoError(t, err)
require.Equal(t, "prepared", r.Phase)
n, err := engine.Table("login_source").Count()
require.NoError(t, err)
require.Zero(t, n)
_, err = auth.ApplySourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
require.NoError(t, err)
case "recover-applied":
r, err := auth.ReconcileSourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
require.NoError(t, err)
require.Equal(t, "applied", r.Phase)
n, err := engine.Table("login_source").Count()
require.NoError(t, err)
require.EqualValues(t, 1, n)
_, err = auth.InverseSourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
require.NoError(t, err)
case "recover-inverse":
r, err := auth.ReconcileSourceOperation(ctx, "postgres-process-operation", "postgres-process-case")
require.NoError(t, err)
require.Equal(t, "reverted", r.Phase)
n, err := engine.Table("login_source").Count()
require.NoError(t, err)
require.Zero(t, n)
default:
t.Fatal("unknown synthetic recovery phase")
}
}

View file

@ -0,0 +1,97 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package auth_test
import (
"context"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
auth "forgejo.org/models/auth"
"forgejo.org/models/db"
oauth2 "forgejo.org/services/auth/source/oauth2"
"github.com/stretchr/testify/require"
"xorm.io/xorm"
"xorm.io/xorm/contexts"
"xorm.io/xorm/names"
)
type operationCrashAfterSourceInsert struct{}
func (operationCrashAfterSourceInsert) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
return c.Ctx, nil
}
func (operationCrashAfterSourceInsert) AfterProcess(c *contexts.ContextHook) error {
if c.Err == nil && strings.Contains(c.SQL, "INSERT INTO") && strings.Contains(c.SQL, "login_source") && !strings.Contains(c.SQL, "login_source_operation") {
// Process death after the real INSERT, before actual ApplySourceOperation's
// post-read/journal update/commit. No artificial operation is substituted.
os.Exit(73)
}
return nil
}
func TestSourceOperationProcessRecovery(t *testing.T) {
if step := os.Getenv("SOURCE_OPERATION_CHILD_STEP"); step != "" {
e, err := xorm.NewEngine("sqlite3", os.Getenv("SOURCE_OPERATION_CHILD_DB")+"?_txlock=deferred")
require.NoError(t, err)
e.SetMapper(names.GonicMapper{})
db.SetDefaultEngine(context.Background(), e)
require.NoError(t, db.SyncAllTables())
auth.RegisterTypeConfig(auth.OAuth2, new(oauth2.Source))
ctx := db.DefaultContext
switch step {
case "prepare":
_, err = auth.PrepareSourceOperation(ctx, "operation-process-recovery", "case-process-recovery", 0, operationSource())
require.NoError(t, err)
case "crash":
e.AddHook(operationCrashAfterSourceInsert{})
_, err = auth.ApplySourceOperation(ctx, "operation-process-recovery", "case-process-recovery")
require.NoError(t, err)
t.Fatal("real insert did not reach crash hook")
case "recover-prepared":
r, err := auth.ReconcileSourceOperation(ctx, "operation-process-recovery", "case-process-recovery")
require.NoError(t, err)
require.Equal(t, "prepared", r.Phase)
n, err := db.GetEngine(ctx).Table("login_source").Count()
require.NoError(t, err)
require.Zero(t, n)
_, err = auth.ApplySourceOperation(ctx, "operation-process-recovery", "case-process-recovery")
require.NoError(t, err)
case "recover-applied":
r, err := auth.ReconcileSourceOperation(ctx, "operation-process-recovery", "case-process-recovery")
require.NoError(t, err)
require.Equal(t, "applied", r.Phase)
_, err = auth.InverseSourceOperation(ctx, "operation-process-recovery", "case-process-recovery")
require.NoError(t, err)
case "recover-inverse":
r, err := auth.ReconcileSourceOperation(ctx, "operation-process-recovery", "case-process-recovery")
require.NoError(t, err)
require.Equal(t, "reverted", r.Phase)
n, err := db.GetEngine(ctx).Table("login_source").Count()
require.NoError(t, err)
require.Zero(t, n)
default:
t.Fatal("unknown synthetic child step")
}
require.NoError(t, e.Close())
return
}
exe, err := os.Executable()
require.NoError(t, err)
path := filepath.Join(t.TempDir(), "owner-private.sqlite")
for _, step := range []string{"prepare", "crash", "recover-prepared", "recover-applied", "recover-inverse"} {
child := exec.Command(exe, "-test.run=^TestSourceOperationProcessRecovery$", "-test.v")
child.Env = append(os.Environ(), "SOURCE_OPERATION_CHILD_STEP="+step, "SOURCE_OPERATION_CHILD_DB="+path)
output, err := child.CombinedOutput()
if step == "crash" {
exit, ok := err.(*exec.ExitError)
require.True(t, ok, string(output))
require.Equal(t, 73, exit.ExitCode(), string(output))
} else {
require.NoError(t, err, string(output))
}
}
}

View file

@ -0,0 +1,235 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package auth_test
import (
"context"
"encoding/json"
"fmt"
"testing"
auth "forgejo.org/models/auth"
"forgejo.org/models/db"
"forgejo.org/models/unittest"
"forgejo.org/modules/setting"
oauth2 "forgejo.org/services/auth/source/oauth2"
"github.com/stretchr/testify/require"
)
func operationDB(t *testing.T) context.Context {
t.Helper()
require.NoError(t, unittest.PrepareTestDatabase())
_, err := db.GetEngine(db.DefaultContext).Exec("DELETE FROM login_source_operation")
require.NoError(t, err)
auth.RegisterTypeConfig(auth.OAuth2, new(oauth2.Source))
return db.DefaultContext
}
func operationSource() *auth.Source {
return &auth.Source{Type: auth.OAuth2, Name: "operation-test", IsActive: false, Cfg: &oauth2.Source{Provider: "openidConnect", ClientID: "synthetic-client", ClientSecret: "synthetic-private-original", RequiredClaimName: "groups", RequiredClaimValues: []string{"g1", "g2", "g3"}, Scopes: []string{"openid", "profile", "groups"}}}
}
func operationRows(t *testing.T, ctx context.Context) string {
t.Helper()
rows, err := db.GetEngine(ctx).Query("SELECT * FROM login_source ORDER BY id")
require.NoError(t, err)
b, err := json.Marshal(rows)
require.NoError(t, err)
return string(b)
}
func TestSourceOperationCreateAndInverse(t *testing.T) {
ctx := operationDB(t)
before := operationRows(t, ctx)
r, err := auth.PrepareSourceOperation(ctx, "operation-create", "case-create", 0, operationSource())
require.NoError(t, err)
require.Equal(t, "prepared", r.Phase)
require.Equal(t, before, operationRows(t, ctx))
r, err = auth.ApplySourceOperation(ctx, "operation-create", "case-create")
require.NoError(t, err)
require.Equal(t, "applied", r.Phase)
require.Positive(t, r.SourceID)
id := r.SourceID
post := operationRows(t, ctx)
r, err = auth.ApplySourceOperation(ctx, "operation-create", "case-create")
require.NoError(t, err)
require.Equal(t, id, r.SourceID)
require.Equal(t, post, operationRows(t, ctx))
_, err = auth.InverseSourceOperation(ctx, "operation-create", "wrong-case")
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
r, err = auth.InverseSourceOperation(ctx, "operation-create", "case-create")
require.NoError(t, err)
require.Equal(t, "reverted", r.Phase)
require.Equal(t, before, operationRows(t, ctx))
_, err = auth.InverseSourceOperation(ctx, "operation-create", "case-create")
require.NoError(t, err)
_, err = auth.ApplySourceOperation(ctx, "operation-create", "case-create")
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
}
func TestSourceOperationRestoresExactRawPreexisting(t *testing.T) {
ctx := operationDB(t)
s := operationSource()
require.NoError(t, auth.CreateSource(ctx, s))
// The old/unknown field must survive restoration byte for byte, including the
// private synthetic secret. A typed OAuth2 decoder would silently lose it.
raw := `{"Provider":"openidConnect","ClientID":"synthetic-client","ClientSecret":"synthetic-private-original","FutureGuard":{"x":true}}`
_, err := db.GetEngine(ctx).Exec("UPDATE login_source SET cfg = ? WHERE id = ?", raw, s.ID)
require.NoError(t, err)
before := operationRows(t, ctx)
s.Cfg.(*oauth2.Source).ClientSecret = "synthetic-private-new"
_, err = auth.PrepareSourceOperation(ctx, "operation-restore", "case-restore", s.ID, s)
require.NoError(t, err)
_, err = auth.ApplySourceOperation(ctx, "operation-restore", "case-restore")
require.NoError(t, err)
_, err = auth.InverseSourceOperation(ctx, "operation-restore", "case-restore")
require.NoError(t, err)
require.Equal(t, before, operationRows(t, ctx))
source, err := auth.GetSourceByID(ctx, s.ID)
require.NoError(t, err)
require.Equal(t, s.ID, source.ID)
}
func TestSourceOperationUnchangedNeverDeletesPreexisting(t *testing.T) {
ctx := operationDB(t)
s := operationSource()
require.NoError(t, auth.CreateSource(ctx, s))
before := operationRows(t, ctx)
_, err := auth.PrepareSourceOperation(ctx, "operation-unchanged", "case-unchanged", s.ID, s)
require.NoError(t, err)
r, err := auth.ApplySourceOperation(ctx, "operation-unchanged", "case-unchanged")
require.NoError(t, err)
require.Equal(t, "unchanged", r.Phase)
_, err = auth.InverseSourceOperation(ctx, "operation-unchanged", "case-unchanged")
require.NoError(t, err)
require.Equal(t, before, operationRows(t, ctx))
}
func TestSourceOperationPrivateDriftRefuses(t *testing.T) {
for _, stage := range []string{"before-apply", "before-inverse"} {
t.Run(stage, func(t *testing.T) {
ctx := operationDB(t)
s := operationSource()
require.NoError(t, auth.CreateSource(ctx, s))
_, err := auth.PrepareSourceOperation(ctx, "operation-private-drift", "case-private-drift", s.ID, s)
require.NoError(t, err)
if stage == "before-inverse" {
_, err = auth.ApplySourceOperation(ctx, "operation-private-drift", "case-private-drift")
require.NoError(t, err)
}
s.Cfg.(*oauth2.Source).ClientSecret = "synthetic-private-drift"
require.NoError(t, auth.UpdateSource(ctx, s))
drift := operationRows(t, ctx)
if stage == "before-apply" {
_, err = auth.ApplySourceOperation(ctx, "operation-private-drift", "case-private-drift")
} else {
_, err = auth.InverseSourceOperation(ctx, "operation-private-drift", "case-private-drift")
}
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
require.NotContains(t, err.Error(), "synthetic-private")
require.Equal(t, drift, operationRows(t, ctx))
})
}
}
func TestSourceOperationStockCRUDDriftRefuses(t *testing.T) {
ctx := operationDB(t)
_, err := auth.PrepareSourceOperation(ctx, "operation-other-writer", "case-other-writer", 0, operationSource())
require.NoError(t, err)
other := operationSource()
other.Name = "unrelated-source"
require.NoError(t, auth.CreateSource(ctx, other))
drift := operationRows(t, ctx)
_, err = auth.ApplySourceOperation(ctx, "operation-other-writer", "case-other-writer")
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
require.Equal(t, drift, operationRows(t, ctx))
}
func TestSourceOperationLinkedSourceRefusesDeletion(t *testing.T) {
for _, kind := range []string{"user", "external"} {
t.Run(kind, func(t *testing.T) {
ctx := operationDB(t)
_, err := auth.PrepareSourceOperation(ctx, "operation-linked", "case-linked", 0, operationSource())
require.NoError(t, err)
r, err := auth.ApplySourceOperation(ctx, "operation-linked", "case-linked")
require.NoError(t, err)
if kind == "user" {
_, err = db.GetEngine(ctx).Exec("UPDATE `user` SET login_source = ? WHERE id = 1", r.SourceID)
} else {
_, err = db.GetEngine(ctx).Exec("INSERT INTO external_login_user (external_id, user_id, login_source_id) VALUES (?, ?, ?)", "synthetic-external", 1, r.SourceID)
}
require.NoError(t, err)
before := operationRows(t, ctx)
_, err = auth.InverseSourceOperation(ctx, "operation-linked", "case-linked")
require.True(t, auth.IsErrSourceInUse(err))
require.Equal(t, before, operationRows(t, ctx))
if kind == "user" {
rows, e := db.GetEngine(ctx).Query("SELECT login_source FROM `user` WHERE id = 1")
require.NoError(t, e)
require.NotEqual(t, []byte("0"), rows[0]["login_source"])
} else {
n, e := db.GetEngine(ctx).Table("external_login_user").Count()
require.NoError(t, e)
require.EqualValues(t, 1, n)
}
})
}
}
func TestSourceOperationPreparedCancellationAndIDReuse(t *testing.T) {
ctx := operationDB(t)
before := operationRows(t, ctx)
_, err := auth.PrepareSourceOperation(ctx, "operation-cancelled", "case-cancelled", 0, operationSource())
require.NoError(t, err)
_, err = auth.PrepareSourceOperation(ctx, "operation-cancelled", "case-cancelled", 0, operationSource())
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
r, err := auth.InverseSourceOperation(ctx, "operation-cancelled", "case-cancelled")
require.NoError(t, err)
require.Equal(t, "cancelled", r.Phase)
require.Equal(t, before, operationRows(t, ctx))
}
func TestSourceOperationRejectsUnsafeRuntime(t *testing.T) {
ctx := operationDB(t)
setting.Database.LogSQL = true
t.Cleanup(func() { setting.Database.LogSQL = false })
_, err := auth.PrepareSourceOperation(ctx, "operation-no-logging", "case-no-logging", 0, operationSource())
require.ErrorIs(t, err, auth.ErrSourceOperationUnsupported)
setting.Database.LogSQL = false
err = db.WithTx(ctx, func(tx context.Context) error {
_, err := auth.PrepareSourceOperation(tx, "operation-nested", "case-nested", 0, operationSource())
return err
})
require.ErrorIs(t, err, auth.ErrSourceOperationUnsupported)
}
func TestSourceOperationRefusesSharedManagedSource(t *testing.T) {
ctx := operationDB(t)
_, err := auth.PrepareSourceOperation(ctx, "operation-first-case", "case-first-case", 0, operationSource())
require.NoError(t, err)
r, err := auth.ApplySourceOperation(ctx, "operation-first-case", "case-first-case")
require.NoError(t, err)
shared := operationSource()
shared.ID = r.SourceID
_, err = auth.PrepareSourceOperation(ctx, "operation-second-case", "case-second-case", r.SourceID, shared)
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
}
func TestSourceOperationBoundsPrivateJournalCustody(t *testing.T) {
ctx := operationDB(t)
// A full unresolved-journal budget refuses new preparation, without deleting
// any intent or replacing private recovery data with an expiry assumption.
for i := 0; i < 64; i++ {
_, err := db.GetEngine(ctx).Exec("INSERT INTO login_source_operation (operation_id, case_id, target_name, phase) VALUES (?, ?, ?, ?)", fmt.Sprintf("bounded-operation-%03d", i), "bounded-case", fmt.Sprintf("bounded-target-%03d", i), "prepared")
require.NoError(t, err)
}
_, err := auth.PrepareSourceOperation(ctx, "operation-budget-full", "case-budget-full", 0, operationSource())
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
n, err := db.GetEngine(ctx).Table("login_source_operation").Count()
require.NoError(t, err)
require.EqualValues(t, 64, n)
}
func TestSourceOperationRequiresExactPostState(t *testing.T) {
ctx := operationDB(t)
before := operationRows(t, ctx)
_, err := auth.PrepareSourceOperation(ctx, "operation-post-exact", "case-post-exact", 0, operationSource())
require.NoError(t, err)
_, err = db.GetEngine(ctx).Exec("CREATE TRIGGER source_operation_test_drift AFTER INSERT ON login_source BEGIN UPDATE login_source SET name = 'unexpected-rewrite' WHERE id = NEW.id; END")
require.NoError(t, err)
defer db.GetEngine(ctx).Exec("DROP TRIGGER source_operation_test_drift")
_, err = auth.ApplySourceOperation(ctx, "operation-post-exact", "case-post-exact")
require.ErrorIs(t, err, auth.ErrSourceOperationConflict)
require.Equal(t, before, operationRows(t, ctx))
r, err := auth.ReconcileSourceOperation(ctx, "operation-post-exact", "case-post-exact")
require.NoError(t, err)
require.Equal(t, "prepared", r.Phase)
}

View file

@ -0,0 +1,340 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db
import (
"context"
"database/sql"
"xorm.io/builder"
"xorm.io/xorm"
)
// DefaultContext is the default context to run xorm queries in
// will be overwritten by Init with HammerContext
var DefaultContext context.Context
// contextKey is a value for use with context.WithValue.
type contextKey struct {
name string
}
// enginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context
var (
enginedContextKey = &contextKey{"engined"}
_ Engined = &Context{}
)
// Context represents a db context
type Context struct {
context.Context
e Engine
transaction bool
}
func newContext(ctx context.Context, e Engine, transaction bool) *Context {
return &Context{
Context: ctx,
e: e,
transaction: transaction,
}
}
// InTransaction if context is in a transaction
func (ctx *Context) InTransaction() bool {
return ctx.transaction
}
// Engine returns db engine
func (ctx *Context) Engine() Engine {
return ctx.e
}
// Value shadows Value for context.Context but allows us to get ourselves and an Engined object
func (ctx *Context) Value(key any) any {
if key == enginedContextKey {
return ctx
}
return ctx.Context.Value(key)
}
// WithContext returns this engine tied to this context
func (ctx *Context) WithContext(other context.Context) *Context {
return newContext(ctx, ctx.e.Context(other), ctx.transaction)
}
// Engined structs provide an Engine
type Engined interface {
Engine() Engine
}
// GetEngine will get a db Engine from this context or return an Engine restricted to this context
func GetEngine(ctx context.Context) Engine {
if e := getEngine(ctx); e != nil {
// A private-query marker must reach XORM's real query hooks, including
// inside an existing engine/transaction context. Wrapping the Go
// context alone does not change XORM's stored session context.
if isSensitiveSQL(ctx) {
return e.Context(ctx)
}
return e
}
return x.Context(ctx)
}
// getEngine will get a db Engine from this context or return nil
func getEngine(ctx context.Context) Engine {
if engined, ok := ctx.(Engined); ok {
return engined.Engine()
}
enginedInterface := ctx.Value(enginedContextKey)
if enginedInterface != nil {
return enginedInterface.(Engined).Engine()
}
return nil
}
// Committer represents an interface to Commit or Close the Context
type Committer interface {
Commit() error
Close() error
}
// halfCommitter is a wrapper of Committer.
// It can be closed early, but can't be committed early, it is useful for reusing a transaction.
type halfCommitter struct {
committer Committer
committed bool
}
func (c *halfCommitter) Commit() error {
c.committed = true
// should do nothing, and the parent committer will commit later
return nil
}
func (c *halfCommitter) Close() error {
if c.committed {
// it's "commit and close", should do nothing, and the parent committer will commit later
return nil
}
// it's "rollback and close", let the parent committer rollback right now
return c.committer.Close()
}
// TxContext represents a transaction Context,
// it will reuse the existing transaction in the parent context or create a new one.
// Some tips to use:
//
// 1 It's always recommended to use `WithTx` in new code instead of `TxContext`, since `WithTx` will handle the transaction automatically.
// 2. To maintain the old code which uses `TxContext`:
// a. Always call `Close()` before returning regardless of whether `Commit()` has been called.
// b. Always call `Commit()` before returning if there are no errors, even if the code did not change any data.
// c. Remember the `Committer` will be a halfCommitter when a transaction is being reused.
// So calling `Commit()` will do nothing, but calling `Close()` without calling `Commit()` will rollback the transaction.
// And all operations submitted by the caller stack will be rollbacked as well, not only the operations in the current function.
// d. It doesn't mean rollback is forbidden, but always do it only when there is an error, and you do want to rollback.
func TxContext(parentCtx context.Context) (*Context, Committer, error) {
if sess, ok := inTransaction(parentCtx); ok {
return newContext(parentCtx, sess, true), &halfCommitter{committer: sess}, nil
}
sess := x.NewSession()
if err := sess.Begin(); err != nil {
sess.Close()
return nil, nil, err
}
return newContext(DefaultContext, sess, true), sess, nil
}
// WithTx represents executing database operations on a transaction, if the transaction exist,
// this function will reuse it otherwise will create a new one and close it when finished.
func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error {
if sess, ok := inTransaction(parentCtx); ok {
err := f(newContext(parentCtx, sess, true))
if err != nil {
// rollback immediately, in case the caller ignores returned error and tries to commit the transaction.
_ = sess.Close()
}
return err
}
return txWithNoCheck(parentCtx, f)
}
func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if err := f(newContext(parentCtx, sess, true)); err != nil {
return err
}
return sess.Commit()
}
// Insert inserts records into database
func Insert(ctx context.Context, beans ...any) error {
_, err := GetEngine(ctx).Insert(beans...)
return err
}
// Exec executes a sql with args
func Exec(ctx context.Context, sqlAndArgs ...any) (sql.Result, error) {
return GetEngine(ctx).Exec(sqlAndArgs...)
}
func Get[T any](ctx context.Context, cond builder.Cond) (object *T, exist bool, err error) {
if !cond.IsValid() {
panic("cond is invalid in db.Get(ctx, cond). This should not be possible.")
}
var bean T
has, err := GetEngine(ctx).Where(cond).NoAutoCondition().Get(&bean)
if err != nil {
return nil, false, err
} else if !has {
return nil, false, nil
}
return &bean, true, nil
}
func GetByID[T any](ctx context.Context, id int64) (object *T, exist bool, err error) {
var bean T
has, err := GetEngine(ctx).ID(id).NoAutoCondition().Get(&bean)
if err != nil {
return nil, false, err
} else if !has {
return nil, false, nil
}
return &bean, true, nil
}
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
if !cond.IsValid() {
panic("cond is invalid in db.Exist(ctx, cond). This should not be possible.")
}
var bean T
return GetEngine(ctx).Where(cond).NoAutoCondition().Exist(&bean)
}
func ExistByID[T any](ctx context.Context, id int64) (bool, error) {
var bean T
return GetEngine(ctx).ID(id).NoAutoCondition().Exist(&bean)
}
// DeleteByID deletes the given bean with the given ID
func DeleteByID[T any](ctx context.Context, id int64) (int64, error) {
var bean T
return GetEngine(ctx).ID(id).NoAutoCondition().NoAutoTime().Delete(&bean)
}
func DeleteByIDs[T any](ctx context.Context, ids ...int64) error {
if len(ids) == 0 {
return nil
}
var bean T
_, err := GetEngine(ctx).In("id", ids).NoAutoCondition().NoAutoTime().Delete(&bean)
return err
}
func Delete[T any](ctx context.Context, opts FindOptions) (int64, error) {
if opts == nil || !opts.ToConds().IsValid() {
panic("opts are empty or invalid in db.Delete(ctx, opts). This should not be possible.")
}
var bean T
return GetEngine(ctx).Where(opts.ToConds()).NoAutoCondition().NoAutoTime().Delete(&bean)
}
// DeleteByBean deletes all records according non-empty fields of the bean as conditions.
func DeleteByBean(ctx context.Context, bean any) (int64, error) {
return GetEngine(ctx).Delete(bean)
}
// FindIDs finds the IDs for the given table name satisfying the given condition
// By passing a different value than "id" for "idCol", you can query for foreign IDs, i.e. the repo IDs which satisfy the condition
func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([]int64, error) {
ids := make([]int64, 0, 10)
if err := GetEngine(ctx).Table(tableName).
Cols(idCol).
Where(cond).
Find(&ids); err != nil {
return nil, err
}
return ids, nil
}
// DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one
// Timestamps of the entities won't be updated
func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean any) error {
if len(ids) == 0 {
return nil
}
_, err := GetEngine(ctx).Decr(decrCol).In("id", ids).NoAutoCondition().NoAutoTime().Update(bean)
return err
}
// DeleteBeans deletes all given beans, beans must contain delete conditions.
func DeleteBeans(ctx context.Context, beans ...any) (err error) {
e := GetEngine(ctx)
for i := range beans {
if _, err = e.Delete(beans[i]); err != nil {
return err
}
}
return nil
}
// TruncateBeans deletes all given beans, beans may contain delete conditions.
func TruncateBeans(ctx context.Context, beans ...any) (err error) {
e := GetEngine(ctx)
for i := range beans {
if _, err = e.Truncate(beans[i]); err != nil {
return err
}
}
return nil
}
// CountByBean counts the number of database records according non-empty fields of the bean as conditions.
func CountByBean(ctx context.Context, bean any) (int64, error) {
return GetEngine(ctx).Count(bean)
}
// TableName returns the table name according a bean object
func TableName(bean any) string {
return x.TableName(bean)
}
// InTransaction returns true if the engine is in a transaction otherwise return false
func InTransaction(ctx context.Context) bool {
_, ok := inTransaction(ctx)
return ok
}
func inTransaction(ctx context.Context) (*xorm.Session, bool) {
e := getEngine(ctx)
if e == nil {
return nil, false
}
switch t := e.(type) {
case *xorm.Engine:
return nil, false
case *xorm.Session:
if t.IsInTx() {
return t, true
}
return nil, false
default:
return nil, false
}
}

View file

@ -0,0 +1,391 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db
import (
"context"
"database/sql"
"errors"
"fmt"
"io"
"reflect"
"runtime/trace"
"strings"
"time"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"xorm.io/xorm"
"xorm.io/xorm/contexts"
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
_ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver
_ "github.com/lib/pq" // Needed for the Postgresql driver
)
var (
x *xorm.Engine
tables []any
initFuncs []func() error
)
// Engine represents a xorm engine or session.
type Engine interface {
Table(tableNameOrBean any) *xorm.Session
Count(...any) (int64, error)
Decr(column string, arg ...any) *xorm.Session
Delete(...any) (int64, error)
Truncate(...any) (int64, error)
Exec(...any) (sql.Result, error)
Find(any, ...any) error
Get(beans ...any) (bool, error)
ID(any) *xorm.Session
In(string, ...any) *xorm.Session
Incr(column string, arg ...any) *xorm.Session
Insert(...any) (int64, error)
Iterate(any, xorm.IterFunc) error
IsTableExist(any) (bool, error)
Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session
SQL(any, ...any) *xorm.Session
Where(any, ...any) *xorm.Session
Asc(colNames ...string) *xorm.Session
Desc(colNames ...string) *xorm.Session
Limit(limit int, start ...int) *xorm.Session
NoAutoTime() *xorm.Session
SumInt(bean any, columnName string) (res int64, err error)
Sync(...any) error
Select(string) *xorm.Session
SetExpr(string, any) *xorm.Session
NotIn(string, ...any) *xorm.Session
OrderBy(any, ...any) *xorm.Session
Exist(...any) (bool, error)
Distinct(...string) *xorm.Session
Query(...any) ([]map[string][]byte, error)
Cols(...string) *xorm.Session
Context(ctx context.Context) *xorm.Session
Ping() error
}
// TableInfo returns table's information via an object
func TableInfo(v any) (*schemas.Table, error) {
return x.TableInfo(v)
}
// DumpTables dump tables information
func DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
return x.DumpTables(tables, w, tp...)
}
// RegisterModel registers model, if initfunc provided, it will be invoked after data model sync
func RegisterModel(bean any, initFunc ...func() error) {
tables = append(tables, bean)
if len(initFuncs) > 0 && initFunc[0] != nil {
initFuncs = append(initFuncs, initFunc[0])
}
}
func init() {
gonicNames := []string{"SSL", "UID"}
for _, name := range gonicNames {
names.LintGonicMapper[name] = true
}
}
// newXORMEngine returns a new XORM engine from the configuration
func newXORMEngine() (*xorm.Engine, error) {
connStr, err := setting.DBConnStr()
if err != nil {
return nil, err
}
var engine *xorm.Engine
if setting.Database.Type.IsPostgreSQL() && len(setting.Database.Schema) > 0 {
// OK whilst we sort out our schema issues - create a schema aware postgres
registerPostgresSchemaDriver()
engine, err = xorm.NewEngine("postgresschema", connStr)
} else {
engine, err = xorm.NewEngine(setting.Database.Type.String(), connStr)
}
if err != nil {
return nil, err
}
if setting.Database.Type.IsMySQL() {
engine.Dialect().SetParams(map[string]string{"rowFormat": "DYNAMIC"})
}
engine.SetSchema(setting.Database.Schema)
return engine, nil
}
// SyncAllTables sync the schemas of all tables, is required by unit test code
func SyncAllTables() error {
_, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{
WarnIfDatabaseColumnMissed: true,
}, tables...)
return err
}
// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext
func InitEngine(ctx context.Context) error {
xormEngine, err := newXORMEngine()
if err != nil {
return fmt.Errorf("failed to connect to database: %w", err)
}
xormEngine.SetMapper(names.GonicMapper{})
// WARNING: for serv command, MUST remove the output to os.stdout,
// so use log file to instead print to stdout.
xormEngine.SetLogger(NewXORMLogger(setting.Database.LogSQL))
xormEngine.ShowSQL(setting.Database.LogSQL)
xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns)
xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns)
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
xormEngine.SetConnMaxIdleTime(setting.Database.ConnMaxIdleTime)
xormEngine.SetDefaultContext(ctx)
if setting.Database.SlowQueryThreshold > 0 {
xormEngine.AddHook(&SlowQueryHook{
Treshold: setting.Database.SlowQueryThreshold,
Logger: log.GetLogger("xorm"),
})
}
errorLogger := log.GetLogger("xorm")
if setting.IsInTesting {
errorLogger = log.GetLogger(log.DEFAULT)
}
xormEngine.AddHook(&ErrorQueryHook{
Logger: errorLogger,
})
xormEngine.AddHook(&TracingHook{})
SetDefaultEngine(ctx, xormEngine)
return nil
}
// SetDefaultEngine sets the default engine for db
func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) {
x = eng
DefaultContext = &Context{
Context: ctx,
e: x,
}
}
// UnsetDefaultEngine closes and unsets the default engine
// We hope the SetDefaultEngine and UnsetDefaultEngine can be paired, but it's impossible now,
// there are many calls to InitEngine -> SetDefaultEngine directly to overwrite the `x` and DefaultContext without close
// Global database engine related functions are all racy and there is no graceful close right now.
func UnsetDefaultEngine() {
if x != nil {
_ = x.Close()
x = nil
}
DefaultContext = nil
}
// InitEngineWithMigration initializes a new xorm.Engine and sets it as the db.DefaultContext
// This function must never call .Sync() if the provided migration function fails.
// When called from the "doctor" command, the migration function is a version check
// that prevents the doctor from fixing anything in the database if the migration level
// is different from the expected value.
func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) {
if err = InitEngine(ctx); err != nil {
return err
}
if err = x.Ping(); err != nil {
return err
}
preprocessDatabaseCollation(x)
// We have to run migrateFunc here in case the user is re-running installation on a previously created DB.
// If we do not then table schemas will be changed and there will be conflicts when the migrations run properly.
//
// Installation should only be being re-run if users want to recover an old database.
// However, we should think carefully about should we support re-install on an installed instance,
// as there may be other problems due to secret reinitialization.
if err = migrateFunc(x); err != nil {
return fmt.Errorf("migrate: %w", err)
}
if err = SyncAllTables(); err != nil {
return fmt.Errorf("sync database struct error: %w", err)
}
for _, initFunc := range initFuncs {
if err := initFunc(); err != nil {
return fmt.Errorf("initFunc failed: %w", err)
}
}
return nil
}
// NamesToBean return a list of beans or an error
func NamesToBean(names ...string) ([]any, error) {
beans := []any{}
if len(names) == 0 {
beans = append(beans, tables...)
return beans, nil
}
// Need to map provided names to beans...
beanMap := make(map[string]any)
for _, bean := range tables {
beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean
beanMap[strings.ToLower(x.TableName(bean))] = bean
beanMap[strings.ToLower(x.TableName(bean, true))] = bean
}
gotBean := make(map[any]bool)
for _, name := range names {
bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))]
if !ok {
return nil, fmt.Errorf("no table found that matches: %s", name)
}
if !gotBean[bean] {
beans = append(beans, bean)
gotBean[bean] = true
}
}
return beans, nil
}
// DumpDatabase dumps all data from database according the special database SQL syntax to file system.
func DumpDatabase(filePath, dbType string) error {
var tbs []*schemas.Table
for _, t := range tables {
t, err := x.TableInfo(t)
if err != nil {
return err
}
tbs = append(tbs, t)
}
type Version struct {
ID int64 `xorm:"pk autoincr"`
Version int64
}
t, err := x.TableInfo(&Version{})
if err != nil {
return err
}
tbs = append(tbs, t)
if len(dbType) > 0 {
return x.DumpTablesToFile(tbs, filePath, schemas.DBType(dbType))
}
return x.DumpTablesToFile(tbs, filePath)
}
// MaxBatchInsertSize returns the table's max batch insert size
func MaxBatchInsertSize(bean any) int {
t, err := x.TableInfo(bean)
if err != nil {
return 50
}
return 999 / len(t.ColumnsSeq())
}
// IsTableNotEmpty returns true if table has at least one record
func IsTableNotEmpty(beanOrTableName any) (bool, error) {
return x.Table(beanOrTableName).Exist()
}
// DeleteAllRecords will delete all the records of this table
func DeleteAllRecords(tableName string) error {
_, err := x.Exec(fmt.Sprintf("DELETE FROM %s", tableName))
return err
}
// GetMaxID will return max id of the table
func GetMaxID(beanOrTableName any) (maxID int64, err error) {
_, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID)
return maxID, err
}
func SetLogSQL(ctx context.Context, on bool) {
e := GetEngine(ctx)
if x, ok := e.(*xorm.Engine); ok {
x.ShowSQL(on)
} else if sess, ok := e.(*xorm.Session); ok {
sess.Engine().ShowSQL(on)
}
}
type TracingHook struct{}
var _ contexts.Hook = &TracingHook{}
type sqlTask struct{}
func (TracingHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
ctx, task := trace.NewTask(c.Ctx, "sql")
ctx = context.WithValue(ctx, sqlTask{}, task)
// XORM passes the same ContextHook to every BeforeProcess hook. Publish
// the task there too, so a later hook returning c.Ctx retains it.
c.Ctx = ctx
if isSensitiveSQL(c.Ctx) {
trace.Log(ctx, "query", "[owner-confined query]")
} else {
trace.Log(ctx, "query", c.SQL)
trace.Logf(ctx, "args", "%v", c.Args)
}
return ctx, nil
}
func (TracingHook) AfterProcess(c *contexts.ContextHook) error {
c.Ctx.Value(sqlTask{}).(*trace.Task).End()
return nil
}
type SlowQueryHook struct {
Treshold time.Duration
Logger log.Logger
}
var _ contexts.Hook = &SlowQueryHook{}
func (SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
return c.Ctx, nil
}
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
if c.ExecuteTime >= h.Treshold {
if isSensitiveSQL(c.Ctx) {
h.Logger.Log(8, log.WARN, "[Slow SQL Query] [owner-confined query] - %v", c.ExecuteTime)
} else {
h.Logger.Log(8, log.WARN, "[Slow SQL Query] %s %v - %v", c.SQL, c.Args, c.ExecuteTime)
}
}
return nil
}
type ErrorQueryHook struct {
Logger log.Logger
}
var _ contexts.Hook = &ErrorQueryHook{}
func (ErrorQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
return c.Ctx, nil
}
func (h *ErrorQueryHook) AfterProcess(c *contexts.ContextHook) error {
if c.Err != nil && !errors.Is(c.Err, context.Canceled) {
if isSensitiveSQL(c.Ctx) {
h.Logger.Log(8, log.ERROR, "[Error SQL Query] [owner-confined query]")
} else {
h.Logger.Log(8, log.ERROR, "[Error SQL Query] %s %v - %v", c.SQL, c.Args, c.Err)
}
}
return nil
}

View file

@ -0,0 +1,47 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db
import (
"context"
"forgejo.org/modules/setting"
"xorm.io/xorm"
)
type sensitiveSQLKey struct{}
// WithSensitiveSQL suppresses argument, statement and driver-error disclosure
// in the application's query hooks. The caller must separately refuse raw XORM
// SQL logging; custom driver/server auditing remains an owner custody concern.
func WithSensitiveSQL(ctx context.Context) context.Context {
return context.WithValue(ctx, sensitiveSQLKey{}, true)
}
func isSensitiveSQL(ctx context.Context) bool {
sensitive, _ := ctx.Value(sensitiveSQLKey{}).(bool)
return sensitive
}
// SQLLoggingEnabled checks the underlying engine without changing the context or
// assuming that a sensitive context still exposes an Engine rather than Session.
// Unknown/uninitialized backends refuse, and query redaction never permits raw
// XORM logging. It neither creates a transaction nor closes an existing one.
func SQLLoggingEnabled(ctx context.Context) bool {
if setting.Database.LogSQL {
return true
}
engine := getEngine(ctx)
if engine == nil {
if x == nil {
return true
}
engine = x
}
switch current := engine.(type) {
case *xorm.Engine:
return current == nil || current.Logger() == nil || current.Logger().IsShowSQL()
case *xorm.Session:
return current == nil || current.Engine() == nil || current.Engine().Logger() == nil || current.Engine().Logger().IsShowSQL()
default:
return true
}
}

View file

@ -0,0 +1,41 @@
package db
import (
"context"
"testing"
"forgejo.org/modules/setting"
"xorm.io/xorm"
)
func TestSensitiveSQLLoggingChecksUnderlyingEngine(t *testing.T) {
engine, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
if err != nil {
t.Fatal(err)
}
defer engine.Close()
engine.SetLogger(NewXORMLogger(false))
session := engine.NewSession()
defer session.Close()
previous := setting.Database.LogSQL
defer func() { setting.Database.LogSQL = previous }()
for name, backend := range map[string]Engine{"engine": engine, "session": session} {
t.Run(name, func(t *testing.T) {
ctx := WithSensitiveSQL(newContext(context.Background(), backend, false))
setting.Database.LogSQL = false
engine.ShowSQL(false)
if SQLLoggingEnabled(ctx) {
t.Fatal("private context with disabled actual SQL logger refused")
}
engine.ShowSQL(true)
if !SQLLoggingEnabled(ctx) {
t.Fatal("actual raw SQL logger escaped refusal")
}
engine.ShowSQL(false)
setting.Database.LogSQL = true
if !SQLLoggingEnabled(ctx) {
t.Fatal("configured raw SQL logger escaped refusal")
}
})
}
}

View file

@ -0,0 +1,103 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package db
import (
"bytes"
"context"
"errors"
"fmt"
"runtime/trace"
"strings"
"testing"
"time"
"forgejo.org/modules/log"
"xorm.io/xorm"
"xorm.io/xorm/contexts"
)
type sensitiveSQLLog struct {
log.Logger
entries []string
}
func (l *sensitiveSQLLog) Log(_ int, _ log.Level, f string, v ...any) {
l.entries = append(l.entries, fmt.Sprintf(f, v...))
}
func TestSensitiveSQLHooks(t *testing.T) {
const secret = "synthetic-query-private-sentinel"
for _, sensitive := range []bool{false, true} {
ctx := context.Background()
if sensitive {
ctx = WithSensitiveSQL(ctx)
}
h := &contexts.ContextHook{Ctx: ctx, SQL: "SELECT '" + secret + "'", Args: []any{secret}, Err: errors.New(secret), ExecuteTime: time.Second}
logger := new(sensitiveSQLLog)
if err := (&SlowQueryHook{Logger: logger}).AfterProcess(h); err != nil {
t.Fatal(err)
}
if err := (&ErrorQueryHook{Logger: logger}).AfterProcess(h); err != nil {
t.Fatal(err)
}
found := strings.Contains(strings.Join(logger.entries, "\n"), secret)
if found == sensitive {
t.Fatal("query hook disclosure/redaction mismatch")
}
var out bytes.Buffer
if err := trace.Start(&out); err != nil {
t.Fatal(err)
}
next, err := (TracingHook{}).BeforeProcess(h)
if err != nil {
trace.Stop()
t.Fatal(err)
}
h.Ctx = next
err = (TracingHook{}).AfterProcess(h)
trace.Stop()
if err != nil {
t.Fatal(err)
}
if bytes.Contains(out.Bytes(), []byte(secret)) == sensitive {
t.Fatal("trace disclosure/redaction mismatch")
}
}
}
func TestSensitiveSQLActualContextPropagation(t *testing.T) {
engine, ok := GetEngine(DefaultContext).(*xorm.Engine)
if !ok {
t.Fatal("missing actual fixture engine")
}
logger := new(sensitiveSQLLog)
engine.AddHook(&SlowQueryHook{Logger: logger})
engine.AddHook(&ErrorQueryHook{Logger: logger})
engine.AddHook(TracingHook{})
const secret = "synthetic-actual-xorm-private-sentinel"
for _, sensitive := range []bool{false, true} {
logger.entries = nil
ctx := context.Context(DefaultContext)
if sensitive {
ctx = WithSensitiveSQL(ctx)
}
var out bytes.Buffer
if err := trace.Start(&out); err != nil {
t.Fatal(err)
}
_, err := GetEngine(ctx).Exec("SELECT missing_synthetic_function(?)", secret)
trace.Stop()
if err == nil {
t.Fatal("expected actual SQLite function refusal")
}
if len(logger.entries) < 2 {
t.Fatal("actual query hooks did not execute")
}
if strings.Contains(strings.Join(logger.entries, "\n"), secret) == sensitive {
t.Fatal("actual XORM query context did not preserve redaction")
}
if bytes.Contains(out.Bytes(), []byte(secret)) == sensitive {
t.Fatal("actual XORM trace context did not preserve redaction")
}
}
}

View file

@ -0,0 +1,54 @@
package db
import (
"bytes"
"context"
"runtime/trace"
"testing"
"xorm.io/xorm"
"xorm.io/xorm/contexts"
)
type followingQueryHook struct{}
func (followingQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
return c.Ctx, nil
}
func (followingQueryHook) AfterProcess(*contexts.ContextHook) error { return nil }
func TestTracingContextSurvivesFollowingHooks(t *testing.T) {
for _, sensitive := range []bool{false, true} {
for _, fail := range []bool{false, true} {
engine, err := xorm.NewEngine("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
engine.AddHook(TracingHook{})
engine.AddHook(followingQueryHook{})
ctx := context.Background()
if sensitive {
ctx = WithSensitiveSQL(ctx)
}
var output bytes.Buffer
if err := trace.Start(&output); err != nil {
t.Fatal(err)
}
func() {
defer trace.Stop()
query := "SELECT ?"
if fail {
query = "SELECT missing_synthetic_function(?)"
}
_, err = engine.Context(ctx).Exec(query, "synthetic-following-hook-sentinel")
}()
_ = engine.Close()
if (err != nil) != fail {
t.Fatal("unexpected real SQLite query outcome")
}
if bytes.Contains(output.Bytes(), []byte("synthetic-following-hook-sentinel")) == sensitive {
t.Fatal("following hook changed private or ordinary trace disclosure")
}
}
}
}

View file

@ -0,0 +1,408 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package admin
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"forgejo.org/models/auth"
"forgejo.org/models/db"
"forgejo.org/modules/auth/pam"
"forgejo.org/modules/base"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"forgejo.org/modules/web"
auth_service "forgejo.org/services/auth"
"forgejo.org/services/auth/source/ldap"
"forgejo.org/services/auth/source/oauth2"
pam_service "forgejo.org/services/auth/source/pam"
"forgejo.org/services/auth/source/smtp"
"forgejo.org/services/context"
"forgejo.org/services/forms"
"xorm.io/xorm/convert"
)
const (
tplAuths base.TplName = "admin/auth/list"
tplAuthNew base.TplName = "admin/auth/new"
tplAuthEdit base.TplName = "admin/auth/edit"
)
// Authentications show authentication config page
func Authentications(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.authentication")
ctx.Data["PageIsAdminAuthentications"] = true
var err error
ctx.Data["Sources"], ctx.Data["Total"], err = db.FindAndCount[auth.Source](ctx, auth.FindSourcesOptions{})
if err != nil {
ctx.ServerError("auth.Sources", err)
return
}
ctx.HTML(http.StatusOK, tplAuths)
}
type dropdownItem struct {
Name string
Type any
}
var (
authSources = func() []dropdownItem {
items := []dropdownItem{
{auth.LDAP.String(), auth.LDAP},
{auth.DLDAP.String(), auth.DLDAP},
{auth.SMTP.String(), auth.SMTP},
{auth.OAuth2.String(), auth.OAuth2},
}
if pam.Supported {
items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM})
}
return items
}()
securityProtocols = []dropdownItem{
{ldap.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted], ldap.SecurityProtocolUnencrypted},
{ldap.SecurityProtocolNames[ldap.SecurityProtocolLDAPS], ldap.SecurityProtocolLDAPS},
{ldap.SecurityProtocolNames[ldap.SecurityProtocolStartTLS], ldap.SecurityProtocolStartTLS},
}
)
// NewAuthSource render adding a new auth source page
func NewAuthSource(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.auths.new")
ctx.Data["PageIsAdminAuthentications"] = true
ctx.Data["type"] = auth.LDAP.Int()
ctx.Data["CurrentTypeName"] = auth.Names[auth.LDAP]
ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocolUnencrypted]
ctx.Data["smtp_auth"] = "PLAIN"
ctx.Data["is_active"] = true
ctx.Data["is_sync_enabled"] = true
ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
// only the first as default
ctx.Data["oauth2_provider"] = oauth2providers[0].Name()
ctx.HTML(http.StatusOK, tplAuthNew)
}
func parseLDAPConfig(form forms.AuthenticationForm) *ldap.Source {
var pageSize uint32
if form.UsePagedSearch {
pageSize = uint32(form.SearchPageSize)
}
return &ldap.Source{
Name: form.Name,
Host: form.Host,
Port: form.Port,
SecurityProtocol: ldap.SecurityProtocol(form.SecurityProtocol),
SkipVerify: form.SkipVerify,
BindDN: form.BindDN,
UserDN: form.UserDN,
BindPassword: form.BindPassword,
UserBase: form.UserBase,
DefaultDomainName: form.DefaultDomainName,
AttributeUsername: form.AttributeUsername,
AttributeName: form.AttributeName,
AttributeSurname: form.AttributeSurname,
AttributeMail: form.AttributeMail,
AttributesInBind: form.AttributesInBind,
AttributeSSHPublicKey: form.AttributeSSHPublicKey,
AttributeAvatar: form.AttributeAvatar,
SearchPageSize: pageSize,
Filter: form.Filter,
GroupsEnabled: form.GroupsEnabled,
GroupDN: form.GroupDN,
GroupFilter: form.GroupFilter,
GroupMemberUID: form.GroupMemberUID,
GroupTeamMap: form.GroupTeamMap,
GroupTeamMapRemoval: form.GroupTeamMapRemoval,
UserUID: form.UserUID,
AdminFilter: form.AdminFilter,
RestrictedFilter: form.RestrictedFilter,
AllowDeactivateAll: form.AllowDeactivateAll,
Enabled: true,
SkipLocalTwoFA: form.SkipLocalTwoFA,
}
}
func parseSMTPConfig(form forms.AuthenticationForm) *smtp.Source {
return &smtp.Source{
Auth: form.SMTPAuth,
Host: form.SMTPHost,
Port: form.SMTPPort,
AllowedDomains: form.AllowedDomains,
ForceSMTPS: form.ForceSMTPS,
SkipVerify: form.SkipVerify,
HeloHostname: form.HeloHostname,
DisableHelo: form.DisableHelo,
SkipLocalTwoFA: form.SkipLocalTwoFA,
}
}
func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
var customURLMapping *oauth2.CustomURLMapping
if form.Oauth2UseCustomURL {
customURLMapping = &oauth2.CustomURLMapping{
TokenURL: form.Oauth2TokenURL,
AuthURL: form.Oauth2AuthURL,
ProfileURL: form.Oauth2ProfileURL,
EmailURL: form.Oauth2EmailURL,
Tenant: form.Oauth2Tenant,
}
} else {
customURLMapping = nil
}
var scopes []string
for _, s := range strings.Split(form.Oauth2Scopes, ",") {
s = strings.TrimSpace(s)
if s != "" {
scopes = append(scopes, s)
}
}
return &oauth2.Source{
Provider: form.Oauth2Provider,
ClientID: form.Oauth2Key,
ClientSecret: form.Oauth2Secret,
OpenIDConnectAutoDiscoveryURL: form.OpenIDConnectAutoDiscoveryURL,
CustomURLMapping: customURLMapping,
IconURL: form.Oauth2IconURL,
Scopes: scopes,
AttributeSSHPublicKey: form.Oauth2AttributeSSHPublicKey,
RequiredClaimName: form.Oauth2RequiredClaimName,
RequiredClaimValue: form.Oauth2RequiredClaimValue,
SkipLocalTwoFA: form.SkipLocalTwoFA,
GroupClaimName: form.Oauth2GroupClaimName,
RestrictedGroup: form.Oauth2RestrictedGroup,
AdminGroup: form.Oauth2AdminGroup,
GroupTeamMap: form.Oauth2GroupTeamMap,
GroupTeamMapRemoval: form.Oauth2GroupTeamMapRemoval,
}
}
// NewAuthSourcePost response for adding an auth source
func NewAuthSourcePost(ctx *context.Context) {
form := *web.GetForm(ctx).(*forms.AuthenticationForm)
ctx.Data["Title"] = ctx.Tr("admin.auths.new")
ctx.Data["PageIsAdminAuthentications"] = true
ctx.Data["CurrentTypeName"] = auth.Type(form.Type).String()
ctx.Data["CurrentSecurityProtocol"] = ldap.SecurityProtocolNames[ldap.SecurityProtocol(form.SecurityProtocol)]
ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
hasTLS := false
var config convert.Conversion
switch auth.Type(form.Type) {
case auth.LDAP, auth.DLDAP:
config = parseLDAPConfig(form)
hasTLS = ldap.SecurityProtocol(form.SecurityProtocol) > ldap.SecurityProtocolUnencrypted
case auth.SMTP:
config = parseSMTPConfig(form)
hasTLS = true
case auth.PAM:
config = &pam_service.Source{
ServiceName: form.PAMServiceName,
EmailDomain: form.PAMEmailDomain,
SkipLocalTwoFA: form.SkipLocalTwoFA,
}
case auth.OAuth2:
config = parseOAuth2Config(form)
oauth2Config := config.(*oauth2.Source)
if oauth2Config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
ctx.Data["Err_DiscoveryURL"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthNew, form)
return
}
}
default:
ctx.Error(http.StatusBadRequest)
return
}
ctx.Data["HasTLS"] = hasTLS
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplAuthNew)
return
}
if err := auth.CreateSource(ctx, &auth.Source{
Type: auth.Type(form.Type),
Name: form.Name,
IsActive: form.IsActive,
IsSyncEnabled: form.IsSyncEnabled,
Cfg: config,
}); err != nil {
if auth.IsErrSourceAlreadyExist(err) {
ctx.Data["Err_Name"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form)
} else if oauth2.IsErrOpenIDConnectInitialize(err) {
ctx.Data["Err_DiscoveryURL"] = true
unwrapped := err.(oauth2.ErrOpenIDConnectInitialize).Unwrap()
ctx.RenderWithErr(ctx.Tr("admin.auths.unable_to_initialize_openid", unwrapped), tplAuthNew, form)
} else {
ctx.ServerError("auth.CreateSource", err)
}
return
}
log.Trace("Authentication created by admin(%s): %s", ctx.Doer.Name, form.Name)
ctx.Flash.Success(ctx.Tr("admin.auths.new_success", form.Name))
ctx.Redirect(setting.AppSubURL + "/admin/auths")
}
// EditAuthSource render editing auth source page
func EditAuthSource(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.auths.edit")
ctx.Data["PageIsAdminAuthentications"] = true
ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid"))
if err != nil {
ctx.ServerError("auth.GetSourceByID", err)
return
}
ctx.Data["Source"] = source
ctx.Data["HasTLS"] = source.HasTLS()
if source.IsOAuth2() {
type Named interface {
Name() string
}
for _, provider := range oauth2providers {
if provider.Name() == source.Cfg.(Named).Name() {
ctx.Data["CurrentOAuth2Provider"] = provider
break
}
}
}
ctx.HTML(http.StatusOK, tplAuthEdit)
}
// EditAuthSourcePost response for editing auth source
func EditAuthSourcePost(ctx *context.Context) {
form := *web.GetForm(ctx).(*forms.AuthenticationForm)
ctx.Data["Title"] = ctx.Tr("admin.auths.edit")
ctx.Data["PageIsAdminAuthentications"] = true
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid"))
if err != nil {
ctx.ServerError("auth.GetSourceByID", err)
return
}
ctx.Data["Source"] = source
ctx.Data["HasTLS"] = source.HasTLS()
// The legacy form has no representation for the opt-in current-UserInfo gate.
// Refuse rather than silently dropping it during source reconstruction.
if current, ok := source.Cfg.(*oauth2.Source); ok && current.RequiredClaimValues != nil {
ctx.RenderWithErr("This OAuth source uses required-claim-values; update it through the reviewed CLI.", tplAuthEdit, form)
return
}
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplAuthEdit)
return
}
var config convert.Conversion
switch auth.Type(form.Type) {
case auth.LDAP, auth.DLDAP:
config = parseLDAPConfig(form)
case auth.SMTP:
config = parseSMTPConfig(form)
case auth.PAM:
config = &pam_service.Source{
ServiceName: form.PAMServiceName,
EmailDomain: form.PAMEmailDomain,
}
case auth.OAuth2:
config = parseOAuth2Config(form)
oauth2Config := config.(*oauth2.Source)
if oauth2Config.Provider == "openidConnect" {
discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL)
if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") {
ctx.Data["Err_DiscoveryURL"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthEdit, form)
return
}
}
default:
ctx.Error(http.StatusBadRequest)
return
}
source.Name = form.Name
source.IsActive = form.IsActive
source.IsSyncEnabled = form.IsSyncEnabled
source.Cfg = config
if err := auth.UpdateSource(ctx, source); err != nil {
if auth.IsErrSourceAlreadyExist(err) {
ctx.Data["Err_Name"] = true
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthEdit, form)
} else if oauth2.IsErrOpenIDConnectInitialize(err) {
ctx.Flash.Error(err.Error(), true)
ctx.Data["Err_DiscoveryURL"] = true
ctx.HTML(http.StatusOK, tplAuthEdit)
} else {
ctx.ServerError("UpdateSource", err)
}
return
}
log.Trace("Authentication changed by admin(%s): %d", ctx.Doer.Name, source.ID)
ctx.Flash.Success(ctx.Tr("admin.auths.update_success"))
ctx.Redirect(setting.AppSubURL + "/admin/auths/" + strconv.FormatInt(form.ID, 10))
}
// DeleteAuthSource response for deleting an auth source
func DeleteAuthSource(ctx *context.Context) {
source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid"))
if err != nil {
ctx.ServerError("auth.GetSourceByID", err)
return
}
if err = auth_service.DeleteSource(ctx, source); err != nil {
if auth.IsErrSourceInUse(err) {
ctx.Flash.Error(ctx.Tr("admin.auths.still_in_used"))
} else {
ctx.Flash.Error(fmt.Sprintf("auth_service.DeleteSource: %v", err))
}
ctx.JSONRedirect(setting.AppSubURL + "/admin/auths/" + url.PathEscape(ctx.Params(":authid")))
return
}
log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID)
ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success"))
ctx.JSONRedirect(setting.AppSubURL + "/admin/auths")
}

View file

@ -0,0 +1,247 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2
import (
"context"
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/markbates/goth"
"github.com/netkingdom/access-engine/pkg/api"
"github.com/netkingdom/access-engine/pkg/decisionpep"
"keycape/pkg/tokenstatus"
)
var errApplicationDecision = errors.New("current authenticated application access refused")
// AccessEngineAdmission contains owner-admitted public trust, with only a file
// reference to the private PDP caller token. It is not persisted OAuth source
// configuration and cannot be replaced by token claims or provider discovery.
type AccessEngineAdmission struct {
SourceName string `json:"source_name"`
Issuer string `json:"issuer"`
Audience string `json:"audience"`
ApplicationTenant string `json:"application_tenant"`
TokenType string `json:"token_type"`
Algorithms []string `json:"algorithms"`
IdentityKeys map[string]string `json:"identity_keys"`
TenantClaim string `json:"tenant_claim"`
PrincipalTypeClaim string `json:"principal_type_claim"`
AssuranceClaim string `json:"assurance_claim"`
TenantSourceClaim string `json:"tenant_source_claim"`
MaxAuthenticationAge string `json:"max_authentication_age"`
MaxIdentityAge string `json:"max_identity_age"`
Decision decisionpep.Admission `json:"decision"`
CurrentToken tokenstatus.OnlineAdmission `json:"current_token"`
}
// VerifiedApplicationIdentity is derived only by VerifyApplicationIdentity from
// independently admitted JOSE keys. RawData and UserInfo never supply these
// fields. This snapshot is an identity fact, never an authorization verdict.
type VerifiedApplicationIdentity struct {
Issuer string
Subject string
Tenant string
PrincipalType api.SubjectType
Assurance string
TenantSource string
AssuranceData map[string]any
AuthenticatedAt time.Time
IssuedAt time.Time
SigningAlgorithm string
AccessTokenHash string
NotAfter time.Time
}
func loadAccessEngineAdmission(path string) (AccessEngineAdmission, error) {
var a AccessEngineAdmission
if !filepath.IsAbs(path) {
return a, errApplicationDecision
}
f, e := os.Open(path)
if e != nil {
return a, errApplicationDecision
}
defer f.Close()
stat, e := f.Stat()
if e != nil || !stat.Mode().IsRegular() || stat.Mode().Perm()&0022 != 0 {
return a, errApplicationDecision
}
raw, e := io.ReadAll(io.LimitReader(f, 1<<20+1))
if e != nil || len(raw) > 1<<20 {
return a, errApplicationDecision
}
d := json.NewDecoder(strings.NewReader(string(raw)))
d.DisallowUnknownFields()
if d.Decode(&a) != nil {
return a, errApplicationDecision
}
if _, e = d.Token(); e != io.EOF {
return a, errApplicationDecision
}
return a, nil
}
// VerifyApplicationIdentity validates the ID token independently of Goth's
// decode/claim checks and of the UserInfo merge. Keys and accepted algorithm,
// issuer, audience and type are admitted separately; none come from the JWT URL.
func VerifyApplicationIdentity(raw string, a AccessEngineAdmission, now time.Time) (VerifiedApplicationIdentity, error) {
var out VerifiedApplicationIdentity
age, e := time.ParseDuration(a.MaxIdentityAge)
authAge, authErr := time.ParseDuration(a.MaxAuthenticationAge)
if e != nil || authErr != nil || authAge <= 0 || age <= 0 || a.TenantSourceClaim == "" || a.Issuer == "" || a.Audience == "" || a.TokenType == "" || len(a.Algorithms) == 0 || len(a.IdentityKeys) == 0 || a.TenantClaim == "" || a.PrincipalTypeClaim == "" || a.AssuranceClaim == "" {
return out, errApplicationDecision
}
for _, alg := range a.Algorithms {
if alg != "RS256" && alg != "RS384" && alg != "RS512" && alg != "ES256" && alg != "ES384" && alg != "ES512" && alg != "EdDSA" {
return out, errApplicationDecision
}
}
claims := jwt.MapClaims{}
token, e := jwt.ParseWithClaims(raw, claims, func(t *jwt.Token) (any, error) {
if typ, ok := t.Header["typ"].(string); !ok || typ != a.TokenType {
return nil, errApplicationDecision
}
kid, ok := t.Header["kid"].(string)
if !ok || kid == "" {
return nil, errApplicationDecision
}
text, ok := a.IdentityKeys[kid]
if !ok {
return nil, errApplicationDecision
}
block, rest := pem.Decode([]byte(text))
if block == nil || block.Type != "PUBLIC KEY" || len(strings.TrimSpace(string(rest))) != 0 {
return nil, errApplicationDecision
}
key, e := x509.ParsePKIXPublicKey(block.Bytes)
if e != nil {
return nil, errApplicationDecision
}
var pub crypto.PublicKey = key
return pub, nil
}, jwt.WithValidMethods(a.Algorithms), jwt.WithIssuer(a.Issuer), jwt.WithAudience(a.Audience), jwt.WithExpirationRequired(), jwt.WithIssuedAt(), jwt.WithJSONNumber(), jwt.WithTimeFunc(func() time.Time { return now }))
if e != nil || token == nil || !token.Valid {
return out, errApplicationDecision
}
// The admitted KeyCape ID-token profile never carries access-grant fields.
// Both producer tokens use typ JWT, so the header alone cannot separate them.
for _, field := range []string{"jti", "scope"} {
if _, exists := claims[field]; exists {
return out, errApplicationDecision
}
}
subject, e := claims.GetSubject()
if e != nil || subject == "" {
return out, errApplicationDecision
}
issued, e := claims.GetIssuedAt()
if e != nil || issued == nil || issued.Time.After(now) || now.Sub(issued.Time) > age {
return out, errApplicationDecision
}
end, e := claims.GetExpirationTime()
if e != nil || end == nil || !now.Before(end.Time) {
return out, errApplicationDecision
}
aud, e := claims.GetAudience()
if e != nil {
return out, errApplicationDecision
}
if authorizedParty, present := claims["azp"]; present {
if azp, ok := authorizedParty.(string); !ok || azp != a.Audience {
return out, errApplicationDecision
}
} else if len(aud) > 1 {
return out, errApplicationDecision
}
value := func(name string) (string, bool) { v, ok := claims[name].(string); return v, ok && validClaimLiteral(v) }
tenant, ok := value(a.TenantClaim)
if !ok || a.ApplicationTenant == "" || tenant != a.ApplicationTenant {
return out, errApplicationDecision
}
principal, ok := value(a.PrincipalTypeClaim)
if !ok {
return out, errApplicationDecision
}
tenantSource, ok := value(a.TenantSourceClaim)
if !ok || (tenantSource != "directory" && tenantSource != "registration" && tenantSource != "default") {
return out, errApplicationDecision
}
assurance, assuranceData, authenticatedAt, authenticationEnd, err := verifiedKeyCapeAssurance(claims[a.AssuranceClaim], issued.Time, now, authAge)
if err != nil {
return out, errApplicationDecision
}
validUntil := end.Time
for _, bound := range []time.Time{issued.Time.Add(age), authenticationEnd} {
if bound.Before(validUntil) {
validUntil = bound
}
}
if !now.Before(validUntil) {
return out, errApplicationDecision
}
accessHash, _ := claims["at_hash"].(string)
return VerifiedApplicationIdentity{Issuer: a.Issuer, Subject: subject, Tenant: tenant, PrincipalType: api.SubjectType(principal), Assurance: assurance, TenantSource: tenantSource, AssuranceData: assuranceData, AuthenticatedAt: authenticatedAt, IssuedAt: issued.Time, SigningAlgorithm: token.Method.Alg(), AccessTokenHash: accessHash, NotAfter: validUntil}, nil
}
func (source *Source) authorizeAccessEngine(ctx context.Context, user goth.User) error {
// The guarded target extension cannot silently fall back to a local group
// decision when admission, keys, facts, PDP or caller authentication is absent.
if source.Provider != "openidConnect" || ValidateRequiredClaimValues(source.RequiredClaimName, source.RequiredClaimValue, source.RequiredClaimValues) != nil {
return errApplicationDecision
}
a, e := loadAccessEngineAdmission(os.Getenv("FORGEJO_ACCESS_ENGINE_ADMISSION_FILE"))
if e != nil || source.authSource == nil || a.SourceName != source.authSource.Name || a.Audience != source.ClientID {
return errApplicationDecision
}
identity, e := VerifyApplicationIdentity(user.IDToken, a, time.Now().UTC())
if e != nil {
return errApplicationDecision
}
// RequireCurrentUserInfoClaim has refused missing/malformed current groups
// before Goth can merge. The validated ID subject must still match that join.
sub, ok := user.RawData["sub"].(string)
if !ok || sub != identity.Subject || user.UserID != identity.Subject {
return errApplicationDecision
}
groups, ok := strictClaimValues(user.RawData[source.RequiredClaimName])
if !ok {
return errApplicationDecision
}
current, e := verifyCurrentApplicationToken(ctx, user.AccessToken, a, identity)
if e != nil {
return errApplicationDecision
}
client, e := decisionpep.New(a.Decision)
if e != nil {
return errApplicationDecision
}
request := api.CheckRequest{Tenant: identity.Tenant,
Subject: api.SubjectRef{ID: identity.Subject, Type: identity.PrincipalType, Tenant: identity.Tenant,
Attributes: map[string]any{"issuer": identity.Issuer, "assurance": identity.Assurance, "current_userinfo_groups": groups}},
Action: "application.access", Resource: api.ResourceRef{ID: "application:forgejo", Type: "application", System: "forgejo", Tenant: identity.Tenant},
Context: map[string]any{"identity_issuer": identity.Issuer, "identity_subject": identity.Subject, "identity_tenant": identity.Tenant, "identity_principal_type": string(identity.PrincipalType), "identity_assurance": identity.Assurance, "identity_assurance_claim": identity.AssuranceData, "identity_authenticated_at": identity.AuthenticatedAt.UTC().Format(time.RFC3339Nano), "identity_tenant_source": identity.TenantSource, "identity_not_after": identity.NotAfter.UTC().Format(time.RFC3339Nano), "current_userinfo_groups": groups, "required_groups_any_of": source.RequiredClaimValues, "source_name": a.SourceName, "source_id": source.authSource.ID}}
if _, e = client.Check(ctx, request); e != nil {
return errApplicationDecision
}
// A slow decision must not turn a revoked authentication into a sign-in.
current, e = verifyCurrentApplicationToken(ctx, user.AccessToken, a, identity)
if e != nil {
return errApplicationDecision
}
if !time.Now().UTC().Before(identity.NotAfter) || !time.Now().UTC().Before(current.NotAfter) {
return errApplicationDecision
}
return nil
}

View file

@ -0,0 +1,54 @@
package oauth2
import (
"encoding/json"
"time"
)
// Validate the actual IAM v0.3 object only after issuer JOSE verification. These
// are identity facts, not an access verdict: aal1/service facts still reach the
// PDP, which owns the application's assurance and principal policy. Preserve the
// full object and its integer timestamp in the signed decision request.
func verifiedKeyCapeAssurance(raw any, issued, now time.Time, maxAge time.Duration) (string, map[string]any, time.Time, time.Time, error) {
refuse := func() (string, map[string]any, time.Time, time.Time, error) {
return "", nil, time.Time{}, time.Time{}, errApplicationDecision
}
claim, ok := raw.(map[string]any)
if !ok || len(claim) != 5 {
return refuse()
}
level, ok := claim["level"].(string)
if !ok || (level != "aal1" && level != "aal2" && level != "service") {
return refuse()
}
mfa, ok := claim["mfa"].(bool)
if !ok || mfa != (level == "aal2") || claim["source"] != "key-cape" {
return refuse()
}
methods, ok := claim["methods"].([]any)
if !ok || len(methods) == 0 {
return refuse()
}
seen := map[string]bool{}
for _, method := range methods {
value, ok := method.(string)
if !ok || !validClaimLiteral(value) || seen[value] {
return refuse()
}
seen[value] = true
}
number, ok := claim["at"].(json.Number)
if !ok {
return refuse()
}
seconds, err := number.Int64()
if err != nil || seconds <= 0 || seconds > issued.Unix() || seconds > now.Unix() {
return refuse()
}
authenticated := time.Unix(seconds, 0).UTC()
end := authenticated.Add(maxAge)
if maxAge <= 0 || !now.Before(end) {
return refuse()
}
return level, claim, authenticated, end, nil
}

View file

@ -0,0 +1,75 @@
package oauth2
import (
"encoding/json"
"testing"
"time"
)
func TestAccessEngineKeyCapeAssuranceRefusals(t *testing.T) {
cases := map[string]func(map[string]any, time.Time){
"stale-authentication": func(c map[string]any, now time.Time) { c["at"] = now.Add(-10 * time.Minute).Unix() },
"future-authentication": func(c map[string]any, now time.Time) { c["at"] = now.Add(time.Minute).Unix() },
"missing-at": func(c map[string]any, _ time.Time) { delete(c, "at") },
"string-at": func(c map[string]any, _ time.Time) { c["at"] = "recent" },
"fractional-at": func(c map[string]any, now time.Time) { c["at"] = float64(now.Unix()) - 1.5 },
"integer-mfa": func(c map[string]any, _ time.Time) { c["mfa"] = 1 },
"inconsistent-mfa": func(c map[string]any, _ time.Time) { c["mfa"] = false },
"wrong-source": func(c map[string]any, _ time.Time) { c["source"] = "userinfo" },
"wrong-level": func(c map[string]any, _ time.Time) { c["level"] = "verified" },
"object-methods": func(c map[string]any, _ time.Time) { c["methods"] = map[string]any{"pwd": true} },
"empty-methods": func(c map[string]any, _ time.Time) { c["methods"] = []string{} },
"mixed-methods": func(c map[string]any, _ time.Time) { c["methods"] = []any{"pwd", 1} },
"duplicate-methods": func(c map[string]any, _ time.Time) { c["methods"] = []string{"pwd", "pwd"} },
"unknown-object-field": func(c map[string]any, _ time.Time) { c["verified"] = true },
}
for name, mutate := range cases {
t.Run(name, func(t *testing.T) {
admission, key, claims, now := identityFixture(t)
mutate(claims["assurance"].(map[string]any), now)
if _, err := VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), admission, now); err == nil {
t.Fatal("malformed or stale issuer assurance accepted")
}
})
}
for _, source := range []string{"", "assumed"} {
admission, key, claims, now := identityFixture(t)
claims["tenant_source"] = source
if _, err := VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), admission, now); err == nil {
t.Fatal("missing or unknown tenant provenance accepted")
}
}
}
func TestAccessEngineKeyCapeIdentityFactsAndFreshnessBounds(t *testing.T) {
admission, key, claims, now := identityFixture(t)
admission.MaxAuthenticationAge = "5s"
claims["assurance"].(map[string]any)["at"] = now.Add(-3 * time.Second).Unix()
identity, err := VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), admission, now)
if err != nil {
t.Fatal(err)
}
if !identity.NotAfter.Equal(now.Add(2*time.Second)) || !identity.AuthenticatedAt.Equal(now.Add(-3*time.Second)) || identity.TenantSource != "directory" {
t.Fatal("snapshot overstated authentication freshness or tenant provenance")
}
if _, ok := identity.AssuranceData["at"].(json.Number); !ok || identity.AssuranceData["mfa"] != true || identity.AssuranceData["source"] != "key-cape" {
t.Fatal("source assurance object lost its actual wire types")
}
admission.MaxIdentityAge = "2s"
identity, err = VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), admission, now)
if err != nil || !identity.NotAfter.Equal(now.Add(time.Second)) {
t.Fatal("snapshot overstated token-age validity", err)
}
}
func TestAccessEngineKeyCapeLowerAssuranceIsAnIdentityFact(t *testing.T) {
admission, key, claims, now := identityFixture(t)
claim := claims["assurance"].(map[string]any)
claim["level"] = "aal1"
claim["mfa"] = false
claim["methods"] = []string{"pwd"}
identity, err := VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), admission, now)
if err != nil || identity.Assurance != "aal1" || identity.AssuranceData["mfa"] != false {
t.Fatal("identity parser substituted an application authorization policy", err)
}
}

View file

@ -0,0 +1,34 @@
package oauth2
import "testing"
func TestAccessEngineAuthorizedPartyRefusesAnotherClient(t *testing.T) {
for name, value := range map[string]any{
"other-client": "other-client",
"number": 17,
"array": []string{"forgejo"},
"empty": "",
} {
t.Run(name, func(t *testing.T) {
admission, key, claims, now := identityFixture(t)
claims["azp"] = value
token := signedIdentity(t, key, claims, nil)
if _, err := VerifyApplicationIdentity(token, admission, now); err == nil {
t.Fatal("a valid signature and audience concealed an invalid authorized party")
}
})
}
}
func TestAccessEngineAuthorizedPartyAcceptsAdmittedClient(t *testing.T) {
for _, multiple := range []bool{false, true} {
admission, key, claims, now := identityFixture(t)
claims["azp"] = admission.Audience
if multiple {
claims["aud"] = []string{admission.Audience, "another-resource"}
}
if _, err := VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), admission, now); err != nil {
t.Fatal("admitted authorized party refused", err)
}
}
}

View file

@ -0,0 +1,207 @@
package oauth2
import (
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"encoding/json"
"encoding/pem"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"reflect"
"strings"
"sync/atomic"
"testing"
"time"
authmodel "forgejo.org/models/auth"
"github.com/golang-jwt/jwt/v5"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
"github.com/netkingdom/access-engine/pkg/api"
"github.com/netkingdom/access-engine/pkg/decisionpep"
"github.com/netkingdom/access-engine/testsupport/signfixture"
)
// This fixture substitutes only Goth's completed authentication result. The
// actual Source.Callback, JOSE verifier, HTTPS PEP client and producer signature
// implementation execute. Actual issuer login and native admission are separate.
func callbackFixture(t *testing.T, group, mode string) (*Source, *goth.User, *atomic.Int64) {
return callbackFixtureWithIdentity(t, group, mode, nil, "")
}
func callbackFixtureWithIdentity(t *testing.T, group, mode string, suppliedAdmission *AccessEngineAdmission, suppliedToken string, suppliedAccess ...string) (*Source, *goth.User, *atomic.Int64) {
t.Helper()
admission, identityKey, claims, _ := identityFixture(t)
var accessToken string
var currentState *callbackCurrentState
if suppliedAdmission == nil {
currentState = newCallbackCurrentFixture(t, &admission, claims)
accessToken = currentState.token
} else if len(suppliedAccess) == 1 {
accessToken = suppliedAccess[0]
}
identityToken := signedIdentity(t, identityKey, claims, nil)
if currentState != nil {
identityToken = currentState.signIdentity(t, claims)
}
if suppliedAdmission != nil {
admission, identityToken = *suppliedAdmission, suppliedToken
}
verified, err := VerifyApplicationIdentity(identityToken, admission, time.Now().UTC())
if err != nil {
t.Fatal("callback fixture identity invalid")
}
pub, key, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
calls := new(atomic.Int64)
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls.Add(1)
if r.URL.Path != "/v1/check" || r.Method != http.MethodPost || r.Header.Get("Authorization") != "Bearer synthetic-caller" || r.Header.Get("Access-Engine-JSON-Profile") != decisionpep.JSONProfile {
t.Error("incorrect authenticated PDP request")
w.WriteHeader(403)
return
}
var request api.CheckRequest
decoder := json.NewDecoder(r.Body)
decoder.UseNumber()
if err := decoder.Decode(&request); err != nil {
t.Error(err)
w.WriteHeader(400)
return
}
c := request.Context
if request.Subject.ID != verified.Subject || request.Subject.Tenant != verified.Tenant || request.Subject.Type != verified.PrincipalType || request.Action != "application.access" || request.Resource.ID != "application:forgejo" || c["identity_issuer"] != admission.Issuer || c["identity_assurance"] != verified.Assurance || c["identity_tenant"] != verified.Tenant || c["source_id"] != json.Number("17") || !reflect.DeepEqual(c["identity_assurance_claim"], verified.AssuranceData) || c["identity_authenticated_at"] != verified.AuthenticatedAt.UTC().Format(time.RFC3339Nano) || c["identity_tenant_source"] != verified.TenantSource {
t.Error("PDP request did not use authenticated identity and source")
w.WriteHeader(400)
return
}
now := time.Now().UTC()
envelope := api.DecisionEnvelope{ID: "synthetic-decision", RequestID: request.ID, Effect: api.DecisionEffectAllow, MatchedPolicyVersion: "v1", Subject: request.Subject, Resource: request.Resource, Binding: api.NewDecisionBindingFor(request, request), Provenance: api.DecisionProvenance{Evaluator: "flex-auth/local", Mode: "standalone/" + decisionpep.JSONProfile, PolicyPackage: "forgejo.application-access", PolicyVersion: "v1", PolicyPackageDigest: "sha256:" + strings.Repeat("1", 64), RegistrySnapshotDigest: "sha256:" + strings.Repeat("2", 64), Caller: &api.CallerProvenance{Mode: "enforce", Principal: "system:serviceaccount:synthetic:forgejo", Audience: "access-engine", NotAfter: now.Add(time.Minute).Format(time.RFC3339Nano)}}}
api.CompleteDecision(&envelope, request, api.DecisionCompletion{AllowTTL: "10s", Now: now})
switch mode {
case "revoke-current":
currentState.mode.Store("inactive")
case "deny":
envelope.Effect = api.DecisionEffectDeny
case "wrong-binding":
envelope.Binding.Subject.ID = "other"
case "wrong-pin":
envelope.Provenance.PolicyPackageDigest = "sha256:" + strings.Repeat("3", 64)
case "expired":
envelope.Lifetime.ExpiresAt = now.Add(-time.Hour).Format(time.RFC3339Nano)
}
if mode != "unsigned" {
signingKey := key
if mode == "wrong-key" {
_, signingKey, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Error(err)
return
}
}
if err := signfixture.Sign(&envelope, signingKey, "synthetic-pdp"); err != nil {
t.Error(err)
return
}
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(envelope); err != nil {
t.Error(err)
}
}))
t.Cleanup(server.Close)
dir := t.TempDir()
ca := filepath.Join(dir, "ca.pem")
token := filepath.Join(dir, "caller")
config := filepath.Join(dir, "admission.json")
if err := os.WriteFile(ca, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw}), 0600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(token, []byte("synthetic-caller"), 0600); err != nil {
t.Fatal(err)
}
admission.SourceName = "synthetic-source"
admission.Decision = decisionpep.Admission{Endpoint: server.URL + "/v1/check", CAFile: ca, TokenFile: token, Keys: map[string]string{"synthetic-pdp": base64.RawURLEncoding.EncodeToString(pub)}, Evaluator: "flex-auth/local", PolicyPackage: "forgejo.application-access", PolicyVersion: "v1", PolicyDigest: "sha256:" + strings.Repeat("1", 64), RegistryDigest: "sha256:" + strings.Repeat("2", 64), CallerPrincipal: "system:serviceaccount:synthetic:forgejo", CallerAudience: "access-engine", MaxTTL: "30s", MaxClockSkew: "2s"}
raw, err := json.Marshal(admission)
if err != nil {
t.Fatal(err)
}
if err = os.WriteFile(config, raw, 0600); err != nil {
t.Fatal(err)
}
t.Setenv("FORGEJO_ACCESS_ENGINE_ADMISSION_FILE", config)
source := &Source{Provider: "openidConnect", ClientID: admission.Audience, RequiredClaimName: "groups", RequiredClaimValues: []string{"forge-admins", "forge-coulomb-owners", "forge-coulomb-readers"}}
source.authSource = &authmodel.Source{ID: 17, Name: admission.SourceName}
user := &goth.User{UserID: verified.Subject, IDToken: identityToken, AccessToken: accessToken, RawData: map[string]any{"sub": verified.Subject, "groups": []string{group}}}
previous := gothic.CompleteUserAuth
gothic.CompleteUserAuth = func(http.ResponseWriter, *http.Request) (goth.User, error) { return *user, nil }
t.Cleanup(func() { gothic.CompleteUserAuth = previous })
return source, user, calls
}
func TestAccessEngineActualCallbackAllowsAuthenticatedDecision(t *testing.T) {
for _, group := range []string{"forge-admins", "forge-coulomb-owners", "forge-coulomb-readers"} {
t.Run(group, func(t *testing.T) {
source, user, calls := callbackFixture(t, group, "allow")
// Mutable UserInfo cannot supply identity assurance, issuer or tenant.
user.RawData["acr"] = "aal1"
user.RawData["assurance"] = map[string]any{"level": "aal1", "source": "attacker"}
user.RawData["tenant_source"] = "attacker"
user.RawData["tenant"] = "tenant:attacker"
user.RawData["iss"] = "https://attacker.invalid"
got, err := source.Callback(httptest.NewRequest(http.MethodGet, "https://forgejo.invalid/callback", nil), httptest.NewRecorder(), "")
if err != nil || got.UserID != user.UserID || calls.Load() != 1 {
t.Fatalf("callback result: err=%v requests=%d", err, calls.Load())
}
})
}
}
func TestAccessEngineActualCallbackRefusesUnverifiedPDP(t *testing.T) {
for _, mode := range []string{"deny", "unsigned", "wrong-key", "wrong-pin", "wrong-binding", "expired"} {
t.Run(mode, func(t *testing.T) {
source, _, calls := callbackFixture(t, "forge-admins", mode)
got, err := source.Callback(httptest.NewRequest(http.MethodGet, "https://forgejo.invalid/callback", nil), httptest.NewRecorder(), "")
if err == nil || got.UserID != "" || got.IDToken != "" || calls.Load() != 1 {
t.Fatalf("unverified decision escaped callback: requests=%d err=%v", calls.Load(), err)
}
})
}
}
func TestAccessEngineActualCallbackChecksIdentityBeforePDP(t *testing.T) {
cases := map[string]func(*Source, *goth.User){
"missing-token": func(_ *Source, u *goth.User) { u.IDToken = "" },
"mismatched-user": func(_ *Source, u *goth.User) { u.UserID = "other" },
"mismatched-subject": func(_ *Source, u *goth.User) { u.RawData["sub"] = "other" },
"missing-groups": func(_ *Source, u *goth.User) { delete(u.RawData, "groups") },
"wrong-source": func(s *Source, _ *goth.User) { s.authSource.Name = "other" },
"wrong-provider": func(s *Source, _ *goth.User) { s.Provider = "other" },
"wrong-audience": func(s *Source, _ *goth.User) { s.ClientID = "other" },
"missing-admission": func(_ *Source, _ *goth.User) { t.Setenv("FORGEJO_ACCESS_ENGINE_ADMISSION_FILE", "") },
"expired-token": func(_ *Source, u *goth.User) {
_, key, claims, now := identityFixture(t)
claims["exp"] = now.Add(-time.Hour).Unix()
u.IDToken = signedIdentity(t, key, claims, nil)
},
"untrusted-identity": func(_ *Source, u *goth.User) {
token := jwt.NewWithClaims(jwt.SigningMethodNone, jwt.MapClaims{"sub": "user:synthetic"})
u.IDToken, _ = token.SignedString(jwt.UnsafeAllowNoneSignatureType)
},
}
for name, mutate := range cases {
t.Run(name, func(t *testing.T) {
source, user, calls := callbackFixture(t, "forge-admins", "allow")
mutate(source, user)
got, err := source.Callback(httptest.NewRequest(http.MethodGet, "https://forgejo.invalid/callback", nil), httptest.NewRecorder(), "")
if err == nil || got.UserID != "" || calls.Load() != 0 {
t.Fatalf("unverified identity reached PDP: requests=%d err=%v", calls.Load(), err)
}
})
}
}

View file

@ -0,0 +1,46 @@
package oauth2
import (
"bytes"
"context"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"time"
"keycape/pkg/tokenstatus"
)
// This consumer joins its independently verified ID token to the producer's
// current access-grant observation. It supplies no token registry or evaluator.
// Native credential/session continuation retains its separate owner profile.
func verifyCurrentApplicationToken(ctx context.Context, raw string, a AccessEngineAdmission, identity VerifiedApplicationIdentity) (tokenstatus.CurrentToken, error) {
var refused tokenstatus.CurrentToken
digest := sha256.Sum256([]byte(raw))
if identity.SigningAlgorithm != "RS256" || identity.AccessTokenHash != base64.RawURLEncoding.EncodeToString(digest[:16]) {
return refused, errApplicationDecision
}
if a.CurrentToken.Issuer != identity.Issuer || a.CurrentToken.ClientID != a.Audience {
return refused, errApplicationDecision
}
client, err := tokenstatus.NewOnlineClient(a.CurrentToken)
if err != nil {
return refused, errApplicationDecision
}
current, err := client.Check(ctx, raw, tokenstatus.ExpectedIdentity{
Subject: identity.Subject, Tenant: identity.Tenant, PrincipalType: string(identity.PrincipalType),
})
if err != nil || !current.IssuedAt.Equal(identity.IssuedAt) {
return refused, errApplicationDecision
}
facts := current.Claims()
actual, err := json.Marshal(facts[a.AssuranceClaim])
expected, expectedErr := json.Marshal(identity.AssuranceData)
if err != nil || expectedErr != nil || !bytes.Equal(actual, expected) || facts[a.TenantSourceClaim] != identity.TenantSource {
return refused, errApplicationDecision
}
if !time.Now().UTC().Before(identity.NotAfter) {
return refused, errApplicationDecision
}
return current, nil
}

View file

@ -0,0 +1,179 @@
package oauth2
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"math/big"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"sync/atomic"
"testing"
"github.com/golang-jwt/jwt/v5"
"keycape/pkg/tokenstatus"
)
// This unit status responder is synthetic. Separate actual-issuer integration
// tests exercise the producer's real PKCE, introspection and revocation handlers.
type callbackCurrentState struct {
key *rsa.PrivateKey
claims jwt.MapClaims
token string
mode atomic.Value
calls atomic.Int64
}
func (s *callbackCurrentState) sign(t *testing.T) string {
t.Helper()
token := jwt.NewWithClaims(jwt.SigningMethodRS256, s.claims)
token.Header["kid"] = "access-fixture"
raw, err := token.SignedString(s.key)
if err != nil {
t.Fatal("synthetic access signing failed")
}
return raw
}
func (s *callbackCurrentState) signIdentity(t *testing.T, claims jwt.MapClaims) string {
t.Helper()
id := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
id.Header["kid"] = "access-fixture"
raw, err := id.SignedString(s.key)
if err != nil {
t.Fatal("synthetic ID signing failed")
}
return raw
}
func newCallbackCurrentFixture(t *testing.T, a *AccessEngineAdmission, idClaims jwt.MapClaims) *callbackCurrentState {
t.Helper()
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal("synthetic access key failed")
}
f := &callbackCurrentState{key: key}
f.mode.Store("")
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
f.calls.Add(1)
client, secret, ok := r.BasicAuth()
if !ok || client != a.Audience || secret != "synthetic-current" || r.URL.Path != "/introspect" || r.Method != http.MethodPost || r.ParseForm() != nil || r.Form.Get("token") != f.token {
w.WriteHeader(http.StatusForbidden)
return
}
if f.mode.Load().(string) == "unavailable" {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
if f.mode.Load().(string) == "inactive" {
_ = json.NewEncoder(w).Encode(map[string]any{"active": false})
return
}
status := map[string]any{"active": true, "client_id": a.Audience, "token_type": "Bearer", "iss": a.Issuer, "sub": f.claims["sub"], "aud": a.CurrentToken.Audience, "jti": f.claims["jti"], "scope": f.claims["scope"], "iat": f.claims["iat"], "exp": f.claims["exp"]}
if f.mode.Load().(string) == "wrong-status" {
status["jti"] = "other"
}
_ = json.NewEncoder(w).Encode(status)
}))
t.Cleanup(server.Close)
a.Issuer = server.URL
idClaims["iss"] = a.Issuer
raw, _ := json.Marshal(idClaims)
if json.Unmarshal(raw, &f.claims) != nil {
t.Fatal("synthetic claims failed")
}
f.claims["jti"] = base64.RawURLEncoding.EncodeToString(make([]byte, 32))
f.claims["scope"] = "openid profile groups"
f.token = f.sign(t)
digest := sha256.Sum256([]byte(f.token))
idClaims["at_hash"] = base64.RawURLEncoding.EncodeToString(digest[:16])
der, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
t.Fatal("synthetic identity public key failed")
}
a.Algorithms = []string{"RS256"}
a.IdentityKeys = map[string]string{"access-fixture": string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}))}
dir := t.TempDir()
ca, keys, secret := filepath.Join(dir, "current-ca.pem"), filepath.Join(dir, "current-jwks.json"), filepath.Join(dir, "current-client")
jwks := map[string]any{"keys": []any{map[string]any{"kty": "RSA", "use": "sig", "alg": "RS256", "kid": "access-fixture", "n": base64.RawURLEncoding.EncodeToString(key.N.Bytes()), "e": base64.RawURLEncoding.EncodeToString(big.NewInt(int64(key.E)).Bytes())}}}
public, _ := json.Marshal(jwks)
if os.WriteFile(ca, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw}), 0644) != nil || os.WriteFile(keys, public, 0644) != nil || os.WriteFile(secret, []byte("synthetic-current"), 0600) != nil {
t.Fatal("synthetic admission failed")
}
a.CurrentToken = tokenstatus.OnlineAdmission{Issuer: a.Issuer, Audience: a.Audience, ClientID: a.Audience, CAFile: ca, JWKSFile: keys, SecretFile: secret, MaxTokenAge: "5m"}
return f
}
func TestAccessEngineCurrentTokenRejectsBeforeDecision(t *testing.T) {
for _, mode := range []string{"missing", "id-as-access", "subject", "tenant", "principal", "assurance", "authentication-time", "tenant-source", "issued-at", "inactive", "unavailable", "wrong-status", "missing-current-admission", "wrong-client", "withdrawn-key", "mismatched-grant", "missing-at-hash"} {
t.Run(mode, func(t *testing.T) {
a, _, claims, _ := identityFixture(t)
state := newCallbackCurrentFixture(t, &a, claims)
switch mode {
case "mismatched-grant":
state.claims["jti"] = base64.RawURLEncoding.EncodeToString([]byte("12345678901234567890123456789012"))
case "subject":
state.claims["sub"] = "other"
case "tenant":
state.claims["tenant"] = "other"
case "principal":
state.claims["principal_type"] = "service"
case "assurance":
state.claims["assurance"].(map[string]any)["level"] = "aal1"
case "authentication-time":
state.claims["assurance"].(map[string]any)["at"] = state.claims["iat"].(float64) - 1
case "tenant-source":
state.claims["tenant_source"] = "default"
case "issued-at":
state.claims["iat"] = state.claims["iat"].(float64) - 1
case "inactive", "unavailable", "wrong-status":
state.mode.Store(mode)
case "missing-current-admission":
a.CurrentToken = tokenstatus.OnlineAdmission{}
case "wrong-client":
a.CurrentToken.ClientID = "other"
case "withdrawn-key":
if os.WriteFile(a.CurrentToken.JWKSFile, []byte(`{"keys":[]}`), 0644) != nil {
t.Fatal("fixture withdrawal failed")
}
}
state.token = state.sign(t)
if mode != "mismatched-grant" {
digest := sha256.Sum256([]byte(state.token))
claims["at_hash"] = base64.RawURLEncoding.EncodeToString(digest[:16])
}
if mode == "missing-at-hash" {
delete(claims, "at_hash")
}
id := state.signIdentity(t, claims)
access := state.token
if mode == "missing" {
access = ""
}
if mode == "id-as-access" {
access = id
}
source, _, calls := callbackFixtureWithIdentity(t, "forge-admins", "allow", &a, id, access)
got, err := source.Callback(httptest.NewRequest(http.MethodGet, "https://forgejo.invalid/callback", nil), httptest.NewRecorder(), "")
if err == nil || got.UserID != "" || calls.Load() != 0 {
t.Fatal("invalid current authentication reached the decision or callback")
}
})
}
}
func TestAccessEngineCurrentTokenRevokedDuringDecisionRefusesSignIn(t *testing.T) {
source, _, calls := callbackFixture(t, "forge-admins", "revoke-current")
got, err := source.Callback(httptest.NewRequest(http.MethodGet, "https://forgejo.invalid/callback", nil), httptest.NewRecorder(), "")
if err == nil || got.UserID != "" || calls.Load() != 1 {
t.Fatal("revocation during decision escaped the final current check")
}
}

View file

@ -0,0 +1,28 @@
package oauth2
import "testing"
func TestVerifiedApplicationIdentityRefusesAccessTokenGrantClaims(t *testing.T) {
for _, field := range []string{"jti", "scope"} {
t.Run(field, func(t *testing.T) {
a, key, claims, now := identityFixture(t)
claims[field] = "synthetic-access-grant"
if _, err := VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), a, now); err == nil {
t.Fatal("access-token grant fields were accepted as an ID token")
}
})
}
}
func TestVerifiedApplicationIdentityCannotChooseApplicationTenant(t *testing.T) {
a, key, claims, now := identityFixture(t)
claims["tenant"] = "tenant:another-valid-issuer-tenant"
if _, err := VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), a, now); err == nil {
t.Fatal("a valid signature let the caller select the application's tenant")
}
a.ApplicationTenant = ""
claims["tenant"] = "tenant:synthetic"
if _, err := VerifyApplicationIdentity(signedIdentity(t, key, claims, nil), a, now); err == nil {
t.Fatal("missing owner application tenant became a permissive default")
}
}

View file

@ -0,0 +1,101 @@
package oauth2
import (
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
)
func identityFixture(t *testing.T) (AccessEngineAdmission, ed25519.PrivateKey, jwt.MapClaims, time.Time) {
t.Helper()
pub, key, e := ed25519.GenerateKey(rand.Reader)
if e != nil {
t.Fatal(e)
}
der, e := x509.MarshalPKIXPublicKey(pub)
if e != nil {
t.Fatal(e)
}
now := time.Now().UTC().Truncate(time.Second)
a := AccessEngineAdmission{Issuer: "https://issuer.invalid", Audience: "forgejo", TokenType: "JWT", Algorithms: []string{"EdDSA"}, IdentityKeys: map[string]string{"synthetic": string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}))}, TenantClaim: "tenant", PrincipalTypeClaim: "principal_type", AssuranceClaim: "assurance", TenantSourceClaim: "tenant_source", MaxAuthenticationAge: "5m", MaxIdentityAge: "5m"}
c := jwt.MapClaims{"iss": a.Issuer, "aud": a.Audience, "sub": "user:synthetic", "iat": now.Add(-time.Second).Unix(), "exp": now.Add(time.Minute).Unix(), "tenant": "tenant:synthetic", "principal_type": "human", "tenant_source": "directory", "assurance": map[string]any{"level": "aal2", "mfa": true, "source": "key-cape", "methods": []string{"pwd", "otp"}, "at": now.Add(-time.Second).Unix()}}
a.ApplicationTenant = "tenant:synthetic"
return a, key, c, now
}
func signedIdentity(t *testing.T, key ed25519.PrivateKey, c jwt.MapClaims, mutate func(*jwt.Token)) string {
t.Helper()
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, c)
token.Header["kid"] = "synthetic"
token.Header["typ"] = "JWT"
if mutate != nil {
mutate(token)
}
raw, e := token.SignedString(key)
if e != nil {
t.Fatal(e)
}
return raw
}
func TestVerifiedApplicationIdentityActualJOSE(t *testing.T) {
a, key, c, now := identityFixture(t)
raw := signedIdentity(t, key, c, nil)
got, e := VerifyApplicationIdentity(raw, a, now)
if e != nil {
t.Fatal(e)
}
if got.Subject != "user:synthetic" || got.Tenant != "tenant:synthetic" || got.Assurance != "aal2" || got.Issuer != a.Issuer || string(got.PrincipalType) != "human" {
t.Fatal("identity did not come from verified token")
}
cases := map[string]func(jwt.MapClaims){"issuer": func(c jwt.MapClaims) { c["iss"] = "https://other.invalid" }, "audience": func(c jwt.MapClaims) { c["aud"] = "other" }, "expired": func(c jwt.MapClaims) { c["exp"] = now.Add(-time.Nanosecond).Unix() }, "future-issued": func(c jwt.MapClaims) { c["iat"] = now.Add(time.Minute).Unix() }, "old-issued": func(c jwt.MapClaims) { c["iat"] = now.Add(-10 * time.Minute).Unix() }, "no-exp": func(c jwt.MapClaims) { delete(c, "exp") }, "no-iat": func(c jwt.MapClaims) { delete(c, "iat") }, "no-tenant": func(c jwt.MapClaims) { delete(c, "tenant") }, "no-principal": func(c jwt.MapClaims) { delete(c, "principal_type") }, "no-assurance": func(c jwt.MapClaims) { delete(c, "assurance") }, "multi-audience-no-azp": func(c jwt.MapClaims) { c["aud"] = []string{"forgejo", "other"} }, "empty-subject": func(c jwt.MapClaims) { c["sub"] = "" }}
for name, change := range cases {
t.Run(name, func(t *testing.T) {
copy := jwt.MapClaims{}
for k, v := range c {
copy[k] = v
}
change(copy)
if _, e := VerifyApplicationIdentity(signedIdentity(t, key, copy, nil), a, now); e == nil {
t.Fatal("unverified identity accepted")
}
})
}
t.Run("wrong-key", func(t *testing.T) {
_, other, _ := ed25519.GenerateKey(rand.Reader)
if _, e := VerifyApplicationIdentity(signedIdentity(t, other, c, nil), a, now); e == nil {
t.Fatal("wrong key accepted")
}
})
t.Run("unknown-key", func(t *testing.T) {
if _, e := VerifyApplicationIdentity(signedIdentity(t, key, c, func(t *jwt.Token) { t.Header["kid"] = "other" }), a, now); e == nil {
t.Fatal("unknown key accepted")
}
})
t.Run("wrong-type", func(t *testing.T) {
if _, e := VerifyApplicationIdentity(signedIdentity(t, key, c, func(t *jwt.Token) { t.Header["typ"] = "at+jwt" }), a, now); e == nil {
t.Fatal("access token accepted as ID token")
}
})
t.Run("algorithm-confusion", func(t *testing.T) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
token.Header["kid"] = "synthetic"
token.Header["typ"] = "JWT"
raw, _ := token.SignedString([]byte(a.IdentityKeys["synthetic"]))
if _, e := VerifyApplicationIdentity(raw, a, now); e == nil {
t.Fatal("algorithm confusion accepted")
}
})
t.Run("none", func(t *testing.T) {
token := jwt.NewWithClaims(jwt.SigningMethodNone, c)
token.Header["kid"] = "synthetic"
token.Header["typ"] = "JWT"
raw, _ := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
if _, e := VerifyApplicationIdentity(raw, a, now); e == nil {
t.Fatal("unsigned identity accepted")
}
})
}

View file

@ -0,0 +1,68 @@
//go:build current_issuer_fixture
package oauth2
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/openidConnect"
"keycape/pkg/tokenstatus"
)
// Explicit isolated integration input only; never run against a deployed issuer.
func TestAccessEngineLiveFixtureCurrentIssuer(t *testing.T) {
var fixture struct {
IDToken string `json:"id_token"`
AccessToken string `json:"access_token"`
Key string `json:"public_key_pem"`
Tenant string `json:"tenant"`
Phase string `json:"phase"`
Current tokenstatus.OnlineAdmission `json:"current"`
}
raw, err := os.ReadFile(os.Getenv("FORGEJO_CURRENT_ISSUER_FIXTURE"))
if err != nil || json.Unmarshal(raw, &fixture) != nil {
t.Fatal("private isolated issuer fixture missing")
}
a, _, _, _ := identityFixture(t)
a.Issuer, a.Audience, a.ApplicationTenant = fixture.Current.Issuer, fixture.Current.ClientID, fixture.Tenant
a.Algorithms, a.IdentityKeys = []string{"RS256"}, map[string]string{"key-1": fixture.Key}
a.CurrentToken = fixture.Current
source, _, calls := callbackFixtureWithIdentity(t, "forge-admins", "allow", &a, fixture.IDToken, fixture.AccessToken)
if fixture.Phase == "active" {
ca, err := os.ReadFile(fixture.Current.CAFile)
roots := x509.NewCertPool()
if err != nil || !roots.AppendCertsFromPEM(ca) {
t.Fatal("private fixture trust missing")
}
transport := &http.Transport{Proxy: nil, TLSClientConfig: &tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12}}
t.Cleanup(transport.CloseIdleConnections)
oldClient := http.DefaultClient
http.DefaultClient = &http.Client{Transport: transport, Timeout: 5 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return errApplicationDecision }}
t.Cleanup(func() { http.DefaultClient = oldClient })
source.OpenIDConnectAutoDiscoveryURL = fixture.Current.Issuer + "/.well-known/openid-configuration"
provider, err := (&OpenIDProvider{}).CreateGothProvider("synthetic-source", "https://forgejo.invalid/callback", source)
if err != nil {
t.Fatal("actual provider discovery failed")
}
gothic.CompleteUserAuth = func(http.ResponseWriter, *http.Request) (goth.User, error) {
return provider.FetchUser(&openidConnect.Session{IDToken: fixture.IDToken, AccessToken: fixture.AccessToken, ExpiresAt: time.Now().Add(time.Minute)})
}
}
got, err := source.Callback(httptest.NewRequest(http.MethodGet, "https://forgejo.invalid/callback", nil), httptest.NewRecorder(), "")
if fixture.Phase == "active" {
if err != nil || got.UserID == "" || calls.Load() != 1 {
t.Fatal("actual issuer/provider/current-token/decision callback did not pass")
}
} else if err == nil || got.UserID != "" || got.IDToken != "" || calls.Load() != 0 {
t.Fatal("revoked, suspended or substituted grant escaped current callback check")
}
}

View file

@ -0,0 +1,79 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2
import (
"errors"
"html/template"
"forgejo.org/modules/log"
"forgejo.org/modules/setting"
"forgejo.org/modules/svg"
"github.com/markbates/goth"
"github.com/markbates/goth/providers/openidConnect"
)
// OpenIDProvider is a GothProvider for OpenID
type OpenIDProvider struct{}
// Name provides the technical name for this provider
func (o *OpenIDProvider) Name() string {
return "openidConnect"
}
// DisplayName returns the friendly name for this provider
func (o *OpenIDProvider) DisplayName() string {
return "OpenID Connect"
}
// IconHTML returns icon HTML for this provider
func (o *OpenIDProvider) IconHTML(size int) template.HTML {
return svg.RenderHTML("gitea-openid", size, "tw-mr-2")
}
// CreateGothProvider creates a GothProvider from this Provider
func (o *OpenIDProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) {
scopes := setting.OAuth2Client.OpenIDConnectScopes
if len(scopes) == 0 {
scopes = append(scopes, source.Scopes...)
}
provider, err := openidConnect.New(source.ClientID, source.ClientSecret, callbackURL, source.OpenIDConnectAutoDiscoveryURL, scopes...)
if err != nil {
log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err)
}
if err != nil {
return nil, err
}
if source.RequiredClaimValues != nil {
if err := ValidateRequiredClaimValues(source.RequiredClaimName, source.RequiredClaimValue, source.RequiredClaimValues); err != nil {
return nil, err
}
if provider.OpenIDConfig == nil || provider.SkipUserInfoRequest {
return nil, errors.New("required-claim-values needs current UserInfo")
}
guarded, err := RequireCurrentUserInfoClaim(provider.Client(), provider.OpenIDConfig.UserInfoEndpoint, source.RequiredClaimName)
if err != nil {
return nil, err
}
provider.HTTPClient = guarded
}
return provider, err
}
// CustomURLSettings returns the custom url settings for this provider
func (o *OpenIDProvider) CustomURLSettings() *CustomURLSettings {
return nil
}
func (o *OpenIDProvider) CanProvideSSHKeys() bool {
return true
}
var _ GothProvider = &OpenIDProvider{}
func init() {
RegisterGothProvider(&OpenIDProvider{})
}

View file

@ -0,0 +1,202 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strings"
"unicode"
"unicode/utf8"
)
const maxClaimValues = 1024
const maxUserInfoBytes = 1 << 20
// ValidateRequiredClaimValues validates only the opt-in list extension. A nil
// list retains every legacy single-value behavior. An explicitly empty list
// fails closed rather than turning a configured gate into claim-presence-only.
func ValidateRequiredClaimValues(name, legacy string, values []string) error {
if values == nil {
return nil
}
if !validClaimLiteral(name) || legacy != "" || len(values) == 0 || len(values) > 64 {
return errors.New("invalid or conflicting required-claim-values configuration")
}
seen := map[string]bool{}
for _, value := range values {
if !validClaimLiteral(value) || seen[value] {
return errors.New("invalid required-claim-values literal")
}
seen[value] = true
}
return nil
}
func validClaimLiteral(value string) bool {
return value != "" && len(value) <= 256 && utf8.ValidString(value) &&
value == strings.TrimSpace(value) && !strings.ContainsAny(value, "\x00\r\n,") &&
!strings.ContainsFunc(value, func(r rune) bool { return unicode.IsControl(r) || r == utf8.RuneError })
}
// strictClaimValues accepts only a well-formed claim array. No scalar parsing,
// coercion, regex or prefix matching is introduced into the new gate.
func strictClaimValues(raw any) ([]string, bool) {
var values []string
switch v := raw.(type) {
case []string:
values = v
case []any:
values = make([]string, len(v))
for i, value := range v {
s, ok := value.(string)
if !ok {
return nil, false
}
values[i] = s
}
default:
return nil, false
}
if len(values) > maxClaimValues {
return nil, false
}
for _, value := range values {
if !validClaimLiteral(value) {
return nil, false
}
}
return values, true
}
// MatchRequiredClaimValues evaluates the new consumer gate against current
// authenticated provider data; it does not mutate or manufacture issuer claims.
func MatchRequiredClaimValues(claims map[string]any, name, legacy string, required []string) bool {
if required == nil || ValidateRequiredClaimValues(name, legacy, required) != nil {
return false
}
raw, present := claims[name]
if !present {
return false
}
values, ok := strictClaimValues(raw)
if !ok {
return false
}
for _, value := range values {
for _, wanted := range required {
if value == wanted {
return true
}
}
}
return false
}
// RequireCurrentUserInfoClaim preserves the existing HTTP client settings and
// adds a strict response guard for this opt-in source only. A current UserInfo
// array must overwrite any stale ID-token claim. Missing/invalid data and HTTP
// failures cannot fall back to the ID-token map in Goth's merging code.
func RequireCurrentUserInfoClaim(client *http.Client, endpoint, claim string) (*http.Client, error) {
u, err := url.Parse(endpoint)
if err != nil || u.Scheme != "https" || u.Host == "" || u.User != nil || u.Fragment != "" || !validClaimLiteral(claim) {
return nil, errors.New("required-claim-values needs an HTTPS UserInfo endpoint")
}
if client == nil {
client = http.DefaultClient
}
result := *client
base := result.Transport
if base == nil {
base = http.DefaultTransport
}
result.Transport = &currentClaimTransport{base: base, endpoint: endpoint, claim: claim}
// Redirects could evade endpoint matching or send a bearer to another origin.
// They are unsupported for the new guarded provider; legacy clients are intact.
result.CheckRedirect = func(*http.Request, []*http.Request) error {
return errors.New("guarded OAuth redirects are not supported")
}
return &result, nil
}
type currentClaimTransport struct {
base http.RoundTripper
endpoint, claim string
}
func (t *currentClaimTransport) RoundTrip(req *http.Request) (*http.Response, error) {
response, err := t.base.RoundTrip(req)
if req.URL.String() != t.endpoint {
return response, err
}
if err != nil {
if response != nil && response.Body != nil {
response.Body.Close()
}
return nil, errors.New("current UserInfo lookup failed")
}
if response == nil || response.Body == nil {
return nil, errors.New("current UserInfo response missing")
}
defer response.Body.Close()
if req.Method != http.MethodGet || response.StatusCode != http.StatusOK {
return nil, errors.New("current UserInfo refused")
}
body, err := io.ReadAll(io.LimitReader(response.Body, maxUserInfoBytes+1))
if err != nil || len(body) > maxUserInfoBytes || !utf8.Valid(body) {
return nil, errors.New("current UserInfo response invalid")
}
// Reject duplicate top-level keys before Unmarshal can hide a conflicting
// groups or subject claim. json.RawMessage permits unrelated normal claims.
decoder := json.NewDecoder(bytes.NewReader(body))
token, err := decoder.Token()
if err != nil || token != json.Delim('{') {
return nil, errors.New("current UserInfo object required")
}
claims := map[string]json.RawMessage{}
for decoder.More() {
keyToken, err := decoder.Token()
if err != nil {
return nil, errors.New("current UserInfo key invalid")
}
key, ok := keyToken.(string)
if !ok {
return nil, errors.New("current UserInfo key invalid")
}
if _, duplicate := claims[key]; duplicate {
return nil, errors.New("current UserInfo duplicate claim")
}
var value json.RawMessage
if decoder.Decode(&value) != nil {
return nil, errors.New("current UserInfo value invalid")
}
claims[key] = value
}
if token, err = decoder.Token(); err != nil || token != json.Delim('}') {
return nil, errors.New("current UserInfo object invalid")
}
var trailing any
if decoder.Decode(&trailing) != io.EOF {
return nil, errors.New("current UserInfo trailing data")
}
raw, present := claims[t.claim]
if !present {
return nil, errors.New("current UserInfo required claim missing")
}
var value any
if json.Unmarshal(raw, &value) != nil {
return nil, errors.New("current UserInfo required claim invalid")
}
if _, ok := strictClaimValues(value); !ok {
return nil, errors.New("current UserInfo required claim array invalid")
}
// Return the original public response bytes to Goth. Subject equality and
// audience/issuer checks remain in its existing authenticated provider path.
response.Body = io.NopCloser(bytes.NewReader(body))
return response, nil
}

View file

@ -0,0 +1,144 @@
// Copyright 2026 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2
import (
"errors"
"io"
"net/http"
"reflect"
"strings"
"testing"
)
var caseValues = []string{"forge-admins", "forge-coulomb-owners", "forge-coulomb-readers"}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func mockUserInfo(body string, status int) *http.Client {
return &http.Client{Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: status, Header: http.Header{}, Body: io.NopCloser(strings.NewReader(body)), Request: r}, nil
})}
}
func TestRequiredClaimExactAnyOfAndNoMutation(t *testing.T) {
for _, group := range caseValues {
groups := []any{group, "unrelated"}
claims := map[string]any{"groups": groups, "other": "preserved"}
if !MatchRequiredClaimValues(claims, "groups", "", caseValues) {
t.Fatal("individual allowed group rejected")
}
if !reflect.DeepEqual(claims, map[string]any{"groups": groups, "other": "preserved"}) {
t.Fatal("claims mutated")
}
}
if !MatchRequiredClaimValues(map[string]any{"groups": append([]string(nil), caseValues...)}, "groups", "", caseValues) {
t.Fatal("multiple allowed groups rejected")
}
for _, raw := range []any{nil, true, 1, "forge-admins", "forge-admins,forge-coulomb-owners", []any{"forge-admins", 1}, []any{"forge-admins", nil}, []string{}, []any{}, []string{"forge-admins-lookalike"}, []string{" Forge-admins"}, []string{"forge-admins\n"}, []string{"Forge-admins"}, []string{"forge-admins,other"}, []string{"forge-admins", ""}} {
if MatchRequiredClaimValues(map[string]any{"groups": raw}, "groups", "", caseValues) {
t.Fatal("malformed/nonmatching claim accepted")
}
}
if MatchRequiredClaimValues(map[string]any{}, "groups", "", caseValues) {
t.Fatal("absent claim accepted")
}
if MatchRequiredClaimValues(map[string]any{"unrelated": []string{"forge-admins"}}, "groups", "", caseValues) {
t.Fatal("unrelated claim used")
}
}
func TestRequiredClaimConfigurationRefusalsAndLegacyIsolation(t *testing.T) {
if ValidateRequiredClaimValues("", "old arbitrary literal", nil) != nil {
t.Fatal("legacy config changed")
}
for _, cfg := range []struct {
name, legacy string
values []string
}{
{"groups", "", []string{}}, {"groups", "", []string{""}}, {"groups", "old", caseValues}, {"", "", caseValues},
{"groups", "", []string{"a", "a"}}, {"groups", "", []string{"a,b"}}, {"groups", "", []string{" a"}},
} {
if ValidateRequiredClaimValues(cfg.name, cfg.legacy, cfg.values) == nil {
t.Fatal("invalid config accepted")
}
}
if ValidateRequiredClaimValues("groups", "", caseValues) != nil {
t.Fatal("case config invalid")
}
}
func TestCurrentUserInfoGuardRejectsMissingMalformedAndUnavailable(t *testing.T) {
for _, body := range []string{
`{}`, `{"sub":"alice"}`, `{"groups":null}`, `{"groups":true}`, `{"groups":"forge-admins"}`,
`{"groups":["forge-admins",null]}`, `{"groups":["forge-admins",1]}`,
`{"groups":["forge-admins"],"groups":[]}`, `{"groups":[]} {}`, `[]`, `{"groups":[]`,
`{"groups":["forge-admins",""]}`, strings.Repeat(" ", maxUserInfoBytes+1),
} {
client, err := RequireCurrentUserInfoClaim(mockUserInfo(body, 200), "https://issuer.example/userinfo", "groups")
if err != nil {
t.Fatal(err)
}
response, err := client.Get("https://issuer.example/userinfo")
if err == nil {
response.Body.Close()
t.Fatal("invalid current claim did not fail closed")
}
}
client, _ := RequireCurrentUserInfoClaim(mockUserInfo(`{"groups":[]}`, 503), "https://issuer.example/userinfo", "groups")
if response, err := client.Get("https://issuer.example/userinfo"); err == nil {
response.Body.Close()
t.Fatal("non200 accepted")
}
broken := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) { return nil, errors.New("private backend error") })}
client, _ = RequireCurrentUserInfoClaim(broken, "https://issuer.example/userinfo", "groups")
if _, err := client.Get("https://issuer.example/userinfo"); err == nil || strings.Contains(err.Error(), "private backend") {
t.Fatal("lookup failure not contained")
}
}
func TestCurrentUserInfoEmptyArrayOverwritesAndGroupRemoval(t *testing.T) {
for _, body := range []string{`{"sub":"alice","groups":[]}`, `{"sub":"alice","groups":["forge-coulomb-readers"]}`} {
client, err := RequireCurrentUserInfoClaim(mockUserInfo(body, 200), "https://issuer.example/userinfo", "groups")
if err != nil {
t.Fatal(err)
}
response, err := client.Get("https://issuer.example/userinfo")
if err != nil {
t.Fatal(err)
}
b, _ := io.ReadAll(response.Body)
response.Body.Close()
if string(b) != body {
t.Fatal("response changed")
}
}
}
func TestCurrentUserInfoGuardIsOptInAndRedirectsRefuse(t *testing.T) {
original := mockUserInfo(`{"unrelated":true}`, 200)
guarded, err := RequireCurrentUserInfoClaim(original, "https://issuer.example/userinfo", "groups")
if err != nil {
t.Fatal(err)
}
response, err := original.Get("https://issuer.example/userinfo")
if err != nil {
t.Fatal("original client mutated")
}
response.Body.Close()
response, err = guarded.Get("https://issuer.example/unrelated")
if err != nil {
t.Fatal("unrelated endpoint changed")
}
response.Body.Close()
if guarded.CheckRedirect == nil || guarded.CheckRedirect(nil, nil) == nil {
t.Fatal("redirect can bypass guard")
}
for _, endpoint := range []string{"", "http://issuer.example/userinfo", "https://user:pass@issuer.example/userinfo", "https://issuer.example/userinfo#fragment"} {
if _, err := RequireCurrentUserInfoClaim(original, endpoint, "groups"); err == nil {
t.Fatal("invalid endpoint accepted")
}
}
}

View file

@ -0,0 +1,60 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2
import (
"strings"
"forgejo.org/models/auth"
"forgejo.org/modules/json"
)
// Source holds configuration for the OAuth2 login source.
type Source struct {
Provider string
ClientID string
ClientSecret string
OpenIDConnectAutoDiscoveryURL string
CustomURLMapping *CustomURLMapping
IconURL string
Scopes []string
AttributeSSHPublicKey string
RequiredClaimName string
RequiredClaimValues []string `json:",omitempty"`
RequiredClaimValue string
GroupClaimName string
AdminGroup string
GroupTeamMap string
GroupTeamMapRemoval bool
RestrictedGroup string
SkipLocalTwoFA bool `json:",omitempty"`
// reference to the authSource
authSource *auth.Source
}
// FromDB fills up an OAuth2Config from serialized format.
func (source *Source) FromDB(bs []byte) error {
return json.UnmarshalHandleDoubleEncode(bs, &source)
}
// ToDB exports an SMTPConfig to a serialized format.
func (source *Source) ToDB() ([]byte, error) {
return json.Marshal(source)
}
// ProvidesSSHKeys returns if this source provides SSH Keys
func (source *Source) ProvidesSSHKeys() bool {
return len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0
}
// SetAuthSource sets the related AuthSource
func (source *Source) SetAuthSource(authSource *auth.Source) {
source.authSource = authSource
}
func init() {
auth.RegisterTypeConfig(auth.OAuth2, &Source{})
}

View file

@ -0,0 +1,74 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package oauth2
import (
"net/http"
"net/url"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
)
// Callout redirects request/response pair to authenticate against the provider
func (source *Source) Callout(request *http.Request, response http.ResponseWriter, codeChallengeS256 string) error {
// not sure if goth is thread safe (?) when using multiple providers
request.Header.Set(ProviderHeaderKey, source.authSource.Name)
var querySuffix string
if codeChallengeS256 != "" {
querySuffix = "&" + url.Values{
"code_challenge_method": []string{"S256"},
"code_challenge": []string{codeChallengeS256},
}.Encode()
}
// don't use the default gothic begin handler to prevent issues when some error occurs
// normally the gothic library will write some custom stuff to the response instead of our own nice error page
// gothic.BeginAuthHandler(response, request)
gothRWMutex.RLock()
defer gothRWMutex.RUnlock()
url, err := gothic.GetAuthURL(response, request)
if err == nil {
// hacky way to set the code_challenge, but no better way until
// https://github.com/markbates/goth/issues/516 is resolved
http.Redirect(response, request, url+querySuffix, http.StatusTemporaryRedirect)
}
return err
}
// Callback handles OAuth callback, resolve to a goth user and send back to original url
// this will trigger a new authentication request, but because we save it in the session we can use that
func (source *Source) Callback(request *http.Request, response http.ResponseWriter, codeVerifier string) (goth.User, error) {
// not sure if goth is thread safe (?) when using multiple providers
request.Header.Set(ProviderHeaderKey, source.authSource.Name)
if codeVerifier != "" {
// hacky way to set the code_verifier...
// Will be picked up inside CompleteUserAuth: params := req.URL.Query()
// https://github.com/markbates/goth/pull/474/files
request = request.Clone(request.Context())
q := request.URL.Query()
q.Add("code_verifier", codeVerifier)
request.URL.RawQuery = q.Encode()
}
gothRWMutex.RLock()
defer gothRWMutex.RUnlock()
user, err := gothic.CompleteUserAuth(response, request)
if err != nil {
return user, err
}
if source.RequiredClaimValues != nil {
if err := source.authorizeAccessEngine(request.Context(), user); err != nil {
return goth.User{}, err
}
}
return user, nil
}

View file

@ -0,0 +1,151 @@
# Target Revenue Source License
**Version 1.0, Candidate 1 (V1C1)**
---
> **PRELIMINARY CANDIDATE — SUBJECT TO CHANGE — NOT FINAL**
>
> This repository is governed by the Target Revenue Source License
> (TRSL), Version 1, Candidate 1. This is the framework's first working
> candidate, adopted as the org's preliminary operating license across
> all repos (maintainer decision, 2026-07-29) during the build/alpha
> stage — see `SCOPE.md` §1 and Appendix A of the canonical text (link
> below) for the alpha/beta risk-acceptance decision this reflects and
> what it does and does not mean. Full specialist legal review is
> explicitly deferred until the framework moves out of beta.
>
> **Canonical source, full candidate-status banner, and Appendix A
> (non-normative candidate notes tracking every open item):** the
> `coulomb` org's `target-revenue` repository,
> `specs/TargetRevenueSourceLicense-V1C1.md` — this file is the operative
> legal text (Preamble through Section 11) only; the canonical document
> is authoritative if this copy and that document ever diverge.
---
## Preamble
This Target Revenue Source License ("**License**") governs the Software identified in the applicable Phase Manifest. It implements the Target Revenue Framework: a defined development Phase accumulates Development Credit and Remission Credit against an immutable Initial Target until the Milestone Release automatically and irrevocably converts to a declared permissive Future License.
Commercial beneficiaries fund the creation and early availability of a software improvement; once the declared target is satisfied, the governed release becomes permissively open source.
## 1. Definitions
Capitalized terms used in this License have the meanings given below. Where a term is also defined in the Phase Manifest or Target Ledger for a specific Phase, the Phase Manifest and Target Ledger govern the *values* (amounts, dates, identifiers) and this License governs the *legal effect* of those values — the two must not be read as conflicting definitions of the same concept.
**"Commercial Entitlement"** means a right, purchased or otherwise granted under a Commercial Use Agreement, to make Commercial Use of the Software during a Phase.
**"Commercial Use"** means billing, invoicing, or otherwise charging any customer a fee, subscription, license fee, or other consideration for or in connection with use of the Software, at any time before the Conversion Event for the applicable Phase, regardless of whether the person or organization billed would otherwise qualify for Noncommercial Use. Commercial Use occurs by virtue of such billing alone, whether or not the resulting payment is registered with the Trust Service; in particular, billing a customer for pre-conversion use of the Software without recording the corresponding payment in the applicable Phase's Target Ledger is Commercial Use without a valid Commercial Entitlement — a violation of Section 3, addressed under Section 7 and, where applicable, the Enforcement Network described in the canonical repository's `specs/EnforcementNetworkConcept.md`.
**"Commercial Use Agreement"** means the separate agreement, referenced by the applicable Phase Manifest, under which a Commercial Entitlement is purchased or granted. This License does not itself set pricing, metering, or payment terms — those are governed by the Commercial Use Agreement.
**"Conversion Event"** means the moment the Outstanding Target for a Phase reaches zero, as computed from the Phase Manifest and Target Ledger per the Target Ledger Specification. The Conversion Event occurs automatically and is not conditioned on any declaration, attestation, or other act by the Licensor or any Trust Service.
**"Development Credit"** means the portion of a collected and settled payment explicitly allocated toward satisfying the Initial Target of a specific Phase, as recorded in that Phase's Target Ledger.
**"Future License"** means the permissive license identified in the applicable Phase Manifest, being either the MIT License or the Apache License, Version 2.0, which applies to the Milestone Release upon the Conversion Event.
**"Initial Target"** means the immutable monetary target declared for a Phase in its Phase Manifest.
**"Licensor"** means **Binky Hedgehog GmbH**, the party that publishes the Phase Manifest and holds the rights necessary to grant this License and the Future License for the Milestone Release.
**"Milestone Release"** means the precisely identified software release designated in the applicable Phase Manifest, identified by an immutable source revision, release artifact, or cryptographic digest.
**"Noncommercial Use"** means use of the Software for personal purposes, private study, hobby or amateur projects; use by any charitable organization, educational institution, public research organization, or government institution acting in a non-revenue-generating capacity; or other use of a materially similar character.
**"Outstanding Target"** means, at any time, `max(0, Initial Target − cumulative Development Credit − cumulative Remission Credit)` for a Phase, as computed from that Phase's Target Ledger.
**"Phase"** means a bounded development undertaking governed by one Initial Target, one Milestone Release, one degeneration policy, and one Future License declaration, as declared in a Phase Manifest.
**"Phase Manifest"** means the published, immutable declaration identifying a Phase, its Milestone Release, Initial Target, Future License, degeneration policy, and Target Ledger location, as specified in the Phase Manifest Specification.
**"Remission Credit"** means a transparent, non-revenue reduction of a Phase's Outstanding Target, generated under that Phase's published degeneration policy and recorded in the Target Ledger.
**"Settled Payment"** means a payment that has cleared through its payment processor and is no longer subject to reversal in the ordinary course (chargeback, dispute, or equivalent), as further specified by the applicable Commercial Use Agreement or monetization extension.
**"Software"** means the source code, object code, and associated documentation of the Milestone Release identified in the applicable Phase Manifest.
**"Target Ledger"** means the append-only record of Development Credit, Remission Credit, and correction entries for a Phase, as specified in the Target Ledger Specification.
**"You"** or **"Licensee"** means the individual or entity exercising rights under this License.
## 2. Grant of Rights for Noncommercial Use
Subject to the terms of this License, the Licensor grants You a worldwide, royalty-free, non-exclusive license, during the applicable Phase, to:
(a) use, reproduce, and study the Software for any Noncommercial Use;
(b) modify the Software and create derivative works of it for any Noncommercial Use; and
(c) redistribute the Software and Your modifications, in source or object form, for any Noncommercial Use, provided that You include this License, unmodified, with any such redistribution, and that You do not remove or alter any copyright, patent, trademark, or attribution notices contained in the Software.
This grant does not extend to Commercial Use. Commercial Use requires a Commercial Entitlement under Section 3.
## 3. Commercial Use
You may not make Commercial Use of the Software during the applicable Phase unless You hold a valid, current Commercial Entitlement under a Commercial Use Agreement with the Licensor covering the applicable Phase. A Commercial Entitlement granted under one Phase's Commercial Use Agreement does not extend to a later Phase's Milestone Release unless the Commercial Use Agreement expressly says so.
This Section 3 states the existence and boundary of the commercial-use restriction. It does not itself set pricing, invoicing, metering, audit rights, or payment terms — those are governed exclusively by the applicable Commercial Use Agreement.
## 4. Patent License
Subject to the terms of this License, each contributor to the Software grants You, during the applicable Phase and solely to the extent of rights granted under Sections 2 and 3, a perpetual (subject to the termination below), worldwide, non-exclusive, no-charge, royalty-free patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Software, limited to those patent claims licensable by that contributor that are necessarily infringed by their contribution(s) alone or by combination of their contribution(s) with the Software.
If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Software or a contribution incorporated within it constitutes direct or contributory patent infringement, then any patent licenses granted to You under this Section 4 for the Software shall terminate as of the date such litigation is filed.
## 5. Automatic Conversion to the Future License
**5.1 Automatic effect.** Upon the Conversion Event for a Phase, the rights and restrictions in Sections 3 (Commercial Use) of this License, as they apply to that Phase's Milestone Release, terminate automatically. In their place, the Milestone Release is licensed under the Future License identified in that Phase's Phase Manifest, effective as of the Conversion Event, without any further act, declaration, or attestation required by the Licensor, any Trust Service, or any other party.
**5.2 Irrevocability.** Once a valid Conversion Event has occurred for a Phase, no subsequent refund, chargeback, accounting correction, dispute, or termination of this License for an unrelated breach shall revoke, suspend, or otherwise impair the Future License grant for that Phase's Milestone Release. Any shortfall or dispute arising after a Conversion Event is a commercial or accounting matter between the relevant parties and does not reinstate a commercial-use restriction over already-converted Software.
**5.3 Prior freedom preserved.** A later Phase covering subsequent improvements to the Software does not restrict, withdraw, or otherwise affect the rights granted under the Future License for an earlier Phase's already-converted Milestone Release.
**5.4 Evidence, not cause.** A Trust Service may publish a Conversion Attestation documenting a Conversion Event. Such an attestation is evidence that the Conversion Event occurred; it is not a condition of, and its absence or delay does not postpone, the automatic effect described in Section 5.1. Any person may independently verify whether a Conversion Event has occurred directly from the Phase Manifest and Target Ledger.
## 6. Successive Phases
The Licensor may declare a new Phase covering subsequent improvements to the Software following a Milestone Release's Conversion Event. Each Phase is independently governed by its own Phase Manifest, Initial Target, degeneration policy, and Target Ledger. Nothing in a later Phase's Phase Manifest may be construed to reduce or withdraw rights already granted under Section 5 for an earlier Phase's Milestone Release.
## 7. Term and Termination
**7.1 Term.** This License applies to the Software for the duration of the applicable Phase, and, for the Milestone Release, indefinitely following that Phase's Conversion Event under the Future License.
**7.2 Termination for breach.** If You breach Section 3 (Commercial Use) or Section 2(c) (redistribution notice requirement), the Licensor may terminate this License as to You. Before such termination becomes effective, the Licensor shall provide You written notice of the breach; if You cure the breach within thirty (30) days of that notice, this License continues in effect. A second breach of the same provision within twelve (12) months may be terminated immediately without a further cure opportunity.
**7.3 Effect of termination.** Termination under this Section 7 affects only Your rights under Sections 2 and 3 for the Phase in which the breach occurred. It does not affect any rights already vested under Section 5 (Automatic Conversion) for a Milestone Release whose Conversion Event has already occurred, per Section 5.2.
**7.4 Public record of breach and resolution.** The Licensor shall cause the Trust Service to publish, as part of the public record for the affected Phase, notice of: (a) any breach notice issued under Section 7.2, stating the general nature of the breach and the date of notice; (b) whether the breach was cured within the applicable cure period, and the date of cure; and (c) any termination determination made under this Section 7, including its effective date and scope. This public record exists to give the ecosystem a transparent, verifiable conformity signal for the Phase, distinct from and in addition to the Development Credit and Remission Credit facts already published under Section 5.4 and the Target Ledger Specification.
A breach that You dispute, and that has not been finally determined, shall be recorded as **alleged**; it shall be recorded as **determined** only once the cure period has run without cure, or the dispute has been resolved against You under the applicable Commercial Use Agreement's dispute process, if any. The Trust Service shall update the record promptly upon resolution in either direction. Recording an alleged or determined breach under this Section 7.4 is a ministerial act of publishing the Licensor's determination (or a dispute process's outcome); it does not give the Trust Service discretionary authority to decide whether a breach occurred, consistent with Section 5.4's evidence-not-cause principle.
Whether, and under what conditions, the public record identifies a Commercial Entitlement holder by name is governed exclusively by the applicable Commercial Use Agreement, which the Licensor and that Commercial Entitlement holder negotiate and agree to directly. This License does not itself set a naming default. Where no Commercial Use Agreement addresses the question, or where the affected party has no Commercial Use Agreement at all (for example, a Section 2(c) breach by a Noncommercial Use licensee), the public record states the Phase and breach category only, without naming the party.
## 8. Disclaimer of Warranty
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. THE LICENSOR DOES NOT WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE OR THAT ANY PHASE WILL REACH ITS CONVERSION EVENT.
## 9. Limitation of Liability
IN NO EVENT SHALL THE LICENSOR OR ANY CONTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE, EXCEPT TO THE EXTENT SUCH LIMITATION IS PROHIBITED BY APPLICABLE LAW.
## 10. Trademarks
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary attribution.
## 11. General Provisions
**11.1 Governing law and venue.** Adopted for alpha/beta 2026-07-29: disputes arising under this License shall be resolved by binding arbitration, seated at a neutral, arbitration-mature venue (Singapore or London are the two candidates concretely supported by current research), rather than by litigation in a national court. The specific arbitral institution and substantive governing law remain a per-deployment blank pending final selection; they are not fixed by this candidate. See the canonical repository's `history/260729-TRSL-Jurisdiction-Synthesis.md` §2.
**11.2 Severability.** If any provision of this License is held unenforceable, the remaining provisions remain in full force, and the unenforceable provision shall be reformed to the minimum extent necessary to make it enforceable.
**11.3 No waiver.** Failure to enforce any provision of this License is not a waiver of future enforcement of that or any other provision.
**11.4 Entire agreement (as to licensing).** This License, together with the applicable Phase Manifest and, where applicable, the Commercial Use Agreement, constitutes the entire agreement between You and the Licensor regarding the Software's licensing terms. Operations, service, and consulting arrangements are governed by separate agreements, if any, and are not part of this License.
**11.5 Definitions control.** Marketing materials, documentation, or other non-normative communications about the Software must not describe pre-Conversion-Event Software as "Open Source," "free software," or "open core." Pre-conversion Noncommercial Use is **source-available**; pre-conversion Commercial Use requires a **Commercial Entitlement**; only post-conversion Software may be described as Open Source, under the Future License.
---
**No Phase is currently declared for this repository under this License.** Until a Phase Manifest is published and registered with the Trust Service for a Milestone Release in this repository, Sections 2–7 above have no operative subject matter here — this License establishes the governing framework in advance of that declaration, consistent with the org-wide rollout decision recorded in `target-revenue`'s `workplans/TREV-WP-0008-governance-and-pilot-rollout.md`.

View file

@ -0,0 +1,3 @@
module github.com/netkingdom/access-engine
go 1.24

View file

@ -0,0 +1,95 @@
// Package sign attaches a detached Ed25519 signature to a decision envelope
// (FLEX-WP-0024). The signed material is json.Marshal of the envelope with
// the signature field omitted.
package sign
import (
"bytes"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"github.com/netkingdom/access-engine/pkg/api"
)
const AlgorithmEd25519 = "ed25519"
// CanonicalBytes is the exact payload that is signed and verified: the
// envelope as emitted, with signature absent.
func CanonicalBytes(envelope api.DecisionEnvelope) ([]byte, error) {
envelope.Signature = nil
data, err := json.Marshal(envelope)
if err != nil {
return nil, fmt.Errorf("marshal canonical envelope: %w", err)
}
return data, nil
}
func Sign(envelope *api.DecisionEnvelope, private ed25519.PrivateKey, kid string) error {
if envelope == nil {
return fmt.Errorf("envelope is required")
}
if len(private) != ed25519.PrivateKeySize {
return fmt.Errorf("ed25519 private key has length %d", len(private))
}
if kid == "" {
return fmt.Errorf("kid is required")
}
payload, err := CanonicalBytes(*envelope)
if err != nil {
return err
}
sig := ed25519.Sign(private, payload)
envelope.Signature = &api.EnvelopeSignature{
Mode: api.EnvelopeSignatureSigned,
Alg: AlgorithmEd25519,
Kid: kid,
Value: base64.RawURLEncoding.EncodeToString(sig),
}
return nil
}
func Verify(envelope api.DecisionEnvelope, keys map[string]ed25519.PublicKey) error {
sig := envelope.Signature
if sig == nil || sig.Mode != api.EnvelopeSignatureSigned {
return fmt.Errorf("envelope is not signed")
}
if sig.Alg != AlgorithmEd25519 {
return fmt.Errorf("unsupported signature alg %q", sig.Alg)
}
pub, ok := keys[sig.Kid]
if !ok {
return fmt.Errorf("unknown signature kid %q", sig.Kid)
}
raw, err := base64.RawURLEncoding.DecodeString(sig.Value)
if err != nil {
return fmt.Errorf("decode signature: %w", err)
}
payload, err := CanonicalBytes(envelope)
if err != nil {
return err
}
if !ed25519.Verify(pub, payload, raw) {
return fmt.Errorf("signature does not match envelope")
}
return nil
}
// DecodeEnvelope preserves JSON numeric kinds/lexemes emitted by task inputs.
// Legacy producer envelopes remain byte-compatible. It does not invent another
// signature canonicalization: CanonicalBytes still calls typed json.Marshal.
func DecodeEnvelope(raw []byte) (api.DecisionEnvelope, error) {
var envelope api.DecisionEnvelope
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
decoder.DisallowUnknownFields()
if err := decoder.Decode(&envelope); err != nil {
return envelope, err
}
if _, err := decoder.Token(); err != io.EOF {
return api.DecisionEnvelope{}, fmt.Errorf("trailing envelope data")
}
return envelope, nil
}

View file

@ -0,0 +1,699 @@
package api
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"strings"
"time"
)
// ProtectedSystemManifest describes a system that delegates authorization to
// flex-auth.
type ProtectedSystemManifest struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
ClientBinding *ClientBinding `json:"client_binding,omitempty" yaml:"client_binding,omitempty"`
ResourceTypes []ResourceType `json:"resource_types,omitempty" yaml:"resource_types,omitempty"`
Actions []ActionDefinition `json:"actions,omitempty" yaml:"actions,omitempty"`
CaringProfiles []string `json:"caring_profiles,omitempty" yaml:"caring_profiles,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// ClientBinding names the key-cape client a protected system's users sign in
// through. It is descriptive only: the readback registry projection lists it,
// and it never decides an allow (FLEX-DEC-2026-019).
type ClientBinding struct {
Issuer string `json:"issuer" yaml:"issuer"`
ClientID string `json:"client_id" yaml:"client_id"`
}
// ResourceType describes a resource namespace entry owned by a protected system.
type ResourceType struct {
Name string `json:"name" yaml:"name"`
ParentTypes []string `json:"parent_types,omitempty" yaml:"parent_types,omitempty"`
ScopeLevel ScopeLevel `json:"scope_level,omitempty" yaml:"scope_level,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// ActionDefinition maps a protected-system action to CARING capabilities.
type ActionDefinition struct {
Name string `json:"name" yaml:"name"`
Capabilities []Capability `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// SubjectManifest declares subjects, groups, teams, and tenants for local
// registry loading.
type SubjectManifest struct {
ID string `json:"id" yaml:"id"`
Subjects []Subject `json:"subjects,omitempty" yaml:"subjects,omitempty"`
Groups []Group `json:"groups,omitempty" yaml:"groups,omitempty"`
Teams []Team `json:"teams,omitempty" yaml:"teams,omitempty"`
Tenants []Tenant `json:"tenants,omitempty" yaml:"tenants,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Subject is a human, service, automation, agent, or other acting identity.
type Subject struct {
ID string `json:"id" yaml:"id"`
Type SubjectType `json:"type" yaml:"type"`
DisplayName string `json:"display_name,omitempty" yaml:"display_name,omitempty"`
OrganizationRelation OrganizationRelation `json:"organization_relation,omitempty" yaml:"organization_relation,omitempty"`
Roles []CanonicalRole `json:"roles,omitempty" yaml:"roles,omitempty"`
Groups []string `json:"groups,omitempty" yaml:"groups,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Claims map[string]any `json:"claims,omitempty" yaml:"claims,omitempty"`
CaringDescriptors []CaringAccessDescriptor `json:"caring_descriptors,omitempty" yaml:"caring_descriptors,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Group is an assignment convenience, not a canonical role.
type Group struct {
ID string `json:"id" yaml:"id"`
DisplayName string `json:"display_name,omitempty" yaml:"display_name,omitempty"`
Members []string `json:"members,omitempty" yaml:"members,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
CaringDescriptors []CaringAccessDescriptor `json:"caring_descriptors,omitempty" yaml:"caring_descriptors,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Team is a group-like ownership unit used by protected systems.
type Team struct {
ID string `json:"id" yaml:"id"`
DisplayName string `json:"display_name,omitempty" yaml:"display_name,omitempty"`
Members []string `json:"members,omitempty" yaml:"members,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
CaringDescriptors []CaringAccessDescriptor `json:"caring_descriptors,omitempty" yaml:"caring_descriptors,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Tenant is a structural isolation boundary.
type Tenant struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// RelationshipFact records a relation between subjects, groups, teams, tenants,
// and resources.
type RelationshipFact struct {
ID string `json:"id" yaml:"id"`
System string `json:"system,omitempty" yaml:"system,omitempty"`
Subject string `json:"subject" yaml:"subject"`
Relation string `json:"relation" yaml:"relation"`
Object string `json:"object" yaml:"object"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Conditions []Condition `json:"conditions,omitempty" yaml:"conditions,omitempty"`
Caring *CaringAccessDescriptor `json:"caring,omitempty" yaml:"caring,omitempty"`
Provenance map[string]any `json:"provenance,omitempty" yaml:"provenance,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// PolicyPackageMetadata is the frontmatter contract for Rego-in-Markdown
// policy packages.
type PolicyPackageMetadata struct {
ID string `json:"id" yaml:"id"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
Version string `json:"version" yaml:"version"`
Status string `json:"status,omitempty" yaml:"status,omitempty"`
Package string `json:"package" yaml:"package"`
Actions []string `json:"actions,omitempty" yaml:"actions,omitempty"`
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
Fixtures []string `json:"fixtures,omitempty" yaml:"fixtures,omitempty"`
Caring CaringPolicyMetadata `json:"caring" yaml:"caring"`
Activation map[string]any `json:"activation,omitempty" yaml:"activation,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
// AllowTTL is a Go duration (for example "15m") that bounds every allow
// this package produces. Omit to use DefaultAllowTTL. "none" or "0s"
// means no stated end; the engine denies those allows (§9.7.1).
AllowTTL string `json:"allow_ttl,omitempty" yaml:"allow_ttl,omitempty"`
}
// CaringPolicyMetadata declares the CARING envelope a policy governs.
type CaringPolicyMetadata struct {
Profile string `json:"profile" yaml:"profile"`
Enforce bool `json:"enforce,omitempty" yaml:"enforce,omitempty"`
CanonicalRoles []CanonicalRole `json:"canonical_roles,omitempty" yaml:"canonical_roles,omitempty"`
OrganizationRelations []OrganizationRelation `json:"organization_relations,omitempty" yaml:"organization_relations,omitempty"`
Scopes []CaringScope `json:"scopes,omitempty" yaml:"scopes,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
Capabilities []Capability `json:"capabilities,omitempty" yaml:"capabilities,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
Conditions []Condition `json:"conditions,omitempty" yaml:"conditions,omitempty"`
Restrictions []Restriction `json:"restrictions,omitempty" yaml:"restrictions,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// PolicyFixture binds a check request to an expected decision.
type PolicyFixture struct {
ID string `json:"id" yaml:"id"`
Request CheckRequest `json:"request" yaml:"request"`
Expect DecisionExpectation `json:"expect" yaml:"expect"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// DecisionExpectation is the compact fixture expectation for policy tests.
type DecisionExpectation struct {
Effect DecisionEffect `json:"effect" yaml:"effect"`
Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
Obligations []Obligation `json:"obligations,omitempty" yaml:"obligations,omitempty"`
ConformanceFindings []CaringConformanceFinding `json:"conformance_findings,omitempty" yaml:"conformance_findings,omitempty"`
}
// CheckRequest is the stable protected-system-facing decision request.
type CheckRequest struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Action string `json:"action" yaml:"action"`
Resource ResourceRef `json:"resource" yaml:"resource"`
Context map[string]any `json:"context,omitempty" yaml:"context,omitempty"`
CaringContext *CaringAccessDescriptor `json:"caring_context,omitempty" yaml:"caring_context,omitempty"`
PolicyVersion string `json:"policy_version,omitempty" yaml:"policy_version,omitempty"`
// Purpose is empty on every enforcement request. The readback pin sets it
// to PurposeReadback, and it is hashed into every request digest, so an
// advisory readback decision can never pass a PEP's §6.4.2 replay test for
// an enforcement request (FLEX-DEC-2026-019). /v1/check and /v1/batch_check
// refuse a request that carries it.
Purpose string `json:"purpose,omitempty" yaml:"purpose,omitempty"`
}
// PurposeReadback marks an advisory application-access readback decision.
const PurposeReadback = "readback"
// BatchCheckRequest evaluates one subject/action against multiple resources.
type BatchCheckRequest struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Action string `json:"action" yaml:"action"`
Resources []ResourceRef `json:"resources" yaml:"resources"`
Context map[string]any `json:"context,omitempty" yaml:"context,omitempty"`
PolicyVersion string `json:"policy_version,omitempty" yaml:"policy_version,omitempty"`
}
// SubjectRef is a normalized subject reference in request and decision shapes.
type SubjectRef struct {
ID string `json:"id" yaml:"id"`
Type SubjectType `json:"type,omitempty" yaml:"type,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// ResourceRef is a normalized resource reference in request and decision shapes.
type ResourceRef struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type,omitempty" yaml:"type,omitempty"`
System string `json:"system,omitempty" yaml:"system,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// DecisionEffect is the stable decision outcome vocabulary.
type DecisionEffect string
const (
DecisionEffectAllow DecisionEffect = "allow"
DecisionEffectDeny DecisionEffect = "deny"
DecisionEffectRedact DecisionEffect = "redact"
DecisionEffectAuditOnly DecisionEffect = "audit_only"
DecisionEffectNotApplicable DecisionEffect = "not_applicable"
)
// DecisionEffects returns the complete effect vocabulary. Every effect is an
// emission class in cadence.yaml; a test fails if a constant above is missing
// here, so a new effect cannot ship unclassified (FLEX-WP-0031-T03).
func DecisionEffects() []DecisionEffect {
return []DecisionEffect{
DecisionEffectAllow,
DecisionEffectDeny,
DecisionEffectRedact,
DecisionEffectAuditOnly,
DecisionEffectNotApplicable,
}
}
// Restricts reports whether an effect withholds authority. This classification
// does not exempt an effect from required durable emission (GH-DEC-2026-024).
func (e DecisionEffect) Restricts() bool {
switch e {
case DecisionEffectDeny, DecisionEffectRedact, DecisionEffectNotApplicable:
return true
}
return false
}
// DecisionRecordContractV1 is the published decision-record contract identifier.
const DecisionRecordContractV1 = "flex-auth.decision-record.v1"
// DecisionEnvelope is the stable response produced by standalone and delegated
// evaluators. It is flex-auth's published decision-record contract (§17).
type DecisionEnvelope struct {
ID string `json:"id" yaml:"id"`
ContractVersion string `json:"contract_version,omitempty" yaml:"contract_version,omitempty"`
RequestID string `json:"request_id,omitempty" yaml:"request_id,omitempty"`
Effect DecisionEffect `json:"effect" yaml:"effect"`
Reason string `json:"reason,omitempty" yaml:"reason,omitempty"`
MatchedPolicyVersion string `json:"matched_policy_version,omitempty" yaml:"matched_policy_version,omitempty"`
MatchedRule string `json:"matched_rule,omitempty" yaml:"matched_rule,omitempty"`
Resource ResourceRef `json:"resource" yaml:"resource"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Binding *DecisionBinding `json:"binding,omitempty" yaml:"binding,omitempty"`
Lifetime *DecisionLifetime `json:"lifetime,omitempty" yaml:"lifetime,omitempty"`
Obligations []Obligation `json:"obligations,omitempty" yaml:"obligations,omitempty"`
Diagnostics map[string]any `json:"diagnostics,omitempty" yaml:"diagnostics,omitempty"`
Provenance DecisionProvenance `json:"provenance" yaml:"provenance"`
Signature *EnvelopeSignature `json:"signature,omitempty" yaml:"signature,omitempty"`
Caring *CaringDecisionMetadata `json:"caring,omitempty" yaml:"caring,omitempty"`
}
// EnvelopeSignatureMode is how the responder authenticates the envelope.
type EnvelopeSignatureMode string
const (
EnvelopeSignatureSigned EnvelopeSignatureMode = "signed"
EnvelopeSignatureUnsigned EnvelopeSignatureMode = "unsigned"
)
// EnvelopeSignature is a detached signature over the canonical envelope with
// this field omitted (FLEX-WP-0024). It is not decision material and is not
// part of request_digest.
type EnvelopeSignature struct {
Mode EnvelopeSignatureMode `json:"mode" yaml:"mode"`
Alg string `json:"alg,omitempty" yaml:"alg,omitempty"`
Kid string `json:"kid,omitempty" yaml:"kid,omitempty"`
Value string `json:"value,omitempty" yaml:"value,omitempty"`
}
func UnsignedEnvelopeSignature() *EnvelopeSignature {
return &EnvelopeSignature{Mode: EnvelopeSignatureUnsigned}
}
// DecisionLifetimeKind identifies how an allow ends.
type DecisionLifetimeKind string
const (
DecisionLifetimeTTL DecisionLifetimeKind = "ttl"
)
// DefaultAllowTTL is the engine default when a policy package omits allow_ttl.
const DefaultAllowTTL = 15 * time.Minute
// ReasonAllowLifetimeUnstated is the deny reason for an allow with no stated end.
const ReasonAllowLifetimeUnstated = "allow_lifetime_unstated"
// DecisionLifetime bounds an allow (§9.7.1). flex-auth has no session concept,
// so the first honest shape is a policy-package-declared TTL.
type DecisionLifetime struct {
Kind DecisionLifetimeKind `json:"kind" yaml:"kind"`
TTL string `json:"ttl,omitempty" yaml:"ttl,omitempty"`
NotBefore string `json:"not_before,omitempty" yaml:"not_before,omitempty"`
ExpiresAt string `json:"expires_at" yaml:"expires_at"`
}
// DecisionBinding is the exact normalized authorization request evaluated by
// a decision. It lets a consumer verify structured action, target, actor, and
// context fields without parsing reason or diagnostic prose.
type DecisionBinding struct {
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Action string `json:"action" yaml:"action"`
Resource ResourceRef `json:"resource" yaml:"resource"`
Context map[string]any `json:"context,omitempty" yaml:"context,omitempty"`
// Purpose is "readback" on an advisory readback decision and absent on an
// enforcement decision (FLEX-DEC-2026-019).
Purpose string `json:"purpose,omitempty" yaml:"purpose,omitempty"`
RequestDigest string `json:"request_digest" yaml:"request_digest"`
// ApprovalBindingDigest is RequestDigest computed with the approval claim
// removed from context. It exists to break a circularity: an approval's
// pdp_digest is recorded at issue time, but the request that later carries
// the claim inside its hashed context has a different RequestDigest by
// construction, so a claim can never name the request that carries it.
//
// This digest is stable across attaching the claim, so it is the value
// claim.binding.pdp_digest must equal (GH-DEC-2026-008). It is NOT a replay
// identity and must not be used as one: two requests differing only in
// which approval was presented share it, and the decision does not.
ApprovalBindingDigest string `json:"approval_binding_digest,omitempty" yaml:"approval_binding_digest,omitempty"`
// SubmittedRequestDigest is RequestDigest over the request exactly as the
// consumer sent it, before the evaluator overlays registry facts onto the
// subject and resource.
//
// It exists because RequestDigest is not consumer-computable and was
// published as though it were. The evaluator hashes the ENRICHED request —
// subject.tenant, subject.attributes, and resource attributes the registry
// contributed — and the registry is flex-auth's, so a consumer recomputing
// the digest over what it sent gets a different value on every real allow.
// secrets-engine found this on its first live request (FLEX-DEC-2026-012).
//
// This is the digest a PEP compares for §6.4 obligation 2's replay test.
// Nothing is lost by hashing the pre-enrichment form: enrichment is a
// function of the request and the registry snapshot, and
// provenance.registry_snapshot_digest already pins the snapshot, so
// SubmittedRequestDigest together with that digest identifies the evaluated
// request completely.
SubmittedRequestDigest string `json:"submitted_request_digest,omitempty" yaml:"submitted_request_digest,omitempty"`
}
// ApprovalContextKey is the context key carrying an approval-engine
// approval-claim. It is excluded from ApprovalBindingDigest and from nothing
// else.
const ApprovalContextKey = "approval"
// requestDigestMaterial is the exact tuple hashed for §6.4.2 replay. Request
// id, policy version, and caring_context are excluded: id is correlation, the
// version is provenance, and caring_context is an input-claim digest.
type requestDigestMaterial struct {
Tenant string `json:"tenant,omitempty"`
Subject SubjectRef `json:"subject"`
Action string `json:"action"`
Resource ResourceRef `json:"resource"`
Context map[string]any `json:"context,omitempty"`
Purpose string `json:"purpose,omitempty"`
}
// NewDecisionBinding returns a stable structured binding for the exact request
// an evaluator consumed.
//
// Prefer NewDecisionBindingFor, which also records the digest of the request as
// submitted. This form leaves SubmittedRequestDigest empty, which reads as "the
// evaluator did not record one" rather than "the two are equal".
func NewDecisionBinding(request CheckRequest) *DecisionBinding {
contextCopy := make(map[string]any, len(request.Context))
for key, value := range request.Context {
contextCopy[key] = value
}
binding := &DecisionBinding{
Tenant: request.Tenant,
Subject: request.Subject,
Action: request.Action,
Resource: request.Resource,
Context: contextCopy,
Purpose: request.Purpose,
RequestDigest: RequestDigest(request),
}
if _, carries := request.Context[ApprovalContextKey]; carries {
binding.ApprovalBindingDigest = ApprovalBindingDigest(request)
}
return binding
}
// NewDecisionBindingFor records the binding of the evaluated request together
// with the digest of the request as submitted.
//
// The two arguments are the same request before and after the evaluator
// overlaid registry facts. Where a request named nothing the registry knows they
// are equal, and the field is emitted anyway: a consumer that only sees it on
// enriched decisions would build a check that passes by absence.
func NewDecisionBindingFor(evaluated, submitted CheckRequest) *DecisionBinding {
binding := NewDecisionBinding(evaluated)
binding.SubmittedRequestDigest = RequestDigest(submitted)
return binding
}
// ApprovalBindingDigest is RequestDigest over the same material with the
// approval claim removed from context.
//
// When a request carries no approval claim the two are identical, which is the
// point: an approval issued against a claim-free Check records that Check's
// request_digest, and the later claim-bearing request reproduces the same value
// here. Comparing it to claim.binding.pdp_digest establishes correspondence by
// identity rather than by translating between two action vocabularies
// (GH-DEC-2026-008).
func ApprovalBindingDigest(request CheckRequest) string {
if _, carries := request.Context[ApprovalContextKey]; !carries {
return RequestDigest(request)
}
stripped := make(map[string]any, len(request.Context))
for key, value := range request.Context {
if key == ApprovalContextKey {
continue
}
stripped[key] = value
}
if len(stripped) == 0 {
stripped = nil
}
return CanonicalDigest(requestDigestMaterial{
Tenant: request.Tenant,
Subject: request.Subject,
Action: request.Action,
Resource: request.Resource,
Context: stripped,
Purpose: request.Purpose,
})
}
// RequestDigest is the mechanical §6.4.2 replay test: SHA-256 over canonical
// JSON of tenant, subject, action, resource, context, and purpose. Purpose is
// omitted when empty, so enforcement digests are unchanged by its existence.
func RequestDigest(request CheckRequest) string {
return CanonicalDigest(requestDigestMaterial{
Tenant: request.Tenant,
Subject: request.Subject,
Action: request.Action,
Resource: request.Resource,
Context: request.Context,
Purpose: request.Purpose,
})
}
// CanonicalDigest returns "sha256:" plus the hex SHA-256 of canonical JSON.
// encoding/json sorts map keys, so two equal Go values agree.
func CanonicalDigest(value any) string {
data, err := json.Marshal(value)
if err != nil {
sum := sha256.Sum256(nil)
return "sha256:" + hex.EncodeToString(sum[:])
}
sum := sha256.Sum256(data)
return "sha256:" + hex.EncodeToString(sum[:])
}
// InputClaimDigests hashes the request-time claim classes the evaluator joined.
func InputClaimDigests(request CheckRequest) map[string]string {
digests := make(map[string]string)
if len(request.Context) > 0 {
digests["context"] = CanonicalDigest(request.Context)
}
if request.CaringContext != nil {
digests["caring_context"] = CanonicalDigest(request.CaringContext)
}
if len(digests) == 0 {
return nil
}
return digests
}
// DecisionCompletion carries evaluator-side inputs used to finish an envelope.
type DecisionCompletion struct {
AllowTTL string
Now time.Time
}
// CompleteDecision stamps contract version, input-claim digests, decision time,
// and an explicit allow lifetime. An allow with no stated end becomes a deny.
func CompleteDecision(envelope *DecisionEnvelope, request CheckRequest, completion DecisionCompletion) {
if envelope == nil {
return
}
if envelope.ContractVersion == "" {
envelope.ContractVersion = DecisionRecordContractV1
}
if envelope.Provenance.InputClaimDigests == nil {
envelope.Provenance.InputClaimDigests = InputClaimDigests(request)
}
ApplyAllowLifetime(envelope, completion.AllowTTL, completion.Now)
}
// ParseAllowTTL resolves a package-declared TTL. ok is false when the allow
// would have no stated end. Invalid strings return an error so package
// validation can reject them.
func ParseAllowTTL(declared string) (time.Duration, error) {
trimmed := strings.TrimSpace(declared)
if trimmed == "" {
return DefaultAllowTTL, nil
}
if strings.EqualFold(trimmed, "none") {
return 0, nil
}
ttl, err := time.ParseDuration(trimmed)
if err != nil {
return 0, fmt.Errorf("allow_ttl %q is not a Go duration: %w", declared, err)
}
if ttl <= 0 {
return 0, nil
}
return ttl, nil
}
// ApplyAllowLifetime sets DecisionTime and, for allows, an explicit TTL. A
// missing or zero TTL denies the allow rather than mint a standing grant.
func ApplyAllowLifetime(envelope *DecisionEnvelope, declaredTTL string, now time.Time) {
if envelope == nil {
return
}
if now.IsZero() {
now = time.Now().UTC()
} else {
now = now.UTC()
}
if envelope.Provenance.DecisionTime == "" {
envelope.Provenance.DecisionTime = now.Format(time.RFC3339)
}
if envelope.Effect != DecisionEffectAllow {
return
}
ttl, err := ParseAllowTTL(declaredTTL)
if err != nil || ttl <= 0 {
if envelope.Diagnostics == nil {
envelope.Diagnostics = map[string]any{}
}
if envelope.Reason != "" {
envelope.Diagnostics["unstated_allow_reason"] = envelope.Reason
}
envelope.Effect = DecisionEffectDeny
envelope.Reason = ReasonAllowLifetimeUnstated
envelope.MatchedRule = ReasonAllowLifetimeUnstated
envelope.Lifetime = nil
return
}
display := strings.TrimSpace(declaredTTL)
if display == "" {
display = "15m"
}
envelope.Lifetime = &DecisionLifetime{
Kind: DecisionLifetimeTTL,
TTL: display,
NotBefore: now.Format(time.RFC3339),
ExpiresAt: now.Add(ttl).Format(time.RFC3339),
}
}
// ActionAuthorizationStatus is the lifecycle state of a durable authorization.
type ActionAuthorizationStatus string
const (
ActionAuthorizationPending ActionAuthorizationStatus = "pending"
ActionAuthorizationApproved ActionAuthorizationStatus = "approved"
ActionAuthorizationDenied ActionAuthorizationStatus = "denied"
ActionAuthorizationSuperseded ActionAuthorizationStatus = "superseded"
ActionAuthorizationExpired ActionAuthorizationStatus = "expired"
ActionAuthorizationRevoked ActionAuthorizationStatus = "revoked"
)
// ActionAuthorization joins a durable approval lifecycle to one exact
// flex-auth request and decision. Storage and approval collection remain the
// responsibility of the organizational decision authority.
type ActionAuthorization struct {
SchemaVersion string `json:"schema_version" yaml:"schema_version"`
ID string `json:"id" yaml:"id"`
Status ActionAuthorizationStatus `json:"status" yaml:"status"`
SupersededBy string `json:"superseded_by,omitempty" yaml:"superseded_by,omitempty"`
Request CheckRequest `json:"request" yaml:"request"`
Validity ActionAuthorizationValidity `json:"validity" yaml:"validity"`
Approvals ActionAuthorizationApprovals `json:"approvals" yaml:"approvals"`
Decision DecisionEnvelope `json:"decision" yaml:"decision"`
Provenance map[string]any `json:"provenance,omitempty" yaml:"provenance,omitempty"`
}
// ActionAuthorizationValidity bounds execution of an approved action.
type ActionAuthorizationValidity struct {
NotBefore string `json:"not_before,omitempty" yaml:"not_before,omitempty"`
ExpiresAt string `json:"expires_at" yaml:"expires_at"`
}
// ActionAuthorizationApprovals declares the approval threshold and evidence.
type ActionAuthorizationApprovals struct {
RequiredCount int `json:"required_count" yaml:"required_count"`
Entries []ActionAuthorizationApprovalEntry `json:"entries" yaml:"entries"`
}
// ActionAuthorizationApprovalEntry is one authenticated approver's evidence.
type ActionAuthorizationApprovalEntry struct {
SubjectID string `json:"subject_id" yaml:"subject_id"`
ApprovedAt string `json:"approved_at" yaml:"approved_at"`
Assurance string `json:"assurance,omitempty" yaml:"assurance,omitempty"`
EvidenceRef string `json:"evidence_ref,omitempty" yaml:"evidence_ref,omitempty"`
}
// Obligation describes a follow-up behavior required by a decision.
type Obligation struct {
Type string `json:"type" yaml:"type"`
Parameters map[string]any `json:"parameters,omitempty" yaml:"parameters,omitempty"`
}
// DecisionProvenance captures evaluator and policy provenance.
type DecisionProvenance struct {
Evaluator string `json:"evaluator" yaml:"evaluator"`
Mode string `json:"mode" yaml:"mode"`
PolicyPackage string `json:"policy_package,omitempty" yaml:"policy_package,omitempty"`
PolicyVersion string `json:"policy_version,omitempty" yaml:"policy_version,omitempty"`
PolicyPackageDigest string `json:"policy_package_digest,omitempty" yaml:"policy_package_digest,omitempty"`
RegistrySnapshotDigest string `json:"registry_snapshot_digest,omitempty" yaml:"registry_snapshot_digest,omitempty"`
DirectoryETag string `json:"directory_etag,omitempty" yaml:"directory_etag,omitempty"`
InputClaimDigests map[string]string `json:"input_claim_digests,omitempty" yaml:"input_claim_digests,omitempty"`
DecisionTime string `json:"decision_time,omitempty" yaml:"decision_time,omitempty"`
ApprovalObservation *ApprovalObservationProvenance `json:"approval_observation,omitempty" yaml:"approval_observation,omitempty"`
Caller *CallerProvenance `json:"caller,omitempty" yaml:"caller,omitempty"`
}
// ApprovalObservationProvenance binds fresh server-owned fact evidence without
// rewriting the original carried claim/context or either PDP request digest.
type ApprovalObservationProvenance struct {
SourceID string `json:"source_id"`
SourceRevision string `json:"source_revision"`
ConfigurationDigest string `json:"configuration_digest"`
ClaimDigest string `json:"claim_digest"`
NativeBindingDigest string `json:"native_binding_digest"`
ObservedAt string `json:"observed_at"`
NotAfter string `json:"not_after"`
ValidatedAt string `json:"validated_at"`
}
// CallerProvenance records how the request was authenticated to flex-auth.
// It is not decision material: the same request from a different authenticated
// caller must decide identically. Mode is required. Under disabled the object
// is {"mode":"disabled"} with no principal (FLEX-DEC-2026-009).
type CallerProvenance struct {
Mode string `json:"mode" yaml:"mode"`
Principal string `json:"principal,omitempty" yaml:"principal,omitempty"`
Audience string `json:"audience,omitempty" yaml:"audience,omitempty"`
NotAfter string `json:"not_after,omitempty" yaml:"not_after,omitempty"`
}
// CaringDecisionMetadata carries CARING descriptor and conformance details in
// a decision envelope.
type CaringDecisionMetadata struct {
Profile string `json:"profile" yaml:"profile"`
Descriptor *CaringAccessDescriptor `json:"descriptor,omitempty" yaml:"descriptor,omitempty"`
RestrictionsEvaluated []Restriction `json:"restrictions_evaluated,omitempty" yaml:"restrictions_evaluated,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
DerivedCapabilities []CaringDerivedCapability `json:"derived_capabilities,omitempty" yaml:"derived_capabilities,omitempty"`
ConformanceFindings []CaringConformanceFinding `json:"conformance_findings,omitempty" yaml:"conformance_findings,omitempty"`
ExposureEvent *CaringExposureEvent `json:"exposure_event,omitempty" yaml:"exposure_event,omitempty"`
}
// AuditEvent is the local log shape for decisions and exposure events.
type AuditEvent struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type" yaml:"type"`
DecisionID string `json:"decision_id,omitempty" yaml:"decision_id,omitempty"`
Subject SubjectRef `json:"subject" yaml:"subject"`
Resource ResourceRef `json:"resource,omitempty" yaml:"resource,omitempty"`
Action string `json:"action,omitempty" yaml:"action,omitempty"`
Effect DecisionEffect `json:"effect,omitempty" yaml:"effect,omitempty"`
Timestamp string `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
ExposureEvent *CaringExposureEvent `json:"exposure_event,omitempty" yaml:"exposure_event,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}

View file

@ -0,0 +1,335 @@
package api
// CaringProfileCaring040RC2 is the executable profile identifier for the
// CARING 0.4.0-RC2 standard pinned by flex-auth.
const CaringProfileCaring040RC2 = "caring-0.4.0-rc2"
// SubjectType is the CARING subject dimension.
type SubjectType string
const (
SubjectTypeHuman SubjectType = "Human"
SubjectTypeGroup SubjectType = "Group"
SubjectTypeOrganization SubjectType = "Organization"
SubjectTypeService SubjectType = "Service"
SubjectTypeAutomation SubjectType = "Automation"
SubjectTypeAgent SubjectType = "Agent"
SubjectTypeSystem SubjectType = "System"
SubjectTypeDevice SubjectType = "Device"
SubjectTypeProcess SubjectType = "Process"
SubjectTypeAnonymous SubjectType = "Anonymous"
SubjectTypeUnknown SubjectType = "Unknown"
)
// OrganizationRelation is the CARING organization-relation dimension.
type OrganizationRelation string
const (
OrganizationRelationVendor OrganizationRelation = "Vendor"
OrganizationRelationServiceProvider OrganizationRelation = "ServiceProvider"
OrganizationRelationDistributor OrganizationRelation = "Distributor"
OrganizationRelationConsultant OrganizationRelation = "Consultant"
OrganizationRelationCustomer OrganizationRelation = "Customer"
OrganizationRelationCommunity OrganizationRelation = "Community"
OrganizationRelationAuthority OrganizationRelation = "Authority"
OrganizationRelationUnknown OrganizationRelation = "Unknown"
)
// CanonicalRole is the CARING lifecycle responsibility posture.
type CanonicalRole string
const (
CanonicalRoleCreator CanonicalRole = "Creator"
CanonicalRoleBuilder CanonicalRole = "Builder"
CanonicalRoleVerifier CanonicalRole = "Verifier"
CanonicalRoleMaintainer CanonicalRole = "Maintainer"
CanonicalRoleIntegrator CanonicalRole = "Integrator"
CanonicalRoleOperator CanonicalRole = "Operator"
CanonicalRoleManager CanonicalRole = "Manager"
CanonicalRoleCoach CanonicalRole = "Coach"
CanonicalRoleDoer CanonicalRole = "Doer"
)
// ScopeLevel is the CARING scope ladder.
type ScopeLevel string
const (
ScopeLevelEcosystem ScopeLevel = "Ecosystem"
ScopeLevelProduct ScopeLevel = "Product"
ScopeLevelPlatform ScopeLevel = "Platform"
ScopeLevelCluster ScopeLevel = "Cluster"
ScopeLevelEnvironment ScopeLevel = "Environment"
ScopeLevelTenant ScopeLevel = "Tenant"
ScopeLevelNamespace ScopeLevel = "Namespace"
ScopeLevelDomain ScopeLevel = "Domain"
ScopeLevelWorkspace ScopeLevel = "Workspace"
ScopeLevelProject ScopeLevel = "Project"
ScopeLevelProcess ScopeLevel = "Process"
ScopeLevelDataset ScopeLevel = "Dataset"
ScopeLevelResource ScopeLevel = "Resource"
ScopeLevelSubresource ScopeLevel = "Subresource"
ScopeLevelRecord ScopeLevel = "Record"
ScopeLevelField ScopeLevel = "Field"
ScopeLevelAction ScopeLevel = "Action"
)
// Plane is the CARING access-surface dimension.
type Plane string
const (
PlaneIntent Plane = "Intent"
PlaneBuild Plane = "Build"
PlaneRuntime Plane = "Runtime"
PlaneExecution Plane = "Execution"
PlaneConfiguration Plane = "Configuration"
PlaneData Plane = "Data"
PlaneIdentity Plane = "Identity"
PlanePolicy Plane = "Policy"
PlaneSecret Plane = "Secret"
PlaneAudit Plane = "Audit"
PlaneCommercial Plane = "Commercial"
PlaneCommunity Plane = "Community"
)
// Capability is a CARING capability verb.
type Capability string
const (
CapabilityView Capability = "View"
CapabilityViewCollection Capability = "ViewCollection"
CapabilityObserve Capability = "Observe"
CapabilityCreate Capability = "Create"
CapabilityEditOwn Capability = "EditOwn"
CapabilityEditAssigned Capability = "EditAssigned"
CapabilityEditAny Capability = "EditAny"
CapabilityDeleteOwn Capability = "DeleteOwn"
CapabilityDeleteAny Capability = "DeleteAny"
CapabilityBulkDelete Capability = "BulkDelete"
CapabilitySubmit Capability = "Submit"
CapabilityComment Capability = "Comment"
CapabilityReview Capability = "Review"
CapabilityApprove Capability = "Approve"
CapabilityReject Capability = "Reject"
CapabilityPublish Capability = "Publish"
CapabilityArchive Capability = "Archive"
CapabilityRestore Capability = "Restore"
CapabilityExecute Capability = "Execute"
CapabilityConfigure Capability = "Configure"
CapabilityOperate Capability = "Operate"
CapabilityDeploy Capability = "Deploy"
CapabilityIntegrate Capability = "Integrate"
CapabilityGrant Capability = "Grant"
CapabilityRevoke Capability = "Revoke"
CapabilityDelegate Capability = "Delegate"
CapabilityImpersonate Capability = "Impersonate"
CapabilityExport Capability = "Export"
CapabilityImport Capability = "Import"
CapabilityReplicate Capability = "Replicate"
CapabilityEncrypt Capability = "Encrypt"
CapabilityDecrypt Capability = "Decrypt"
CapabilityMask Capability = "Mask"
CapabilityInspect Capability = "Inspect"
CapabilityAudit Capability = "Audit"
CapabilityOverride Capability = "Override"
CapabilityEscalate Capability = "Escalate"
CapabilityBind Capability = "Bind"
CapabilityUse Capability = "Use"
)
// ExposureMode describes how much information becomes visible or extractable.
type ExposureMode string
const (
ExposureModeNone ExposureMode = "None"
ExposureModeMetadata ExposureMode = "Metadata"
ExposureModeMasked ExposureMode = "Masked"
ExposureModeAggregated ExposureMode = "Aggregated"
ExposureModeSynthetic ExposureMode = "Synthetic"
ExposureModePseudonymous ExposureMode = "Pseudonymous"
ExposureModeEncrypted ExposureMode = "Encrypted"
ExposureModePlaintext ExposureMode = "Plaintext"
ExposureModeSecretMaterial ExposureMode = "SecretMaterial"
ExposureModeExportable ExposureMode = "Exportable"
ExposureModeCrossTenantAggregate ExposureMode = "CrossTenantAggregate"
)
// Condition is a CARING runtime or governance condition.
type Condition string
const (
ConditionMFARequired Condition = "MFARequired"
ConditionDeviceTrusted Condition = "DeviceTrusted"
ConditionNetworkTrusted Condition = "NetworkTrusted"
ConditionTicketRequired Condition = "TicketRequired"
ConditionTenantConsentRequired Condition = "TenantConsentRequired"
ConditionCustomerApprovalRequired Condition = "CustomerApprovalRequired"
ConditionDualApprovalRequired Condition = "DualApprovalRequired"
ConditionTimeLimited Condition = "TimeLimited"
ConditionBusinessHoursOnly Condition = "BusinessHoursOnly"
ConditionEmergencyOnly Condition = "EmergencyOnly"
ConditionTrainingRequired Condition = "TrainingRequired"
ConditionContractRequired Condition = "ContractRequired"
ConditionNDARequired Condition = "NDARequired"
ConditionPurposeBound Condition = "PurposeBound"
ConditionCaseBound Condition = "CaseBound"
ConditionEnvironmentBound Condition = "EnvironmentBound"
ConditionNamespaceBound Condition = "NamespaceBound"
ConditionPipelineBound Condition = "PipelineBound"
ConditionChangeWindowBound Condition = "ChangeWindowBound"
ConditionLogged Condition = "Logged"
ConditionRecorded Condition = "Recorded"
ConditionNotificationRequired Condition = "NotificationRequired"
ConditionPostReviewRequired Condition = "PostReviewRequired"
ConditionHumanReviewRequired Condition = "HumanReviewRequired"
ConditionPolicyReviewRequired Condition = "PolicyReviewRequired"
ConditionWorkloadIdentityRequired Condition = "WorkloadIdentityRequired"
)
// LifecycleState describes why access exists now.
type LifecycleState string
const (
LifecycleStateDesign LifecycleState = "Design"
LifecycleStateBuild LifecycleState = "Build"
LifecycleStateTest LifecycleState = "Test"
LifecycleStateReview LifecycleState = "Review"
LifecycleStateRelease LifecycleState = "Release"
LifecycleStateOnboard LifecycleState = "Onboard"
LifecycleStateIntegrate LifecycleState = "Integrate"
LifecycleStateMigrate LifecycleState = "Migrate"
LifecycleStateOperate LifecycleState = "Operate"
LifecycleStateSupport LifecycleState = "Support"
LifecycleStateImprove LifecycleState = "Improve"
LifecycleStateDeprecate LifecycleState = "Deprecate"
LifecycleStateArchive LifecycleState = "Archive"
LifecycleStateIncident LifecycleState = "Incident"
LifecycleStateLegal LifecycleState = "Legal"
LifecycleStateTerminate LifecycleState = "Terminate"
)
// Restriction is an overriding CARING deny or limiting policy effect.
type Restriction string
const (
RestrictionNoAccess Restriction = "NoAccess"
RestrictionSuspended Restriction = "Suspended"
RestrictionTerminated Restriction = "Terminated"
RestrictionQuarantined Restriction = "Quarantined"
RestrictionScopeExcluded Restriction = "ScopeExcluded"
RestrictionDataClassRestricted Restriction = "DataClassRestricted"
RestrictionLegalHold Restriction = "LegalHold"
RestrictionExportBlocked Restriction = "ExportBlocked"
RestrictionImpersonationBlocked Restriction = "ImpersonationBlocked"
RestrictionCrossTenantBlocked Restriction = "CrossTenantBlocked"
RestrictionSecretAccessBlocked Restriction = "SecretAccessBlocked"
RestrictionPolicyFrozen Restriction = "PolicyFrozen"
RestrictionEmergencyLocked Restriction = "EmergencyLocked"
RestrictionRiskDenied Restriction = "RiskDenied"
RestrictionExecutionBlocked Restriction = "ExecutionBlocked"
RestrictionWorkloadCreationBlocked Restriction = "WorkloadCreationBlocked"
RestrictionPrivilegeEscalationBlocked Restriction = "PrivilegeEscalationBlocked"
)
// ExposureEventType is a CARING exceptional or irregular access class.
type ExposureEventType string
const (
ExposureEventSupport ExposureEventType = "X-Support"
ExposureEventBreakGlass ExposureEventType = "X-BreakGlass"
ExposureEventSecurityTest ExposureEventType = "X-SecurityTest"
ExposureEventIncident ExposureEventType = "X-Incident"
ExposureEventLegalDemand ExposureEventType = "X-LegalDemand"
ExposureEventComplianceAudit ExposureEventType = "X-ComplianceAudit"
ExposureEventMigration ExposureEventType = "X-Migration"
ExposureEventRecovery ExposureEventType = "X-Recovery"
ExposureEventAdversarial ExposureEventType = "X-Adversarial"
ExposureEventMisconfiguration ExposureEventType = "X-Misconfiguration"
ExposureEventInducedAccess ExposureEventType = "X-InducedAccess"
ExposureEventPrivilegeEscalation ExposureEventType = "X-PrivilegeEscalation"
)
// AccessPath describes how access is exercised.
type AccessPath string
const (
AccessPathDirect AccessPath = "direct"
AccessPathDelegated AccessPath = "delegated"
AccessPathMediated AccessPath = "mediated"
AccessPathInduced AccessPath = "induced"
)
// CaringScope identifies where a CARING descriptor applies.
type CaringScope struct {
Level ScopeLevel `json:"level" yaml:"level"`
ID string `json:"id" yaml:"id"`
Parent string `json:"parent,omitempty" yaml:"parent,omitempty"`
Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"`
Resource string `json:"resource,omitempty" yaml:"resource,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// CaringAccessDescriptor is the executable flex-auth representation of a
// CARING access assignment.
type CaringAccessDescriptor struct {
ID string `json:"id,omitempty" yaml:"id,omitempty"`
Profile string `json:"profile" yaml:"profile"`
SubjectType SubjectType `json:"subject_type" yaml:"subject_type"`
OrganizationRelation OrganizationRelation `json:"organization_relation" yaml:"organization_relation"`
CanonicalRole CanonicalRole `json:"canonical_role" yaml:"canonical_role"`
Scope CaringScope `json:"scope" yaml:"scope"`
Planes []Plane `json:"planes" yaml:"planes"`
Capabilities []Capability `json:"capabilities" yaml:"capabilities"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
Conditions []Condition `json:"conditions,omitempty" yaml:"conditions,omitempty"`
LifecycleState LifecycleState `json:"lifecycle_state,omitempty" yaml:"lifecycle_state,omitempty"`
Restrictions []Restriction `json:"restrictions,omitempty" yaml:"restrictions,omitempty"`
ExposureEvent ExposureEventType `json:"exposure_event,omitempty" yaml:"exposure_event,omitempty"`
DerivedCapabilities []CaringDerivedCapability `json:"derived_capabilities,omitempty" yaml:"derived_capabilities,omitempty"`
AccessPath AccessPath `json:"access_path,omitempty" yaml:"access_path,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// CaringDerivedCapability records effective authority created by another grant.
type CaringDerivedCapability struct {
Capability Capability `json:"capability" yaml:"capability"`
Reason string `json:"reason" yaml:"reason"`
Source string `json:"source,omitempty" yaml:"source,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
}
// CaringConformanceFinding is a diagnostic emitted by descriptive or
// prescriptive CARING validation.
type CaringConformanceFinding struct {
Code string `json:"code" yaml:"code"`
Severity string `json:"severity" yaml:"severity"`
Message string `json:"message" yaml:"message"`
Fields []string `json:"fields,omitempty" yaml:"fields,omitempty"`
Descriptor string `json:"descriptor,omitempty" yaml:"descriptor,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// CaringExposureEvent records exceptional or irregular information exposure.
type CaringExposureEvent struct {
ID string `json:"id" yaml:"id"`
Type ExposureEventType `json:"type" yaml:"type"`
Actor string `json:"actor" yaml:"actor"`
Subject string `json:"subject" yaml:"subject"`
Descriptor *CaringAccessDescriptor `json:"descriptor,omitempty" yaml:"descriptor,omitempty"`
Scope *CaringScope `json:"scope,omitempty" yaml:"scope,omitempty"`
Planes []Plane `json:"planes,omitempty" yaml:"planes,omitempty"`
CapabilitiesUsed []Capability `json:"capabilities_used,omitempty" yaml:"capabilities_used,omitempty"`
DerivedCapabilities []CaringDerivedCapability `json:"derived_capabilities,omitempty" yaml:"derived_capabilities,omitempty"`
ExposureModes []ExposureMode `json:"exposure_modes,omitempty" yaml:"exposure_modes,omitempty"`
Reason string `json:"reason" yaml:"reason"`
AuthoritySource string `json:"authority_source,omitempty" yaml:"authority_source,omitempty"`
Approval string `json:"approval,omitempty" yaml:"approval,omitempty"`
StartTime string `json:"start_time,omitempty" yaml:"start_time,omitempty"`
EndTime string `json:"end_time,omitempty" yaml:"end_time,omitempty"`
ResourcesAccessed []string `json:"resources_accessed,omitempty" yaml:"resources_accessed,omitempty"`
Evidence []string `json:"evidence,omitempty" yaml:"evidence,omitempty"`
NotificationStatus string `json:"notification_status,omitempty" yaml:"notification_status,omitempty"`
PostReview string `json:"post_review,omitempty" yaml:"post_review,omitempty"`
ConformanceFindings []CaringConformanceFinding `json:"conformance_findings,omitempty" yaml:"conformance_findings,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}

View file

@ -0,0 +1,32 @@
package api
// ResourceManifest is the shape a protected system publishes to register
// its resources with flex-auth. The shape is pinned against the
// Markitect-side emitter in markitect-tool (MKTT-WP-0014); see
// schemas/resource_manifest.schema.json for the JSON Schema and
// examples/markitect/resource_manifest.yaml for the canonical example.
type ResourceManifest struct {
ID string `json:"id" yaml:"id"`
System string `json:"system" yaml:"system"`
Resources []Resource `json:"resources" yaml:"resources"`
Actions []string `json:"actions,omitempty" yaml:"actions,omitempty"`
CaringProfile string `json:"caring_profile,omitempty" yaml:"caring_profile,omitempty"`
Metadata map[string]any `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// Resource is one entry in a ResourceManifest.
type Resource struct {
ID string `json:"id" yaml:"id"`
Type string `json:"type" yaml:"type"`
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Parent string `json:"parent,omitempty" yaml:"parent,omitempty"`
Labels []string `json:"labels,omitempty" yaml:"labels,omitempty"`
TrustZone string `json:"trust_zone,omitempty" yaml:"trust_zone,omitempty"`
Owner string `json:"owner,omitempty" yaml:"owner,omitempty"`
Caring *CaringAccessDescriptor `json:"caring,omitempty" yaml:"caring,omitempty"`
Attributes map[string]any `json:"attributes,omitempty" yaml:"attributes,omitempty"`
}
// FlexAuthContractV0 is the metadata.flex_auth_contract value that
// signals the v0 resource-registration contract.
const FlexAuthContractV0 = "resource-registration-v0"

View file

@ -0,0 +1,296 @@
// Package decisionpep authenticates the producer's decision record for one
// current request. It never evaluates policy, caches a verdict, or discovers trust.
package decisionpep
import (
"bytes"
"context"
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/netkingdom/access-engine/internal/sign"
"github.com/netkingdom/access-engine/pkg/api"
)
var ErrRefused = errors.New("authenticated decision refused")
const nonceKey = "decisionpep_request_nonce"
const JSONProfile = "lossless-json-v1"
const provenanceMode = "standalone/lossless-json-v1"
const maxBytes = 2 << 20
// Admission is public, independently admitted owner configuration. TokenFile is
// only a path to a private rotating caller credential, never a literal token.
type Admission struct {
Endpoint string `json:"endpoint"`
CAFile string `json:"ca_file"`
TokenFile string `json:"token_file"`
Keys map[string]string `json:"keys"`
Evaluator string `json:"evaluator"`
PolicyPackage string `json:"policy_package"`
PolicyVersion string `json:"policy_version"`
PolicyDigest string `json:"policy_digest"`
RegistryDigest string `json:"registry_digest"`
CallerPrincipal string `json:"caller_principal"`
CallerAudience string `json:"caller_audience"`
MaxTTL string `json:"max_ttl"`
MaxClockSkew string `json:"max_clock_skew"`
}
type Client struct {
admission Admission
keys map[string]ed25519.PublicKey
http *http.Client
maxTTL, skew time.Duration
now func() time.Time
}
// Load reads a current independently admitted file. No trust is bootstrapped
// from a PDP response, token contents, discovery endpoint, or environment value.
func Load(path string) (*Client, error) {
raw, err := readFile(path, false)
if err != nil {
return nil, ErrRefused
}
var a Admission
d := json.NewDecoder(bytes.NewReader(raw))
d.DisallowUnknownFields()
if d.Decode(&a) != nil {
return nil, ErrRefused
}
if _, err = d.Token(); err != io.EOF {
return nil, ErrRefused
}
return New(a)
}
func New(a Admission) (*Client, error) {
u, err := url.Parse(a.Endpoint)
if err != nil || u.Scheme != "https" || u.Host == "" || u.User != nil || u.Path != "/v1/check" || u.RawQuery != "" || u.Fragment != "" {
return nil, ErrRefused
}
if !filepath.IsAbs(a.CAFile) || !filepath.IsAbs(a.TokenFile) || a.Evaluator == "" || a.PolicyPackage == "" || a.PolicyVersion == "" || a.CallerPrincipal == "" || a.CallerAudience == "" {
return nil, ErrRefused
}
if !validDigest(a.PolicyDigest) || !validDigest(a.RegistryDigest) || len(a.Keys) == 0 {
return nil, ErrRefused
}
ttl, err := time.ParseDuration(a.MaxTTL)
if err != nil || ttl <= 0 {
return nil, ErrRefused
}
skew, err := time.ParseDuration(a.MaxClockSkew)
if err != nil || skew < 0 || skew > ttl {
return nil, ErrRefused
}
ca, err := readFile(a.CAFile, false)
if err != nil {
return nil, ErrRefused
}
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM(ca) {
return nil, ErrRefused
}
keys := map[string]ed25519.PublicKey{}
for kid, value := range a.Keys {
b, e := base64.RawURLEncoding.Strict().DecodeString(value)
if e != nil || kid == "" || len(b) != ed25519.PublicKeySize {
return nil, ErrRefused
}
keys[kid] = append(ed25519.PublicKey(nil), b...)
}
transport := &http.Transport{Proxy: nil, TLSClientConfig: &tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12}, DisableKeepAlives: true}
client := &http.Client{Transport: transport, Timeout: 10 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return ErrRefused }}
return &Client{admission: a, keys: keys, http: client, maxTTL: ttl, skew: skew, now: time.Now}, nil
}
func validDigest(value string) bool {
if !strings.HasPrefix(value, "sha256:") || len(value) != 71 {
return false
}
_, e := hex.DecodeString(value[7:])
return e == nil && strings.ToLower(value) == value
}
func readFile(path string, private bool) ([]byte, error) {
if !filepath.IsAbs(path) {
return nil, ErrRefused
}
f, e := os.Open(path)
if e != nil {
return nil, ErrRefused
}
defer f.Close()
info, e := f.Stat()
if e != nil || !info.Mode().IsRegular() || info.Mode().Perm()&0022 != 0 {
return nil, ErrRefused
}
if private && info.Mode().Perm()&0007 != 0 {
return nil, ErrRefused
}
b, e := io.ReadAll(io.LimitReader(f, maxBytes+1))
if e != nil || len(b) > maxBytes {
return nil, ErrRefused
}
return b, nil
}
// Check sends exactly one request with a cryptographically fresh nonce. The
// returned envelope is the producer's authenticated typed interpretation.
// Callers must execute only the request's exact operation and honor their own
// execution boundary; a login decision is never a later resource decision.
func (c *Client) Check(ctx context.Context, request api.CheckRequest) (api.DecisionEnvelope, error) {
refuse := func() (api.DecisionEnvelope, error) { return api.DecisionEnvelope{}, ErrRefused }
if c == nil || request.Purpose != "" || request.Tenant == "" || request.Subject.ID == "" || request.Subject.Type == "" || request.Resource.ID == "" || request.Resource.Type == "" || request.Resource.System == "" || request.Action == "" {
return refuse()
}
if request.Subject.Tenant != request.Tenant || request.Resource.Tenant != request.Tenant {
return refuse()
}
if request.PolicyVersion != "" && request.PolicyVersion != c.admission.PolicyVersion {
return refuse()
}
// Use the producer's typed representation and preserve numeric lexemes. A
// producer that rounds before hashing fails this join rather than weakening it.
raw, err := json.Marshal(request)
if err != nil {
return refuse()
}
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
if decoder.Decode(&request) != nil {
return refuse()
}
if request.Context == nil {
request.Context = map[string]any{}
}
if _, exists := request.Context[nonceKey]; exists {
return refuse()
}
nonce := make([]byte, 32)
if _, err = rand.Read(nonce); err != nil {
return refuse()
}
request.Context[nonceKey] = hex.EncodeToString(nonce)
request.Context["decisionpep_json_profile"] = JSONProfile
request.ID = hex.EncodeToString(nonce)
request.PolicyVersion = c.admission.PolicyVersion
raw, err = json.Marshal(request)
if err != nil || len(raw) > maxBytes {
return refuse()
}
token, err := readFile(c.admission.TokenFile, true)
if err != nil {
return refuse()
}
value := strings.TrimSpace(string(token))
if value == "" || strings.ContainsAny(value, " \t\r\n") {
return refuse()
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.admission.Endpoint, bytes.NewReader(raw))
if err != nil {
return refuse()
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Access-Engine-JSON-Profile", JSONProfile)
req.Header.Set("Authorization", "Bearer "+value)
started := c.now().UTC()
response, err := c.http.Do(req)
if err != nil {
return refuse()
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return refuse()
}
body, err := io.ReadAll(io.LimitReader(response.Body, maxBytes+1))
if err != nil || len(body) > maxBytes {
return refuse()
}
envelope, err := sign.DecodeEnvelope(body)
if err != nil || sign.Verify(envelope, c.keys) != nil {
return refuse()
}
if c.validate(envelope, request, started, c.now().UTC()) != nil {
return refuse()
}
return envelope, nil
}
func (c *Client) validate(e api.DecisionEnvelope, r api.CheckRequest, started, now time.Time) error {
p := e.Provenance
b := e.Binding
l := e.Lifetime
if e.ID == "" || e.ContractVersion != api.DecisionRecordContractV1 || e.Effect != api.DecisionEffectAllow || e.RequestID != r.ID || len(e.Obligations) != 0 || b == nil || l == nil {
return ErrRefused
}
if p.Evaluator != c.admission.Evaluator || p.Mode != provenanceMode || p.PolicyPackage != c.admission.PolicyPackage || p.PolicyVersion != c.admission.PolicyVersion || p.PolicyPackageDigest != c.admission.PolicyDigest || p.RegistrySnapshotDigest != c.admission.RegistryDigest || e.MatchedPolicyVersion != p.PolicyVersion {
return ErrRefused
}
if b.Purpose != "" || b.SubmittedRequestDigest != api.RequestDigest(r) || b.Tenant != r.Tenant || b.Action != r.Action || api.CanonicalDigest(b.Context) != api.CanonicalDigest(r.Context) {
return ErrRefused
}
if !sameSubject(b.Subject, r.Subject) || !sameResource(b.Resource, r.Resource) || !sameSubject(e.Subject, b.Subject) || !sameResource(e.Resource, b.Resource) {
return ErrRefused
}
evaluated := api.CheckRequest{Tenant: b.Tenant, Subject: b.Subject, Action: b.Action, Resource: b.Resource, Context: b.Context, Purpose: b.Purpose}
if b.RequestDigest != api.RequestDigest(evaluated) {
return ErrRefused
}
for k, v := range api.InputClaimDigests(r) {
if p.InputClaimDigests[k] != v {
return ErrRefused
}
}
caller := p.Caller
if caller == nil || caller.Mode != "enforce" || caller.Principal != c.admission.CallerPrincipal || caller.Audience != c.admission.CallerAudience {
return ErrRefused
}
callerEnd, err := time.Parse(time.RFC3339Nano, caller.NotAfter)
if err != nil || !now.Before(callerEnd) {
return ErrRefused
}
decision, err := time.Parse(time.RFC3339Nano, p.DecisionTime)
if err != nil || decision.Before(started.Add(-c.skew)) || decision.After(now.Add(c.skew)) {
return ErrRefused
}
begin, err := time.Parse(time.RFC3339Nano, l.NotBefore)
if err != nil || begin.After(now) {
return ErrRefused
}
end, err := time.Parse(time.RFC3339Nano, l.ExpiresAt)
if err != nil || !now.Before(end) || !end.After(begin) || end.Sub(begin) > c.maxTTL || end.After(callerEnd) {
return ErrRefused
}
ttl, err := time.ParseDuration(l.TTL)
if err != nil || l.Kind != api.DecisionLifetimeTTL || ttl <= 0 || ttl > c.maxTTL {
return ErrRefused
}
// Producer CompleteDecision uses its exact decision instant for both bounds.
// RFC3339 output can truncate subsecond precision, so do not invent lost time.
if !begin.Equal(decision) || end.Sub(begin) != ttl {
return ErrRefused
}
return nil
}
func sameSubject(a, b api.SubjectRef) bool {
return a.ID == b.ID && a.Type == b.Type && a.Tenant == b.Tenant
}
func sameResource(a, b api.ResourceRef) bool {
return a.ID == b.ID && a.Type == b.Type && a.System == b.System && a.Tenant == b.Tenant
}

View file

@ -0,0 +1,211 @@
package decisionpep
import (
"context"
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/json"
"encoding/pem"
"io"
"math/big"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/netkingdom/access-engine/internal/sign"
"github.com/netkingdom/access-engine/pkg/api"
)
type transportFunc func(*http.Request) (*http.Response, error)
func (f transportFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func testClient(t *testing.T) (*Client, ed25519.PrivateKey, time.Time) {
t.Helper()
pub, key, e := ed25519.GenerateKey(rand.Reader)
if e != nil {
t.Fatal(e)
}
now := time.Date(2026, 10, 18, 1, 2, 3, 0, time.UTC)
dir := t.TempDir()
cert, e := x509.CreateCertificate(rand.Reader, &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "test-only"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign}, &x509.Certificate{SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "test-only"}, NotBefore: now.Add(-time.Hour), NotAfter: now.Add(time.Hour), IsCA: true, BasicConstraintsValid: true, KeyUsage: x509.KeyUsageCertSign}, pub, key)
if e != nil {
t.Fatal(e)
}
ca := filepath.Join(dir, "ca.pem")
token := filepath.Join(dir, "token")
if e = os.WriteFile(ca, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0600); e != nil {
t.Fatal(e)
}
if e = os.WriteFile(token, []byte("synthetic-caller-token"), 0600); e != nil {
t.Fatal(e)
}
a := Admission{Endpoint: "https://pdp.invalid/v1/check", CAFile: ca, TokenFile: token, Keys: map[string]string{"test-key": base64.RawURLEncoding.EncodeToString(pub)}, Evaluator: "flex-auth/local", PolicyPackage: "forgejo.application-access", PolicyVersion: "v1", PolicyDigest: "sha256:" + strings.Repeat("1", 64), RegistryDigest: "sha256:" + strings.Repeat("2", 64), CallerPrincipal: "system:serviceaccount:test:forgejo", CallerAudience: "access-engine", MaxTTL: "30s", MaxClockSkew: "1s"}
c, e := New(a)
if e != nil {
t.Fatal(e)
}
c.now = func() time.Time { return now }
return c, key, now
}
func testRequest() api.CheckRequest {
return api.CheckRequest{Tenant: "tenant:test", Subject: api.SubjectRef{ID: "subject:test", Type: api.SubjectType("Human"), Tenant: "tenant:test", Attributes: map[string]any{"groups": []string{"engineers"}}}, Action: "application.access", Resource: api.ResourceRef{ID: "application:forgejo", Type: "application", System: "forgejo", Tenant: "tenant:test"}, Context: map[string]any{"big": json.Number("9007199254740993"), "fraction": json.Number("0.123456789123456789")}}
}
func envelope(c *Client, r api.CheckRequest, key ed25519.PrivateKey, now time.Time) api.DecisionEnvelope {
e := api.DecisionEnvelope{ID: "test-decision", RequestID: r.ID, Effect: api.DecisionEffectAllow, MatchedPolicyVersion: c.admission.PolicyVersion, Subject: r.Subject, Resource: r.Resource, Binding: api.NewDecisionBindingFor(r, r), Provenance: api.DecisionProvenance{Evaluator: c.admission.Evaluator, Mode: provenanceMode, PolicyPackage: c.admission.PolicyPackage, PolicyVersion: c.admission.PolicyVersion, PolicyPackageDigest: c.admission.PolicyDigest, RegistrySnapshotDigest: c.admission.RegistryDigest, Caller: &api.CallerProvenance{Mode: "enforce", Principal: c.admission.CallerPrincipal, Audience: c.admission.CallerAudience, NotAfter: now.Add(time.Minute).Format(time.RFC3339Nano)}}}
api.CompleteDecision(&e, r, api.DecisionCompletion{AllowTTL: "10s", Now: now})
if sign.Sign(&e, key, "test-key") != nil {
panic("test sign")
}
return e
}
func respond(t *testing.T, c *Client, key ed25519.PrivateKey, now time.Time, mutate func(*api.DecisionEnvelope), resign bool) {
t.Helper()
c.http = &http.Client{Transport: transportFunc(func(req *http.Request) (*http.Response, error) {
if req.Header.Get("Access-Engine-JSON-Profile") != JSONProfile {
t.Fatal("missing lossless profile")
}
if req.Header.Get("Authorization") != "Bearer synthetic-caller-token" {
t.Fatal("wrong synthetic caller token")
}
var r api.CheckRequest
d := json.NewDecoder(req.Body)
d.UseNumber()
if d.Decode(&r) != nil {
t.Fatal("request decode")
}
if r.Context["big"] != json.Number("9007199254740993") || r.Context["fraction"] != json.Number("0.123456789123456789") {
t.Fatal("request numeric precision lost")
}
if len(r.Context[nonceKey].(string)) != 64 || r.ID != r.Context[nonceKey] {
t.Fatal("nonce")
}
e := envelope(c, r, key, now)
if mutate != nil {
mutate(&e)
}
if resign {
if sign.Sign(&e, key, "test-key") != nil {
t.Fatal("sign")
}
}
b, err := json.Marshal(e)
if err != nil {
t.Fatal(err)
}
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(string(b))), Header: http.Header{}}, nil
})}
}
func TestAuthenticDecisionProducerCanonicalPrecision(t *testing.T) {
c, key, now := testClient(t)
respond(t, c, key, now, nil, false)
e, err := c.Check(context.Background(), testRequest())
if err != nil {
t.Fatal(err)
}
if e.Binding.Context["big"] != json.Number("9007199254740993") || e.Binding.Context["fraction"] != json.Number("0.123456789123456789") {
t.Fatal("signed typed precision lost")
}
if sign.Verify(e, c.keys) != nil {
t.Fatal("returned envelope not authentic producer bytes")
}
}
func TestDecisionAdverseBindings(t *testing.T) {
cases := map[string]func(*api.DecisionEnvelope){
"unsigned": func(e *api.DecisionEnvelope) { e.Signature = api.UnsignedEnvelopeSignature() },
"effect": func(e *api.DecisionEnvelope) { e.Effect = api.DecisionEffectDeny },
"evaluator": func(e *api.DecisionEnvelope) { e.Provenance.Evaluator = "impostor" },
"policy": func(e *api.DecisionEnvelope) { e.Provenance.PolicyPackage = "other" },
"version": func(e *api.DecisionEnvelope) { e.Provenance.PolicyVersion = "v2" },
"policy-digest": func(e *api.DecisionEnvelope) { e.Provenance.PolicyPackageDigest = "sha256:" + strings.Repeat("3", 64) },
"registry": func(e *api.DecisionEnvelope) {
e.Provenance.RegistrySnapshotDigest = "sha256:" + strings.Repeat("4", 64)
},
"tenant": func(e *api.DecisionEnvelope) { e.Binding.Tenant = "other" },
"actor": func(e *api.DecisionEnvelope) { e.Binding.Subject.ID = "other" },
"action": func(e *api.DecisionEnvelope) { e.Binding.Action = "write" },
"resource": func(e *api.DecisionEnvelope) { e.Binding.Resource.ID = "other" },
"purpose": func(e *api.DecisionEnvelope) { e.Binding.Purpose = "readback" },
"submitted": func(e *api.DecisionEnvelope) { e.Binding.SubmittedRequestDigest = "wrong" },
"context": func(e *api.DecisionEnvelope) { e.Binding.Context["fraction"] = json.Number("0.123456789123456788") },
"legacy-numeric-profile": func(e *api.DecisionEnvelope) { e.Provenance.Mode = "standalone" },
"caller-warn": func(e *api.DecisionEnvelope) { e.Provenance.Caller.Mode = "warn" },
"caller-principal": func(e *api.DecisionEnvelope) { e.Provenance.Caller.Principal = "other" },
"caller-audience": func(e *api.DecisionEnvelope) { e.Provenance.Caller.Audience = "other" },
"caller-expired": func(e *api.DecisionEnvelope) { e.Provenance.Caller.NotAfter = "2020-01-01T00:00:00Z" },
"stale-decision": func(e *api.DecisionEnvelope) { e.Provenance.DecisionTime = "2020-01-01T00:00:00Z" },
"expired": func(e *api.DecisionEnvelope) { e.Lifetime.ExpiresAt = "2020-01-01T00:00:00Z" },
"future-begin": func(e *api.DecisionEnvelope) { e.Lifetime.NotBefore = "2030-01-01T00:00:00Z" },
"subnanosecond-expiry": func(e *api.DecisionEnvelope) { e.Lifetime.ExpiresAt = "2026-10-18T01:02:02.999999999Z" },
"missing-TTL": func(e *api.DecisionEnvelope) { e.Lifetime = nil },
"excess-TTL": func(e *api.DecisionEnvelope) { e.Lifetime.TTL = "1h" },
"obligation": func(e *api.DecisionEnvelope) { e.Obligations = []api.Obligation{{Type: "unimplemented"}} },
}
for name, mutation := range cases {
t.Run(name, func(t *testing.T) {
c, key, now := testClient(t)
respond(t, c, key, now, mutation, name != "unsigned")
if _, err := c.Check(context.Background(), testRequest()); err == nil {
t.Fatal("accepted wrong signed binding")
}
})
}
}
func TestSignatureForgeryAndReplay(t *testing.T) {
t.Run("forged-payload", func(t *testing.T) {
c, key, now := testClient(t)
respond(t, c, key, now, func(e *api.DecisionEnvelope) { e.Reason = "changed after signature" }, false)
if _, e := c.Check(context.Background(), testRequest()); e == nil {
t.Fatal("forgery accepted")
}
})
t.Run("wrong-key", func(t *testing.T) {
c, _, now := testClient(t)
_, key, _ := ed25519.GenerateKey(rand.Reader)
respond(t, c, key, now, nil, false)
if _, e := c.Check(context.Background(), testRequest()); e == nil {
t.Fatal("wrong key accepted")
}
})
t.Run("replay", func(t *testing.T) {
c, key, now := testClient(t)
var saved []byte
c.http = &http.Client{Transport: transportFunc(func(req *http.Request) (*http.Response, error) {
if saved == nil {
var r api.CheckRequest
d := json.NewDecoder(req.Body)
d.UseNumber()
if d.Decode(&r) != nil {
t.Fatal("decode")
}
e := envelope(c, r, key, now)
saved, _ = json.Marshal(e)
}
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(string(saved)))}, nil
})}
if _, e := c.Check(context.Background(), testRequest()); e != nil {
t.Fatal(e)
}
if _, e := c.Check(context.Background(), testRequest()); e == nil {
t.Fatal("old decision replay accepted for new nonce")
}
})
}
func TestUnavailableAndPublicTokenFileRefuse(t *testing.T) {
c, _, _ := testClient(t)
c.http = &http.Client{Transport: transportFunc(func(*http.Request) (*http.Response, error) { return nil, io.ErrUnexpectedEOF })}
if _, e := c.Check(context.Background(), testRequest()); e == nil {
t.Fatal("unavailable allowed")
}
if e := os.Chmod(c.admission.TokenFile, 0644); e != nil {
t.Fatal(e)
}
if _, e := c.Check(context.Background(), testRequest()); e == nil {
t.Fatal("public token file allowed")
}
}

View file

@ -0,0 +1,30 @@
{
"upstream": "github.com/netkingdom/access-engine",
"commit": "d328a2601095686f74376d87791bff01e2a3a2a3",
"exact_unchanged_files": {
"pkg/api/canonical.go": "a122a2e8cd524333b13c5939c7a3685e41efbe47497a64e0c7297c92b7f10d19",
"pkg/api/caring.go": "1907156f0f889e7b9e9c14970cf7a86a20d00478a496efaf92c7231f27fec5e3",
"pkg/api/resource_manifest.go": "f0fa4080287d389ed48c2b1d6af96cc62e57ef72c902333f677f964420be133f",
"internal/sign/sign.go": "0df9515922e90775ebbfac798361dcafb20da8a4213796b44e246b8a92046cde",
"LICENSE": "ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6"
},
"module": "Standalone Go1.24 source slice with original module/import identity; no canonical/schema/signature modifications",
"new_code": [
"pkg/decisionpep/client.go"
],
"fixture_code": [
"testsupport/signfixture/sign.go"
],
"source_of_truth": "repository root; generated with tools/export_decisionpep_adapter.py",
"exported_file_sha256": {
"LICENSE": "ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"go.mod": "fea700810e47c1ba5638b86a168f8531c083a6e1222ca80bd2a8947b2ea6f2c3",
"internal/sign/sign.go": "0df9515922e90775ebbfac798361dcafb20da8a4213796b44e246b8a92046cde",
"pkg/api/canonical.go": "a122a2e8cd524333b13c5939c7a3685e41efbe47497a64e0c7297c92b7f10d19",
"pkg/api/caring.go": "1907156f0f889e7b9e9c14970cf7a86a20d00478a496efaf92c7231f27fec5e3",
"pkg/api/resource_manifest.go": "f0fa4080287d389ed48c2b1d6af96cc62e57ef72c902333f677f964420be133f",
"pkg/decisionpep/client.go": "0d57f6ddaca5c3f81ce426c0e8b14ba14ea5962096d3be5909aa456eef19eeba",
"pkg/decisionpep/client_test.go": "1a6be3485f580f95a1f1406fb76e525f2c4bf02fe04a424cfe0095bb728e3f62",
"testsupport/signfixture/sign.go": "fe4aa86f444dc20145aefbaca35b24f046264b2c5c8b4e3b057a4496b77a4d29"
}
}

View file

@ -0,0 +1,14 @@
// Package signfixture exposes producer signing only for synthetic consumer tests.
// Callers supply test keys; this package contains no credentials or runtime signer.
package signfixture
import (
"crypto/ed25519"
"github.com/netkingdom/access-engine/internal/sign"
"github.com/netkingdom/access-engine/pkg/api"
)
// Sign uses the unchanged producer envelope format with a caller-owned fixture key.
func Sign(envelope *api.DecisionEnvelope, key ed25519.PrivateKey, kid string) error {
return sign.Sign(envelope, key, kid)
}

View file

@ -0,0 +1,151 @@
# Target Revenue Source License
**Version 1.0, Candidate 1 (V1C1)**
---
> **PRELIMINARY CANDIDATE — SUBJECT TO CHANGE — NOT FINAL**
>
> This repository is governed by the Target Revenue Source License
> (TRSL), Version 1, Candidate 1. This is the framework's first working
> candidate, adopted as the org's preliminary operating license across
> all repos (maintainer decision, 2026-07-29) during the build/alpha
> stage — see `SCOPE.md` §1 and Appendix A of the canonical text (link
> below) for the alpha/beta risk-acceptance decision this reflects and
> what it does and does not mean. Full specialist legal review is
> explicitly deferred until the framework moves out of beta.
>
> **Canonical source, full candidate-status banner, and Appendix A
> (non-normative candidate notes tracking every open item):** the
> `coulomb` org's `target-revenue` repository,
> `specs/TargetRevenueSourceLicense-V1C1.md` — this file is the operative
> legal text (Preamble through Section 11) only; the canonical document
> is authoritative if this copy and that document ever diverge.
---
## Preamble
This Target Revenue Source License ("**License**") governs the Software identified in the applicable Phase Manifest. It implements the Target Revenue Framework: a defined development Phase accumulates Development Credit and Remission Credit against an immutable Initial Target until the Milestone Release automatically and irrevocably converts to a declared permissive Future License.
Commercial beneficiaries fund the creation and early availability of a software improvement; once the declared target is satisfied, the governed release becomes permissively open source.
## 1. Definitions
Capitalized terms used in this License have the meanings given below. Where a term is also defined in the Phase Manifest or Target Ledger for a specific Phase, the Phase Manifest and Target Ledger govern the *values* (amounts, dates, identifiers) and this License governs the *legal effect* of those values — the two must not be read as conflicting definitions of the same concept.
**"Commercial Entitlement"** means a right, purchased or otherwise granted under a Commercial Use Agreement, to make Commercial Use of the Software during a Phase.
**"Commercial Use"** means billing, invoicing, or otherwise charging any customer a fee, subscription, license fee, or other consideration for or in connection with use of the Software, at any time before the Conversion Event for the applicable Phase, regardless of whether the person or organization billed would otherwise qualify for Noncommercial Use. Commercial Use occurs by virtue of such billing alone, whether or not the resulting payment is registered with the Trust Service; in particular, billing a customer for pre-conversion use of the Software without recording the corresponding payment in the applicable Phase's Target Ledger is Commercial Use without a valid Commercial Entitlement — a violation of Section 3, addressed under Section 7 and, where applicable, the Enforcement Network described in the canonical repository's `specs/EnforcementNetworkConcept.md`.
**"Commercial Use Agreement"** means the separate agreement, referenced by the applicable Phase Manifest, under which a Commercial Entitlement is purchased or granted. This License does not itself set pricing, metering, or payment terms — those are governed by the Commercial Use Agreement.
**"Conversion Event"** means the moment the Outstanding Target for a Phase reaches zero, as computed from the Phase Manifest and Target Ledger per the Target Ledger Specification. The Conversion Event occurs automatically and is not conditioned on any declaration, attestation, or other act by the Licensor or any Trust Service.
**"Development Credit"** means the portion of a collected and settled payment explicitly allocated toward satisfying the Initial Target of a specific Phase, as recorded in that Phase's Target Ledger.
**"Future License"** means the permissive license identified in the applicable Phase Manifest, being either the MIT License or the Apache License, Version 2.0, which applies to the Milestone Release upon the Conversion Event.
**"Initial Target"** means the immutable monetary target declared for a Phase in its Phase Manifest.
**"Licensor"** means **Binky Hedgehog GmbH**, the party that publishes the Phase Manifest and holds the rights necessary to grant this License and the Future License for the Milestone Release.
**"Milestone Release"** means the precisely identified software release designated in the applicable Phase Manifest, identified by an immutable source revision, release artifact, or cryptographic digest.
**"Noncommercial Use"** means use of the Software for personal purposes, private study, hobby or amateur projects; use by any charitable organization, educational institution, public research organization, or government institution acting in a non-revenue-generating capacity; or other use of a materially similar character.
**"Outstanding Target"** means, at any time, `max(0, Initial Target − cumulative Development Credit − cumulative Remission Credit)` for a Phase, as computed from that Phase's Target Ledger.
**"Phase"** means a bounded development undertaking governed by one Initial Target, one Milestone Release, one degeneration policy, and one Future License declaration, as declared in a Phase Manifest.
**"Phase Manifest"** means the published, immutable declaration identifying a Phase, its Milestone Release, Initial Target, Future License, degeneration policy, and Target Ledger location, as specified in the Phase Manifest Specification.
**"Remission Credit"** means a transparent, non-revenue reduction of a Phase's Outstanding Target, generated under that Phase's published degeneration policy and recorded in the Target Ledger.
**"Settled Payment"** means a payment that has cleared through its payment processor and is no longer subject to reversal in the ordinary course (chargeback, dispute, or equivalent), as further specified by the applicable Commercial Use Agreement or monetization extension.
**"Software"** means the source code, object code, and associated documentation of the Milestone Release identified in the applicable Phase Manifest.
**"Target Ledger"** means the append-only record of Development Credit, Remission Credit, and correction entries for a Phase, as specified in the Target Ledger Specification.
**"You"** or **"Licensee"** means the individual or entity exercising rights under this License.
## 2. Grant of Rights for Noncommercial Use
Subject to the terms of this License, the Licensor grants You a worldwide, royalty-free, non-exclusive license, during the applicable Phase, to:
(a) use, reproduce, and study the Software for any Noncommercial Use;
(b) modify the Software and create derivative works of it for any Noncommercial Use; and
(c) redistribute the Software and Your modifications, in source or object form, for any Noncommercial Use, provided that You include this License, unmodified, with any such redistribution, and that You do not remove or alter any copyright, patent, trademark, or attribution notices contained in the Software.
This grant does not extend to Commercial Use. Commercial Use requires a Commercial Entitlement under Section 3.
## 3. Commercial Use
You may not make Commercial Use of the Software during the applicable Phase unless You hold a valid, current Commercial Entitlement under a Commercial Use Agreement with the Licensor covering the applicable Phase. A Commercial Entitlement granted under one Phase's Commercial Use Agreement does not extend to a later Phase's Milestone Release unless the Commercial Use Agreement expressly says so.
This Section 3 states the existence and boundary of the commercial-use restriction. It does not itself set pricing, invoicing, metering, audit rights, or payment terms — those are governed exclusively by the applicable Commercial Use Agreement.
## 4. Patent License
Subject to the terms of this License, each contributor to the Software grants You, during the applicable Phase and solely to the extent of rights granted under Sections 2 and 3, a perpetual (subject to the termination below), worldwide, non-exclusive, no-charge, royalty-free patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Software, limited to those patent claims licensable by that contributor that are necessarily infringed by their contribution(s) alone or by combination of their contribution(s) with the Software.
If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Software or a contribution incorporated within it constitutes direct or contributory patent infringement, then any patent licenses granted to You under this Section 4 for the Software shall terminate as of the date such litigation is filed.
## 5. Automatic Conversion to the Future License
**5.1 Automatic effect.** Upon the Conversion Event for a Phase, the rights and restrictions in Sections 3 (Commercial Use) of this License, as they apply to that Phase's Milestone Release, terminate automatically. In their place, the Milestone Release is licensed under the Future License identified in that Phase's Phase Manifest, effective as of the Conversion Event, without any further act, declaration, or attestation required by the Licensor, any Trust Service, or any other party.
**5.2 Irrevocability.** Once a valid Conversion Event has occurred for a Phase, no subsequent refund, chargeback, accounting correction, dispute, or termination of this License for an unrelated breach shall revoke, suspend, or otherwise impair the Future License grant for that Phase's Milestone Release. Any shortfall or dispute arising after a Conversion Event is a commercial or accounting matter between the relevant parties and does not reinstate a commercial-use restriction over already-converted Software.
**5.3 Prior freedom preserved.** A later Phase covering subsequent improvements to the Software does not restrict, withdraw, or otherwise affect the rights granted under the Future License for an earlier Phase's already-converted Milestone Release.
**5.4 Evidence, not cause.** A Trust Service may publish a Conversion Attestation documenting a Conversion Event. Such an attestation is evidence that the Conversion Event occurred; it is not a condition of, and its absence or delay does not postpone, the automatic effect described in Section 5.1. Any person may independently verify whether a Conversion Event has occurred directly from the Phase Manifest and Target Ledger.
## 6. Successive Phases
The Licensor may declare a new Phase covering subsequent improvements to the Software following a Milestone Release's Conversion Event. Each Phase is independently governed by its own Phase Manifest, Initial Target, degeneration policy, and Target Ledger. Nothing in a later Phase's Phase Manifest may be construed to reduce or withdraw rights already granted under Section 5 for an earlier Phase's Milestone Release.
## 7. Term and Termination
**7.1 Term.** This License applies to the Software for the duration of the applicable Phase, and, for the Milestone Release, indefinitely following that Phase's Conversion Event under the Future License.
**7.2 Termination for breach.** If You breach Section 3 (Commercial Use) or Section 2(c) (redistribution notice requirement), the Licensor may terminate this License as to You. Before such termination becomes effective, the Licensor shall provide You written notice of the breach; if You cure the breach within thirty (30) days of that notice, this License continues in effect. A second breach of the same provision within twelve (12) months may be terminated immediately without a further cure opportunity.
**7.3 Effect of termination.** Termination under this Section 7 affects only Your rights under Sections 2 and 3 for the Phase in which the breach occurred. It does not affect any rights already vested under Section 5 (Automatic Conversion) for a Milestone Release whose Conversion Event has already occurred, per Section 5.2.
**7.4 Public record of breach and resolution.** The Licensor shall cause the Trust Service to publish, as part of the public record for the affected Phase, notice of: (a) any breach notice issued under Section 7.2, stating the general nature of the breach and the date of notice; (b) whether the breach was cured within the applicable cure period, and the date of cure; and (c) any termination determination made under this Section 7, including its effective date and scope. This public record exists to give the ecosystem a transparent, verifiable conformity signal for the Phase, distinct from and in addition to the Development Credit and Remission Credit facts already published under Section 5.4 and the Target Ledger Specification.
A breach that You dispute, and that has not been finally determined, shall be recorded as **alleged**; it shall be recorded as **determined** only once the cure period has run without cure, or the dispute has been resolved against You under the applicable Commercial Use Agreement's dispute process, if any. The Trust Service shall update the record promptly upon resolution in either direction. Recording an alleged or determined breach under this Section 7.4 is a ministerial act of publishing the Licensor's determination (or a dispute process's outcome); it does not give the Trust Service discretionary authority to decide whether a breach occurred, consistent with Section 5.4's evidence-not-cause principle.
Whether, and under what conditions, the public record identifies a Commercial Entitlement holder by name is governed exclusively by the applicable Commercial Use Agreement, which the Licensor and that Commercial Entitlement holder negotiate and agree to directly. This License does not itself set a naming default. Where no Commercial Use Agreement addresses the question, or where the affected party has no Commercial Use Agreement at all (for example, a Section 2(c) breach by a Noncommercial Use licensee), the public record states the Phase and breach category only, without naming the party.
## 8. Disclaimer of Warranty
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. THE LICENSOR DOES NOT WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE OR THAT ANY PHASE WILL REACH ITS CONVERSION EVENT.
## 9. Limitation of Liability
IN NO EVENT SHALL THE LICENSOR OR ANY CONTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE, EXCEPT TO THE EXTENT SUCH LIMITATION IS PROHIBITED BY APPLICABLE LAW.
## 10. Trademarks
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary attribution.
## 11. General Provisions
**11.1 Governing law and venue.** Adopted for alpha/beta 2026-07-29: disputes arising under this License shall be resolved by binding arbitration, seated at a neutral, arbitration-mature venue (Singapore or London are the two candidates concretely supported by current research), rather than by litigation in a national court. The specific arbitral institution and substantive governing law remain a per-deployment blank pending final selection; they are not fixed by this candidate. See the canonical repository's `history/260729-TRSL-Jurisdiction-Synthesis.md` §2.
**11.2 Severability.** If any provision of this License is held unenforceable, the remaining provisions remain in full force, and the unenforceable provision shall be reformed to the minimum extent necessary to make it enforceable.
**11.3 No waiver.** Failure to enforce any provision of this License is not a waiver of future enforcement of that or any other provision.
**11.4 Entire agreement (as to licensing).** This License, together with the applicable Phase Manifest and, where applicable, the Commercial Use Agreement, constitutes the entire agreement between You and the Licensor regarding the Software's licensing terms. Operations, service, and consulting arrangements are governed by separate agreements, if any, and are not part of this License.
**11.5 Definitions control.** Marketing materials, documentation, or other non-normative communications about the Software must not describe pre-Conversion-Event Software as "Open Source," "free software," or "open core." Pre-conversion Noncommercial Use is **source-available**; pre-conversion Commercial Use requires a **Commercial Entitlement**; only post-conversion Software may be described as Open Source, under the Future License.
---
**No Phase is currently declared for this repository under this License.** Until a Phase Manifest is published and registered with the Trust Service for a Milestone Release in this repository, Sections 2–7 above have no operative subject matter here — this License establishes the governing framework in advance of that declaration, consistent with the org-wide rollout decision recorded in `target-revenue`'s `workplans/TREV-WP-0008-governance-and-pilot-rollout.md`.

View file

@ -0,0 +1,171 @@
// Package jose provides strict RS256 JWT signature verification against a JWK
// set. It is deliberately narrow: it establishes that a token was signed by a
// key in the given set and returns its claims. It applies no claim policy —
// issuer, audience, expiry and nonce rules differ per caller and belong with
// the caller that knows them.
//
// The strictness here is the point. A verifier that accepts an unexpected
// algorithm, an unbounded exponent or an undersized modulus is worse than no
// verifier, because callers stop checking what it appears to guarantee.
package jose
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"math/big"
"strings"
)
// ErrVerification is returned for every verification failure. The cause is
// deliberately not distinguished: a caller acting on why a token failed tends to
// leak that distinction to whoever supplied the token.
var ErrVerification = errors.New("jose: token verification failed")
// KeySet maps key IDs to RSA public keys.
type KeySet map[string]*rsa.PublicKey
// ParseJWKS parses a JWK set, keeping only RSA signing keys usable with RS256.
// Keys that are malformed, too small, or carry an even or trivial exponent are
// rejected rather than skipped, since a key set that is partly wrong is not
// evidence of anything.
func ParseJWKS(raw []byte) (KeySet, error) {
var document struct {
Keys []struct {
Kty string `json:"kty"`
Use string `json:"use"`
Alg string `json:"alg"`
Kid string `json:"kid"`
N string `json:"n"`
E string `json:"e"`
} `json:"keys"`
}
if err := json.Unmarshal(raw, &document); err != nil {
return nil, ErrVerification
}
keys := make(KeySet, len(document.Keys))
for _, key := range document.Keys {
// Ignore keys for other algorithms or purposes; they are not errors.
if key.Kty != "RSA" || (key.Use != "" && key.Use != "sig") || (key.Alg != "" && key.Alg != "RS256") {
continue
}
if key.Kid == "" {
return nil, ErrVerification
}
modulus, errN := decode(key.N)
exponent, errE := decode(key.E)
if errN != nil || errE != nil || len(exponent) == 0 || len(exponent) > 4 {
return nil, ErrVerification
}
public := &rsa.PublicKey{
N: new(big.Int).SetBytes(modulus),
E: int(new(big.Int).SetBytes(exponent).Int64()),
}
if public.N.BitLen() < 2048 || public.E < 3 || public.E%2 == 0 {
return nil, ErrVerification
}
if _, duplicate := keys[key.Kid]; duplicate {
return nil, ErrVerification
}
keys[key.Kid] = public
}
if len(keys) == 0 {
return nil, ErrVerification
}
return keys, nil
}
// KeyID returns the key ID from a token's JOSE header, after checking that the
// header names RS256 and carries no critical extensions. Callers use it to
// decide whether to refresh their key set before verifying.
func KeyID(token string) (string, error) {
header, _, _, err := split(token)
if err != nil {
return "", err
}
return header.Kid, nil
}
// Verify checks a token's RS256 signature against the key set and returns its
// claims. It does not inspect any claim.
func Verify(token string, keys KeySet) (map[string]interface{}, error) {
return verify(token, keys, "")
}
// VerifyTyped applies the same signature verifier with an explicit JOSE type.
// Existing Verify callers retain their claim/type policy; online access-token
// consumers opt into the producer's exact JWT header contract here.
func VerifyTyped(token string, keys KeySet, expectedType string) (map[string]interface{}, error) {
if expectedType == "" {
return nil, ErrVerification
}
return verify(token, keys, expectedType)
}
func verify(token string, keys KeySet, expectedType string) (map[string]interface{}, error) {
header, signingInput, signature, err := split(token)
if err != nil {
return nil, err
}
if expectedType != "" && header.Typ != expectedType {
return nil, ErrVerification
}
public, ok := keys[header.Kid]
if !ok {
return nil, ErrVerification
}
digest := sha256.Sum256([]byte(signingInput))
if rsa.VerifyPKCS1v15(public, crypto.SHA256, digest[:], signature) != nil {
return nil, ErrVerification
}
payload, err := decode(strings.Split(signingInput, ".")[1])
if err != nil {
return nil, ErrVerification
}
claims := map[string]interface{}{}
if json.Unmarshal(payload, &claims) != nil {
return nil, ErrVerification
}
return claims, nil
}
type joseHeader struct {
Alg string `json:"alg"`
Typ string `json:"typ"`
Kid string `json:"kid"`
Crit []string `json:"crit"`
}
// split validates the token's shape and header and returns the header, the
// signing input, and the decoded signature.
func split(token string) (joseHeader, string, []byte, error) {
parts := strings.Split(token, ".")
if len(parts) != 3 {
return joseHeader{}, "", nil, ErrVerification
}
raw, err := decode(parts[0])
if err != nil {
return joseHeader{}, "", nil, ErrVerification
}
var header joseHeader
if json.Unmarshal(raw, &header) != nil {
return joseHeader{}, "", nil, ErrVerification
}
// An unexpected algorithm must fail before any key is selected, so "none"
// and symmetric algorithms can never reach the RSA path.
if header.Alg != "RS256" || header.Kid == "" || len(header.Crit) != 0 {
return joseHeader{}, "", nil, ErrVerification
}
signature, err := decode(parts[2])
if err != nil {
return joseHeader{}, "", nil, ErrVerification
}
return header, parts[0] + "." + parts[1], signature, nil
}
func decode(segment string) ([]byte, error) {
return base64.RawURLEncoding.DecodeString(segment)
}

View file

@ -0,0 +1,379 @@
package tokenstatus
import (
"bytes"
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"io"
"math"
"mime"
"net"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"keycape/internal/jose"
)
var ErrOnlineToken = errors.New("current authenticated token refused")
// OnlineAdmission is independently admitted owner configuration. The public
// JWKS/CA and private client credential files are reread for every request.
// It selects the owning confidential client, not a resource-server delegation.
type OnlineAdmission struct {
Issuer string `json:"issuer"`
Audience string `json:"audience"`
ClientID string `json:"client_id"`
CAFile string `json:"ca_file"`
JWKSFile string `json:"jwks_file"`
SecretFile string `json:"secret_file"`
MaxTokenAge string `json:"max_token_age"`
}
type ExpectedIdentity struct {
Subject string
Tenant string
PrincipalType string
}
// CurrentToken contains signed identity facts joined to a current issuer
// observation. It is never a permission, approval or portable execution receipt.
type CurrentToken struct {
Issuer string `json:"issuer"`
Audience string `json:"audience"`
ClientID string `json:"client_id"`
Subject string `json:"subject"`
Tenant string `json:"tenant"`
PrincipalType string `json:"principal_type"`
TokenID string `json:"jti"`
TokenDigest string `json:"token_sha256"`
IssuedAt time.Time `json:"issued_at"`
NotAfter time.Time `json:"not_after"`
CheckedAt time.Time `json:"checked_at"`
claims map[string]interface{}
}
// Claims returns a separate copy of the verified signed facts. Authentication
// assurance/age, tenant provenance, current groups and resource/effect policy
// remain the consumer's existing profile obligations, not a new client policy.
func (t CurrentToken) Claims() map[string]interface{} {
if t.claims == nil {
return nil
}
raw, _ := json.Marshal(t.claims)
var out map[string]interface{}
_ = json.Unmarshal(raw, &out)
return out
}
// InactiveForClient proves only the issuer's current negative response for the
// named client/token digest. An unknown/other-client token gives the same result;
// original grant ownership is separately required before asserting cleanup.
type InactiveForClient struct {
Issuer string `json:"issuer"`
ClientID string `json:"client_id"`
TokenDigest string `json:"token_sha256"`
CheckedAt time.Time `json:"checked_at"`
}
type OnlineClient struct {
admission OnlineAdmission
age time.Duration
now func() time.Time
// Nil uses the normal resolver/dialer. Tests route a certified DNS name to
// isolated loopback; the TLS name/CA and JWT trust checks remain unchanged.
dialContext func(context.Context, string, string) (net.Conn, error)
}
func NewOnlineClient(a OnlineAdmission) (*OnlineClient, error) {
u, err := url.Parse(a.Issuer)
if err != nil || u.Scheme != "https" || u.Hostname() == "" || u.User != nil || strings.ContainsAny(a.Issuer, "?#") || u.RawPath != "" || (u.Path != "" && path.Clean(u.Path) != u.Path) || strings.HasSuffix(a.Issuer, "/") {
return nil, ErrOnlineToken
}
if a.Audience == "" || a.ClientID == "" || !filepath.IsAbs(a.CAFile) || !filepath.IsAbs(a.JWKSFile) || !filepath.IsAbs(a.SecretFile) {
return nil, ErrOnlineToken
}
age, err := time.ParseDuration(a.MaxTokenAge)
if err != nil || age <= 0 || age > time.Hour {
return nil, ErrOnlineToken
}
return &OnlineClient{admission: a, age: age, now: time.Now}, nil
}
func readOnlineFile(path string, private bool) ([]byte, error) {
limit := int64(1 << 20)
if private {
limit = 16384
}
return readOnlineBoundedFile(path, private, limit)
}
func readOnlineBoundedFile(path string, private bool, limit int64) ([]byte, error) {
if limit <= 0 || limit > 1<<20 {
return nil, ErrOnlineToken
}
if !filepath.IsAbs(path) {
return nil, ErrOnlineToken
}
f, err := os.OpenFile(path, os.O_RDONLY|syscall.O_NONBLOCK, 0)
if err != nil {
return nil, ErrOnlineToken
}
defer f.Close()
info, err := f.Stat()
if err != nil || !info.Mode().IsRegular() || info.Mode().Perm()&0022 != 0 || (private && info.Mode().Perm()&0007 != 0) {
return nil, ErrOnlineToken
}
raw, err := io.ReadAll(io.LimitReader(f, limit+1))
if err != nil || len(raw) > int(limit) || len(raw) == 0 {
return nil, ErrOnlineToken
}
return raw, nil
}
func tokenDigest(raw string) string {
digest := sha256.Sum256([]byte(raw))
return "sha256:" + hex.EncodeToString(digest[:])
}
func exactUnix(value interface{}) (time.Time, bool) {
n, ok := value.(float64)
if !ok || n <= 0 || n > 9007199254740991 || math.Trunc(n) != n {
return time.Time{}, false
}
return time.Unix(int64(n), 0).UTC(), true
}
func (c *OnlineClient) verify(raw string, expected ExpectedIdentity, now time.Time) (CurrentToken, error) {
var out CurrentToken
if c == nil || len(raw) == 0 || len(raw) > 32768 || expected.Subject == "" || expected.Tenant == "" || (expected.PrincipalType != "human" && expected.PrincipalType != "service") {
return out, ErrOnlineToken
}
keysRaw, err := readOnlineFile(c.admission.JWKSFile, false)
if err != nil {
return out, ErrOnlineToken
}
keys, err := jose.ParseJWKS(keysRaw)
if err != nil {
return out, ErrOnlineToken
}
claims, err := jose.VerifyTyped(raw, keys, "JWT")
if err != nil {
return out, ErrOnlineToken
}
if claims["iss"] != c.admission.Issuer || claims["aud"] != c.admission.Audience || claims["sub"] != expected.Subject || claims["tenant"] != expected.Tenant || claims["principal_type"] != expected.PrincipalType {
return out, ErrOnlineToken
}
if azp, present := claims["azp"]; present && azp != c.admission.ClientID {
return out, ErrOnlineToken
}
_, scopeOK := claims["scope"].(string)
jti, jtiOK := claims["jti"].(string)
decodedID, idErr := base64.RawURLEncoding.Strict().DecodeString(jti)
issued, issuedOK := exactUnix(claims["iat"])
expires, expiresOK := exactUnix(claims["exp"])
if !scopeOK || !jtiOK || idErr != nil || len(decodedID) != 32 || !issuedOK || !expiresOK || issued.After(now) || !expires.After(issued) || !now.Before(expires) {
return out, ErrOnlineToken
}
if nbf, present := claims["nbf"]; present {
start, valid := exactUnix(nbf)
if !valid || start.After(now) {
return out, ErrOnlineToken
}
}
end := issued.Add(c.age)
if expires.Before(end) {
end = expires
}
if !now.Before(end) {
return out, ErrOnlineToken
}
return CurrentToken{Issuer: c.admission.Issuer, Audience: c.admission.Audience, ClientID: c.admission.ClientID,
Subject: expected.Subject, Tenant: expected.Tenant, PrincipalType: expected.PrincipalType,
TokenID: jti, TokenDigest: tokenDigest(raw), IssuedAt: issued, NotAfter: end, CheckedAt: now, claims: claims}, nil
}
type statusWire struct {
Active *bool `json:"active"`
ClientID string `json:"client_id"`
TokenType string `json:"token_type"`
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience string `json:"aud"`
Scope string `json:"scope"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
TokenID string `json:"jti"`
}
func decodeOnlineObject(raw []byte, out interface{}) (map[string]bool, error) {
d := json.NewDecoder(bytes.NewReader(raw))
first, err := d.Token()
if err != nil || first != json.Delim('{') {
return nil, ErrOnlineToken
}
seen := map[string]bool{}
for d.More() {
key, err := d.Token()
name, ok := key.(string)
if err != nil || !ok || seen[name] {
return nil, ErrOnlineToken
}
seen[name] = true
var value json.RawMessage
if d.Decode(&value) != nil || bytes.Equal(bytes.TrimSpace(value), []byte("null")) {
return nil, ErrOnlineToken
}
}
if last, err := d.Token(); err != nil || last != json.Delim('}') {
return nil, ErrOnlineToken
}
if _, err := d.Token(); err != io.EOF {
return nil, ErrOnlineToken
}
d = json.NewDecoder(bytes.NewReader(raw))
d.DisallowUnknownFields()
if d.Decode(out) != nil {
return nil, ErrOnlineToken
}
return seen, nil
}
func decodeOnlineStatus(raw []byte) (statusWire, error) {
var out statusWire
seen, err := decodeOnlineObject(raw, &out)
if err != nil || out.Active == nil || (!*out.Active && len(seen) != 1) {
return out, ErrOnlineToken
}
if *out.Active {
if len(seen) != 10 {
return out, ErrOnlineToken
}
for _, field := range []string{"active", "client_id", "token_type", "iss", "sub", "aud", "scope", "iat", "exp", "jti"} {
if !seen[field] {
return out, ErrOnlineToken
}
}
}
return out, nil
}
func (c *OnlineClient) request(ctx context.Context, endpoint, raw string) (statusWire, error) {
var out statusWire
if c == nil || ctx == nil || ctx.Err() != nil || len(raw) == 0 || len(raw) > 32768 || (endpoint != "/introspect" && endpoint != "/revoke") {
return out, ErrOnlineToken
}
ca, err := readOnlineFile(c.admission.CAFile, false)
if err != nil {
return out, ErrOnlineToken
}
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM(ca) {
return out, ErrOnlineToken
}
secretRaw, err := readOnlineFile(c.admission.SecretFile, true)
if err != nil {
return out, ErrOnlineToken
}
secret := strings.TrimSuffix(strings.TrimSuffix(string(secretRaw), "\n"), "\r")
if secret == "" || strings.ContainsAny(secret, "\r\n\x00") {
return out, ErrOnlineToken
}
form := url.Values{"token": {raw}, "token_type_hint": {"access_token"}}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.admission.Issuer+endpoint, strings.NewReader(form.Encode()))
if err != nil {
return out, ErrOnlineToken
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cache-Control", "no-cache")
req.SetBasicAuth(url.QueryEscape(c.admission.ClientID), url.QueryEscape(secret))
transport := &http.Transport{Proxy: nil, TLSClientConfig: &tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12}, DisableKeepAlives: true, DialContext: c.dialContext}
defer transport.CloseIdleConnections()
client := &http.Client{Transport: transport, Timeout: 5 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return ErrOnlineToken }}
response, err := client.Do(req)
if err != nil {
return out, ErrOnlineToken
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK || response.Header.Get("Cache-Control") != "no-store" {
return out, ErrOnlineToken
}
body, err := io.ReadAll(io.LimitReader(response.Body, 65537))
if err != nil || len(body) > 65536 {
return out, ErrOnlineToken
}
if endpoint == "/revoke" {
if len(body) != 0 {
return out, ErrOnlineToken
}
return out, nil
}
media, _, err := mime.ParseMediaType(response.Header.Get("Content-Type"))
if err != nil || media != "application/json" {
return out, ErrOnlineToken
}
return decodeOnlineStatus(body)
}
// Check verifies expected identity before accessing the private client credential,
// sends a new authenticated introspection and rechecks lifetime after the network.
func (c *OnlineClient) Check(ctx context.Context, raw string, expected ExpectedIdentity) (CurrentToken, error) {
if c == nil || ctx == nil || ctx.Err() != nil {
return CurrentToken{}, ErrOnlineToken
}
identity, err := c.verify(raw, expected, c.now().UTC())
if err != nil {
return CurrentToken{}, ErrOnlineToken
}
status, err := c.request(ctx, "/introspect", raw)
if err != nil || status.Active == nil || !*status.Active {
return CurrentToken{}, ErrOnlineToken
}
expires, _ := exactUnix(identity.claims["exp"])
if status.ClientID != c.admission.ClientID || status.TokenType != "Bearer" || status.Issuer != identity.Issuer || status.Subject != identity.Subject || status.Audience != identity.Audience || status.TokenID != identity.TokenID || status.Scope != identity.claims["scope"] || status.IssuedAt != identity.IssuedAt.Unix() || status.ExpiresAt != expires.Unix() {
return CurrentToken{}, ErrOnlineToken
}
end := c.now().UTC()
if end.Before(identity.CheckedAt) {
return CurrentToken{}, ErrOnlineToken
}
// A slow lookup cannot retain expired facts or a withdrawn JWT trust key.
return c.verify(raw, expected, end)
}
// Revoke performs cleanup only for the exact opaque token. An empty 200 is not
// proof: a separate authenticated introspection must confirm inactive-for-client.
// The caller retains original grant ownership and uncertainty/retry duties.
func (c *OnlineClient) Revoke(ctx context.Context, raw string) (InactiveForClient, error) {
if c == nil {
return InactiveForClient{}, ErrOnlineToken
}
if _, err := c.request(ctx, "/revoke", raw); err != nil {
return InactiveForClient{}, ErrOnlineToken
}
return c.ObserveInactive(ctx, raw)
}
// ObserveInactive is read-only. Unavailability or a malformed response is never
// interpreted as revocation; the negative result remains scoped to this client.
func (c *OnlineClient) ObserveInactive(ctx context.Context, raw string) (InactiveForClient, error) {
if c == nil {
return InactiveForClient{}, ErrOnlineToken
}
status, err := c.request(ctx, "/introspect", raw)
if err != nil || status.Active == nil || *status.Active {
return InactiveForClient{}, ErrOnlineToken
}
return InactiveForClient{Issuer: c.admission.Issuer, ClientID: c.admission.ClientID, TokenDigest: tokenDigest(raw), CheckedAt: c.now().UTC()}, nil
}

View file

@ -0,0 +1,18 @@
{
"source_of_truth": "key-cape source; generated by tools/export_tokenstatus_client.py",
"source_files": [
"src/internal/authclient/online.go",
"src/internal/jose/jose.go",
"LICENSE"
],
"transformation": "replace only the first package declaration authclient with tokenstatus; all remaining client bytes and shared JOSE bytes exact",
"module": "keycape",
"profile": "issuer online access-token status; no policy evaluator or deployment",
"supported_file_boundary": "Linux owner execution profile; POSIX mode checks; not a Windows ACL contract",
"sha256": {
"LICENSE": "sha256:ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6",
"go.mod": "sha256:e8b028e1002e63a76dba78a4abbd67882307ad198e4783bad631652061aa0f54",
"internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"pkg/tokenstatus/client.go": "sha256:f34626a85d67590b9c61474f19ce1021f8afd17093f8f522b3f98e9cef34e575"
}
}

View file

@ -0,0 +1,223 @@
{
"returncode": 0,
"seconds": 29.8,
"argv": [
"bwrap",
"--unshare-net",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-keycape-token-pair-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod",
"/modules",
"--ro-bind",
"/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64",
"/toolchain",
"--chdir",
"/work/source/src",
"--clearenv",
"--bind",
"/tmp/custodian-keycape-token-status-r1/gocache",
"/work/gocache",
"--setenv",
"PATH",
"/toolchain/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOMODCACHE",
"/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--",
"go",
"test",
"-p",
"4",
"-race",
"-json",
"-count=1",
"./..."
],
"source_sha256_at_start": {
"src/go.mod": "sha256:ab299ad4f94feb78e217e2228a4aa161b166460aa28df0c5009451ac1a337456",
"src/Makefile": "sha256:6cdd3aa6e1cccaf4e028278acafe3e6a8d28170de89ff3462984d4f6eb56bd96",
"src/go.sum": "sha256:b10ad54277fb2bd927d3af727fc4dbb9ebe91a038a75be0469d0253ca699226d",
"src/tests/negative/negative_test.go": "sha256:757485bf1967ed95e872d4a1987b7fd985fbffccd05cf41270b9f4061431de3f",
"src/tests/conformance/external_test.go": "sha256:b26bd465e31086f957c9e374b6bee1f0f077b7481eda9daa29cf63816bb87fdb",
"src/tests/migration/scenario_c_test.go": "sha256:9722146ac224768aff85476acbd83f3d478c1e060e5a3c6f99b1786858879b80",
"src/tests/migration/fixtures_test.go": "sha256:3fbcb8f531fa206225f719a67b744b2ecb908095fbae0538432796697971e6de",
"src/tests/migration/scenario_b_test.go": "sha256:40ae7239d116f3e284c767570d39e1f32d44fc29b6fa37ea8835987027942ac0",
"src/tests/profile/profile_test.go": "sha256:e5ab4568b15f681a13c9295b17907ca3872ee33b702bb56aea749f17758d29e6",
"src/internal/domain/assurance_test.go": "sha256:87680e7afd47294dadc13092ca2bb7c47b9625c80e48a701765c00df3293d755",
"src/internal/domain/conformance_test.go": "sha256:6420ec21d4e7a97f60902e1f0debbaf20df91aeac6ab110629b82a67f4c5c9a1",
"src/internal/domain/repository.go": "sha256:28f6d8f7f3d822e73f164af5e877f252db7dc6faa97f0ebf75d76aad64078011",
"src/internal/domain/auth.go": "sha256:ae800c5b1f58408147dd28a5c87ab2603b1537500a289397e711a4cb0e132cee",
"src/internal/domain/model.go": "sha256:ce4309fc7426c66e4ebc18022d2f704c10ebbec243cc98e657d1de765ad09029",
"src/internal/domain/assurance.go": "sha256:d3a29ece7c4fa4d9daa8b5fda89cbe0dd1ffef2ee2ecae0051ee68010edb8fd7",
"src/internal/domain/mfa.go": "sha256:1b7875b08a0f280a20a14a4fa4fdb4fc84c3942795b1d1417943d32634121b87",
"src/internal/authclient/cli.go": "sha256:009c9ce900524b62483cf4955f3cbacc0dcb9027b8da77924b51b22e3fa5170c",
"src/internal/authclient/client.go": "sha256:61fb150349c5d3714450a90834c59af9a579a8a649c4149297a402cfcf6e39c5",
"src/internal/authclient/online_cli.go": "sha256:6763c1e85396726b044067e77179ee2220e397bbd3888ec8de071c2826c6c7f2",
"src/internal/authclient/verify.go": "sha256:804735be927ba7d0228ac378056730040d3ccf2ed020b4da0714085bae972f25",
"src/internal/authclient/online.go": "sha256:182ec3971ded3a7f3923aab7a23b7317de3ec0f657b8da6d9d882a43bcb0961a",
"src/internal/authclient/online_export_test.go": "sha256:5695f233e4caf581e494087cb203a74613d39a5626bbcc323b9f2d553a96b7ba",
"src/internal/authclient/client_test.go": "sha256:23642488fcff45e369d22ba0000c47e0e450c5f92560e643ab96303e2a2f43aa",
"src/internal/authclient/online_cli_test.go": "sha256:5408a183beda6a885ebae00d3fa420d3fb0d51e4aeb72965fef535d822270c4f",
"src/internal/authclient/online_test.go": "sha256:6288329ca2576f72a361256050c94f9d1995e9fbe53b2a6318da2954bcbc7c6a",
"src/internal/authclient/online_human_test.go": "sha256:e501c30b6d710a6dea89559ccf8b98de68502c2ee58d1778a56e97f1b7f3b6e0",
"src/internal/authclient/verify_test.go": "sha256:7febc6698c87eac5fd8d2520c4f77c68e82f18a2aafa448cfa4990b96fdc46fc",
"src/internal/validator/report.go": "sha256:6bc4d31f932ea9a32020cf33203978906730dfe3601350ded0206ff6c8c297a8",
"src/internal/validator/validator_test.go": "sha256:8216027a464eab4167d8e87b4d27d130854d7b9cd4cc8a1b6f1a47ff1f9a2276",
"src/internal/validator/validator.go": "sha256:3d0a8bf8ebbe54e21e162c8f7df6e4a4d69ab997a01969b74dcbd53decdf559f",
"src/internal/config/account_test.go": "sha256:c6b75097d14dd42415c836407fa2a509857cdd2b771fb7e53d4507cbad2f4b1d",
"src/internal/config/validate.go": "sha256:2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405",
"src/internal/config/optional_mfa_test.go": "sha256:03a00bc309943adbed68bdb9bf47facb6b3b50ef380fd49143b5ed54d684f63e",
"src/internal/config/config_test.go": "sha256:af8602010985a0a5a6b4b2bf779f824e7eb5141e6bc2e8d8fcf600b964955100",
"src/internal/config/token_status_test.go": "sha256:b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5",
"src/internal/config/config.go": "sha256:19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683",
"src/internal/errors/taxonomy.go": "sha256:7d60f30a4ff0c9a21a3942451eb0292320a6346d3d81508e414a7dfbdc3c666c",
"src/internal/errors/taxonomy_test.go": "sha256:6c0bdf1f8e90ea343d88f57f80f6c916c92428a9ac2191a518ee7b70df0eed45",
"src/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"src/internal/jose/jose_test.go": "sha256:cbbcbac620b27c8e93d9dc3fefc3ca4fcd20ab58c6bc8f79eb041b980aaa9a43",
"src/internal/server/policy/policy_test.go": "sha256:25644557ca532b608dff4e02b202c5c3b7c423fb31e98ad0609febf3660183a2",
"src/internal/server/policy/policy.go": "sha256:015d0b42419c501a46171d09a8f7d588dd517d4c98ad14344ff52113294e0fe7",
"src/internal/server/errors/enforcement.go": "sha256:f644546b1423992518fc4078ab561ab68fea1fcff00b734795af6652ae3dd4e2",
"src/internal/server/errors/enforcement_test.go": "sha256:b4ea0c9a3aaebd66b0b0fcc73f544c76d5a377ac0c9561ccc2638a236844374c",
"src/internal/server/telemetry/emitter.go": "sha256:e92f008b6a487a47b8ee2b98011a3a5b821838a61be9c4db4d56cda600e6fda0",
"src/internal/server/telemetry/emitter_test.go": "sha256:29fae2460deb7219eb8edc725b3701a02d2b341fee62bffb2661a520f5a7381a",
"src/internal/server/telemetry/events.go": "sha256:3f9df9822649ae2701c3bbf475c8e6d600bc60b7ccdea4d0e54193162d5b0167",
"src/internal/server/readiness/readiness_test.go": "sha256:10163e0b1d2f41f336d61782c3bfffdd1116a8459247cc933ffb40be645184c6",
"src/internal/server/readiness/readiness.go": "sha256:ffe22b99633b1aa50fc299371398ec0f3fc090c9c8a5f4957c6bfa76b001d073",
"src/internal/server/oidc/discovery_test.go": "sha256:2d0dc24d59497aa5a9aa581f8fd3d9cca300d7e0cb4962907630570d60e7e7f0",
"src/internal/server/oidc/tenant_precondition_test.go": "sha256:2f2c0d90d26da4e3a6c9145aec8c9d7121c791b6b993c2fe8893a763a30fa72f",
"src/internal/server/oidc/userinfo.go": "sha256:f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f",
"src/internal/server/oidc/account.go": "sha256:b0e36d8cb5f9b1bf4cee573e46b204800c0e3162a912a122b271def4712ab72a",
"src/internal/server/oidc/policy_runtime_test.go": "sha256:856146013d2aaa60836e669c297024923ed8e2856d31a89744a99baadae4a74a",
"src/internal/server/oidc/handoff_test.go": "sha256:23268b7f9f103f8a8798a168004580bc8fdb3a175161220dac26480a89f28e02",
"src/internal/server/oidc/human_tenant_test.go": "sha256:459664f90d76224958b3dd807d95d7bfcf0fce5f113498bd9886a41be19903a5",
"src/internal/server/oidc/discovery.go": "sha256:00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5",
"src/internal/server/oidc/jwks.go": "sha256:426d6596979ed6d097d6aaae66b6475d1c97687888c6ab1050c05cb14653ec60",
"src/internal/server/oidc/login_session.go": "sha256:efb0fcd066d81d753b7043c9c3d9e74e35cd510ca4304d21a51ba29dec833e1f",
"src/internal/server/oidc/hardening_test.go": "sha256:b41d6a987d5507d5160d970d0f2110ce681851fca1f4f7a3a9573163e8cd6f3e",
"src/internal/server/oidc/jwks_test.go": "sha256:9100e83e58ce2cc2f6d2f47804ee0ce035ca424b209597a12c4ef1a8896163aa",
"src/internal/server/oidc/account_test.go": "sha256:db1f7b8a2989b1d0deffee205ec9f4f35c7472135dcb8a10b5498893d881d763",
"src/internal/server/oidc/token_status_discovery_test.go": "sha256:27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031",
"src/internal/server/oidc/principal_type_guard_test.go": "sha256:b3fa097691f07594616e09e8775512d586a019ccdf8302bf594c1ac94f5f3f40",
"src/internal/server/oidc/token_status_http.go": "sha256:a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97",
"src/internal/server/oidc/native_optional_test.go": "sha256:329907bf7089d8f3ea05baad919d05677b8c3aaa201e6f233795ae9f69c003b5",
"src/internal/server/oidc/authorize_test.go": "sha256:61042d55e20a11fb6e1e9e6c054e50c0f629b702ed6d8da281d52529e5c29b0d",
"src/internal/server/oidc/session.go": "sha256:48473ebe123285ed21e7738834d9d9168dfc855d911cb9e7ce7e87b069a23a3c",
"src/internal/server/oidc/logout.go": "sha256:d4de80f8056e7a65c7a41f244dd38e447a4e04df729760a8a59cb6bb4e063893",
"src/internal/server/oidc/optional_mfa_test.go": "sha256:d758848d845c181cf1b18710e6ca24977f6b7576320dcad1f7293a5433c3b529",
"src/internal/server/oidc/token.go": "sha256:e6905bfa0474dde6122a83a6627d6b75a4c3a7730fb26106546e7a34eef8302b",
"src/internal/server/oidc/token_status.go": "sha256:d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3",
"src/internal/server/oidc/userinfo_test.go": "sha256:e88c42c7cdf50d5953c9f594f0b99b4f87f5e223ba1cb44b70d696972016da80",
"src/internal/server/oidc/signin_diagnosis_test.go": "sha256:97e80be67c480dfe8eef0683f25868a91a0fdc96472b5296b734b56274270b3e",
"src/internal/server/oidc/token_pair_test.go": "sha256:efeb929becf61d62111286ead4d3635c1a59bfd34423280084ccfec2169a2b1d",
"src/internal/server/oidc/policy_isolation_test.go": "sha256:35cb9870f5942c61c0e8c6ca875e9080bbd71c46c7b20fb18da01be88e0fa077",
"src/internal/server/oidc/token_test.go": "sha256:5721617c6e5eb79a3b5e86efa63e9278a8d81a0d7c01bf7b184255c102848e33",
"src/internal/server/oidc/audience_test.go": "sha256:1b751e1aafb5dbdcb751b42b001e32ddfaccfa3d5ba9d5af67b44148d9802ae3",
"src/internal/server/oidc/token_status_transport_test.go": "sha256:3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347",
"src/internal/server/oidc/tenant_test.go": "sha256:8a3879789589f0c3d6e473ba56dd7d7c86ec6fbdddaafa4b13354f82d5f88713",
"src/internal/server/oidc/assurance_time_test.go": "sha256:911f65e8cea87ef867d6bf98199ed55abd29427b9f8d0838977d228e6186b08a",
"src/internal/server/oidc/signin_diagnosis.go": "sha256:b7976a1cfe1d8d1dca890cf0762cc82d08978a2e7537e45988b147ed0222042f",
"src/internal/server/oidc/authorize.go": "sha256:04f9bfb6b146ecf869161b7a0eb00b6486f424710c2c867e4f4b70af5f44ff5e",
"src/internal/server/oidc/handoff.go": "sha256:a2ffede12c9140a6c7d14632122a39ffbd0e707cd357dcc11d3ad885dc2a7fa4",
"src/internal/server/oidc/token_status_internal_test.go": "sha256:3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53",
"src/internal/server/oidc/token_status_test.go": "sha256:e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90",
"src/internal/migration/lldapexport/exporter.go": "sha256:cb041827c9c0edf9da1338e966e7722e3765065582933b8c17b0d9008d44d103",
"src/internal/migration/lldapexport/exporter_test.go": "sha256:6f5cf0e43ea8aea9ceeb6d4d3ae0bfd9f3a5d18636dd49bea0c574c5253a44c2",
"src/internal/migration/tokeycloak/transformer_test.go": "sha256:5c3f5145a37079262db805f5bedaabe1d42edf2ccec1788c09c02f5b8336639b",
"src/internal/migration/tokeycloak/transformer.go": "sha256:856e26fd2075f020ef02373b5b7e9e5a1e98473b5caca95b60b7983287fc10aa",
"src/internal/migration/toldap/generator.go": "sha256:e59ba05203145f52818b3f4e34ab62126481faa9dde697f643fc1cda0638bb46",
"src/internal/migration/toldap/generator_test.go": "sha256:73b0969ce2e2cb018aa5734714c86152d1695f0099a5caf7dd96b773717fc2cc",
"src/internal/adapters/tenantengine/adapter.go": "sha256:a54ef12fafd2efc24c547d562e0a558a7ecef586394e79f7de345dc8cbbbbef3",
"src/internal/adapters/tenantengine/adapter_test.go": "sha256:b9c2db04bc187e6e6e56d4a42d4e657b9e56fef06843500fb4640477dee553e0",
"src/internal/adapters/lldap/adapter.go": "sha256:e7df7a159e9dd33a97932fd8cbde49a61091b8722796bf54ae4be887b4a3f5ef",
"src/internal/adapters/lldap/railiance_admin_test.go": "sha256:63db481bb6e592e91bf4a555e75065fbd3bd8cd408a7967a4b7786e12bef5d5f",
"src/internal/adapters/lldap/adapter_test.go": "sha256:650394ccc25d54c73c03746977a1880b1ed905634da8382da0ecf35843454081",
"src/internal/adapters/lldap/config_internal_test.go": "sha256:cbc71ce9a795b73c40b2714153eb2ee4d2e4007f7f56a7882eb862c23868cb68",
"src/internal/adapters/lldap/config.go": "sha256:33059a27418b00e1987b7ced0b21dd39c1765338bf575fe2333ce3e606e5546b",
"src/internal/adapters/privacyidea/credential_file_test.go": "sha256:9db15562c6fdf01e0806bbc478915413b17adb63c7e9ba213acfc4f9b2b2f0b6",
"src/internal/adapters/privacyidea/read_scope_test.go": "sha256:4685e1b84dad044db12708ecd231d4f91ec22ac45901ed2b5177e3be17d8cdc3",
"src/internal/adapters/privacyidea/adapter.go": "sha256:2559c68b5aa22bc5f61961e47d696b376f8bede533be03715ffc80c0d89ed904",
"src/internal/adapters/privacyidea/adapter_test.go": "sha256:6257489b9c51270da2c96f165c0efd1750b273218df51dca907d29c64c98b349",
"src/internal/adapters/privacyidea/optional_mfa_test.go": "sha256:e2e415c671d1d0e7f30a0ce22f5ec58914d8e366b12e445e13b1d645a9ca6a65",
"src/internal/adapters/privacyidea/enrollment_state_test.go": "sha256:dcf24c486629fa4835cddaa786af31ec78c05907694d4d93bce0f32876806377",
"src/internal/adapters/privacyidea/config.go": "sha256:8dd769b18c04c562714794484630acdbda81b34ab1cfaf9cc7445513207e5021",
"src/internal/adapters/authelia/verify.go": "sha256:189f9bc18b8d4319d2d88ee76a76e3b86468ebafde85ef460a4bafb7b350b4c4",
"src/internal/adapters/authelia/adapter.go": "sha256:2887bd546f43a16338547dab5ce4a90850c670ed6f6c0d610a5d08c72c7f3f45",
"src/internal/adapters/authelia/adapter_test.go": "sha256:d450b37e2b2405c189d444e50f0612e64177d6698cb4665a06ad967ae2224f93",
"src/internal/adapters/authelia/issuer_probe.go": "sha256:5846e2b514077a572611b898f449c04b096b086aeb724da055b2416994a7a7bb",
"src/internal/adapters/authelia/issuer_probe_test.go": "sha256:a85e02399f8a5c81401dbf159e8054420bff56e08c8a98e8026cad8d31fcecec",
"src/internal/adapters/authelia/config.go": "sha256:1b85ea3ffd324bced1ba108492ec780af8ba2b015d22cd51b294afaf36f2ec55",
"src/internal/adapters/authelia/verify_test.go": "sha256:f1570604d5038e49c14791f4d1aa278ad76a7e90294e82813d2c21e6a80a5511",
"src/cmd/keycape-to-keycloak/main.go": "sha256:6141d3f86bbe3df2a814a0c9995e537d32bf7b19f5549e89d40974eb3270faf7",
"src/cmd/lldap-to-ldap/main.go": "sha256:adcc9481d009cad909bd55a71d95c17ce30e29ca63e88a7c9e8101b92d4b872e",
"src/cmd/keycape/clients_test.go": "sha256:6a3982c1b2bd9b15c387ba64d4c87b37304dc6f615ed2ab22eec888f620cb1da",
"src/cmd/keycape/checkconfig.go": "sha256:2c47c9aedf3457627fdec991703e3be50e5ef554e4104cf3741f8347687279f2",
"src/cmd/keycape/main.go": "sha256:553094de91cfafb021b7607eac1263fa155fedeb32c1e99e7c71e8d86e20ffd0",
"src/cmd/keycape/checkconfig_test.go": "sha256:06955c996774109b26e4751ab20787f12718a1de96e3483a775fb19688d90e66",
"src/cmd/validator/main.go": "sha256:e53b0a7860537dfb1cec69a00cdafe7073b35802421b5977f2fece4de01b9c1f",
"src/cmd/lldap-export/main_test.go": "sha256:f105e490c717a414a5a3d1e65ceb4edcf74341ad7713b0accf8117a9b1ee02d2",
"src/cmd/lldap-export/main.go": "sha256:f019a6dbb0849d06d5c3729874293d520478ca2887929a0595b59a324097ae75"
},
"counts_including_subtests": {
"pass": 726,
"fail": 0,
"skip": 6
},
"failures": [],
"skips": [
"keycape/tests/conformance:TestIssuerAdvertisesItself",
"keycape/tests/conformance:TestProfileAuthorizationSurface",
"keycape/tests/conformance:TestPublishedKeysAreUsable",
"keycape/tests/conformance:TestExcludedGrantsAreNotAdvertised",
"keycape/tests/conformance:TestServiceTokenVerifiesAgainstPublishedKeys",
"keycape/internal/server/oidc:TestNativeOptionalEnrollmentAndOldSession"
],
"source": "/tmp/custodian-keycape-token-pair-r1/source",
"native_acceptance": false,
"credentials_accessed": false,
"network": "private empty namespace; fixture loopback only",
"home": "new empty sandbox HOME"
}

View file

@ -0,0 +1,218 @@
{
"returncode": 0,
"seconds": 9.36,
"argv": [
"bwrap",
"--unshare-net",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-keycape-token-pair-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod",
"/modules",
"--ro-bind",
"/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64",
"/toolchain",
"--bind",
"/tmp/custodian-keycape-token-status-r1/gocache",
"/work/gocache",
"--ro-bind",
"/tmp/custodian-forgejo-current-token-r1/forgejo-oauth2.test",
"/forgejo-oauth2.test",
"--chdir",
"/work/source/src",
"--clearenv",
"--setenv",
"PATH",
"/toolchain/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOMODCACHE",
"/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"FORGEJO_CURRENT_TEST_BINARY",
"/forgejo-oauth2.test",
"--setenv",
"FORGEJO_CURRENT_REPORT",
"/work/forgejo-current-result.json",
"--",
"go",
"test",
"-p",
"4",
"-race",
"-json",
"-count=1",
"-tags",
"forgejo_current_fixture",
"-run",
"^TestOnlineActualForgejoCallbackCurrentIssuer$",
"./internal/authclient"
],
"source_sha256_at_start": {
"src/tests/negative/negative_test.go": "sha256:757485bf1967ed95e872d4a1987b7fd985fbffccd05cf41270b9f4061431de3f",
"src/tests/conformance/external_test.go": "sha256:b26bd465e31086f957c9e374b6bee1f0f077b7481eda9daa29cf63816bb87fdb",
"src/tests/migration/scenario_c_test.go": "sha256:9722146ac224768aff85476acbd83f3d478c1e060e5a3c6f99b1786858879b80",
"src/tests/migration/fixtures_test.go": "sha256:3fbcb8f531fa206225f719a67b744b2ecb908095fbae0538432796697971e6de",
"src/tests/migration/scenario_b_test.go": "sha256:40ae7239d116f3e284c767570d39e1f32d44fc29b6fa37ea8835987027942ac0",
"src/tests/profile/profile_test.go": "sha256:e5ab4568b15f681a13c9295b17907ca3872ee33b702bb56aea749f17758d29e6",
"src/internal/domain/assurance_test.go": "sha256:87680e7afd47294dadc13092ca2bb7c47b9625c80e48a701765c00df3293d755",
"src/internal/domain/conformance_test.go": "sha256:6420ec21d4e7a97f60902e1f0debbaf20df91aeac6ab110629b82a67f4c5c9a1",
"src/internal/domain/repository.go": "sha256:28f6d8f7f3d822e73f164af5e877f252db7dc6faa97f0ebf75d76aad64078011",
"src/internal/domain/auth.go": "sha256:ae800c5b1f58408147dd28a5c87ab2603b1537500a289397e711a4cb0e132cee",
"src/internal/domain/model.go": "sha256:ce4309fc7426c66e4ebc18022d2f704c10ebbec243cc98e657d1de765ad09029",
"src/internal/domain/assurance.go": "sha256:d3a29ece7c4fa4d9daa8b5fda89cbe0dd1ffef2ee2ecae0051ee68010edb8fd7",
"src/internal/domain/mfa.go": "sha256:1b7875b08a0f280a20a14a4fa4fdb4fc84c3942795b1d1417943d32634121b87",
"src/internal/authclient/cli.go": "sha256:009c9ce900524b62483cf4955f3cbacc0dcb9027b8da77924b51b22e3fa5170c",
"src/internal/authclient/forgejo_current_fixture_test.go": "sha256:5456d007ad2a3288e871a8d1ebbc6d533ee374bd3c76d5df3829b893b814bf32",
"src/internal/authclient/client.go": "sha256:61fb150349c5d3714450a90834c59af9a579a8a649c4149297a402cfcf6e39c5",
"src/internal/authclient/online_cli.go": "sha256:6763c1e85396726b044067e77179ee2220e397bbd3888ec8de071c2826c6c7f2",
"src/internal/authclient/verify.go": "sha256:804735be927ba7d0228ac378056730040d3ccf2ed020b4da0714085bae972f25",
"src/internal/authclient/online.go": "sha256:182ec3971ded3a7f3923aab7a23b7317de3ec0f657b8da6d9d882a43bcb0961a",
"src/internal/authclient/online_export_test.go": "sha256:5695f233e4caf581e494087cb203a74613d39a5626bbcc323b9f2d553a96b7ba",
"src/internal/authclient/client_test.go": "sha256:23642488fcff45e369d22ba0000c47e0e450c5f92560e643ab96303e2a2f43aa",
"src/internal/authclient/online_cli_test.go": "sha256:5408a183beda6a885ebae00d3fa420d3fb0d51e4aeb72965fef535d822270c4f",
"src/internal/authclient/online_test.go": "sha256:6288329ca2576f72a361256050c94f9d1995e9fbe53b2a6318da2954bcbc7c6a",
"src/internal/authclient/online_human_test.go": "sha256:e501c30b6d710a6dea89559ccf8b98de68502c2ee58d1778a56e97f1b7f3b6e0",
"src/internal/authclient/verify_test.go": "sha256:7febc6698c87eac5fd8d2520c4f77c68e82f18a2aafa448cfa4990b96fdc46fc",
"src/internal/validator/report.go": "sha256:6bc4d31f932ea9a32020cf33203978906730dfe3601350ded0206ff6c8c297a8",
"src/internal/validator/validator_test.go": "sha256:8216027a464eab4167d8e87b4d27d130854d7b9cd4cc8a1b6f1a47ff1f9a2276",
"src/internal/validator/validator.go": "sha256:3d0a8bf8ebbe54e21e162c8f7df6e4a4d69ab997a01969b74dcbd53decdf559f",
"src/internal/config/account_test.go": "sha256:c6b75097d14dd42415c836407fa2a509857cdd2b771fb7e53d4507cbad2f4b1d",
"src/internal/config/validate.go": "sha256:2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405",
"src/internal/config/optional_mfa_test.go": "sha256:03a00bc309943adbed68bdb9bf47facb6b3b50ef380fd49143b5ed54d684f63e",
"src/internal/config/config_test.go": "sha256:af8602010985a0a5a6b4b2bf779f824e7eb5141e6bc2e8d8fcf600b964955100",
"src/internal/config/token_status_test.go": "sha256:b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5",
"src/internal/config/config.go": "sha256:19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683",
"src/internal/errors/taxonomy.go": "sha256:7d60f30a4ff0c9a21a3942451eb0292320a6346d3d81508e414a7dfbdc3c666c",
"src/internal/errors/taxonomy_test.go": "sha256:6c0bdf1f8e90ea343d88f57f80f6c916c92428a9ac2191a518ee7b70df0eed45",
"src/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"src/internal/jose/jose_test.go": "sha256:cbbcbac620b27c8e93d9dc3fefc3ca4fcd20ab58c6bc8f79eb041b980aaa9a43",
"src/internal/server/policy/policy_test.go": "sha256:25644557ca532b608dff4e02b202c5c3b7c423fb31e98ad0609febf3660183a2",
"src/internal/server/policy/policy.go": "sha256:015d0b42419c501a46171d09a8f7d588dd517d4c98ad14344ff52113294e0fe7",
"src/internal/server/errors/enforcement.go": "sha256:f644546b1423992518fc4078ab561ab68fea1fcff00b734795af6652ae3dd4e2",
"src/internal/server/errors/enforcement_test.go": "sha256:b4ea0c9a3aaebd66b0b0fcc73f544c76d5a377ac0c9561ccc2638a236844374c",
"src/internal/server/telemetry/emitter.go": "sha256:e92f008b6a487a47b8ee2b98011a3a5b821838a61be9c4db4d56cda600e6fda0",
"src/internal/server/telemetry/emitter_test.go": "sha256:29fae2460deb7219eb8edc725b3701a02d2b341fee62bffb2661a520f5a7381a",
"src/internal/server/telemetry/events.go": "sha256:3f9df9822649ae2701c3bbf475c8e6d600bc60b7ccdea4d0e54193162d5b0167",
"src/internal/server/readiness/readiness_test.go": "sha256:10163e0b1d2f41f336d61782c3bfffdd1116a8459247cc933ffb40be645184c6",
"src/internal/server/readiness/readiness.go": "sha256:ffe22b99633b1aa50fc299371398ec0f3fc090c9c8a5f4957c6bfa76b001d073",
"src/internal/server/oidc/discovery_test.go": "sha256:2d0dc24d59497aa5a9aa581f8fd3d9cca300d7e0cb4962907630570d60e7e7f0",
"src/internal/server/oidc/tenant_precondition_test.go": "sha256:2f2c0d90d26da4e3a6c9145aec8c9d7121c791b6b993c2fe8893a763a30fa72f",
"src/internal/server/oidc/userinfo.go": "sha256:f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f",
"src/internal/server/oidc/account.go": "sha256:b0e36d8cb5f9b1bf4cee573e46b204800c0e3162a912a122b271def4712ab72a",
"src/internal/server/oidc/policy_runtime_test.go": "sha256:856146013d2aaa60836e669c297024923ed8e2856d31a89744a99baadae4a74a",
"src/internal/server/oidc/handoff_test.go": "sha256:23268b7f9f103f8a8798a168004580bc8fdb3a175161220dac26480a89f28e02",
"src/internal/server/oidc/human_tenant_test.go": "sha256:459664f90d76224958b3dd807d95d7bfcf0fce5f113498bd9886a41be19903a5",
"src/internal/server/oidc/discovery.go": "sha256:00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5",
"src/internal/server/oidc/jwks.go": "sha256:426d6596979ed6d097d6aaae66b6475d1c97687888c6ab1050c05cb14653ec60",
"src/internal/server/oidc/login_session.go": "sha256:efb0fcd066d81d753b7043c9c3d9e74e35cd510ca4304d21a51ba29dec833e1f",
"src/internal/server/oidc/hardening_test.go": "sha256:b41d6a987d5507d5160d970d0f2110ce681851fca1f4f7a3a9573163e8cd6f3e",
"src/internal/server/oidc/jwks_test.go": "sha256:9100e83e58ce2cc2f6d2f47804ee0ce035ca424b209597a12c4ef1a8896163aa",
"src/internal/server/oidc/account_test.go": "sha256:db1f7b8a2989b1d0deffee205ec9f4f35c7472135dcb8a10b5498893d881d763",
"src/internal/server/oidc/token_status_discovery_test.go": "sha256:27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031",
"src/internal/server/oidc/principal_type_guard_test.go": "sha256:b3fa097691f07594616e09e8775512d586a019ccdf8302bf594c1ac94f5f3f40",
"src/internal/server/oidc/token_status_http.go": "sha256:a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97",
"src/internal/server/oidc/native_optional_test.go": "sha256:329907bf7089d8f3ea05baad919d05677b8c3aaa201e6f233795ae9f69c003b5",
"src/internal/server/oidc/authorize_test.go": "sha256:61042d55e20a11fb6e1e9e6c054e50c0f629b702ed6d8da281d52529e5c29b0d",
"src/internal/server/oidc/session.go": "sha256:48473ebe123285ed21e7738834d9d9168dfc855d911cb9e7ce7e87b069a23a3c",
"src/internal/server/oidc/logout.go": "sha256:d4de80f8056e7a65c7a41f244dd38e447a4e04df729760a8a59cb6bb4e063893",
"src/internal/server/oidc/optional_mfa_test.go": "sha256:d758848d845c181cf1b18710e6ca24977f6b7576320dcad1f7293a5433c3b529",
"src/internal/server/oidc/token.go": "sha256:e6905bfa0474dde6122a83a6627d6b75a4c3a7730fb26106546e7a34eef8302b",
"src/internal/server/oidc/token_status.go": "sha256:d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3",
"src/internal/server/oidc/userinfo_test.go": "sha256:e88c42c7cdf50d5953c9f594f0b99b4f87f5e223ba1cb44b70d696972016da80",
"src/internal/server/oidc/signin_diagnosis_test.go": "sha256:97e80be67c480dfe8eef0683f25868a91a0fdc96472b5296b734b56274270b3e",
"src/internal/server/oidc/token_pair_test.go": "sha256:efeb929becf61d62111286ead4d3635c1a59bfd34423280084ccfec2169a2b1d",
"src/internal/server/oidc/policy_isolation_test.go": "sha256:35cb9870f5942c61c0e8c6ca875e9080bbd71c46c7b20fb18da01be88e0fa077",
"src/internal/server/oidc/token_test.go": "sha256:5721617c6e5eb79a3b5e86efa63e9278a8d81a0d7c01bf7b184255c102848e33",
"src/internal/server/oidc/audience_test.go": "sha256:1b751e1aafb5dbdcb751b42b001e32ddfaccfa3d5ba9d5af67b44148d9802ae3",
"src/internal/server/oidc/token_status_transport_test.go": "sha256:3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347",
"src/internal/server/oidc/tenant_test.go": "sha256:8a3879789589f0c3d6e473ba56dd7d7c86ec6fbdddaafa4b13354f82d5f88713",
"src/internal/server/oidc/assurance_time_test.go": "sha256:911f65e8cea87ef867d6bf98199ed55abd29427b9f8d0838977d228e6186b08a",
"src/internal/server/oidc/signin_diagnosis.go": "sha256:b7976a1cfe1d8d1dca890cf0762cc82d08978a2e7537e45988b147ed0222042f",
"src/internal/server/oidc/authorize.go": "sha256:04f9bfb6b146ecf869161b7a0eb00b6486f424710c2c867e4f4b70af5f44ff5e",
"src/internal/server/oidc/handoff.go": "sha256:a2ffede12c9140a6c7d14632122a39ffbd0e707cd357dcc11d3ad885dc2a7fa4",
"src/internal/server/oidc/token_status_internal_test.go": "sha256:3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53",
"src/internal/server/oidc/token_status_test.go": "sha256:e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90",
"src/internal/migration/lldapexport/exporter.go": "sha256:cb041827c9c0edf9da1338e966e7722e3765065582933b8c17b0d9008d44d103",
"src/internal/migration/lldapexport/exporter_test.go": "sha256:6f5cf0e43ea8aea9ceeb6d4d3ae0bfd9f3a5d18636dd49bea0c574c5253a44c2",
"src/internal/migration/tokeycloak/transformer_test.go": "sha256:5c3f5145a37079262db805f5bedaabe1d42edf2ccec1788c09c02f5b8336639b",
"src/internal/migration/tokeycloak/transformer.go": "sha256:856e26fd2075f020ef02373b5b7e9e5a1e98473b5caca95b60b7983287fc10aa",
"src/internal/migration/toldap/generator.go": "sha256:e59ba05203145f52818b3f4e34ab62126481faa9dde697f643fc1cda0638bb46",
"src/internal/migration/toldap/generator_test.go": "sha256:73b0969ce2e2cb018aa5734714c86152d1695f0099a5caf7dd96b773717fc2cc",
"src/internal/adapters/tenantengine/adapter.go": "sha256:a54ef12fafd2efc24c547d562e0a558a7ecef586394e79f7de345dc8cbbbbef3",
"src/internal/adapters/tenantengine/adapter_test.go": "sha256:b9c2db04bc187e6e6e56d4a42d4e657b9e56fef06843500fb4640477dee553e0",
"src/internal/adapters/lldap/adapter.go": "sha256:e7df7a159e9dd33a97932fd8cbde49a61091b8722796bf54ae4be887b4a3f5ef",
"src/internal/adapters/lldap/railiance_admin_test.go": "sha256:63db481bb6e592e91bf4a555e75065fbd3bd8cd408a7967a4b7786e12bef5d5f",
"src/internal/adapters/lldap/adapter_test.go": "sha256:650394ccc25d54c73c03746977a1880b1ed905634da8382da0ecf35843454081",
"src/internal/adapters/lldap/config_internal_test.go": "sha256:cbc71ce9a795b73c40b2714153eb2ee4d2e4007f7f56a7882eb862c23868cb68",
"src/internal/adapters/lldap/config.go": "sha256:33059a27418b00e1987b7ced0b21dd39c1765338bf575fe2333ce3e606e5546b",
"src/internal/adapters/privacyidea/credential_file_test.go": "sha256:9db15562c6fdf01e0806bbc478915413b17adb63c7e9ba213acfc4f9b2b2f0b6",
"src/internal/adapters/privacyidea/read_scope_test.go": "sha256:4685e1b84dad044db12708ecd231d4f91ec22ac45901ed2b5177e3be17d8cdc3",
"src/internal/adapters/privacyidea/adapter.go": "sha256:2559c68b5aa22bc5f61961e47d696b376f8bede533be03715ffc80c0d89ed904",
"src/internal/adapters/privacyidea/adapter_test.go": "sha256:6257489b9c51270da2c96f165c0efd1750b273218df51dca907d29c64c98b349",
"src/internal/adapters/privacyidea/optional_mfa_test.go": "sha256:e2e415c671d1d0e7f30a0ce22f5ec58914d8e366b12e445e13b1d645a9ca6a65",
"src/internal/adapters/privacyidea/enrollment_state_test.go": "sha256:dcf24c486629fa4835cddaa786af31ec78c05907694d4d93bce0f32876806377",
"src/internal/adapters/privacyidea/config.go": "sha256:8dd769b18c04c562714794484630acdbda81b34ab1cfaf9cc7445513207e5021",
"src/internal/adapters/authelia/verify.go": "sha256:189f9bc18b8d4319d2d88ee76a76e3b86468ebafde85ef460a4bafb7b350b4c4",
"src/internal/adapters/authelia/adapter.go": "sha256:2887bd546f43a16338547dab5ce4a90850c670ed6f6c0d610a5d08c72c7f3f45",
"src/internal/adapters/authelia/adapter_test.go": "sha256:d450b37e2b2405c189d444e50f0612e64177d6698cb4665a06ad967ae2224f93",
"src/internal/adapters/authelia/issuer_probe.go": "sha256:5846e2b514077a572611b898f449c04b096b086aeb724da055b2416994a7a7bb",
"src/internal/adapters/authelia/issuer_probe_test.go": "sha256:a85e02399f8a5c81401dbf159e8054420bff56e08c8a98e8026cad8d31fcecec",
"src/internal/adapters/authelia/config.go": "sha256:1b85ea3ffd324bced1ba108492ec780af8ba2b015d22cd51b294afaf36f2ec55",
"src/internal/adapters/authelia/verify_test.go": "sha256:f1570604d5038e49c14791f4d1aa278ad76a7e90294e82813d2c21e6a80a5511",
"src/cmd/keycape-to-keycloak/main.go": "sha256:6141d3f86bbe3df2a814a0c9995e537d32bf7b19f5549e89d40974eb3270faf7",
"src/cmd/lldap-to-ldap/main.go": "sha256:adcc9481d009cad909bd55a71d95c17ce30e29ca63e88a7c9e8101b92d4b872e",
"src/cmd/keycape/clients_test.go": "sha256:6a3982c1b2bd9b15c387ba64d4c87b37304dc6f615ed2ab22eec888f620cb1da",
"src/cmd/keycape/checkconfig.go": "sha256:2c47c9aedf3457627fdec991703e3be50e5ef554e4104cf3741f8347687279f2",
"src/cmd/keycape/main.go": "sha256:553094de91cfafb021b7607eac1263fa155fedeb32c1e99e7c71e8d86e20ffd0",
"src/cmd/keycape/checkconfig_test.go": "sha256:06955c996774109b26e4751ab20787f12718a1de96e3483a775fb19688d90e66",
"src/cmd/validator/main.go": "sha256:e53b0a7860537dfb1cec69a00cdafe7073b35802421b5977f2fece4de01b9c1f",
"src/cmd/lldap-export/main_test.go": "sha256:f105e490c717a414a5a3d1e65ceb4edcf74341ad7713b0accf8117a9b1ee02d2",
"src/cmd/lldap-export/main.go": "sha256:f019a6dbb0849d06d5c3729874293d520478ca2887929a0595b59a324097ae75"
},
"consumer_test_binary_sha256": "sha256:8052b5598371a595388c32c46ed9815349b3285f3ed864ec3dece75fafa8852e",
"real_credentials": false,
"native_admission": false
}

View file

@ -0,0 +1,8 @@
{"Time":"2026-10-18T10:04:26.152182424Z","Action":"start","Package":"keycape/internal/authclient"}
{"Time":"2026-10-18T10:04:26.222220538Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer"}
{"Time":"2026-10-18T10:04:26.222388827Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Output":"=== RUN TestOnlineActualForgejoCallbackCurrentIssuer\n"}
{"Time":"2026-10-18T10:04:28.845378886Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Output":"--- PASS: TestOnlineActualForgejoCallbackCurrentIssuer (2.62s)\n"}
{"Time":"2026-10-18T10:04:28.845484388Z","Action":"pass","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Elapsed":2.62}
{"Time":"2026-10-18T10:04:28.846007181Z","Action":"output","Package":"keycape/internal/authclient","Output":"PASS\n"}
{"Time":"2026-10-18T10:04:29.857213576Z","Action":"output","Package":"keycape/internal/authclient","Output":"ok \tkeycape/internal/authclient\t3.702s\n"}
{"Time":"2026-10-18T10:04:29.857385445Z","Action":"pass","Package":"keycape/internal/authclient","Elapsed":3.705}

View file

@ -0,0 +1,217 @@
{
"returncode": 1,
"seconds": 2.69,
"argv": [
"bwrap",
"--unshare-net",
"--unshare-pid",
"--die-with-parent",
"--new-session",
"--ro-bind",
"/usr",
"/usr",
"--ro-bind",
"/lib",
"/lib",
"--ro-bind",
"/lib64",
"/lib64",
"--symlink",
"usr/bin",
"/bin",
"--proc",
"/proc",
"--dev",
"/dev",
"--tmpfs",
"/tmp",
"--bind",
"/tmp/custodian-keycape-token-pair-before-r1",
"/work",
"--ro-bind",
"/home/worsch/go/pkg/mod",
"/modules",
"--ro-bind",
"/home/worsch/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.7.linux-amd64",
"/toolchain",
"--bind",
"/tmp/custodian-keycape-token-status-r1/gocache",
"/work/gocache",
"--ro-bind",
"/tmp/custodian-forgejo-current-token-r1/forgejo-oauth2.test",
"/forgejo-oauth2.test",
"--chdir",
"/work/source/src",
"--clearenv",
"--setenv",
"PATH",
"/toolchain/bin:/usr/bin:/bin",
"--setenv",
"HOME",
"/work/home",
"--setenv",
"GOTOOLCHAIN",
"local",
"--setenv",
"GOPROXY",
"off",
"--setenv",
"GOSUMDB",
"off",
"--setenv",
"GOMODCACHE",
"/modules",
"--setenv",
"GOCACHE",
"/work/gocache",
"--setenv",
"GOMAXPROCS",
"4",
"--setenv",
"CGO_ENABLED",
"1",
"--setenv",
"FORGEJO_CURRENT_TEST_BINARY",
"/forgejo-oauth2.test",
"--setenv",
"FORGEJO_CURRENT_REPORT",
"/work/forgejo-current-result.json",
"--",
"go",
"test",
"-p",
"4",
"-race",
"-json",
"-count=1",
"-tags",
"forgejo_current_fixture",
"-run",
"^TestOnlineActualForgejoCallbackCurrentIssuer$",
"./internal/authclient"
],
"source_sha256_at_start": {
"src/tests/negative/negative_test.go": "sha256:757485bf1967ed95e872d4a1987b7fd985fbffccd05cf41270b9f4061431de3f",
"src/tests/conformance/external_test.go": "sha256:b26bd465e31086f957c9e374b6bee1f0f077b7481eda9daa29cf63816bb87fdb",
"src/tests/migration/scenario_c_test.go": "sha256:9722146ac224768aff85476acbd83f3d478c1e060e5a3c6f99b1786858879b80",
"src/tests/migration/fixtures_test.go": "sha256:3fbcb8f531fa206225f719a67b744b2ecb908095fbae0538432796697971e6de",
"src/tests/migration/scenario_b_test.go": "sha256:40ae7239d116f3e284c767570d39e1f32d44fc29b6fa37ea8835987027942ac0",
"src/tests/profile/profile_test.go": "sha256:e5ab4568b15f681a13c9295b17907ca3872ee33b702bb56aea749f17758d29e6",
"src/internal/domain/assurance_test.go": "sha256:87680e7afd47294dadc13092ca2bb7c47b9625c80e48a701765c00df3293d755",
"src/internal/domain/conformance_test.go": "sha256:6420ec21d4e7a97f60902e1f0debbaf20df91aeac6ab110629b82a67f4c5c9a1",
"src/internal/domain/repository.go": "sha256:28f6d8f7f3d822e73f164af5e877f252db7dc6faa97f0ebf75d76aad64078011",
"src/internal/domain/auth.go": "sha256:ae800c5b1f58408147dd28a5c87ab2603b1537500a289397e711a4cb0e132cee",
"src/internal/domain/model.go": "sha256:ce4309fc7426c66e4ebc18022d2f704c10ebbec243cc98e657d1de765ad09029",
"src/internal/domain/assurance.go": "sha256:d3a29ece7c4fa4d9daa8b5fda89cbe0dd1ffef2ee2ecae0051ee68010edb8fd7",
"src/internal/domain/mfa.go": "sha256:1b7875b08a0f280a20a14a4fa4fdb4fc84c3942795b1d1417943d32634121b87",
"src/internal/authclient/cli.go": "sha256:009c9ce900524b62483cf4955f3cbacc0dcb9027b8da77924b51b22e3fa5170c",
"src/internal/authclient/forgejo_current_fixture_test.go": "sha256:5456d007ad2a3288e871a8d1ebbc6d533ee374bd3c76d5df3829b893b814bf32",
"src/internal/authclient/client.go": "sha256:61fb150349c5d3714450a90834c59af9a579a8a649c4149297a402cfcf6e39c5",
"src/internal/authclient/online_cli.go": "sha256:6763c1e85396726b044067e77179ee2220e397bbd3888ec8de071c2826c6c7f2",
"src/internal/authclient/verify.go": "sha256:804735be927ba7d0228ac378056730040d3ccf2ed020b4da0714085bae972f25",
"src/internal/authclient/online.go": "sha256:182ec3971ded3a7f3923aab7a23b7317de3ec0f657b8da6d9d882a43bcb0961a",
"src/internal/authclient/online_export_test.go": "sha256:5695f233e4caf581e494087cb203a74613d39a5626bbcc323b9f2d553a96b7ba",
"src/internal/authclient/client_test.go": "sha256:23642488fcff45e369d22ba0000c47e0e450c5f92560e643ab96303e2a2f43aa",
"src/internal/authclient/online_cli_test.go": "sha256:5408a183beda6a885ebae00d3fa420d3fb0d51e4aeb72965fef535d822270c4f",
"src/internal/authclient/online_test.go": "sha256:6288329ca2576f72a361256050c94f9d1995e9fbe53b2a6318da2954bcbc7c6a",
"src/internal/authclient/online_human_test.go": "sha256:e501c30b6d710a6dea89559ccf8b98de68502c2ee58d1778a56e97f1b7f3b6e0",
"src/internal/authclient/verify_test.go": "sha256:7febc6698c87eac5fd8d2520c4f77c68e82f18a2aafa448cfa4990b96fdc46fc",
"src/internal/validator/report.go": "sha256:6bc4d31f932ea9a32020cf33203978906730dfe3601350ded0206ff6c8c297a8",
"src/internal/validator/validator_test.go": "sha256:8216027a464eab4167d8e87b4d27d130854d7b9cd4cc8a1b6f1a47ff1f9a2276",
"src/internal/validator/validator.go": "sha256:3d0a8bf8ebbe54e21e162c8f7df6e4a4d69ab997a01969b74dcbd53decdf559f",
"src/internal/config/account_test.go": "sha256:c6b75097d14dd42415c836407fa2a509857cdd2b771fb7e53d4507cbad2f4b1d",
"src/internal/config/validate.go": "sha256:2b1766979b817802f43bdd0f999ac61452d86cdfafc1539a5d6be896b900a405",
"src/internal/config/optional_mfa_test.go": "sha256:03a00bc309943adbed68bdb9bf47facb6b3b50ef380fd49143b5ed54d684f63e",
"src/internal/config/config_test.go": "sha256:af8602010985a0a5a6b4b2bf779f824e7eb5141e6bc2e8d8fcf600b964955100",
"src/internal/config/token_status_test.go": "sha256:b2b2ec13215c9661d79142ea698cb8b080f6e017de050bedebf3eb8d16df2ad5",
"src/internal/config/config.go": "sha256:19cd234757f8908880d28b8b40939367dfab1322e0401e4e563d56cd3af06683",
"src/internal/errors/taxonomy.go": "sha256:7d60f30a4ff0c9a21a3942451eb0292320a6346d3d81508e414a7dfbdc3c666c",
"src/internal/errors/taxonomy_test.go": "sha256:6c0bdf1f8e90ea343d88f57f80f6c916c92428a9ac2191a518ee7b70df0eed45",
"src/internal/jose/jose.go": "sha256:a6de154cd81e34a521b8aac5dce6b45a3cfb015681ed16e2beb8f2a24c72c00d",
"src/internal/jose/jose_test.go": "sha256:cbbcbac620b27c8e93d9dc3fefc3ca4fcd20ab58c6bc8f79eb041b980aaa9a43",
"src/internal/server/policy/policy_test.go": "sha256:25644557ca532b608dff4e02b202c5c3b7c423fb31e98ad0609febf3660183a2",
"src/internal/server/policy/policy.go": "sha256:015d0b42419c501a46171d09a8f7d588dd517d4c98ad14344ff52113294e0fe7",
"src/internal/server/errors/enforcement.go": "sha256:f644546b1423992518fc4078ab561ab68fea1fcff00b734795af6652ae3dd4e2",
"src/internal/server/errors/enforcement_test.go": "sha256:b4ea0c9a3aaebd66b0b0fcc73f544c76d5a377ac0c9561ccc2638a236844374c",
"src/internal/server/telemetry/emitter.go": "sha256:e92f008b6a487a47b8ee2b98011a3a5b821838a61be9c4db4d56cda600e6fda0",
"src/internal/server/telemetry/emitter_test.go": "sha256:29fae2460deb7219eb8edc725b3701a02d2b341fee62bffb2661a520f5a7381a",
"src/internal/server/telemetry/events.go": "sha256:3f9df9822649ae2701c3bbf475c8e6d600bc60b7ccdea4d0e54193162d5b0167",
"src/internal/server/readiness/readiness_test.go": "sha256:10163e0b1d2f41f336d61782c3bfffdd1116a8459247cc933ffb40be645184c6",
"src/internal/server/readiness/readiness.go": "sha256:ffe22b99633b1aa50fc299371398ec0f3fc090c9c8a5f4957c6bfa76b001d073",
"src/internal/server/oidc/discovery_test.go": "sha256:2d0dc24d59497aa5a9aa581f8fd3d9cca300d7e0cb4962907630570d60e7e7f0",
"src/internal/server/oidc/tenant_precondition_test.go": "sha256:2f2c0d90d26da4e3a6c9145aec8c9d7121c791b6b993c2fe8893a763a30fa72f",
"src/internal/server/oidc/userinfo.go": "sha256:f9c986f7a2c79f27a9970a296e6b493014fed77af2f22ba96c4a457d049c916f",
"src/internal/server/oidc/account.go": "sha256:b0e36d8cb5f9b1bf4cee573e46b204800c0e3162a912a122b271def4712ab72a",
"src/internal/server/oidc/policy_runtime_test.go": "sha256:856146013d2aaa60836e669c297024923ed8e2856d31a89744a99baadae4a74a",
"src/internal/server/oidc/handoff_test.go": "sha256:23268b7f9f103f8a8798a168004580bc8fdb3a175161220dac26480a89f28e02",
"src/internal/server/oidc/human_tenant_test.go": "sha256:459664f90d76224958b3dd807d95d7bfcf0fce5f113498bd9886a41be19903a5",
"src/internal/server/oidc/discovery.go": "sha256:00e4c831a2b4d29aecf3597a09c943315b87c5834dbcd4912ba5d8d31ab28cd5",
"src/internal/server/oidc/jwks.go": "sha256:426d6596979ed6d097d6aaae66b6475d1c97687888c6ab1050c05cb14653ec60",
"src/internal/server/oidc/login_session.go": "sha256:efb0fcd066d81d753b7043c9c3d9e74e35cd510ca4304d21a51ba29dec833e1f",
"src/internal/server/oidc/hardening_test.go": "sha256:b41d6a987d5507d5160d970d0f2110ce681851fca1f4f7a3a9573163e8cd6f3e",
"src/internal/server/oidc/jwks_test.go": "sha256:9100e83e58ce2cc2f6d2f47804ee0ce035ca424b209597a12c4ef1a8896163aa",
"src/internal/server/oidc/account_test.go": "sha256:db1f7b8a2989b1d0deffee205ec9f4f35c7472135dcb8a10b5498893d881d763",
"src/internal/server/oidc/token_status_discovery_test.go": "sha256:27adb5797afba46ca2b1f16018721f055f82760498088b58b993730be1fa2031",
"src/internal/server/oidc/principal_type_guard_test.go": "sha256:b3fa097691f07594616e09e8775512d586a019ccdf8302bf594c1ac94f5f3f40",
"src/internal/server/oidc/token_status_http.go": "sha256:a7202d3176454f8ab42c090426e2debcb5cc2c1fc089e79746bb78e5c455af97",
"src/internal/server/oidc/native_optional_test.go": "sha256:329907bf7089d8f3ea05baad919d05677b8c3aaa201e6f233795ae9f69c003b5",
"src/internal/server/oidc/authorize_test.go": "sha256:61042d55e20a11fb6e1e9e6c054e50c0f629b702ed6d8da281d52529e5c29b0d",
"src/internal/server/oidc/session.go": "sha256:48473ebe123285ed21e7738834d9d9168dfc855d911cb9e7ce7e87b069a23a3c",
"src/internal/server/oidc/logout.go": "sha256:d4de80f8056e7a65c7a41f244dd38e447a4e04df729760a8a59cb6bb4e063893",
"src/internal/server/oidc/optional_mfa_test.go": "sha256:d758848d845c181cf1b18710e6ca24977f6b7576320dcad1f7293a5433c3b529",
"src/internal/server/oidc/token.go": "sha256:304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f",
"src/internal/server/oidc/token_status.go": "sha256:d9c22a71bb2efad7ea240f2689a98f14768ce3c1519d1d8b4e78ee7af2dc70b3",
"src/internal/server/oidc/userinfo_test.go": "sha256:e88c42c7cdf50d5953c9f594f0b99b4f87f5e223ba1cb44b70d696972016da80",
"src/internal/server/oidc/signin_diagnosis_test.go": "sha256:97e80be67c480dfe8eef0683f25868a91a0fdc96472b5296b734b56274270b3e",
"src/internal/server/oidc/policy_isolation_test.go": "sha256:35cb9870f5942c61c0e8c6ca875e9080bbd71c46c7b20fb18da01be88e0fa077",
"src/internal/server/oidc/token_test.go": "sha256:5721617c6e5eb79a3b5e86efa63e9278a8d81a0d7c01bf7b184255c102848e33",
"src/internal/server/oidc/audience_test.go": "sha256:1b751e1aafb5dbdcb751b42b001e32ddfaccfa3d5ba9d5af67b44148d9802ae3",
"src/internal/server/oidc/token_status_transport_test.go": "sha256:3a9945f0292125e86d0014b5245a20c0475d6c518eccff0f9b936356d8e0b347",
"src/internal/server/oidc/tenant_test.go": "sha256:8a3879789589f0c3d6e473ba56dd7d7c86ec6fbdddaafa4b13354f82d5f88713",
"src/internal/server/oidc/assurance_time_test.go": "sha256:911f65e8cea87ef867d6bf98199ed55abd29427b9f8d0838977d228e6186b08a",
"src/internal/server/oidc/signin_diagnosis.go": "sha256:b7976a1cfe1d8d1dca890cf0762cc82d08978a2e7537e45988b147ed0222042f",
"src/internal/server/oidc/authorize.go": "sha256:04f9bfb6b146ecf869161b7a0eb00b6486f424710c2c867e4f4b70af5f44ff5e",
"src/internal/server/oidc/handoff.go": "sha256:a2ffede12c9140a6c7d14632122a39ffbd0e707cd357dcc11d3ad885dc2a7fa4",
"src/internal/server/oidc/token_status_internal_test.go": "sha256:3d3ab41987c7586bd53af8b5c36ff6667b5b2a32d59789915eb9a36b7ceeba53",
"src/internal/server/oidc/token_status_test.go": "sha256:e2613d44deefcbfbd0ce1db5ce2588849fb4be9b8bed33cb538171cc29f63e90",
"src/internal/migration/lldapexport/exporter.go": "sha256:cb041827c9c0edf9da1338e966e7722e3765065582933b8c17b0d9008d44d103",
"src/internal/migration/lldapexport/exporter_test.go": "sha256:6f5cf0e43ea8aea9ceeb6d4d3ae0bfd9f3a5d18636dd49bea0c574c5253a44c2",
"src/internal/migration/tokeycloak/transformer_test.go": "sha256:5c3f5145a37079262db805f5bedaabe1d42edf2ccec1788c09c02f5b8336639b",
"src/internal/migration/tokeycloak/transformer.go": "sha256:856e26fd2075f020ef02373b5b7e9e5a1e98473b5caca95b60b7983287fc10aa",
"src/internal/migration/toldap/generator.go": "sha256:e59ba05203145f52818b3f4e34ab62126481faa9dde697f643fc1cda0638bb46",
"src/internal/migration/toldap/generator_test.go": "sha256:73b0969ce2e2cb018aa5734714c86152d1695f0099a5caf7dd96b773717fc2cc",
"src/internal/adapters/tenantengine/adapter.go": "sha256:a54ef12fafd2efc24c547d562e0a558a7ecef586394e79f7de345dc8cbbbbef3",
"src/internal/adapters/tenantengine/adapter_test.go": "sha256:b9c2db04bc187e6e6e56d4a42d4e657b9e56fef06843500fb4640477dee553e0",
"src/internal/adapters/lldap/adapter.go": "sha256:e7df7a159e9dd33a97932fd8cbde49a61091b8722796bf54ae4be887b4a3f5ef",
"src/internal/adapters/lldap/railiance_admin_test.go": "sha256:63db481bb6e592e91bf4a555e75065fbd3bd8cd408a7967a4b7786e12bef5d5f",
"src/internal/adapters/lldap/adapter_test.go": "sha256:650394ccc25d54c73c03746977a1880b1ed905634da8382da0ecf35843454081",
"src/internal/adapters/lldap/config_internal_test.go": "sha256:cbc71ce9a795b73c40b2714153eb2ee4d2e4007f7f56a7882eb862c23868cb68",
"src/internal/adapters/lldap/config.go": "sha256:33059a27418b00e1987b7ced0b21dd39c1765338bf575fe2333ce3e606e5546b",
"src/internal/adapters/privacyidea/credential_file_test.go": "sha256:9db15562c6fdf01e0806bbc478915413b17adb63c7e9ba213acfc4f9b2b2f0b6",
"src/internal/adapters/privacyidea/read_scope_test.go": "sha256:4685e1b84dad044db12708ecd231d4f91ec22ac45901ed2b5177e3be17d8cdc3",
"src/internal/adapters/privacyidea/adapter.go": "sha256:2559c68b5aa22bc5f61961e47d696b376f8bede533be03715ffc80c0d89ed904",
"src/internal/adapters/privacyidea/adapter_test.go": "sha256:6257489b9c51270da2c96f165c0efd1750b273218df51dca907d29c64c98b349",
"src/internal/adapters/privacyidea/optional_mfa_test.go": "sha256:e2e415c671d1d0e7f30a0ce22f5ec58914d8e366b12e445e13b1d645a9ca6a65",
"src/internal/adapters/privacyidea/enrollment_state_test.go": "sha256:dcf24c486629fa4835cddaa786af31ec78c05907694d4d93bce0f32876806377",
"src/internal/adapters/privacyidea/config.go": "sha256:8dd769b18c04c562714794484630acdbda81b34ab1cfaf9cc7445513207e5021",
"src/internal/adapters/authelia/verify.go": "sha256:189f9bc18b8d4319d2d88ee76a76e3b86468ebafde85ef460a4bafb7b350b4c4",
"src/internal/adapters/authelia/adapter.go": "sha256:2887bd546f43a16338547dab5ce4a90850c670ed6f6c0d610a5d08c72c7f3f45",
"src/internal/adapters/authelia/adapter_test.go": "sha256:d450b37e2b2405c189d444e50f0612e64177d6698cb4665a06ad967ae2224f93",
"src/internal/adapters/authelia/issuer_probe.go": "sha256:5846e2b514077a572611b898f449c04b096b086aeb724da055b2416994a7a7bb",
"src/internal/adapters/authelia/issuer_probe_test.go": "sha256:a85e02399f8a5c81401dbf159e8054420bff56e08c8a98e8026cad8d31fcecec",
"src/internal/adapters/authelia/config.go": "sha256:1b85ea3ffd324bced1ba108492ec780af8ba2b015d22cd51b294afaf36f2ec55",
"src/internal/adapters/authelia/verify_test.go": "sha256:f1570604d5038e49c14791f4d1aa278ad76a7e90294e82813d2c21e6a80a5511",
"src/cmd/keycape-to-keycloak/main.go": "sha256:6141d3f86bbe3df2a814a0c9995e537d32bf7b19f5549e89d40974eb3270faf7",
"src/cmd/lldap-to-ldap/main.go": "sha256:adcc9481d009cad909bd55a71d95c17ce30e29ca63e88a7c9e8101b92d4b872e",
"src/cmd/keycape/clients_test.go": "sha256:6a3982c1b2bd9b15c387ba64d4c87b37304dc6f615ed2ab22eec888f620cb1da",
"src/cmd/keycape/checkconfig.go": "sha256:2c47c9aedf3457627fdec991703e3be50e5ef554e4104cf3741f8347687279f2",
"src/cmd/keycape/main.go": "sha256:553094de91cfafb021b7607eac1263fa155fedeb32c1e99e7c71e8d86e20ffd0",
"src/cmd/keycape/checkconfig_test.go": "sha256:06955c996774109b26e4751ab20787f12718a1de96e3483a775fb19688d90e66",
"src/cmd/validator/main.go": "sha256:e53b0a7860537dfb1cec69a00cdafe7073b35802421b5977f2fece4de01b9c1f",
"src/cmd/lldap-export/main_test.go": "sha256:f105e490c717a414a5a3d1e65ceb4edcf74341ad7713b0accf8117a9b1ee02d2",
"src/cmd/lldap-export/main.go": "sha256:f019a6dbb0849d06d5c3729874293d520478ca2887929a0595b59a324097ae75"
},
"consumer_test_binary_sha256": "sha256:8052b5598371a595388c32c46ed9815349b3285f3ed864ec3dece75fafa8852e",
"real_credentials": false,
"native_admission": false
}

View file

@ -0,0 +1,12 @@
{"Time":"2026-10-18T10:02:31.733068893Z","Action":"start","Package":"keycape/internal/authclient"}
{"Time":"2026-10-18T10:02:31.754934693Z","Action":"run","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer"}
{"Time":"2026-10-18T10:02:31.754993666Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Output":"=== RUN TestOnlineActualForgejoCallbackCurrentIssuer\n"}
{"Time":"2026-10-18T10:02:32.082192499Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Output":" forgejo_current_fixture_test.go:136: actual consumer phase active failed: === RUN TestAccessEngineLiveFixtureCurrentIssuer\n"}
{"Time":"2026-10-18T10:02:32.08225947Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Output":" current_issuer_fixture_test.go:63: actual issuer/provider/current-token/decision callback did not pass\n"}
{"Time":"2026-10-18T10:02:32.082273619Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Output":" --- FAIL: TestAccessEngineLiveFixtureCurrentIssuer (0.03s)\n"}
{"Time":"2026-10-18T10:02:32.082285296Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Output":" FAIL\n"}
{"Time":"2026-10-18T10:02:32.084104641Z","Action":"output","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Output":"--- FAIL: TestOnlineActualForgejoCallbackCurrentIssuer (0.33s)\n"}
{"Time":"2026-10-18T10:02:32.084164245Z","Action":"fail","Package":"keycape/internal/authclient","Test":"TestOnlineActualForgejoCallbackCurrentIssuer","Elapsed":0.33}
{"Time":"2026-10-18T10:02:32.08447593Z","Action":"output","Package":"keycape/internal/authclient","Output":"FAIL\n"}
{"Time":"2026-10-18T10:02:32.089445611Z","Action":"output","Package":"keycape/internal/authclient","Output":"FAIL\tkeycape/internal/authclient\t0.356s\n"}
{"Time":"2026-10-18T10:02:32.08949324Z","Action":"fail","Package":"keycape/internal/authclient","Elapsed":0.356}

View file

@ -0,0 +1,6 @@
{
"owner": "key-cape",
"base_revision": "git:08c083bae9ff35fbaf028c36d72a95b57d2541b7",
"workspace": "/tmp/custodian-keycape-token-pair-r1/source",
"native_admission": false
}

View file

@ -0,0 +1,14 @@
{
"workplan_id": "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38",
"id": "61276a62-746d-42b6-9209-b267c9b6f02e",
"topic_id": null,
"task_id": "2cade51f-d25a-5719-96b4-b57d50c1b97d",
"decision_id": null,
"event_type": "note",
"summary": "Published KeyCape e2c5a947 exact online human ID/access-grant RS256 at_hash binding, legacy disabled profile unchanged; retains separate ID/resource audiences and no access-only fields in ID token. Full source race suite 726 passes including subtests, six existing native/external skips. Same compiled actual Forgejo provider/callback fails against old issuer and passes five fresh-process actual HTTPS PKCE/discovery/UserInfo/introspection/revocation scenarios against new issuer; mixed/revoked/suspended grants refuse. Synthetic MFA/PDP admission only. All six owner source postimages replay exact; primary/remote and all four task bindings verified. KEY42 independent review/native admission remain open; no native gate closed.",
"detail": null,
"author": "codex",
"session_id": null,
"created_at": "2026-10-18T10:27:23.554249Z",
"workstream_id": "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38"
}

View file

@ -0,0 +1,7 @@
{
"summary": "Published KeyCape e2c5a947 exact online human ID/access-grant RS256 at_hash binding, legacy disabled profile unchanged; retains separate ID/resource audiences and no access-only fields in ID token. Full source race suite 726 passes including subtests, six existing native/external skips. Same compiled actual Forgejo provider/callback fails against old issuer and passes five fresh-process actual HTTPS PKCE/discovery/UserInfo/introspection/revocation scenarios against new issuer; mixed/revoked/suspended grants refuse. Synthetic MFA/PDP admission only. All six owner source postimages replay exact; primary/remote and all four task bindings verified. KEY42 independent review/native admission remain open; no native gate closed.",
"event_type": "note",
"author": "codex",
"workplan_id": "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38",
"task_id": "2cade51f-d25a-5719-96b4-b57d50c1b97d"
}

View file

@ -0,0 +1,40 @@
{
"owner": "key-cape",
"base_revision": "git:08c083bae9ff35fbaf028c36d72a95b57d2541b7",
"files": [
{
"path": "docs/current-token-client.md",
"before_sha256": "sha256:69db63087c752bba0b07b41aa7784484703aacc064181782d15de62310751c4e",
"sha256": "sha256:68a67cd465185513ce1c5cc3110c7bbada2fc85f2e2df2e09f64049c40ee9966"
},
{
"path": "docs/online-token-status.md",
"before_sha256": "sha256:56064b1c0df5ff9a20d5c205396e7b1b2d0d70785ea7945c999d34b4de3aa1b8",
"sha256": "sha256:123e3de853782aeb78a0c7b385f884955fc804f4fb7183d40706b34193a43396"
},
{
"path": "src/internal/authclient/forgejo_current_fixture_test.go",
"before_sha256": null,
"sha256": "sha256:5456d007ad2a3288e871a8d1ebbc6d533ee374bd3c76d5df3829b893b814bf32"
},
{
"path": "src/internal/server/oidc/token.go",
"before_sha256": "sha256:304fa78d79a5436b9174d88991c19c0834356cb5425c6ed369accb85467a402f",
"sha256": "sha256:e6905bfa0474dde6122a83a6627d6b75a4c3a7730fb26106546e7a34eef8302b"
},
{
"path": "src/internal/server/oidc/token_pair_test.go",
"before_sha256": null,
"sha256": "sha256:efeb929becf61d62111286ead4d3635c1a59bfd34423280084ccfec2169a2b1d"
},
{
"path": "workplans/KEY-WP-0042-online-access-token-status.md",
"before_sha256": "sha256:e8b0d15d7461ea34075b4a7f8047af380d96e521f42a0dae6136f6cfdd052b0f",
"sha256": "sha256:ae722ff4fbd8e701c3280d31584ee207f51da7e3f719986e6ce2a052c00f93a3"
}
],
"patch_sha256": "sha256:b26ee48371770d8fc110b77b4e657f58f87cde16d5802b8679c1432c44ec22af",
"exact_replay_all_source_bytes": true,
"fuzz": 0,
"native_admission": false
}

View file

@ -0,0 +1,332 @@
--- a/docs/current-token-client.md
+++ b/docs/current-token-client.md
@@ -27,6 +27,13 @@
not establish Windows ACL behavior or a broader platform admission.
## Current status is an authenticated fact
+
+Online human PKCE ID tokens additionally carry the exact access-token `at_hash`
+binding described in `online-token-status.md`. This client verifies access-token
+facts; a consumer that also uses an ID token separately authenticates that token,
+checks its purpose, and joins its signed hash, identity, issuance, assurance and
+tenant provenance to the exact access grant. Neither token substitutes for the
+other. The online profile's hash is additive; disabled legacy issuance is unchanged.
`OnlineClient.Check(ctx, token, ExpectedIdentity{...})` validates the signature,
issuer, exact single resource audience, subject, tenant, human/service type,
--- a/docs/online-token-status.md
+++ b/docs/online-token-status.md
@@ -31,7 +31,14 @@
authentication methods, and connects the same store to issuance and UserInfo.
Human PKCE and service grants add a cryptographically random `jti` to each access
-token. ID-token claims and existing assurance/tenant provenance remain unchanged.
+token. Online human PKCE ID tokens add `at_hash`, binding the exact access token
+using the RS256 rule in [OIDC Core 3.1.3.6/8](https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken).
+The ID token keeps its relying-party audience and omits access-only `jti`/`scope`;
+the access token keeps its registered resource audience and omits `at_hash`.
+Existing assurance/tenant provenance and the disabled legacy ID-token contract
+remain unchanged. Consumers of this online human profile require the exact signed
+hash before treating an ID token and access token as one grant; equal identity
+facts or issuance seconds alone cannot distinguish separate grants.
Distinct grants in the same second have distinct bearer tokens: a new grant can
never remint a revoked token. A successful response is emitted only after the
token hash, expiry and current registration commitment have been recorded. No
--- /dev/null
+++ b/src/internal/authclient/forgejo_current_fixture_test.go
@@ -0,0 +1,156 @@
+//go:build forgejo_current_fixture
+
+package authclient
+
+import (
+ "bytes"
+ "context"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "keycape/internal/domain"
+ "keycape/internal/server/oidc"
+ "keycape/internal/server/telemetry"
+)
+
+type forgejoFixtureDirectory struct {
+ mu sync.RWMutex
+ user domain.User
+}
+
+func (d *forgejoFixtureDirectory) LookupUser(context.Context, string) (*domain.User, error) {
+ d.mu.RLock()
+ defer d.mu.RUnlock()
+ user := d.user
+ user.Groups = append([]string(nil), d.user.Groups...)
+ return &user, nil
+}
+func (d *forgejoFixtureDirectory) LookupGroups(context.Context, string) ([]domain.Group, error) {
+ return nil, nil
+}
+func (d *forgejoFixtureDirectory) ListUsers(ctx context.Context) ([]domain.User, error) {
+ user, _ := d.LookupUser(ctx, "")
+ return []domain.User{*user}, nil
+}
+func (d *forgejoFixtureDirectory) ValidatePassword(context.Context, string, string) (bool, error) {
+ return false, nil
+}
+
+func TestOnlineActualForgejoCallbackCurrentIssuer(t *testing.T) {
+ f := onlineProvider(t)
+ directory := &forgejoFixtureDirectory{user: domain.User{ID: "uid=operator,ou=people,dc=fixture", Username: "operator", Enabled: true, Tenant: "tenant:fixture", Groups: []string{"forge-admins"}}}
+ client := f.issuer.ClientConfig["current-client"]
+ client.GrantTypes = []string{"authorization_code"}
+ client.ServiceSubject = ""
+ client.Audience = client.ClientID
+ client.AllowedScopes = []string{"openid", "profile", "groups"}
+ client.RedirectURIs = []string{"https://forgejo.invalid/callback"}
+ mux := http.NewServeMux()
+ server := httptest.NewTLSServer(mux)
+ t.Cleanup(server.Close)
+ f.issuer.Users = directory
+ f.issuer.Issuer = server.URL
+ f.status.Users = directory
+ f.status.Issuer = server.URL
+ userinfo := &oidc.UserinfoHandler{Users: directory, SigningKey: &f.issuer.SigningKey.PublicKey, Issuer: server.URL, Tokens: f.issuer.Tokens, ClientConfig: f.issuer.ClientConfig, Emitter: telemetry.NoopEmitter{}}
+ var statusCalls, userinfoCalls atomic.Int64
+ mux.Handle("/token", f.issuer)
+ mux.HandleFunc("/introspect", func(w http.ResponseWriter, r *http.Request) { statusCalls.Add(1); f.status.Introspect(w, r) })
+ mux.HandleFunc("/revoke", f.status.Revoke)
+ mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { userinfoCalls.Add(1); userinfo.ServeHTTP(w, r) })
+ keys := oidc.NewKeySet()
+ keys.AddKey("key-1", &f.issuer.SigningKey.PublicKey)
+ mux.Handle("/jwks", oidc.NewJWKSHandler(keys))
+ mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ _ = json.NewEncoder(w).Encode(map[string]any{"issuer": server.URL, "authorization_endpoint": server.URL + "/authorize", "token_endpoint": server.URL + "/token", "userinfo_endpoint": server.URL + "/userinfo", "jwks_uri": server.URL + "/jwks", "id_token_signing_alg_values_supported": []string{"RS256"}})
+ })
+ admission := f.client.admission
+ admission.Issuer = server.URL
+ admission.Audience = client.ClientID
+ if os.WriteFile(admission.CAFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw}), 0644) != nil {
+ t.Fatal("fixture CA write failed")
+ }
+ var err error
+ f.client, err = NewOnlineClient(admission)
+ if err != nil {
+ t.Fatal("fixture admission failed")
+ }
+ der, err := x509.MarshalPKIXPublicKey(&f.issuer.SigningKey.PublicKey)
+ if err != nil {
+ t.Fatal("fixture public key failed")
+ }
+ publicKey := string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}))
+ issue := func() Tokens {
+ verifier := strings.Repeat("v", 43)
+ digest := sha256.Sum256([]byte(verifier))
+ code := f.issuer.Sessions.Create(&oidc.PKCESession{ClientID: client.ClientID, Username: directory.user.Username, RedirectURI: client.RedirectURIs[0], PKCEChallengeMethod: "S256", PKCEChallenge: base64.RawURLEncoding.EncodeToString(digest[:]), Scopes: client.AllowedScopes, MFAVerified: true, AuthTime: time.Now().Add(-time.Minute).Truncate(time.Second), ExpiresAt: time.Now().Add(time.Minute)})
+ form := url.Values{"grant_type": {"authorization_code"}, "client_id": {client.ClientID}, "code": {code}, "code_verifier": {verifier}, "redirect_uri": {client.RedirectURIs[0]}}
+ r, _ := http.NewRequest(http.MethodPost, server.URL+"/token", strings.NewReader(form.Encode()))
+ r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ r.SetBasicAuth(url.QueryEscape(client.ClientID), url.QueryEscape(client.ClientSecret))
+ response, err := server.Client().Do(r)
+ if err != nil {
+ t.Fatal("actual fixture token HTTP failed")
+ }
+ defer response.Body.Close()
+ var tokens Tokens
+ if response.StatusCode != 200 || json.NewDecoder(response.Body).Decode(&tokens) != nil || tokens.AccessToken == "" || tokens.IDToken == "" {
+ t.Fatal("actual fixture human PKCE failed")
+ }
+ return tokens
+ }
+ first, second := issue(), issue()
+ directoryPath := t.TempDir()
+ var phases []string
+ probe := func(tokens Tokens, phase string) {
+ input := map[string]any{"id_token": tokens.IDToken, "access_token": tokens.AccessToken, "public_key_pem": publicKey, "tenant": directory.user.Tenant, "phase": phase, "current": admission}
+ raw, _ := json.Marshal(input)
+ file := filepath.Join(directoryPath, "consumer-input.json")
+ if os.WriteFile(file, raw, 0600) != nil {
+ t.Fatal("private fixture input failed")
+ }
+ command := exec.Command(os.Getenv("FORGEJO_CURRENT_TEST_BINARY"), "-test.run=^TestAccessEngineLiveFixtureCurrentIssuer$", "-test.v")
+ command.Env = append(os.Environ(), "FORGEJO_CURRENT_ISSUER_FIXTURE="+file)
+ output, err := command.CombinedOutput()
+ for _, private := range []string{first.AccessToken, first.IDToken, second.AccessToken, second.IDToken, client.ClientSecret} {
+ if bytes.Contains(output, []byte(private)) {
+ t.Fatal("consumer fixture diagnostic disclosed private input")
+ }
+ }
+ if err != nil {
+ t.Fatalf("actual consumer phase %s failed: %s", phase, output)
+ }
+ phases = append(phases, phase)
+ }
+ probe(first, "active")
+ probe(Tokens{AccessToken: second.AccessToken, IDToken: first.IDToken}, "mixed-pair")
+ if _, err := f.client.Revoke(context.Background(), first.AccessToken); err != nil {
+ t.Fatal("actual fixture revoke/independent negative failed")
+ }
+ probe(first, "revoked-after-userinfo")
+ probe(second, "active")
+ directory.mu.Lock()
+ directory.user.Groups = []string{"netkingdom-suspended"}
+ directory.mu.Unlock()
+ probe(second, "suspended-after-userinfo")
+ report := map[string]any{"status": "passed", "consumer_processes": len(phases), "phases": phases, "actual_https_human_PKCE": true, "actual_discovery_and_userinfo": true, "actual_introspection_and_revocation": true, "status_calls": statusCalls.Load(), "userinfo_calls": userinfoCalls.Load(), "actual_forgejo_callback_and_generated_client": true, "synthetic_MFA_session_seed_and_PDP_admission": true, "native_acceptance": false, "private_inputs": "fixture memory/tmpfs only"}
+ raw, _ := json.MarshalIndent(report, "", " ")
+ if os.WriteFile(os.Getenv("FORGEJO_CURRENT_REPORT"), append(raw, '\n'), 0600) != nil {
+ t.Fatal("public fixture result failed")
+ }
+}
--- a/src/internal/server/oidc/token.go
+++ b/src/internal/server/oidc/token.go
@@ -224,10 +224,17 @@
// 7. Sign JWT with RSA-SHA256.
kid := "key-1" // static kid for v0.1
- jwtToken, err := buildJWT(claims, kid, h.SigningKey)
- if err != nil {
- http.Error(w, "failed to build JWT", http.StatusInternalServerError)
- return
+ identityClaims := make(map[string]interface{}, len(claims)+1)
+ for name, value := range claims {
+ identityClaims[name] = value
+ }
+ var jwtToken string
+ if h.Tokens == nil {
+ jwtToken, err = buildJWT(identityClaims, kid, h.SigningKey)
+ if err != nil {
+ http.Error(w, "failed to build JWT", http.StatusInternalServerError)
+ return
+ }
}
// Access tokens target the statically registered resource server. ID tokens
@@ -250,6 +257,16 @@
return
}
if h.Tokens != nil {
+ // The online profile binds the signed ID token to this exact access
+ // grant, including same-second distinct jtis. OIDC RS256 at_hash uses
+ // the base64url-encoded left half of SHA-256 over the access token.
+ digest := sha256.Sum256([]byte(accessToken))
+ identityClaims["at_hash"] = base64.RawURLEncoding.EncodeToString(digest[:16])
+ jwtToken, err = buildJWT(identityClaims, kid, h.SigningKey)
+ if err != nil {
+ http.Error(w, "failed to build JWT", http.StatusInternalServerError)
+ return
+ }
if err := h.Tokens.register(accessToken, client, time.Unix(exp.Unix(), 0), now); err != nil {
http.Error(w, "token status unavailable", http.StatusServiceUnavailable)
return
--- /dev/null
+++ b/src/internal/server/oidc/token_pair_test.go
@@ -0,0 +1,62 @@
+package oidc_test
+
+import (
+ "crypto/sha256"
+ "encoding/base64"
+ "net/http"
+ "testing"
+
+ "keycape/internal/jose"
+)
+
+func TestOnlineHumanIDTokenBindsExactAccessGrant(t *testing.T) {
+ f := newStatusFixture(t, 10)
+ f.token.ClientConfig["test-client"].Audience = "resource:forgejo"
+ first, firstID, code := f.issue(t)
+ second, secondID, secondCode := f.issue(t)
+ if code != http.StatusOK || secondCode != http.StatusOK || first == second {
+ t.Fatal("distinct actual grants were not issued")
+ }
+ keys := jose.KeySet{"key-1": &f.token.SigningKey.PublicKey}
+ for _, pair := range [][2]string{{first, firstID}, {second, secondID}} {
+ claims, err := jose.VerifyTyped(pair[1], keys, "JWT")
+ if err != nil {
+ t.Fatal("actual ID signature refused")
+ }
+ digest := sha256.Sum256([]byte(pair[0]))
+ if claims["at_hash"] != base64.RawURLEncoding.EncodeToString(digest[:16]) || claims["aud"] != "test-client" {
+ t.Fatal("ID token lost exact access-grant or relying-party binding")
+ }
+ if _, ok := claims["jti"]; ok {
+ t.Fatal("access-grant jti escaped into ID token")
+ }
+ if _, ok := claims["scope"]; ok {
+ t.Fatal("access scope escaped into ID token")
+ }
+ access, err := jose.VerifyTyped(pair[0], keys, "JWT")
+ if err != nil || access["aud"] != "resource:forgejo" {
+ t.Fatal("resource audience changed")
+ }
+ if _, ok := access["at_hash"]; ok {
+ t.Fatal("ID-only grant binding escaped into access token")
+ }
+ }
+ firstClaims, _ := jose.VerifyTyped(firstID, keys, "JWT")
+ secondClaims, _ := jose.VerifyTyped(secondID, keys, "JWT")
+ if firstClaims["at_hash"] == secondClaims["at_hash"] {
+ t.Fatal("distinct grants shared an ID-token access binding")
+ }
+}
+
+func TestDisabledOnlineProfileKeepsLegacyIDToken(t *testing.T) {
+ f := newStatusFixture(t, 10)
+ f.token.Tokens = nil
+ _, id, code := f.issue(t)
+ claims, err := jose.VerifyTyped(id, jose.KeySet{"key-1": &f.token.SigningKey.PublicKey}, "JWT")
+ if code != http.StatusOK || err != nil {
+ t.Fatal("legacy profile failed")
+ }
+ if _, ok := claims["at_hash"]; ok {
+ t.Fatal("disabled profile changed ID-token contract")
+ }
+}
--- a/workplans/KEY-WP-0042-online-access-token-status.md
+++ b/workplans/KEY-WP-0042-online-access-token-status.md
@@ -33,7 +33,8 @@
The reviewable contract is `docs/online-token-status.md`: opt-in RFC 7662/7009
access-token status; registered owning-client authentication; private hash-only
-bounded state; unique access-token jti; no ID-token change; singleton store-loss
+bounded state; unique access-token jti; online human ID-token exact `at_hash`
+binding with the disabled legacy ID-token contract unchanged; singleton store-loss
refusal; disabled legacy behavior explicit. Source preparation is authorized by
the full Custodian implementation goal. Independent issuer/profile/consumer
assent remains required before admission; no such assent is inferred here.
@@ -53,6 +54,20 @@
cross-client refusals, current registration and human disposition, revocation
during directory lookup, process-store loss and absence of secret output. Keep
the complete existing source suite, disabled profile and build passing.
+
+2026-10-18 T02 source continuation: the actual consumer join exposed missing
+same-grant correspondence. The online human ID token now signs the exact access
+token's RS256 `at_hash`, retaining its own audience and excluding access-only
+fields. Distinct same-second grants cannot share that binding; disabled issuance
+is unchanged. This qualifies the earlier unscoped "no ID-token change" statement
+for the proposed online profile, without changing existing deployment behavior.
+The complete source race suite passes 726 test/subtest results with the same six
+native/external skips. The actual HTTPS PKCE/discovery/UserInfo/status/revocation
+handlers and real Forgejo provider/callback pass five separate consumer processes:
+two current grants accepted, mixed grants/revoked original/suspended human refused.
+The original issuer fails the same current consumer. MFA session seeds and PDP
+admissions are synthetic. Source-only evidence: Custodian forgejo-current-token-r1.
+Independent issuer/profile/consumer review and T03 native admission remain open.
## Admit and accept the issuer lifecycle at native scope

View file

@ -0,0 +1,137 @@
# Shared current-token client and private CLI observations
Source preparation under proposed KEY-WP-0042 and the existing CUST-WP-0080 /
NK48 / Apps operation and application obligations. This is a client of the
issuer's optional online profile, not another issuer, PDP, approval service or
application-local token registry. Native issuer/profile/consumer admission is
still required. Actual operation/effect permission remains with existing owners.
## One producer, two consumption surfaces
`src/internal/authclient/online.go` is the single implementation. The existing
KeyCape executable exposes it as `check-current-token`, `revoke-token` and
`check-inactive-token` for owner processes. Go consumers use the generated
`adapters/go/tokenstatus` module and import `keycape/pkg/tokenstatus` through an
exact pinned source/dependency lock. `make tokenstatus-export` generates that
five-file module; `make tokenstatus-check`, ordinary `make test` and the root Go
suite refuse drift. The sole client transformation is its first package
declaration; all remaining bytes and the shared JOSE primitive are exact.
No policy evaluator, signing key, credential, daemon or deployment is exported.
The client uses the existing producer `internal/jose` verifier. Its additive
`VerifyTyped` path requires the actual RS256 / JWT producer header; legacy
`Verify` callers retain their existing type policy. Keys are independently
admitted public JWKS, never fetched from the status response or discovered by
the token. The minimal generated module uses Go 1.23 and standard libraries.
Its file boundary is the current **Linux owner profile**: POSIX mode checks do
not establish Windows ACL behavior or a broader platform admission.
## Current status is an authenticated fact
Online human PKCE ID tokens additionally carry the exact access-token `at_hash`
binding described in `online-token-status.md`. This client verifies access-token
facts; a consumer that also uses an ID token separately authenticates that token,
checks its purpose, and joins its signed hash, identity, issuance, assurance and
tenant provenance to the exact access grant. Neither token substitutes for the
other. The online profile's hash is additive; disabled legacy issuance is unchanged.
`OnlineClient.Check(ctx, token, ExpectedIdentity{...})` validates the signature,
issuer, exact single resource audience, subject, tenant, human/service type,
optional authorized-party binding, access-token purpose, unique jti and integer
issued/expiry/not-before bounds before reading the private caller credential.
It bounds identity by expiry and an independently admitted token age (at most
one hour), then performs a new authenticated introspection. It joins every
returned identity/client/audience/jti/scope/time field to those signed facts.
After network I/O it checks time again and reloads JWT trust so a delayed answer
cannot retain expired facts or a withdrawn signing key. No positive is cached.
The explicit HTTPS issuer origin supplies the exact `/introspect` and `/revoke`
paths; redirects, query/fragment/ambiguous issuer paths, environment proxies and
ambient transports are not used. CA, JWKS and private caller credential files
are reread for each operation with regular-file/mode/size guards. File opens do
not block on FIFO replacement. Private credentials can be atomically rotated
without retaining a cached value. Requests have a five-second bound and responses
a 64 KiB bound. Duplicate/null/missing/extra fields, mismatched facts, non-JSON or
cacheable status responses and unavailable/unauthenticated transport refuse with
one fixed error. Secret, token and response values are never part of errors.
`CurrentToken.Claims()` returns a separate copy of verified signed facts. Those
facts remain snapshots: original authentication time is not the current lookup
time. Minimum assurance/authentication age, tenant provenance, current groups,
resource/action policy, exact approval and effect authority remain the existing
consumer profile's obligations. A fresh status response is not a new MFA act or
permission. A Forgejo sign-in token does not grant release-write or inverse
authority, and no operator client/route is selected by this source package.
## Negative scope and cleanup
`ObserveInactive` performs an independent **read-only** status lookup. It never
infers inactivity from transport failure, malformed output or an unsuccessful
positive check. `Revoke` sends the exact token, requires an empty successful
response and then obtains that separate negative readback. Both return only
`InactiveForClient`: the exact digest is currently inactive for the configured
client. Unknown and other-client tokens have the same negative response.
That result alone cannot prove original grant ownership, global revocation or a
completed operation's cleanup. The owner retains its independently authenticated
original issuance/current-positive evidence, exact token digest/jti and operation
correspondence before mapping a negative observation to a parent-session receipt.
Cleanup can continue after business-token freshness/key withdrawal; it creates
no business authority. Unavailable/ambiguous cleanup remains unverified and
requires the existing durable owner retry/recovery path. It is not success.
## Existing CLI
The public admission file contains only references and independently admitted
bindings; for example (paths/client are placeholders, not operational defaults):
```json
{
"issuer": "https://owner-admitted-issuer.example",
"audience": "owner-admitted-resource",
"client_id": "owner-admitted-confidential-client",
"ca_file": "/run/owner-identity/issuer-ca.pem",
"jwks_file": "/run/owner-identity/admitted-jwks.json",
"secret_file": "/run/owner-identity/current-client-credential",
"max_token_age": "5m"
}
```
Use an existing owner-acquired private KeyCape token JSON file, and the access
token's exact `sha256:` digest from the owner-bound session/transaction record.
No token or client credential goes in argv. Follow the existing Warden routing /
owner-native acquisition and custody path; this source adds no credential lane.
```text
keycape check-current-token --admission-file /ABS/profile.json --token-file /ABS/private-token.json --token-sha256 sha256:OWNER_BOUND_DIGEST --subject OWNER_EXPECTED_SUBJECT --tenant OWNER_EXPECTED_TENANT --principal-type human --out /ABS/PRIVATE-DIR/new-observation.json
keycape revoke-token --admission-file /ABS/profile.json --token-file /ABS/private-token.json --token-sha256 sha256:OWNER_BOUND_DIGEST --out /ABS/PRIVATE-DIR/new-cleanup-observation.json
keycape check-inactive-token --admission-file /ABS/profile.json --token-file /ABS/private-token.json --token-sha256 sha256:OWNER_BOUND_DIGEST --out /ABS/PRIVATE-DIR/new-independent-negative.json
```
The placeholders deliberately do not form an operational command. Current checks
require explicit expected subject/tenant/type. Negative/cleanup commands refuse
those business-identity flags because they report only the bounded client status.
Duplicate flags and duplicate/unknown/null input fields refuse. The token file
must be private, the digest must match the exact parsed access token, and the new
observation must have a 0700 parent outside Git. Outputs are exclusive 0600 files;
failures remove only their own inode and cannot delete another attempt's replaced
file. Standard output/errors expose fixed status, not claims or credential values.
`keycape.current-token-observation.v1` is a **private, unsigned producer command
result**, not a portable execution receipt. Current output includes bounded
identity/token references and signed assurance/tenant-provenance facts; it omits
bearers, credentials and group/role snapshots. Negative output states its client
scope. The invoking owner authenticates exact binary/config/custody and binds its
own case, acceptance, descriptor, source/target, principal, transaction and effect.
Local file presence, digests or a successful command do not supply those grants.
## Scope of verification
Source checks use actual token/status handlers over isolated HTTPS, actual human
PKCE claim construction and the same CLI implementation. The separate cross-owner
fixture runs a built executable, generated public Go consumer and actual Warden
Executor/SQLite in distinct Python processes. Synthetic case/catalog/owner
attestations and a disposable file target do not establish Forgejo/browser
acceptance, real-factor possession, production custody or approved native effects.
The online issuer profile, owner mappings and source/package/type admission remain
under review; all corresponding native gates stay open.

View file

@ -0,0 +1,131 @@
# Optional online access-token status
Source proposal under KEY-WP-0042, driven by CUST-WP-0080 and existing
NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09. Independent issuer/consumer
contract review and native admission remain required. Existing deployments are
unchanged. This extends the bounded issuer; it does not introduce a broker,
approval store, PDP, or a second application-local token registry.
The legacy profile authenticates self-contained JWTs until expiry. That does
not establish that an operation's initiating token is still unrevoked. The
optional profile adds issuer-owned current access-token status using
[RFC 7662](https://www.rfc-editor.org/rfc/rfc7662.html) introspection and
[RFC 7009](https://www.rfc-editor.org/rfc/rfc7009.html) revocation. It supports
access tokens only, without refresh tokens or token exchange.
## Enablement and lifecycle
Owner-admitted configuration opts in explicitly:
```yaml
tokenStatus:
enabled: true
maxEntries: 10000
```
The issuer must use HTTPS at its existing trusted ingress. The server's internal
HTTP listener and reverse-proxy topology are unchanged; direct HTTP exposure is
not admitted by an HTTPS issuer string. Omitted configuration keeps the existing
expiry-only token/UserInfo contract and advertises no new endpoints. Enabling
status registers `/introspect` and `/revoke`, advertises their implemented client
authentication methods, and connects the same store to issuance and UserInfo.
Human PKCE and service grants add a cryptographically random `jti` to each access
token. Online human PKCE ID tokens add `at_hash`, binding the exact access token
using the RS256 rule in [OIDC Core 3.1.3.6/8](https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken).
The ID token keeps its relying-party audience and omits access-only `jti`/`scope`;
the access token keeps its registered resource audience and omits `at_hash`.
Existing assurance/tenant provenance and the disabled legacy ID-token contract
remain unchanged. Consumers of this online human profile require the exact signed
hash before treating an ID token and access token as one grant; equal identity
facts or issuance seconds alone cannot distinguish separate grants.
Distinct grants in the same second have distinct bearer tokens: a new grant can
never remint a revoked token. A successful response is emitted only after the
token hash, expiry and current registration commitment have been recorded. No
bearer, client secret or identity/group snapshot is retained in the status store.
The store is bounded to the configured count (default 10000, maximum 1000000).
Live records and revocation tombstones are never evicted to admit another grant;
capacity exhaustion refuses issuance with a fixed 503. Expired entries are
collected when admitting a new token. Capacity and per-client ingress rate limits
need workload-representative admission: this is not a throughput/availability
claim or protection against an authorized client filling the bound.
This profile uses KeyCape's existing **single-instance topology**. Status is
process-local. Restart, deployment, rollback or store loss makes every earlier
token inactive at online endpoints, even if its JWT signature/expiry still
validates offline. Consumers must reauthenticate; there is no recovery replay
or token-state import that can resurrect revoked authority. Multiple replicas
are unsupported. Native admission must include this interruption and the
measured reauthentication/recovery procedure. A persisted/shared store is not
introduced here.
## Introspection and revocation
Both endpoints accept POST form bodies only, bound to 64 KiB; token values are
bound to 32 KiB. Query parameters, duplicate form or authentication fields,
unsupported content types and malformed authentication refuse without echoing
input. Responses use `Cache-Control: no-store` and `Pragma: no-cache`.
Introspection requires the existing registered confidential client's
`client_secret_basic` authentication. Only that client's own issued tokens can
be inspected. A separate resource audience is returned as `aud`, not confused
with the issuing `client_id`. Cross-client resource-server delegation needs a
separately reviewed authorization contract and is not implicitly granted by a
matching audience string. Public clients cannot enumerate token status.
An active response means the exact access token was issued in this process,
has not expired or been revoked, still matches its current complete registration,
and passes current issuer/signature/purpose validation. For humans, directory
lookup must succeed and the current account must be enabled and not suspended.
Status is checked again after directory lookup. Service subjects remain bound
to their current registration. Active output contains only standard access-token
metadata; inactive output contains only `active: false`. Unknown tokens, ID
tokens, altered tokens, another client's tokens and expired/revoked tokens all
return that same inactive response to an authenticated introspector.
Revocation authenticates confidential clients using the same method. A registered
public client may revoke its own token using `client_id` plus possession of that
bearer; public-client introspection remains unavailable. Revocation only affects
the exact token issued to that client. Unknown, other-client, expired and already
revoked tokens all produce the same empty 200. No token existence is disclosed.
Token-type hints do not bypass the actual access-token lookup. Removal or changes
to a registration invalidate online status; the commitment explicitly includes
the secret digest and internal token lifetime omitted from public Client JSON.
When enabled, actual UserInfo consults the same store before lookup and again
after it. Revocation therefore affects that existing endpoint immediately on its
next check. Ordinary login/logout and upstream Authelia sessions are distinct:
neither browser logout nor deleting a local cookie is represented as global
JWT revocation.
## Consumer contract and remaining acceptance
Online status establishes current access-token status, not permission
for an application resource or privileged effect. Consumers still independently
validate the actual signed token, admitted issuer/key/audience/type, subject,
tenant, principal type, assurance and freshness. The authenticated introspection
response must refer to that exact token/client. Obtain a new current response
before each protected operation; unavailable, malformed, inactive or ambiguous
responses refuse. A cached positive is not a current-session check.
Access Engine retains resource/action decisions, Approval Engine exact one-shot
approval, Warden admission and owner adapters execution. Bind their existing
descriptor, case, transaction and exact effect before dispatch. A separate new
operation/inverse token must be acquired and revoked on every terminal path;
renewed authentication cannot revive a consumed approval or replay a mutation.
This issuer capability alone implements none of those consumer bindings.
The producer-owned [current-token client](current-token-client.md) supplies the
shared Go/CLI transport and cryptographic/status join. Its private observations
retain this distinction: an inactive-for-client result is not an original
ownership or execution receipt. Native client/profile adoption remains open.
Native completion still requires independent issuer/profile review, exact
image/config/trust admission, bounded retained-token custody, real human/service
issuance, private transport, current-token/revocation integration in each owner,
all exit-path cleanup, restart/expiry/unavailable/refusal checks, ongoing
HTTP/API/Git/SSH authorization and audit evidence. Offline consumers which never
consult the endpoint retain their existing JWT validity; no estate-wide immediate
revocation claim follows from source implementation. Disabling the feature is
not an acceptable fallback for a consumer admitted under the online profile.

View file

@ -0,0 +1,156 @@
//go:build forgejo_current_fixture
package authclient
import (
"bytes"
"context"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"keycape/internal/domain"
"keycape/internal/server/oidc"
"keycape/internal/server/telemetry"
)
type forgejoFixtureDirectory struct {
mu sync.RWMutex
user domain.User
}
func (d *forgejoFixtureDirectory) LookupUser(context.Context, string) (*domain.User, error) {
d.mu.RLock()
defer d.mu.RUnlock()
user := d.user
user.Groups = append([]string(nil), d.user.Groups...)
return &user, nil
}
func (d *forgejoFixtureDirectory) LookupGroups(context.Context, string) ([]domain.Group, error) {
return nil, nil
}
func (d *forgejoFixtureDirectory) ListUsers(ctx context.Context) ([]domain.User, error) {
user, _ := d.LookupUser(ctx, "")
return []domain.User{*user}, nil
}
func (d *forgejoFixtureDirectory) ValidatePassword(context.Context, string, string) (bool, error) {
return false, nil
}
func TestOnlineActualForgejoCallbackCurrentIssuer(t *testing.T) {
f := onlineProvider(t)
directory := &forgejoFixtureDirectory{user: domain.User{ID: "uid=operator,ou=people,dc=fixture", Username: "operator", Enabled: true, Tenant: "tenant:fixture", Groups: []string{"forge-admins"}}}
client := f.issuer.ClientConfig["current-client"]
client.GrantTypes = []string{"authorization_code"}
client.ServiceSubject = ""
client.Audience = client.ClientID
client.AllowedScopes = []string{"openid", "profile", "groups"}
client.RedirectURIs = []string{"https://forgejo.invalid/callback"}
mux := http.NewServeMux()
server := httptest.NewTLSServer(mux)
t.Cleanup(server.Close)
f.issuer.Users = directory
f.issuer.Issuer = server.URL
f.status.Users = directory
f.status.Issuer = server.URL
userinfo := &oidc.UserinfoHandler{Users: directory, SigningKey: &f.issuer.SigningKey.PublicKey, Issuer: server.URL, Tokens: f.issuer.Tokens, ClientConfig: f.issuer.ClientConfig, Emitter: telemetry.NoopEmitter{}}
var statusCalls, userinfoCalls atomic.Int64
mux.Handle("/token", f.issuer)
mux.HandleFunc("/introspect", func(w http.ResponseWriter, r *http.Request) { statusCalls.Add(1); f.status.Introspect(w, r) })
mux.HandleFunc("/revoke", f.status.Revoke)
mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { userinfoCalls.Add(1); userinfo.ServeHTTP(w, r) })
keys := oidc.NewKeySet()
keys.AddKey("key-1", &f.issuer.SigningKey.PublicKey)
mux.Handle("/jwks", oidc.NewJWKSHandler(keys))
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"issuer": server.URL, "authorization_endpoint": server.URL + "/authorize", "token_endpoint": server.URL + "/token", "userinfo_endpoint": server.URL + "/userinfo", "jwks_uri": server.URL + "/jwks", "id_token_signing_alg_values_supported": []string{"RS256"}})
})
admission := f.client.admission
admission.Issuer = server.URL
admission.Audience = client.ClientID
if os.WriteFile(admission.CAFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw}), 0644) != nil {
t.Fatal("fixture CA write failed")
}
var err error
f.client, err = NewOnlineClient(admission)
if err != nil {
t.Fatal("fixture admission failed")
}
der, err := x509.MarshalPKIXPublicKey(&f.issuer.SigningKey.PublicKey)
if err != nil {
t.Fatal("fixture public key failed")
}
publicKey := string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}))
issue := func() Tokens {
verifier := strings.Repeat("v", 43)
digest := sha256.Sum256([]byte(verifier))
code := f.issuer.Sessions.Create(&oidc.PKCESession{ClientID: client.ClientID, Username: directory.user.Username, RedirectURI: client.RedirectURIs[0], PKCEChallengeMethod: "S256", PKCEChallenge: base64.RawURLEncoding.EncodeToString(digest[:]), Scopes: client.AllowedScopes, MFAVerified: true, AuthTime: time.Now().Add(-time.Minute).Truncate(time.Second), ExpiresAt: time.Now().Add(time.Minute)})
form := url.Values{"grant_type": {"authorization_code"}, "client_id": {client.ClientID}, "code": {code}, "code_verifier": {verifier}, "redirect_uri": {client.RedirectURIs[0]}}
r, _ := http.NewRequest(http.MethodPost, server.URL+"/token", strings.NewReader(form.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
r.SetBasicAuth(url.QueryEscape(client.ClientID), url.QueryEscape(client.ClientSecret))
response, err := server.Client().Do(r)
if err != nil {
t.Fatal("actual fixture token HTTP failed")
}
defer response.Body.Close()
var tokens Tokens
if response.StatusCode != 200 || json.NewDecoder(response.Body).Decode(&tokens) != nil || tokens.AccessToken == "" || tokens.IDToken == "" {
t.Fatal("actual fixture human PKCE failed")
}
return tokens
}
first, second := issue(), issue()
directoryPath := t.TempDir()
var phases []string
probe := func(tokens Tokens, phase string) {
input := map[string]any{"id_token": tokens.IDToken, "access_token": tokens.AccessToken, "public_key_pem": publicKey, "tenant": directory.user.Tenant, "phase": phase, "current": admission}
raw, _ := json.Marshal(input)
file := filepath.Join(directoryPath, "consumer-input.json")
if os.WriteFile(file, raw, 0600) != nil {
t.Fatal("private fixture input failed")
}
command := exec.Command(os.Getenv("FORGEJO_CURRENT_TEST_BINARY"), "-test.run=^TestAccessEngineLiveFixtureCurrentIssuer$", "-test.v")
command.Env = append(os.Environ(), "FORGEJO_CURRENT_ISSUER_FIXTURE="+file)
output, err := command.CombinedOutput()
for _, private := range []string{first.AccessToken, first.IDToken, second.AccessToken, second.IDToken, client.ClientSecret} {
if bytes.Contains(output, []byte(private)) {
t.Fatal("consumer fixture diagnostic disclosed private input")
}
}
if err != nil {
t.Fatalf("actual consumer phase %s failed: %s", phase, output)
}
phases = append(phases, phase)
}
probe(first, "active")
probe(Tokens{AccessToken: second.AccessToken, IDToken: first.IDToken}, "mixed-pair")
if _, err := f.client.Revoke(context.Background(), first.AccessToken); err != nil {
t.Fatal("actual fixture revoke/independent negative failed")
}
probe(first, "revoked-after-userinfo")
probe(second, "active")
directory.mu.Lock()
directory.user.Groups = []string{"netkingdom-suspended"}
directory.mu.Unlock()
probe(second, "suspended-after-userinfo")
report := map[string]any{"status": "passed", "consumer_processes": len(phases), "phases": phases, "actual_https_human_PKCE": true, "actual_discovery_and_userinfo": true, "actual_introspection_and_revocation": true, "status_calls": statusCalls.Load(), "userinfo_calls": userinfoCalls.Load(), "actual_forgejo_callback_and_generated_client": true, "synthetic_MFA_session_seed_and_PDP_admission": true, "native_acceptance": false, "private_inputs": "fixture memory/tmpfs only"}
raw, _ := json.MarshalIndent(report, "", " ")
if os.WriteFile(os.Getenv("FORGEJO_CURRENT_REPORT"), append(raw, '\n'), 0600) != nil {
t.Fatal("public fixture result failed")
}
}

View file

@ -0,0 +1,642 @@
package oidc
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"keycape/internal/adapters/tenantengine"
"keycape/internal/domain"
profileerrors "keycape/internal/errors"
"keycape/internal/server/telemetry"
)
// TokenHandler implements POST /token.
type TokenHandler struct {
ClientConfig map[string]*domain.Client
Sessions *SessionStore
Users domain.UserRepository
SigningKey *rsa.PrivateKey
Issuer string
TokenLifetime time.Duration
Emitter telemetry.Emitter
// TenantEngine sources the optional tenant_roles claim (KEY-WP-0005-T02).
// Nil disables it entirely -- token issuance never depends on it.
TenantEngine *tenantengine.Client
// Tokens enables issuer-owned online token status; nil retains the legacy
// expiry-only profile. Enabling it requires explicit deployment admission.
Tokens *TokenStatusStore
}
// tokenResponse is the JSON body returned on a successful token exchange.
type tokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
IDToken string `json:"id_token,omitempty"`
}
// ServeHTTP handles POST /token.
func (h *TokenHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := r.ParseForm(); err != nil {
http.Error(w, "invalid form body", http.StatusBadRequest)
return
}
grantType := r.FormValue("grant_type")
if grantType == "client_credentials" {
h.serveClientCredentials(w, r)
return
}
clientID := r.FormValue("client_id")
code := r.FormValue("code")
codeVerifier := r.FormValue("code_verifier")
// 1. Validate grant_type.
if grantType != "authorization_code" {
profileerrors.FeatureNotSupported(
"only grant_type=authorization_code is supported",
"grant_type="+grantType,
).Write(w, http.StatusBadRequest)
return
}
// 2. Validate client exists and may use this grant.
client, ok := h.ClientConfig[clientID]
if !ok {
profileerrors.InvalidProfileUsage("unknown client_id", "client_id").
Write(w, http.StatusBadRequest)
return
}
// Grant-type eligibility, enforced equivalently to the service path
// (KEY-WP-0016-T02). An empty grantTypes is an implicit authorization-code
// client, matching config validation; a client_credentials-only client must
// not reach the browser path.
if len(client.GrantTypes) > 0 && !containsString(client.GrantTypes, "authorization_code") {
profileerrors.InvalidProfileUsage(
"client is not registered for grant_type=authorization_code",
"grant_type",
).Write(w, http.StatusBadRequest)
return
}
// Confidential authorization-code clients authenticate with their secret,
// using the same credential sources as the service grant. A public client
// must not be able to present a secret and be treated as authenticated.
if client.ClientType == "confidential" {
presentedID, secret, ok := basicClientCredentials(r)
if !ok || presentedID != clientID || client.ClientSecret == "" ||
!secretsEqual(secret, client.ClientSecret) {
profileerrors.InvalidProfileUsage(
"client authentication failed",
"Authorization",
).Write(w, http.StatusUnauthorized)
return
}
}
// 3. Consume the PKCE session. Single-use and atomic: see
// SessionStore.Consume (KEY-WP-0016-T01).
sess, ok := h.Sessions.Consume(code)
if !ok {
profileerrors.InvalidProfileUsage(
"authorization code not found or expired",
"code",
).Write(w, http.StatusBadRequest)
return
}
// Verify client_id matches the session.
if sess.ClientID != clientID {
profileerrors.InvalidProfileUsage(
"client_id does not match the authorization code",
"client_id",
).Write(w, http.StatusBadRequest)
return
}
// Bind the exchange to the redirect URI the code was issued for
// (RFC 6749 section 4.1.3, KEY-WP-0016-T02). /authorize always records an
// exactly-matched registered redirect, so the parameter is always required
// here and must be identical.
if redirectURI := r.FormValue("redirect_uri"); redirectURI != sess.RedirectURI {
profileerrors.InvalidProfileUsage(
"redirect_uri does not match the authorization request",
"redirect_uri",
).Write(w, http.StatusBadRequest)
return
}
// Recheck grants in case the client registration changed after authorization.
for _, scope := range sess.Scopes {
if !containsString(h.ClientConfig[clientID].AllowedScopes, scope) {
profileerrors.InvalidProfileUsage("requested scope is not allowed", "scope").Write(w, http.StatusBadRequest)
return
}
}
// 4. Verify PKCE code_verifier.
if !verifyPKCE(codeVerifier, sess.PKCEChallenge) {
profileerrors.InvalidProfileUsage(
"code_verifier does not match code_challenge",
"code_verifier",
).Write(w, http.StatusBadRequest)
return
}
// 5. Look up user.
user, err := h.Users.LookupUser(ctx, sess.Username)
if err != nil {
http.Error(w, "user not found", http.StatusInternalServerError)
return
}
if isSuspended(user) {
profileerrors.RejectedForSafety(
"account is suspended",
"account_lifecycle",
).Write(w, http.StatusForbidden)
return
}
// 6. Build JWT claims.
now := time.Now()
exp := now.Add(h.TokenLifetime)
claims := map[string]interface{}{
"iss": h.Issuer,
"sub": user.ID,
"aud": clientID,
"exp": exp.Unix(),
"iat": now.Unix(),
}
if sess.Nonce != "" {
claims["nonce"] = sess.Nonce
}
scopeSet := make(map[string]bool)
for _, s := range sess.Scopes {
scopeSet[s] = true
}
if scopeSet["profile"] {
claims["preferred_username"] = user.Username
}
if scopeSet["email"] {
claims["email"] = user.Email
}
// Core claims required by net-kingdom/canon/standards/iam-profile_v0.3.md
// for every production token -- not scope-gated, unlike the recommended
// human claims above (KEY-WP-0005-T01).
tenant, tenantSource, err := humanTenant(h.ClientConfig[clientID], user)
if err != nil {
profileerrors.RejectedForSafety(
"tenant binding conflict",
"tenant_binding",
).Write(w, http.StatusForbidden)
return
}
claims["tenant"] = tenant
claims["tenant_source"] = tenantSource
claims["principal_type"] = "human"
claims["groups"] = nonNilStrings(user.Groups)
claims["roles"] = nonNilStrings(user.Roles)
claims["assurance"] = assuranceClaim(sess.MFAVerified, sess.AuthTime, now)
// Optional cached tenant_roles claim (KEY-WP-0005-T02). Fails open --
// see internal/adapters/tenantengine's package doc for why this is the
// one place in the whole tenant_roles design where that's correct.
if roles, ok := h.TenantEngine.Roles(ctx, tenant); ok {
claims["tenant_roles"] = roles
}
// 7. Sign JWT with RSA-SHA256.
kid := "key-1" // static kid for v0.1
identityClaims := make(map[string]interface{}, len(claims)+1)
for name, value := range claims {
identityClaims[name] = value
}
var jwtToken string
if h.Tokens == nil {
jwtToken, err = buildJWT(identityClaims, kid, h.SigningKey)
if err != nil {
http.Error(w, "failed to build JWT", http.StatusInternalServerError)
return
}
}
// Access tokens target the statically registered resource server. ID tokens
// remain bound to the OIDC relying party.
if audience := h.ClientConfig[clientID].Audience; audience != "" {
claims["aud"] = audience
}
claims["scope"] = strings.Join(sess.Scopes, " ")
if h.Tokens != nil {
id := make([]byte, 32)
if _, err := rand.Read(id); err != nil {
http.Error(w, "token status unavailable", http.StatusServiceUnavailable)
return
}
claims["jti"] = base64.RawURLEncoding.EncodeToString(id)
}
accessToken, err := buildJWT(claims, kid, h.SigningKey)
if err != nil {
http.Error(w, "failed to build JWT", http.StatusInternalServerError)
return
}
if h.Tokens != nil {
// The online profile binds the signed ID token to this exact access
// grant, including same-second distinct jtis. OIDC RS256 at_hash uses
// the base64url-encoded left half of SHA-256 over the access token.
digest := sha256.Sum256([]byte(accessToken))
identityClaims["at_hash"] = base64.RawURLEncoding.EncodeToString(digest[:16])
jwtToken, err = buildJWT(identityClaims, kid, h.SigningKey)
if err != nil {
http.Error(w, "failed to build JWT", http.StatusInternalServerError)
return
}
if err := h.Tokens.register(accessToken, client, time.Unix(exp.Unix(), 0), now); err != nil {
http.Error(w, "token status unavailable", http.StatusServiceUnavailable)
return
}
}
// 8. Build response. The session was already consumed at lookup, so no
// separate replay-prevention delete is needed here.
resp := tokenResponse{
AccessToken: accessToken,
TokenType: "Bearer",
ExpiresIn: int(h.TokenLifetime.Seconds()),
IDToken: jwtToken,
}
// 10. Emit token_issued telemetry.
h.Emitter.Emit(ctx, telemetry.Event{
Timestamp: time.Now(),
EventType: telemetry.EventTokenIssued,
ClientID: clientID,
Endpoint: "/token",
Result: "success",
Scopes: sess.Scopes,
GrantType: grantType,
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(resp)
}
// basicClientCredentials reads client_secret_basic credentials, applying the
// form-encoding decode RFC 6749 appendix B requires of both halves. Shared by
// the service grant and confidential authorization-code client authentication.
func basicClientCredentials(r *http.Request) (clientID, clientSecret string, ok bool) {
clientID, clientSecret, ok = r.BasicAuth()
if !ok {
return "", "", false
}
decodedID, idErr := url.QueryUnescape(clientID)
decodedSecret, secretErr := url.QueryUnescape(clientSecret)
if idErr != nil || secretErr != nil {
return "", "", false
}
return decodedID, decodedSecret, true
}
// secretsEqual compares two secrets in constant time. Digesting first keeps the
// comparison length-independent, so a wrong-length secret is indistinguishable
// from a wrong-value one.
func secretsEqual(presented, expected string) bool {
presentedDigest := sha256.Sum256([]byte(presented))
expectedDigest := sha256.Sum256([]byte(expected))
return subtle.ConstantTimeCompare(presentedDigest[:], expectedDigest[:]) == 1
}
func (h *TokenHandler) serveClientCredentials(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
clientID, clientSecret, ok := r.BasicAuth()
if !ok {
profileerrors.InvalidProfileUsage("client_secret_basic authentication required", "Authorization").
Write(w, http.StatusUnauthorized)
return
}
clientID, idErr := url.QueryUnescape(clientID)
clientSecret, secretErr := url.QueryUnescape(clientSecret)
if idErr != nil || secretErr != nil {
profileerrors.InvalidProfileUsage("invalid client authentication encoding", "Authorization").Write(w, http.StatusUnauthorized)
return
}
client, ok := h.ClientConfig[clientID]
if !ok || client.ClientType != "confidential" || !containsString(client.GrantTypes, "client_credentials") {
profileerrors.InvalidProfileUsage("invalid confidential client", "client_id").
Write(w, http.StatusUnauthorized)
return
}
presentedDigest := sha256.Sum256([]byte(clientSecret))
expectedDigest := sha256.Sum256([]byte(client.ClientSecret))
if client.ClientSecret == "" ||
subtle.ConstantTimeCompare(presentedDigest[:], expectedDigest[:]) != 1 {
profileerrors.InvalidProfileUsage("invalid client authentication", "Authorization").
Write(w, http.StatusUnauthorized)
return
}
requestedScopes := strings.Fields(r.FormValue("scope"))
if len(requestedScopes) == 0 {
requestedScopes = append([]string(nil), client.AllowedScopes...)
}
for _, scope := range requestedScopes {
if !containsString(client.AllowedScopes, scope) {
profileerrors.InvalidProfileUsage("requested scope is not allowed", "scope").
Write(w, http.StatusBadRequest)
return
}
}
now := time.Now()
tokenLifetime := h.TokenLifetime
if client.TokenLifetime > 0 {
tokenLifetime = client.TokenLifetime
}
claims := map[string]interface{}{
"iss": h.Issuer,
"sub": client.ServiceSubject,
"aud": accessAudience(client),
"exp": now.Add(tokenLifetime).Unix(),
"iat": now.Unix(),
"tenant": client.Tenant,
// A service client's tenant is always registration-supplied: there is no
// directory principal behind it to assert one (GH-DEC-2026-013 §5).
"tenant_source": TenantSourceRegistration,
"principal_type": "service",
"groups": []string{},
"roles": nonNilStrings(client.Roles),
"scope": strings.Join(requestedScopes, " "),
"assurance": map[string]interface{}{
"level": "aal1", "methods": []string{"client_secret"},
"mfa": false, "source": "key-cape", "at": now.Unix(),
},
}
if roles, ok := h.TenantEngine.Roles(ctx, client.Tenant); ok {
claims["tenant_roles"] = roles
}
if h.Tokens != nil {
id := make([]byte, 32)
if _, err := rand.Read(id); err != nil {
http.Error(w, "token status unavailable", http.StatusServiceUnavailable)
return
}
claims["jti"] = base64.RawURLEncoding.EncodeToString(id)
}
jwtToken, err := buildJWT(claims, "key-1", h.SigningKey)
if err != nil {
http.Error(w, "failed to build JWT", http.StatusInternalServerError)
return
}
if h.Tokens != nil {
if err := h.Tokens.register(jwtToken, client, time.Unix(now.Add(tokenLifetime).Unix(), 0), now); err != nil {
http.Error(w, "token status unavailable", http.StatusServiceUnavailable)
return
}
}
h.Emitter.Emit(ctx, telemetry.Event{
Timestamp: now, EventType: telemetry.EventTokenIssued, ClientID: clientID,
Endpoint: "/token", Result: "success", Scopes: requestedScopes,
GrantType: "client_credentials",
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(tokenResponse{
AccessToken: jwtToken, TokenType: "Bearer",
ExpiresIn: int(tokenLifetime.Seconds()),
})
}
func containsString(values []string, wanted string) bool {
for _, value := range values {
if value == wanted {
return true
}
}
return false
}
func isSuspended(user *domain.User) bool {
return containsString(user.Groups, "netkingdom-suspended")
}
// ---------------------------------------------------------------------------
// IAM Profile core claims (KEY-WP-0005-T01)
// ---------------------------------------------------------------------------
// defaultTenant is the fallback tenant claim for users with no explicit
// Tenant assignment yet. This workstation currently operates a single
// tenant (tenant:coulomb, ADR-0006); later tenants (e.g. tenant:friendly:binky,
// ADR-0013) require an explicit domain.User.Tenant value -- this default
// never silently assigns a user to a tenant other than the platform's
// original one.
const defaultTenant = "tenant:coulomb"
// effectiveTenant resolves the tenant claim for a user, falling back to
// defaultTenant when the user has no explicit tenant assignment. The IAM
// Profile requires a non-empty tenant claim on every token.
func effectiveTenant(user *domain.User) string {
if user.Tenant != "" {
return user.Tenant
}
return defaultTenant
}
// humanTenant resolves the tenant claim for a human token (KEY-WP-0013-T05).
//
// A human's tenant is normally a property of the person, read from the
// directory. But the approval chain is bound to the landlord zone by decision
// 5ed3fb35-eca9-413a-82b9-95171ba85bf6, and approval-engine compares the claim
// by exact string equality, so an approver client has to be able to state the
// zone it issues into. Without this every human token fell back to
// defaultTenant and would have been refused downstream -- as a failed approval
// rather than as a registration defect.
//
// The rule is deliberately not an override:
//
// - no client tenant declared -> the directory answer, unchanged;
// - declared, user unassigned -> the declared zone;
// - declared and equal -> agreement, no ambiguity;
// - declared and different -> refuse to issue.
//
// So a registration can bind a zone for users the directory has not placed, and
// can never relabel a user the directory HAS placed into a different one. That
// last case fails closed rather than picking a winner, because either answer
// would be a silent cross-tenant assertion. It also means this stays correct if
// the directory later populates Tenant: the same code turns from supplying the
// zone into enforcing agreement with it, with no second migration.
//
// This is only safe because client registrations are static and
// deployment-owned; KeyCape excludes dynamic client registration by design. A
// self-service client that could name its users' tenant would be an escalation.
func humanTenant(client *domain.Client, user *domain.User) (string, string, error) {
if client == nil || client.Tenant == "" {
if user.Tenant != "" {
return user.Tenant, TenantSourceDirectory, nil
}
return defaultTenant, TenantSourceDefault, nil
}
if user.Tenant != "" {
if user.Tenant != client.Tenant {
return "", "", fmt.Errorf("client %q binds tenant %q but the directory assigns this user a different tenant", client.ClientID, client.Tenant)
}
// Agreement: the directory did assert this about the person, so the
// stronger provenance is the true one.
return user.Tenant, TenantSourceDirectory, nil
}
return client.Tenant, TenantSourceRegistration, nil
}
// Tenant provenance values for the tenant_source claim (GH-DEC-2026-013 §5).
//
// A bare tenant string cannot tell a consumer whether the identity layer
// asserted the zone about this PERSON or a registration supplied it about the
// CLIENT they came through. approval-engine admits an approver by exact-matching
// that string while its contract reads as though it relies on the first, so the
// check is sound and the property a reader infers from it is absent. The ruling
// requires the claim to carry its provenance and forbids a consumer treating the
// two as equivalent for any decision turning on a fact about the person. It names
// the property; the field is ours.
//
// Three values, not the two the ruling names, and the third is the point. A
// tenant nobody asserted -- neither directory nor registration, just the
// profile's non-empty default -- is not directory-asserted, and labelling it so
// would reintroduce the same defect one level down: a consumer would read
// "directory" for a fact the directory never stated. That is the unknown-versus-
// absent distinction the ruling cites from GH-DEC-2026-011 §3, applied to our own
// fallback rather than only to the case we were asked about.
const (
// TenantSourceDirectory: the identity layer asserted this zone about this
// person. Includes the agreement case, where a registration declared the
// same zone the directory did -- the directory still asserted it.
TenantSourceDirectory = "directory"
// TenantSourceRegistration: supplied by the client registration for a person
// the directory has placed nowhere. A fact about the client, not the person.
TenantSourceRegistration = "registration"
// TenantSourceDefault: nobody asserted a zone; this is the profile default,
// emitted because the profile requires a non-empty tenant. Weaker than both.
TenantSourceDefault = "default"
)
// nonNilStrings returns s, or an empty (non-nil) slice if s is nil, so the
// claim always serializes as `[]`, never `null` -- the profile requires
// groups/roles to be present, "possibly empty", not absent.
func nonNilStrings(s []string) []string {
if s == nil {
return []string{}
}
return s
}
// assuranceClaim builds the profile's `assurance` object from whether MFA
// was actually verified during this authorization (session.MFAVerified),
// not from static enrollment state -- a user who has MFA enrolled but
// wasn't challenged for it in this particular flow gets aal1, not aal2.
//
// `at` is the time the user authenticated, not the time this token was
// minted. Those differ whenever a browser session is reused, and the gap is
// the whole point: approval-engine stores this object verbatim as the only
// downstream record that MFA occurred (KEY-WP-0013-T05), so mint time would
// overstate how recently the person proved anything. issuedAt is the
// fallback for a session predating this field.
func assuranceClaim(mfaVerified bool, authTime, issuedAt time.Time) map[string]interface{} {
at := authTime
if at.IsZero() {
at = issuedAt
}
level := "aal1"
methods := []string{"pwd"}
if mfaVerified {
level = "aal2"
methods = append(methods, "otp")
}
return map[string]interface{}{
"level": level,
"methods": methods,
"mfa": mfaVerified,
"source": "key-cape",
"at": at.Unix(),
}
}
// ---------------------------------------------------------------------------
// PKCE verification
// ---------------------------------------------------------------------------
// verifyPKCE checks BASE64URL(SHA256(verifier)) == challenge (S256 method).
func verifyPKCE(verifier, challenge string) bool {
h := sha256.New()
h.Write([]byte(verifier))
computed := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
return computed == challenge
}
// ---------------------------------------------------------------------------
// JWT construction (stdlib only — no external JWT library)
// ---------------------------------------------------------------------------
type jwtHeader struct {
Alg string `json:"alg"`
Typ string `json:"typ"`
Kid string `json:"kid"`
}
// buildJWT constructs and signs a JWT using RSA-SHA256 with the standard library.
// Format: base64url(header) + "." + base64url(payload) + "." + base64url(signature)
func buildJWT(claims map[string]interface{}, kid string, key *rsa.PrivateKey) (string, error) {
// Header.
hdr := jwtHeader{Alg: "RS256", Typ: "JWT", Kid: kid}
hdrJSON, err := json.Marshal(hdr)
if err != nil {
return "", err
}
hdrB64 := base64.RawURLEncoding.EncodeToString(hdrJSON)
// Payload.
payloadJSON, err := json.Marshal(claims)
if err != nil {
return "", err
}
payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON)
// Signing input.
signingInput := hdrB64 + "." + payloadB64
// Digest.
digest := sha256.Sum256([]byte(signingInput))
// Sign with PKCS1v15 / SHA256.
sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, digest[:])
if err != nil {
return "", err
}
sigB64 := base64.RawURLEncoding.EncodeToString(sig)
return strings.Join([]string{hdrB64, payloadB64, sigB64}, "."), nil
}
// accessAudience is configured by the issuer, never selected by request input.
func accessAudience(client *domain.Client) string {
if client.Audience != "" {
return client.Audience
}
return client.ClientID
}

View file

@ -0,0 +1,62 @@
package oidc_test
import (
"crypto/sha256"
"encoding/base64"
"net/http"
"testing"
"keycape/internal/jose"
)
func TestOnlineHumanIDTokenBindsExactAccessGrant(t *testing.T) {
f := newStatusFixture(t, 10)
f.token.ClientConfig["test-client"].Audience = "resource:forgejo"
first, firstID, code := f.issue(t)
second, secondID, secondCode := f.issue(t)
if code != http.StatusOK || secondCode != http.StatusOK || first == second {
t.Fatal("distinct actual grants were not issued")
}
keys := jose.KeySet{"key-1": &f.token.SigningKey.PublicKey}
for _, pair := range [][2]string{{first, firstID}, {second, secondID}} {
claims, err := jose.VerifyTyped(pair[1], keys, "JWT")
if err != nil {
t.Fatal("actual ID signature refused")
}
digest := sha256.Sum256([]byte(pair[0]))
if claims["at_hash"] != base64.RawURLEncoding.EncodeToString(digest[:16]) || claims["aud"] != "test-client" {
t.Fatal("ID token lost exact access-grant or relying-party binding")
}
if _, ok := claims["jti"]; ok {
t.Fatal("access-grant jti escaped into ID token")
}
if _, ok := claims["scope"]; ok {
t.Fatal("access scope escaped into ID token")
}
access, err := jose.VerifyTyped(pair[0], keys, "JWT")
if err != nil || access["aud"] != "resource:forgejo" {
t.Fatal("resource audience changed")
}
if _, ok := access["at_hash"]; ok {
t.Fatal("ID-only grant binding escaped into access token")
}
}
firstClaims, _ := jose.VerifyTyped(firstID, keys, "JWT")
secondClaims, _ := jose.VerifyTyped(secondID, keys, "JWT")
if firstClaims["at_hash"] == secondClaims["at_hash"] {
t.Fatal("distinct grants shared an ID-token access binding")
}
}
func TestDisabledOnlineProfileKeepsLegacyIDToken(t *testing.T) {
f := newStatusFixture(t, 10)
f.token.Tokens = nil
_, id, code := f.issue(t)
claims, err := jose.VerifyTyped(id, jose.KeySet{"key-1": &f.token.SigningKey.PublicKey}, "JWT")
if code != http.StatusOK || err != nil {
t.Fatal("legacy profile failed")
}
if _, ok := claims["at_hash"]; ok {
t.Fatal("disabled profile changed ID-token contract")
}
}

View file

@ -0,0 +1,131 @@
---
id: KEY-WP-0042
type: workplan
title: "Provide issuer-owned current access-token status and scoped revocation"
domain: infotech
repo: key-cape
status: proposed
owner: codex
topic_slug: custodian
created: "2026-10-18"
updated: "2026-10-18"
flavor: extension
depends_on: []
state_hub_workstream_id: "e2eecd1c-45c7-5a7c-bd1c-d31c845e3d38"
---
CUST-WP-0080's current-operation/inverse requirement exposed a concrete missing
issuer facility. KEY-WP-0025-T03 truthfully documents the expiry-only limit;
KEY-WP-0038 owns Forgejo's existing client, not a token-liveness API. This is an
explicit proposed scope extension in the existing issuer, not a silent change
to that completed limit record. No new service or duplicate session authority.
Consumer binding/authorization and native case acceptance stay in
NK-WP-0048-T34/T42/T43 and RAPPS-WP-0016-T08/T09; they are not copied here.
## Review the bounded issuer/profile extension
```task
id: KEY-WP-0042-T01
status: progress
priority: high
state_hub_task_id: "d8af8e77-f6e5-5d0d-8b44-794af3dee5cf"
```
The reviewable contract is `docs/online-token-status.md`: opt-in RFC 7662/7009
access-token status; registered owning-client authentication; private hash-only
bounded state; unique access-token jti; online human ID-token exact `at_hash`
binding with the disabled legacy ID-token contract unchanged; singleton store-loss
refusal; disabled legacy behavior explicit. Source preparation is authorized by
the full Custodian implementation goal. Independent issuer/profile/consumer
assent remains required before admission; no such assent is inferred here.
## Implement and verify the actual issuer paths
```task
id: KEY-WP-0042-T02
status: done
priority: high
state_hub_task_id: "2cade51f-d25a-5719-96b4-b57d50c1b97d"
```
Wire the same issuer store to both actual grant types, UserInfo, introspection,
revocation, configuration validation and implemented discovery. Verify distinct
grants, exact expiry, replay/tombstones, capacity, malformed/unauthenticated and
cross-client refusals, current registration and human disposition, revocation
during directory lookup, process-store loss and absence of secret output. Keep
the complete existing source suite, disabled profile and build passing.
2026-10-18 T02 source continuation: the actual consumer join exposed missing
same-grant correspondence. The online human ID token now signs the exact access
token's RS256 `at_hash`, retaining its own audience and excluding access-only
fields. Distinct same-second grants cannot share that binding; disabled issuance
is unchanged. This qualifies the earlier unscoped "no ID-token change" statement
for the proposed online profile, without changing existing deployment behavior.
The complete source race suite passes 726 test/subtest results with the same six
native/external skips. The actual HTTPS PKCE/discovery/UserInfo/status/revocation
handlers and real Forgejo provider/callback pass five separate consumer processes:
two current grants accepted, mixed grants/revoked original/suspended human refused.
The original issuer fails the same current consumer. MFA session seeds and PDP
admissions are synthetic. Source-only evidence: Custodian forgejo-current-token-r1.
Independent issuer/profile/consumer review and T03 native admission remain open.
## Admit and accept the issuer lifecycle at native scope
```task
id: KEY-WP-0042-T03
status: todo
priority: high
state_hub_task_id: "a5539bb0-2501-5774-a7d8-acddfb0938ab"
```
After independent contract review, admit exact issuer image/config/profile,
HTTPS ingress/client custody and measured capacity/headroom. Exercise real
human PKCE and service tokens, actual endpoint authentication, revoked-token
UserInfo refusal, expiry, unavailable dependency, restart/store loss and fresh
reauthentication. Preserve old offline-token semantics and document every
consumer's selected mode. Review disabling/rollback explicitly; never silently
fall back to offline acceptance for online-profile consumers. Provide owner
receipts to existing NK48/Apps consumer tasks and Audit source admission.
2026-10-18 source verification: all 659 test/subtest results in the complete
race suite pass, with six preexisting native/external skips. The standalone
binary passes 26 isolated process checks, including actual main/router/config,
service issuance, capacity, revocation, process restart, fresh issuance and
disabled-profile behavior; fixture diagnostics omit synthetic private inputs.
The HTTPS human PKCE flow and revocation during directory lookups also pass.
An isolated control omitting the revoked-state predicate fails seven intended
checks. This closes source task T02 only. T01 independent issuer/profile review,
T03 native admission and all existing consumer operation/effect bindings remain
open. Evidence is retained by CUST-WP-0080 under keycape-token-status-r1.
## Provide the shared producer client and owner-private command observations
```task
id: KEY-WP-0042-T04
status: done
priority: high
state_hub_task_id: "4c71c9f7-8e14-5fec-8491-d7a7995f97e2"
```
2026-10-18 source outcome: `internal/authclient/online.go` owns strict current
token/status joining, explicit HTTPS/CA/JWKS/current-credential admission,
identity-first credential access, per-check rereads, response/clock/lifetime
rechecks and independent read-only inactive observation. The existing executable
exposes `check-current-token`, `revoke-token`, `check-inactive-token`, with exact
token digest binding and new private observation outputs outside Git. Negative
output is explicitly inactive-for-client, never global revocation/ownership.
The public five-file Go adapter is generated from the same client and JOSE bytes;
ordinary source checks refuse drift. Source/contract: `docs/current-token-client.md`.
The full source race suite passes 724 test/subtest results with the same six
native/external skips. Human PKCE tests retain actual subject/assurance/auth-time/
tenant provenance and refuse ID-token substitution. A separate source fixture
uses the built CLI, generated Go consumer and actual Warden kernel across fresh
owner processes, with real fixture HTTPS status and independent negative cleanup
checks. Owner approvals/case/catalog and target are synthetic; there is no native
admission or second real application acceptance. The first CLI fixture's directory
permissions, archive-only Warden Git metadata failure and original API absence are
retained as failures, not backfilled successes. Source/evidence:
`the-custodian/docs/changes/netkingdom-target-2026-10-18/keycape-current-client-r1/`.
T01 independent issuer/profile review, T03 native lifecycle, NK48/Apps actual
operation/inverse/ongoing-resource bindings and all full target gates remain open.

View file

@ -0,0 +1,156 @@
//go:build forgejo_current_fixture
package authclient
import (
"bytes"
"context"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"keycape/internal/domain"
"keycape/internal/server/oidc"
"keycape/internal/server/telemetry"
)
type forgejoFixtureDirectory struct {
mu sync.RWMutex
user domain.User
}
func (d *forgejoFixtureDirectory) LookupUser(context.Context, string) (*domain.User, error) {
d.mu.RLock()
defer d.mu.RUnlock()
user := d.user
user.Groups = append([]string(nil), d.user.Groups...)
return &user, nil
}
func (d *forgejoFixtureDirectory) LookupGroups(context.Context, string) ([]domain.Group, error) {
return nil, nil
}
func (d *forgejoFixtureDirectory) ListUsers(ctx context.Context) ([]domain.User, error) {
user, _ := d.LookupUser(ctx, "")
return []domain.User{*user}, nil
}
func (d *forgejoFixtureDirectory) ValidatePassword(context.Context, string, string) (bool, error) {
return false, nil
}
func TestOnlineActualForgejoCallbackCurrentIssuer(t *testing.T) {
f := onlineProvider(t)
directory := &forgejoFixtureDirectory{user: domain.User{ID: "uid=operator,ou=people,dc=fixture", Username: "operator", Enabled: true, Tenant: "tenant:fixture", Groups: []string{"forge-admins"}}}
client := f.issuer.ClientConfig["current-client"]
client.GrantTypes = []string{"authorization_code"}
client.ServiceSubject = ""
client.Audience = client.ClientID
client.AllowedScopes = []string{"openid", "profile", "groups"}
client.RedirectURIs = []string{"https://forgejo.invalid/callback"}
mux := http.NewServeMux()
server := httptest.NewTLSServer(mux)
t.Cleanup(server.Close)
f.issuer.Users = directory
f.issuer.Issuer = server.URL
f.status.Users = directory
f.status.Issuer = server.URL
userinfo := &oidc.UserinfoHandler{Users: directory, SigningKey: &f.issuer.SigningKey.PublicKey, Issuer: server.URL, Tokens: f.issuer.Tokens, ClientConfig: f.issuer.ClientConfig, Emitter: telemetry.NoopEmitter{}}
var statusCalls, userinfoCalls atomic.Int64
mux.Handle("/token", f.issuer)
mux.HandleFunc("/introspect", func(w http.ResponseWriter, r *http.Request) { statusCalls.Add(1); f.status.Introspect(w, r) })
mux.HandleFunc("/revoke", f.status.Revoke)
mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) { userinfoCalls.Add(1); userinfo.ServeHTTP(w, r) })
keys := oidc.NewKeySet()
keys.AddKey("key-1", &f.issuer.SigningKey.PublicKey)
mux.Handle("/jwks", oidc.NewJWKSHandler(keys))
mux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"issuer": server.URL, "authorization_endpoint": server.URL + "/authorize", "token_endpoint": server.URL + "/token", "userinfo_endpoint": server.URL + "/userinfo", "jwks_uri": server.URL + "/jwks", "id_token_signing_alg_values_supported": []string{"RS256"}})
})
admission := f.client.admission
admission.Issuer = server.URL
admission.Audience = client.ClientID
if os.WriteFile(admission.CAFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw}), 0644) != nil {
t.Fatal("fixture CA write failed")
}
var err error
f.client, err = NewOnlineClient(admission)
if err != nil {
t.Fatal("fixture admission failed")
}
der, err := x509.MarshalPKIXPublicKey(&f.issuer.SigningKey.PublicKey)
if err != nil {
t.Fatal("fixture public key failed")
}
publicKey := string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}))
issue := func() Tokens {
verifier := strings.Repeat("v", 43)
digest := sha256.Sum256([]byte(verifier))
code := f.issuer.Sessions.Create(&oidc.PKCESession{ClientID: client.ClientID, Username: directory.user.Username, RedirectURI: client.RedirectURIs[0], PKCEChallengeMethod: "S256", PKCEChallenge: base64.RawURLEncoding.EncodeToString(digest[:]), Scopes: client.AllowedScopes, MFAVerified: true, AuthTime: time.Now().Add(-time.Minute).Truncate(time.Second), ExpiresAt: time.Now().Add(time.Minute)})
form := url.Values{"grant_type": {"authorization_code"}, "client_id": {client.ClientID}, "code": {code}, "code_verifier": {verifier}, "redirect_uri": {client.RedirectURIs[0]}}
r, _ := http.NewRequest(http.MethodPost, server.URL+"/token", strings.NewReader(form.Encode()))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
r.SetBasicAuth(url.QueryEscape(client.ClientID), url.QueryEscape(client.ClientSecret))
response, err := server.Client().Do(r)
if err != nil {
t.Fatal("actual fixture token HTTP failed")
}
defer response.Body.Close()
var tokens Tokens
if response.StatusCode != 200 || json.NewDecoder(response.Body).Decode(&tokens) != nil || tokens.AccessToken == "" || tokens.IDToken == "" {
t.Fatal("actual fixture human PKCE failed")
}
return tokens
}
first, second := issue(), issue()
directoryPath := t.TempDir()
var phases []string
probe := func(tokens Tokens, phase string) {
input := map[string]any{"id_token": tokens.IDToken, "access_token": tokens.AccessToken, "public_key_pem": publicKey, "tenant": directory.user.Tenant, "phase": phase, "current": admission}
raw, _ := json.Marshal(input)
file := filepath.Join(directoryPath, "consumer-input.json")
if os.WriteFile(file, raw, 0600) != nil {
t.Fatal("private fixture input failed")
}
command := exec.Command(os.Getenv("FORGEJO_CURRENT_TEST_BINARY"), "-test.run=^TestAccessEngineLiveFixtureCurrentIssuer$", "-test.v")
command.Env = append(os.Environ(), "FORGEJO_CURRENT_ISSUER_FIXTURE="+file)
output, err := command.CombinedOutput()
for _, private := range []string{first.AccessToken, first.IDToken, second.AccessToken, second.IDToken, client.ClientSecret} {
if bytes.Contains(output, []byte(private)) {
t.Fatal("consumer fixture diagnostic disclosed private input")
}
}
if err != nil {
t.Fatalf("actual consumer phase %s failed: %s", phase, output)
}
phases = append(phases, phase)
}
probe(first, "active")
probe(Tokens{AccessToken: second.AccessToken, IDToken: first.IDToken}, "mixed-pair")
if _, err := f.client.Revoke(context.Background(), first.AccessToken); err != nil {
t.Fatal("actual fixture revoke/independent negative failed")
}
probe(first, "revoked-after-userinfo")
probe(second, "active")
directory.mu.Lock()
directory.user.Groups = []string{"netkingdom-suspended"}
directory.mu.Unlock()
probe(second, "suspended-after-userinfo")
report := map[string]any{"status": "passed", "consumer_processes": len(phases), "phases": phases, "actual_https_human_PKCE": true, "actual_discovery_and_userinfo": true, "actual_introspection_and_revocation": true, "status_calls": statusCalls.Load(), "userinfo_calls": userinfoCalls.Load(), "actual_forgejo_callback_and_generated_client": true, "synthetic_MFA_session_seed_and_PDP_admission": true, "native_acceptance": false, "private_inputs": "fixture memory/tmpfs only"}
raw, _ := json.MarshalIndent(report, "", " ")
if os.WriteFile(os.Getenv("FORGEJO_CURRENT_REPORT"), append(raw, '\n'), 0600) != nil {
t.Fatal("public fixture result failed")
}
}

Some files were not shown because too many files have changed in this diff Show more