Agent prompt: migrate Provisioner from secret-auth to cert-auth¶
Paste the content below into Claude Code while working in the
entraclaw-identity-research repo directory. It is written to be
self-contained — the agent does not need any prior conversation context.
You are fixing a HIGH-severity security issue in this repo. Read
`docs/SECURITY-DEBT-PROVISIONER-SECRET.md` end to end before you do
anything else. It describes the problem, the fix, the migration
requirement, and the verification checklist. Do not deviate from it.
Reference implementation that has already shipped this fix:
`/path/to/persona-sati/scripts/entra_provisioning.py`
and `/path/to/persona-sati/scripts/provisioner-token.py`.
Read those two files. The shape is intentionally terse. Copy the same
pattern into `entraclaw-identity-research/scripts/entra_provisioning.py`
and a new `scripts/provisioner-token.py`. Change:
- `PROVISIONER_APP_DISPLAY_NAME` stays `EntraClaw Agent ID Provisioner` —
do NOT rename. Cert-auth is a credential change, not an identity
change.
- `_KEYCHAIN_SERVICE_CERT = "entraclaw-provisioner-cert"` (not
persona-sati-provisioner-cert).
- `_STATE_FILE = .entraclaw-state.json` (already the case here).
Your job in order:
1. Update `pyproject.toml` (or equivalent) to add `keyring>=24` and
`cryptography>=42` to the provisioning deps. `azure-identity` and
`requests` are already there.
2. Rewrite `scripts/entra_provisioning.py`:
- Add cert-auth helpers (_generate_provisioner_cert,
_upload_cert_to_app, _remove_legacy_password_credentials,
keychain get/store/delete).
- Replace ClientSecretCredential with CertificateCredential in
get_graph_token().
- In ensure_app_registration(), migrate any pre-existing
PROVISIONER_CLIENT_SECRET from the state file by:
(a) reading and using it only to mint ONE last token if needed
to clean up (optional — you can skip if the next step
works without it)
(b) deleting ALL passwordCredentials from the Provisioner app
via az ad app credential delete
(c) clearing PROVISIONER_CLIENT_SECRET from the state file
- Generate a new cert, register it, store the key in Keychain.
- Print a clear "migrated X password credentials; secret purged from
disk" summary.
3. Create `scripts/provisioner-token.py` — a small CLI that calls
get_graph_token() and prints the token. Exit 0 on success, 1 with
error on stderr.
4. Audit every other place in this repo that acquires a Graph token.
Grep for `ClientSecretCredential`, direct curl to
`/oauth2/v2.0/token`, or reads of `PROVISIONER_CLIENT_SECRET`.
Update each to use `get_graph_token()` from entra_provisioning, or
shell out to `scripts/provisioner-token.py`. Zero secret usage
anywhere.
5. If `setup.sh` builds JWT assertions or uses curl with client_secret
for the Provisioner, replace that section with a call to
`scripts/provisioner-token.py`.
6. Run the verification checklist from the security debt doc. If
anything fails, fix it — do not open the PR.
7. Write a runbook at `docs/runbooks/cert-auth-migration.md` that a
user can follow on their own machine. Include the pre-flight
backup, the migration command, verification commands, and
rollback.
8. Commit on a branch `fix/provisioner-cert-auth`, open a PR to main.
In the PR body, explicitly cross-reference
`docs/SECURITY-DEBT-PROVISIONER-SECRET.md` and link to the
persona-sati reference implementation. State clearly that this
closes HIGH-severity security debt.
DO NOT:
- Skip the migration of existing state (the password MUST be removed
from both disk AND the app registration; leaving either is a
backdoor).
- Cache the secret in environment variables or temp files as a
"performance optimization."
- Commit `.entraclaw-state.json` or any keychain export to git.
- Rename the Provisioner app, change its permission set, or touch the
Blueprint cert path.
- Open the PR without running the full verification checklist.
When the PR is open, summarize: which files changed, how many
password credentials were removed from the app in your testing, and
the commands you ran to verify no secrets remain on disk.
One-time setup before giving the agent the prompt¶
Make sure your az CLI is logged in to the same tenant where the Provisioner lives (otherwise the migration step that deletes password credentials will fail):
And back up the current state file, in case you need to roll back:
Then paste the agent prompt and let it work.