Skip to content

Latest commit

 

History

History
271 lines (200 loc) · 7.48 KB

File metadata and controls

271 lines (200 loc) · 7.48 KB

Testing Stripe Identity Step-Up

This guide walks through a local end-to-end test of the Stripe Identity step-up example using:

The flow uses --dry-run, so you can test proof verification, OPA authorization, and audit output without needing a live backend API behind the wrapped shell commands.

Prerequisites

  • Node 22+
  • this repository checked out locally
  • either:
    • opa installed locally, or
    • Docker available for running OPA

1. Validate the example manifest

node bin/agentcli.js validate examples/stripe-identity-step-up.json --json

You should see:

{
  "ok": true,
  "errors": [],
  "warnings": []
}

2. Start OPA with the example policy

If you have the opa CLI:

opa run --server --addr 127.0.0.1:8181 examples/stripe-identity-step-up.rego

If you prefer Docker:

docker run --rm \
  -p 8181:8181 \
  -v "$PWD/examples:/policy" \
  openpolicyagent/opa:latest \
  run --server --addr :8181 /policy/stripe-identity-step-up.rego

The example manifest points at:

http://127.0.0.1:8181/v1/data/agentcli/authz/allow

3. Create a local test manifest with a generated public key

The checked-in example uses a placeholder jwks_uri. For local testing, generate an RSA key pair and write a temporary manifest that embeds the public key directly.

node --input-type=module <<'EOF'
import { generateKeyPairSync } from 'node:crypto';
import { readFileSync, writeFileSync } from 'node:fs';

const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 });
writeFileSync('/tmp/agentcli-step-up-private.pem', privateKey.export({ type: 'pkcs8', format: 'pem' }));

const manifest = JSON.parse(readFileSync('examples/stripe-identity-step-up.json', 'utf8'));
delete manifest.authorization_proof_profiles[0].jwks_uri;
manifest.authorization_proof_profiles[0].public_key = publicKey.export({ type: 'spki', format: 'pem' });

writeFileSync('/tmp/stripe-identity-step-up.local.json', JSON.stringify(manifest, null, 2));
EOF

Validate the generated manifest:

node bin/agentcli.js validate /tmp/stripe-identity-step-up.local.json --json

4. Export a dummy primary auth token

The manifest's identity_profile still expects a runtime bearer token. Because this guide uses --dry-run, the token can be a placeholder.

export BOT_ACCESS_TOKEN="dummy-bot-token"

5. Generate a valid signed step-up JWT

This token matches both:

  • the manifest proof check
  • the OPA policy in examples/stripe-identity-step-up.rego
export ACTOR_STEP_UP_JWT="$(
  node --input-type=module <<'EOF'
import { createSign } from 'node:crypto';
import { readFileSync } from 'node:fs';

const privateKey = readFileSync('/tmp/agentcli-step-up-private.pem', 'utf8');
const header = Buffer.from(JSON.stringify({
  alg: 'RS256',
  typ: 'JWT',
  kid: 'local-step-up-test'
})).toString('base64url');

const payload = Buffer.from(JSON.stringify({
  iss: 'https://auth.example.com',
  aud: 'agentcli',
  sub: 'user_demo',
  org_id: 'org_demo',
  on_behalf_of_user_id: 'user_demo',
  delegation_grant_id: 'grant_demo',
  run_id: 'run_demo',
  agent_id: 'support-bot',
  verification_ref: 'vs_local_test',
  verification_level: 'document',
  verification_verified_at: '2026-04-07T12:00:00Z',
  step_up_policy: 'stripe_identity_sensitive_ops',
  exp: Math.floor(Date.now() / 1000) + 3600
})).toString('base64url');

const signer = createSign('RSA-SHA256');
signer.update(`${header}.${payload}`);
process.stdout.write(`${header}.${payload}.${signer.sign(privateKey).toString('base64url')}`);
EOF
)"

6. Dry-run the non-sensitive task

