feat(example): perch guided tour — smart accounts → OZ → Nido → perch → M-of-N, on testnet - #191
feat(example): perch guided tour — smart accounts → OZ → Nido → perch → M-of-N, on testnet#191willemneal wants to merge 4 commits into
Conversation
| } | ||
|
|
||
| export const server = new rpc.Server(RPC_URL); | ||
| export const poster = secp256r1Keypair(POSTER_SEED); |
There was a problem hiding this comment.
Signature & Authentication: Predictable signer key enables impersonation in poster
The demo derives the poster signing key from a committed, deterministic seed (POSTER_SEED). Because this seed is public, any third party can generate the same secp256r1 keypair and produce valid signatures that the on-chain WebAuthn verifier will accept, allowing them to impersonate the demo signer for any actions permitted by the perch policy (here: spamming board.post(..., self) on testnet).
Do not commit deterministic signing seeds for accounts intended to represent a single principal. Generate the key per user/session, or load the seed from a non-committed secret (env/keystore). If this must remain deterministic for reproducibility, hard-fail outside testnet and clearly isolate the demo account from anything of value.
Actions
- Reply
/almanax ask <question>to ask a follow-up question. - Reply
/almanax dismiss [<reason>]and it won't appear again in future scans. - Reply
/almanax resolve [<reason>]to mark the finding as resolved. - Reply
/almanax severity <level> [<reason>]to override the severity.
|
Preview deployed! Account URLs use numeric preview suffixes, for example |
|
Example dApp preview deployed!
status-message runs on testnet, wallet = THIS PR's preview (https://191.nido.fyi); the perch tour drives perch on real testnet. The live home is https://nidohq.github.io/nido/ once merged. |
| const jsonView = el('div', 'codebox'); | ||
| jsonView.innerHTML = [ | ||
| '{ <span class="k">"signer"</span>: <span class="s">"ci"</span>, <span class="k">"scope"</span>: <span class="s">"board"</span>,', | ||
| ` <span class="k">"functions"</span>: [${state.attnFns.map((f) => `<span class="s">"${f}"</span>`).join(', ')}],`, |
There was a problem hiding this comment.
Input and Parameter Validation: HTML injection via attnFns in act5
act5 renders state.attnFns into innerHTML without escaping. If attnFns ever becomes attacker-influenced (e.g., future in-browser policy authoring, URL/state hydration, or any injected script), this enables DOM XSS in a page that can sign and submit real Soroban testnet transactions.
Avoid innerHTML for dynamic values; render function names via textContent/DOM nodes, or strictly HTML-escape f before interpolation (e.g., replace &, <, >, ", '). Treat any future policy-authoring inputs as untrusted.
Actions
- Reply
/almanax ask <question>to ask a follow-up question. - Reply
/almanax dismiss [<reason>]and it won't appear again in future scans. - Reply
/almanax resolve [<reason>]to mark the finding as resolved. - Reply
/almanax severity <level> [<reason>]to override the severity.
| app.append(stage); | ||
| } | ||
| render(); | ||
| (window as unknown as { __tour: unknown }).__tour = { go, state }; |
There was a problem hiding this comment.
Signature & Authentication: Exposed fee signer in global __tour in render
The app exports { go, state } onto window.__tour; state includes feeKp (a Keypair used to sign and pay fees). Any XSS gadget or third-party script running in-origin can read/exfiltrate this secret and use it to submit arbitrary fee-paying transactions during the session.
Remove the global export in production builds, or export a redacted/debug-only view that never includes secrets (Keypair). If you need debugging, gate it behind an explicit dev flag and only expose non-sensitive fields.
| (window as unknown as { __tour: unknown }).__tour = { go, state }; |
Actions
- Reply
/almanax ask <question>to ask a follow-up question. - Reply
/almanax dismiss [<reason>]and it won't appear again in future scans. - Reply
/almanax resolve [<reason>]to mark the finding as resolved. - Reply
/almanax severity <level> [<reason>]to override the severity.
… on testnet A five-act guided tour (in Nido's "Warm Nest" design language) that scopes a CI key with perch, from a raw keypair to a policy the chain enforces: 1 One key, total power 2 The account becomes a program 3 OZ vocabulary + the hand-written-Policy contrast 4 Nido: signers across every verifier (secp256r1, post-quantum ML-DSA-65, Delegated → another account) 5 perch: the full policy (perch composing with OZ-native policies) → describe → attenuate (machine-checked narrowing) → enforce live on testnet (post allowed, clear denied). Uses @nidohq/testkit for real reachable-calls/attenuation and @nidohq/passkey-sdk + @stellar/stellar-sdk for the live invoke (footprint-resimulation fix for policy-gated accounts). Playwright e2e drives all five acts + the on-chain enforce; snapshots in artifacts/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds + nests examples/perch-status-onchain alongside status-message (Pages home at /<repo>/perch-status-onchain/, and the per-PR Cloudflare preview), announcing its URL in the preview comment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1ca2fed to
2d153f8
Compare
Turns Act 5 §① from a static "describe" panel into a live policy builder: a two-column card where you author the CI key's rule with toggles and watch the whole document re-derive on every change. Controls (left): the functions the key may call (post / clear, with clear flagged as the risky one), the `args[1] = self` author guard, and an optional `not-after-ledger` expiry with a number input. Output (right): the exact kebab-case PolicyDoc (syntax-highlighted), its live `doc_hash`, the reachable calls read straight off the doc, and a dynamic safety read that names the problem — over-broad (can `clear`), open-author (no self guard), or "tightly scoped" when it's `post()`-only with the self guard on. `policyModel.ts` gains `BuildConfig` + `buildDoc(cfg)` (functions/self-arg/ expiry) with `ciDoc` retired in favor of it; `main.ts` swaps `state.attnFns` for `state.build` and threads it through describe, attenuation (narrow snaps to publish-only, widen is refused — unchanged assertions), and the doc/hash/reach views; a small `hjson` highlighter renders the wire doc. Attenuation and the on-chain enforce step are unchanged and still drive the same deployed policy. Adds `.tog`/`.toggles` builder styling in the Warm Nest system. Playwright gains an offline builder check (over-broad → toggle clear → tightly scoped → restore) ahead of the existing narrow/widen/enforce flow. typecheck + build clean; two artifact screenshots show the default and scoped+expiry states. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the perch tour with a sixth act that shows a Nido account holding several co-signers and requiring a quorum. perch scopes *what* a key may do; Nido's OZ multisig policy governs *how many* must sign — composed on one account, no bespoke account code either way. - policyModel: a rendered policy panel (signers × rules matrix) reused by Act 5's full-policy view and Act 6; adds the 2-of-3 `ops-quorum` rule. - perchOnchain: `proveThreshold(feeKp, keyCount)` drives the 2-of-3 account's Default rule live — M assertions over one auth digest in a single AuthPayload (injectSignedAuthPayload). Mirrors the proven invokeBoardCall footprint flow; the below-threshold case borrows the passing footprint to land a real, cleanly-failed on-chain tx. - scripts/prove-threshold.ts: deploys the 2-of-3 account (multisig policy, threshold=2) and reproduces allow(2)/deny(1) on testnet. - e2e: Act 6 steps prove 2-of-3 allowed / 1-of-3 denied live; regenerates 08/09/10 screenshots. Drops two stale pre-Act-6 artifacts. - README: six-act framing, Act 6 deployed pieces + proof tx hashes. Proven live on testnet: account CCJLM2X6…, 2-sig allow 21302c3e…, 1-sig deny 6f0265f9… (FAILED). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AVMqraqa1ERu7HATfbNiEj
Plan expiredYour subscription has expired. Please renew your subscription to continue using CI/CD integration and other features. |

A single guided, six-act tour teaching smart accounts, the OpenZeppelin model, what Nido adds, how perch makes authorization easier and safer, and how a rule can require M-of-N signatures — each ending in a live testnet demonstration. Styled in Nido's "Warm Nest" design language. Stacks on #189 (testkit); supersedes #190 (the standalone console is folded in, so the repo ships one example).
Six acts
__check_auth.Policycontrast + INV-2 footgun.2-of-3rule via Nido's OZ multisig policy — perch scopes what a key may do; the threshold policy governs how many must sign, composed on one account. Proven live.Real testnet proof
Act 5 (perch scope):
post→ SUCCESS (ab3d7802…);clear→ FAILED, interpreterenforcetrappedDenied(4170d3d2…). Policy compiled by perchperch-plan(stellar-registry/perch#31).Act 6 (M-of-N quorum): on the 2-of-3 account
CCJLM2X6…,postsigned by 2 of 3 → SUCCESS (21302c3e…); signed by 1 → FAILED, multisigenforcesays threshold not met (6f0265f9…). The M assertions over one auth digest ride a singleAuthPayload(injectSignedAuthPayload);scripts/prove-threshold.tsdeploys the account and reproduces both.Client-side footprint fix: recording sim skips
__check_auth, so re-simulate the signed tx (enforcing) and submit with that footprint — untested until here since every OZ/perch/nido test mocks auth.🤖 Generated with Claude Code