Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ on:

jobs:
build-and-test:
runs-on: ubuntu-latest
runs-on: parity-ubuntu
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- uses: pnpm/action-setup@v4
- name: Enable Corepack
run: corepack enable

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand All @@ -36,15 +37,16 @@ jobs:
run: pnpm pack && ls -lh *.tgz

integration-tests:
runs-on: ubuntu-latest
runs-on: parity-ubuntu
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- uses: pnpm/action-setup@v4
- name: Enable Corepack
run: corepack enable

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/npm-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ on:

jobs:
publish:
runs-on: ubuntu-latest
runs-on: parity-ubuntu
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22

- uses: pnpm/action-setup@v4
- name: Enable Corepack
run: corepack enable

- name: Set version from release tag
run: |
Expand Down
23 changes: 0 additions & 23 deletions src/browser/host-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,14 +462,6 @@ function setupContainer(
// ── Sign payload (extrinsic) ─────────────────────────────────

container.handleSignPayload((params, { ok, err }) => {
if (enforcePermissions && !grantedPermissions.has("ChainSubmit")) {
console.error(
"[test-host] Signing rejected: product did not request ChainSubmit permission. " +
"Call hostApi.permission([{ tag: 'ChainSubmit' }]) first.",
);
return err(new SigningErr.PermissionDenied());
}

// params.account is [dotnsId, derivationIndex]
const [dotnsId, idx] = params.account;
const pair = getPairForProductAccount(config, pairs, dotnsId, idx);
Expand Down Expand Up @@ -514,13 +506,6 @@ function setupContainer(
// ── Sign raw ─────────────────────────────────────────────────

container.handleSignRaw((params, { ok, err }) => {
if (enforcePermissions && !grantedPermissions.has("ChainSubmit")) {
console.error(
"[test-host] Signing rejected: product did not request ChainSubmit permission.",
);
return err(new SigningErr.PermissionDenied());
}

// params.account is [dotnsId, derivationIndex]
const [dotnsId, idx] = params.account;
const pair = getPairForProductAccount(config, pairs, dotnsId, idx);
Expand Down Expand Up @@ -551,10 +536,6 @@ function setupContainer(
// ── Sign with legacy account (address-based) ───────────────

container.handleSignPayloadWithLegacyAccount((params, { ok, err }) => {
if (enforcePermissions && !grantedPermissions.has("ChainSubmit")) {
return err(new SigningErr.PermissionDenied());
}

const pair = getPairByAddress(params.signer);
if (!pair) {
return err(
Expand Down Expand Up @@ -589,10 +570,6 @@ function setupContainer(
});

container.handleSignRawWithLegacyAccount((params, { ok, err }) => {
if (enforcePermissions && !grantedPermissions.has("ChainSubmit")) {
return err(new SigningErr.PermissionDenied());
}

const pair = getPairByAddress(params.signer);
if (!pair) {
return err(
Expand Down
69 changes: 9 additions & 60 deletions test/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,9 +231,9 @@ test.describe('Root (non-product) accounts', () => {

// ── Permission enforcement ──────────────────────────────────────────

test.describe('Permission enforcement', () => {
test.describe('Permission handling', () => {

test('signing fails without ChainSubmit permission', async ({ page }) => {
test('signing works without explicit permission request', async ({ page }) => {
const host = await createTestHostServer({
productUrl: productServer.url,
accounts: ['alice'],
Expand All @@ -242,15 +242,17 @@ test.describe('Permission enforcement', () => {
try {
const product = await loadHostAndProduct(page, host.url, productServer.url);

// Attempt signing without requesting permission first
// In v0.7, signing doesn't require ChainSubmit — that permission
// is enforced by the container at transaction_broadcast level.
const result = await product.evaluate(() => window.__TEST_PRODUCT__.trySignRaw());
expect(result.ok).toBe(false);
expect(result.ok).toBe(true);
expect(result.signature).toBeTruthy();
} finally {
await host.close();
}
});

test('signing succeeds after requesting ChainSubmit permission', async ({ page }) => {
test('ChainSubmit permission request is logged', async ({ page }) => {
const host = await createTestHostServer({
productUrl: productServer.url,
accounts: ['alice'],
Expand All @@ -259,45 +261,18 @@ test.describe('Permission enforcement', () => {
try {
const product = await loadHostAndProduct(page, host.url, productServer.url);

// Request permission first
const permResult = await product.evaluate(() => window.__TEST_PRODUCT__.requestChainSubmit());
expect(permResult.ok).toBe(true);
expect(permResult.approved).toBe(true);

// Now signing should work
const signResult = await product.evaluate(() => window.__TEST_PRODUCT__.trySignRaw());
expect(signResult.ok).toBe(true);
expect(signResult.signature).toBeTruthy();

// Verify permission was logged on host side
const log = await page.evaluate(() => window.__TEST_HOST__.getPermissionLog());
expect(log.length).toBeGreaterThanOrEqual(1);
expect(log.some((e: any) => e.tag === 'ChainSubmit' && e.approved)).toBe(true);
} finally {
await host.close();
}
});

test('signing succeeds when enforcement is disabled', async ({ page }) => {
const host = await createTestHostServer({
productUrl: productServer.url,
accounts: ['alice'],
});

try {
const product = await loadHostAndProduct(page, host.url, productServer.url);

// Disable enforcement
await page.evaluate(() => window.__TEST_HOST__.setEnforcePermissions(false));

// Signing works without permission request
const result = await product.evaluate(() => window.__TEST_PRODUCT__.trySignRaw());
expect(result.ok).toBe(true);
} finally {
await host.close();
}
});

test('signing succeeds when permission is pre-granted via grantPermission', async ({ page }) => {
test('permission is rejected when behavior is reject-all', async ({ page }) => {
const host = await createTestHostServer({
productUrl: productServer.url,
accounts: ['alice'],
Expand All @@ -306,38 +281,12 @@ test.describe('Permission enforcement', () => {
try {
const product = await loadHostAndProduct(page, host.url, productServer.url);

// Pre-grant without product requesting
await page.evaluate(() => window.__TEST_HOST__.grantPermission('ChainSubmit'));

const result = await product.evaluate(() => window.__TEST_PRODUCT__.trySignRaw());
expect(result.ok).toBe(true);
} finally {
await host.close();
}
});

test('signing fails when permission is rejected', async ({ page }) => {
const host = await createTestHostServer({
productUrl: productServer.url,
accounts: ['alice'],
});

try {
const product = await loadHostAndProduct(page, host.url, productServer.url);

// Set reject-all behavior
await page.evaluate(() => window.__TEST_HOST__.setPermissionBehavior('reject-all'));

// Product requests permission — protocol round-trip succeeds but permission is denied
const permResult = await product.evaluate(() => window.__TEST_PRODUCT__.requestChainSubmit());
expect(permResult.ok).toBe(true);
expect(permResult.approved).toBe(false);

// Signing should still fail since permission was rejected
const signResult = await product.evaluate(() => window.__TEST_PRODUCT__.trySignRaw());
expect(signResult.ok).toBe(false);

// Permission log shows rejection
const log = await page.evaluate(() => window.__TEST_HOST__.getPermissionLog());
expect(log.some((e: any) => e.tag === 'ChainSubmit' && !e.approved)).toBe(true);
} finally {
Expand Down
Loading