This task does not require step-up proof or OPA authorization.

node bin/agentcli.js exec /tmp/stripe-identity-step-up.local.json list-safe-state --dry-run --signer none --json

What to look for:

  • "ok": true
  • no authorization proof requirement
  • resolved identity from the ops-bot profile

7. Dry-run the sensitive task with valid step-up proof

node bin/agentcli.js exec /tmp/stripe-identity-step-up.local.json view-sensitive-customer --dry-run --signer none --json

What to look for in the JSON output:

  • "ok": true
  • authorization_proof.verified is true
  • authorization_proof.signature_verified is true
  • authorization.decision is permit
  • actor_context.org_id is org_demo
  • actor_context.verification.ref is vs_local_test
  • step_up.step_up_policy is stripe_identity_sensitive_ops

8. Negative test: missing step-up proof

Unset the JWT and rerun the sensitive task:

unset ACTOR_STEP_UP_JWT
node bin/agentcli.js exec /tmp/stripe-identity-step-up.local.json view-sensitive-customer --dry-run --signer none --json

Expected result:

  • command fails
  • error indicates the authorization proof value was not available

9. Negative test: proof passes but OPA denies

Generate a JWT that still satisfies the manifest proof check but fails the OPA policy by using the wrong org_id.

export ACTOR_STEP_UP_JWT="$(
  node --input-type=module <<'EOF'
import { createSign } from 'node:crypto';
import { readFileSync } from 'node:fs';

const privateKey = readFileSync('/tmp/agentcli-step-up-private.pem', 'utf8');
const header = Buffer.from(JSON.stringify({
  alg: 'RS256',
  typ: 'JWT',
  kid: 'local-step-up-test'
})).toString('base64url');

const payload = Buffer.from(JSON.stringify({
  iss: 'https://auth.example.com',
  aud: 'agentcli',
  sub: 'user_demo',
  org_id: 'org_wrong',
  on_behalf_of_user_id: 'user_demo',
  delegation_grant_id: 'grant_demo',
  run_id: 'run_demo',
  agent_id: 'support-bot',
  verification_ref: 'vs_local_test',
  verification_level: 'document',
  verification_verified_at: '2026-04-07T12:00:00Z',
  step_up_policy: 'stripe_identity_sensitive_ops',
  exp: Math.floor(Date.now() / 1000) + 3600
})).toString('base64url');

const signer = createSign('RSA-SHA256');
signer.update(`${header}.${payload}`);
process.stdout.write(`${header}.${payload}.${signer.sign(privateKey).toString('base64url')}`);
EOF
)"

Now rerun:

node bin/agentcli.js exec /tmp/stripe-identity-step-up.local.json view-sensitive-customer --dry-run --signer none --json

Expected result:

  • proof verification still succeeds
  • OPA returns deny
  • agentcli fails with authorization_denied

10. Inspect audit output

Because the example workflow uses contract.audit: "always", both successful dry-runs and authorization failures are written to the audit log.

node bin/agentcli.js audit --limit 5 --json

What to look for in each record:

  • actor_context
  • authorization_proof
  • step_up
  • authorization
  • trust

For denied runs, look for:

  • authorization_error

For proof failures, look for:

  • authorization_proof.verified: false

11. Clean up local test artifacts

rm -f /tmp/agentcli-step-up-private.pem
rm -f /tmp/stripe-identity-step-up.local.json
unset BOT_ACCESS_TOKEN
unset ACTOR_STEP_UP_JWT

Expected Testing Sequence

  1. validate passes for the checked-in example.
  2. validate passes for the generated local manifest.
  3. list-safe-state --dry-run succeeds without step-up.
  4. view-sensitive-customer --dry-run succeeds with a valid JWT and OPA allow.
  5. view-sensitive-customer --dry-run fails when the JWT is missing.
  6. view-sensitive-customer --dry-run fails with authorization_denied when OPA rejects the actor context.