Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.26.1.
See Evidence Gateway refuse unsafe requests
For the assertion provider and consumer or verifier
Complete Return a governed value before starting this tutorial. You will reuse that project to observe two boundaries: Evidence Gateway denies a purpose the provider did not authorize, and the verifier rejects a response changed after signing.
The commands below assume the project as the governed-value tutorial leaves it, with no access
policy of its own. Control caller access comes first in the
navigation and adds access/ policies to that same project. Once they exist, every preparation
names its application, so add --client service-router to the request prepare command below if
you completed that tutorial. Without it, preparation stops with
evidencectl: the active project requires a registered client selected with --client.
Restart the local boundary
Section titled “Restart the local boundary”In one terminal, return to the first-evidence-assertion directory and serve the checked cases:
evidencectl source mock serve --config adult-status/mocks/source.yamlIn another terminal, enter the existing project and start a fresh local generation:
cd adult-statusevidencectl dev start .Evidence ready at http://127.0.0.1:8080Issuer ready at http://127.0.0.1:8081Prepare one authorized request
Section titled “Prepare one authorized request”Prepare a normal age-bracket request. This records the exact verification expectations before any response exists:
evidencectl request prepare age-bracket \ --purpose service-path-selection \ --subject person_id=person-123 \ --name refusal-checkThe provider configured service-path-selection for this question. Preparation also obtained a
short-lived local token carrying the tutorial caller’s configured authority claims. The token does
not authorize one exact request. Evidence Gateway applies those claims to the request it receives.
Change the purpose after preparation
Section titled “Change the purpose after preparation”Copy the request, then change only its purpose:
cp .evidence/requests/refusal-check/request.json unauthorized-request.jsonpython3 - <<'PY'import jsonfrom pathlib import Path
path = Path("unauthorized-request.json")request = json.loads(path.read_text())request["purpose"] = "age-check"path.write_text(json.dumps(request))PYThe retained authorization does not grant age-check for this requirement. Send the changed
request across the same HTTP boundary:
curl --silent --show-error \ --config .evidence/requests/refusal-check/authorization.curl \ --request POST \ --url http://127.0.0.1:8080/v1/evidence \ --header 'Content-Type: application/json' \ --header 'Accept: application/jose+json' \ --data-binary @unauthorized-request.json \ --output unauthorized-response.json \ --write-out 'HTTP %{http_code}\n'HTTP 403Inspect the public problem:
python3 -m json.tool unauthorized-response.jsonThe response identifies only the closed evidence.denied problem and public trace ID:
{ "code": "evidence.denied"}It
does not reveal source facts, selector values, grants, or credentials. The registry terminal shows
no new GET /people/person-123 because authorization failed before source access.
Obtain and verify the authorized response
Section titled “Obtain and verify the authorized response”Now send the unchanged prepared request:
curl --silent --show-error \ --config .evidence/requests/refusal-check/authorization.curl \ --request POST \ --url http://127.0.0.1:8080/v1/evidence \ --header 'Content-Type: application/json' \ --header 'Accept: application/jose+json' \ --data-binary @.evidence/requests/refusal-check/request.json \ --output authorized-response.jws.jsonVerify it against the expectations retained during preparation:
evidencectl verify authorized-response.jws.json \ --context .evidence/requests/refusal-check/verification.json \ --output authorized-response.verified.jsonVERIFIEDThis is the control case. The authorized, unchanged response is trusted.
Change the signed response
Section titled “Change the signed response”Copy the flattened JWS and change one character in its signed payload:
cp authorized-response.jws.json tampered-response.jws.jsonpython3 - <<'PY'import jsonfrom pathlib import Path
path = Path("tampered-response.jws.json")document = json.loads(path.read_text())payload = document["payload"]document["payload"] = ("A" if payload[0] != "A" else "B") + payload[1:]path.write_text(json.dumps(document))PYTry to verify the changed response:
evidencectl verify tampered-response.jws.json \ --context .evidence/requests/refusal-check/verification.json \ --output tampered-response.verified.jsonevidencectl: Evidence response verification failedVerification fails without publishing a trusted payload: no tampered-response.verified.json
appears. Your application must make decisions only from the verifier output, never from an
unverified response body.
Clean up
Section titled “Clean up”Stop the local services, then remove the stopped generation so the next tutorial starts from a clean project:
evidencectl dev stopevidencectl dev cleanLocal Evidence stoppedRemoved stopped local Evidence stateThis preserves your editable questions, derivations, keys, and the request artifacts under
.evidence/requests/, which Verify an assertion as a consumer
reads. It also leaves .evidence/failed-start, where a start that never reached readiness keeps its
logs, so a failure you have not read yet survives the cleanup.
Return to the registry terminal and press Ctrl+C.
What you proved
Section titled “What you proved”- A valid access token does not widen the provider’s authorized purpose.
- Authorization failure happens before the protected source is called.
- A signed response cannot be changed without invalidating its signature.
- Only verified output is safe for an application to consume.