Operations

Troubleshooting

Common errors and their solutions.

Troubleshooting

DNS Discovery

"No IdP found for domain"

Symptom: Login fails with "Could not discover IdP for this email domain."

Cause: Missing or misconfigured _ddisa TXT record.

Fix:

# Check if the record exists
dig _ddisa.example.com TXT +short

# Expected output:
# "v=ddisa1 idp=https://id.example.com; mode=open"

If no record is returned:

  1. Add the TXT record to your DNS provider
  2. Wait for propagation (check at dnschecker.org)
  3. Verify the format: v=ddisa1 idp=https://...; mode=open

If the SP has a fallbackIdpUrl configured, users from domains without DDISA records will be redirected there.


Passkey Registration

"Registration failed: origin mismatch"

Symptom: Passkey registration fails immediately.

Cause: The rpOrigin in the IdP config doesn't match the browser's origin.

Fix: Ensure rpOrigin exactly matches the URL in the browser address bar:

// ❌ Wrong
rpOrigin: 'https://id.example.com/'  // trailing slash
rpOrigin: 'http://id.example.com'    // wrong protocol

// ✅ Correct
rpOrigin: 'https://id.example.com'

For local development: rpOrigin: 'http://localhost:3000'

"Registration failed: rpID mismatch"

Symptom: Passkey registration fails after biometric prompt.

Cause: The rpID doesn't match the origin's domain.

Fix: rpID must be the domain (without protocol or port):

// ❌ Wrong
rpID: 'https://id.example.com'  // includes protocol
rpID: 'id.example.com:3000'     // includes port

// ✅ Correct
rpID: 'id.example.com'
rpID: 'localhost'  // for development

"Passkeys require HTTPS"

Symptom: Passkey registration fails in production but works locally.

Cause: WebAuthn requires a secure context. localhost is exempt, but production must use HTTPS.

Fix: Ensure your IdP is served over HTTPS with a valid certificate.


OAuth / Login Flow

"OAuth callback error: PKCE mismatch"

Symptom: Login redirects back to SP but fails with a code exchange error.

Cause: The PKCE code_verifier doesn't match the code_challenge sent during authorization.

Fix:

  • Clear browser cookies and try again (session state may be corrupted)
  • Ensure sessionSecret is consistent across deployments (don't rotate mid-session)
  • Check that the SP is not behind a load balancer with inconsistent sticky sessions

"OAuth callback error: audience mismatch"

Symptom: Login fails with "JWT audience does not match client ID."

Cause: The SP's clientId doesn't match what the IdP expects.

Fix:

// SP config — clientId must match the SP's public domain
openapeSp: {
  clientId: 'app.example.com'  // not 'https://app.example.com'
}

In development, clientId auto-derives to localhost:PORT.

"OAuth callback error: expired authorization code"

Symptom: Login fails after a long delay between IdP auth and SP callback.

Cause: Authorization codes expire after 60 seconds.

Fix: Retry the login. If the problem persists, check for network latency between SP and IdP.


Agent Authentication

"Agent not found"

Symptom: /api/agent/challenge returns 404.

Cause: Agent not enrolled, or enrolled with a different email/ID.

Fix:

# List enrolled agents
curl https://id.example.com/api/admin/agents \
  -H "Authorization: Bearer <management-token>"

Verify the agent's email matches what you're sending in agent_id.

"Invalid signature"

Symptom: /api/agent/authenticate returns 401.

Cause: The signature doesn't match the registered public key.

Fix:

  1. Verify you're signing the exact challenge string (no extra newline or whitespace)
  2. Ensure you're using the correct private key (matching the enrolled public key)
  3. Check that the key format is Ed25519 (not RSA or ECDSA)
# Verify key type
ssh-keygen -l -f ~/.ssh/agent_key
# Should show: 256 SHA256:... (ED25519)

"Challenge expired"

Symptom: /api/agent/authenticate returns 401 with "expired challenge."

Cause: Challenges expire after 60 seconds.

Fix: Request a new challenge and authenticate within 60 seconds. Ensure system clocks are synchronized (NTP).


Grants

"Grant already decided"

Symptom: Approving or denying a grant returns 400.

Cause: The grant was already approved, denied, or revoked.

Fix: Check the grant status:

curl https://id.example.com/api/grants/<id> \
  -H "Authorization: Bearer <token>"

"Not authorized to approve"

Symptom: Approving a grant returns 403.

Cause: The logged-in user is not the agent's owner, approver, or an admin.

Fix: Check who the agent's owner/approver is:

curl https://id.example.com/api/admin/agents/<agent-id> \
  -H "Authorization: Bearer <management-token>"

Update the agent's approver field if needed.

"cmd_hash mismatch" (escapes)

Symptom: escapes exits with code 5 and logs "cmd_hash mismatch."

Cause: The command in the grant request doesn't match the command being executed.

Fix: The command array must match exactly:

# Grant was requested for:
#   command: ["systemctl", "restart", "nginx"]

# ❌ This won't match (different arguments)
escapes --grant "$JWT" -- systemctl stop nginx

# ✅ This matches
escapes --grant "$JWT" -- systemctl restart nginx

escapes

"JWT verification failed" (exit code 5)

Symptom: escapes refuses to execute and exits with code 5.

Possible causes and fixes:

Error in audit logCauseFix
"issuer not in allowed_issuers"JWT issuer URL doesn't match configAdd the IdP URL to allowed_issuers in /etc/openape/config.toml
"audience mismatch"JWT aud claim ≠ configured audienceCheck allowed_audiences in config (default: ["escapes"])
"target_host mismatch"JWT target_host ≠ system hostnameSet host in config to match, or fix the grant request
"approver not allowed"JWT decided_by not in allowed listAdd the approver to allowed_approvers in config
"cmd_hash mismatch"Command doesn't match grantRe-request grant with the exact command
"grant already consumed"once grant was already usedRequest a new grant

Proxy

"All requests blocked"

Symptom: The proxy blocks every request.

Cause: default_action is set to block and no allow rules match.

Fix: Add allow rules for expected traffic:

default_action = "block"

[[allow]]
domain = "api.github.com"
methods = ["GET"]

Or change default_action to request for a more permissive default.


Session Issues

"Session not persisting"

Symptom: User is logged in but loses the session on next request.

Cause: sessionSecret not set or changes between deployments.

Fix:

  • Set a stable sessionSecret environment variable
  • Ensure the secret is the same across all instances (if load-balanced)
  • Check cookie settings: domain must match, and Secure flag requires HTTPS

"Session expired unexpectedly"

Symptom: User is logged out before sessionMaxAge.

Cause: Server restart (in-memory sessions) or cookie domain mismatch.

Fix:

  • Configure persistent storage for sessions
  • Verify cookie domain matches the app's domain