diff --git a/runtime/kusama/src/xcm_config.rs b/runtime/kusama/src/xcm_config.rs
index 7d69c1aad487..abc52f42c1be 100644
--- a/runtime/kusama/src/xcm_config.rs
+++ b/runtime/kusama/src/xcm_config.rs
@@ -22,7 +22,7 @@ use super::{
 };
 use frame_support::{
 	match_types, parameter_types,
-	traits::{Everything, Nothing},
+	traits::{Contains, Everything, Nothing},
 	weights::Weight,
 };
 use runtime_common::{xcm_sender, ToAuthor};
@@ -135,6 +135,185 @@ pub type Barrier = (
 	AllowSubscriptionsFrom<OnlyParachains>,
 );
 
+/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly
+/// account for proof size weights.
+///
+/// Calls that are allowed through this filter must:
+/// 1. Have a fixed weight;
+/// 2. Cannot lead to another call being made;
+/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters.
+pub struct SafeCallFilter;
+impl Contains<RuntimeCall> for SafeCallFilter {
+	fn contains(t: &RuntimeCall) -> bool {
+		match t {
+			RuntimeCall::System(
+				frame_system::Call::fill_block { .. } |
+				frame_system::Call::kill_prefix { .. } |
+				frame_system::Call::set_heap_pages { .. },
+			) |
+			RuntimeCall::Babe(..) |
+			RuntimeCall::Timestamp(..) |
+			RuntimeCall::Indices(..) |
+			RuntimeCall::Balances(..) |
+			RuntimeCall::Staking(
+				pallet_staking::Call::bond { .. } |
+				pallet_staking::Call::bond_extra { .. } |
+				pallet_staking::Call::unbond { .. } |
+				pallet_staking::Call::withdraw_unbonded { .. } |
+				pallet_staking::Call::validate { .. } |
+				pallet_staking::Call::chill { .. } |
+				pallet_staking::Call::set_payee { .. } |
+				pallet_staking::Call::set_controller { .. } |
+				pallet_staking::Call::set_validator_count { .. } |
+				pallet_staking::Call::increase_validator_count { .. } |
+				pallet_staking::Call::scale_validator_count { .. } |
+				pallet_staking::Call::force_no_eras { .. } |
+				pallet_staking::Call::force_new_era { .. } |
+				pallet_staking::Call::set_invulnerables { .. } |
+				pallet_staking::Call::force_unstake { .. } |
+				pallet_staking::Call::force_new_era_always { .. } |
+				pallet_staking::Call::payout_stakers { .. } |
+				pallet_staking::Call::unbond { .. } |
+				pallet_staking::Call::reap_stash { .. } |
+				pallet_staking::Call::set_staking_configs { .. } |
+				pallet_staking::Call::chill_other { .. } |
+				pallet_staking::Call::force_apply_min_commission { .. },
+			) |
+			RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) |
+			RuntimeCall::Grandpa(..) |
+			RuntimeCall::ImOnline(..) |
+			RuntimeCall::Democracy(
+				pallet_democracy::Call::second { .. } |
+				pallet_democracy::Call::vote { .. } |
+				pallet_democracy::Call::emergency_cancel { .. } |
+				pallet_democracy::Call::fast_track { .. } |
+				pallet_democracy::Call::veto_external { .. } |
+				pallet_democracy::Call::cancel_referendum { .. } |
+				pallet_democracy::Call::delegate { .. } |
+				pallet_democracy::Call::undelegate { .. } |
+				pallet_democracy::Call::clear_public_proposals { .. } |
+				pallet_democracy::Call::unlock { .. } |
+				pallet_democracy::Call::remove_vote { .. } |
+				pallet_democracy::Call::remove_other_vote { .. } |
+				pallet_democracy::Call::blacklist { .. } |
+				pallet_democracy::Call::cancel_proposal { .. },
+			) |
+			RuntimeCall::Council(
+				pallet_collective::Call::vote { .. } |
+				pallet_collective::Call::close_old_weight { .. } |
+				pallet_collective::Call::disapprove_proposal { .. } |
+				pallet_collective::Call::close { .. },
+			) |
+			RuntimeCall::TechnicalCommittee(
+				pallet_collective::Call::vote { .. } |
+				pallet_collective::Call::close_old_weight { .. } |
+				pallet_collective::Call::disapprove_proposal { .. } |
+				pallet_collective::Call::close { .. },
+			) |
+			RuntimeCall::PhragmenElection(
+				pallet_elections_phragmen::Call::remove_voter { .. } |
+				pallet_elections_phragmen::Call::submit_candidacy { .. } |
+				pallet_elections_phragmen::Call::renounce_candidacy { .. } |
+				pallet_elections_phragmen::Call::remove_member { .. } |
+				pallet_elections_phragmen::Call::clean_defunct_voters { .. },
+			) |
+			RuntimeCall::TechnicalMembership(
+				pallet_membership::Call::add_member { .. } |
+				pallet_membership::Call::remove_member { .. } |
+				pallet_membership::Call::swap_member { .. } |
+				pallet_membership::Call::change_key { .. } |
+				pallet_membership::Call::set_prime { .. } |
+				pallet_membership::Call::clear_prime { .. },
+			) |
+			RuntimeCall::Treasury(..) |
+			RuntimeCall::ConvictionVoting(..) |
+			RuntimeCall::Referenda(
+				pallet_referenda::Call::place_decision_deposit { .. } |
+				pallet_referenda::Call::refund_decision_deposit { .. } |
+				pallet_referenda::Call::cancel { .. } |
+				pallet_referenda::Call::kill { .. } |
+				pallet_referenda::Call::nudge_referendum { .. } |
+				pallet_referenda::Call::one_fewer_deciding { .. },
+			) |
+			RuntimeCall::FellowshipCollective(..) |
+			RuntimeCall::FellowshipReferenda(
+				pallet_referenda::Call::place_decision_deposit { .. } |
+				pallet_referenda::Call::refund_decision_deposit { .. } |
+				pallet_referenda::Call::cancel { .. } |
+				pallet_referenda::Call::kill { .. } |
+				pallet_referenda::Call::nudge_referendum { .. } |
+				pallet_referenda::Call::one_fewer_deciding { .. },
+			) |
+			RuntimeCall::Claims(
+				super::claims::Call::claim { .. } |
+				super::claims::Call::mint_claim { .. } |
+				super::claims::Call::move_claim { .. },
+			) |
+			RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) |
+			RuntimeCall::Identity(
+				pallet_identity::Call::add_registrar { .. } |
+				pallet_identity::Call::set_identity { .. } |
+				pallet_identity::Call::clear_identity { .. } |
+				pallet_identity::Call::request_judgement { .. } |
+				pallet_identity::Call::cancel_request { .. } |
+				pallet_identity::Call::set_fee { .. } |
+				pallet_identity::Call::set_account_id { .. } |
+				pallet_identity::Call::set_fields { .. } |
+				pallet_identity::Call::provide_judgement { .. } |
+				pallet_identity::Call::kill_identity { .. } |
+				pallet_identity::Call::add_sub { .. } |
+				pallet_identity::Call::rename_sub { .. } |
+				pallet_identity::Call::remove_sub { .. } |
+				pallet_identity::Call::quit_sub { .. },
+			) |
+			RuntimeCall::Society(
+				pallet_society::Call::bid { .. } |
+				pallet_society::Call::unbid { .. } |
+				pallet_society::Call::vouch { .. } |
+				pallet_society::Call::unvouch { .. } |
+				pallet_society::Call::vote { .. } |
+				pallet_society::Call::defender_vote { .. } |
+				pallet_society::Call::payout { .. } |
+				pallet_society::Call::unfound { .. } |
+				pallet_society::Call::judge_suspended_member { .. } |
+				pallet_society::Call::judge_suspended_candidate { .. } |
+				pallet_society::Call::set_max_members { .. },
+			) |
+			RuntimeCall::Recovery(..) |
+			RuntimeCall::Vesting(..) |
+			RuntimeCall::Bounties(
+				pallet_bounties::Call::propose_bounty { .. } |
+				pallet_bounties::Call::approve_bounty { .. } |
+				pallet_bounties::Call::propose_curator { .. } |
+				pallet_bounties::Call::unassign_curator { .. } |
+				pallet_bounties::Call::accept_curator { .. } |
+				pallet_bounties::Call::award_bounty { .. } |
+				pallet_bounties::Call::claim_bounty { .. } |
+				pallet_bounties::Call::close_bounty { .. },
+			) |
+			RuntimeCall::ChildBounties(..) |
+			RuntimeCall::ElectionProviderMultiPhase(..) |
+			RuntimeCall::Gilt(..) |
+			RuntimeCall::VoterList(..) |
+			RuntimeCall::NominationPools(
+				pallet_nomination_pools::Call::join { .. } |
+				pallet_nomination_pools::Call::bond_extra { .. } |
+				pallet_nomination_pools::Call::claim_payout { .. } |
+				pallet_nomination_pools::Call::unbond { .. } |
+				pallet_nomination_pools::Call::pool_withdraw_unbonded { .. } |
+				pallet_nomination_pools::Call::withdraw_unbonded { .. } |
+				pallet_nomination_pools::Call::create { .. } |
+				pallet_nomination_pools::Call::create_with_pool_id { .. } |
+				pallet_nomination_pools::Call::set_state { .. } |
+				pallet_nomination_pools::Call::set_configs { .. } |
+				pallet_nomination_pools::Call::update_roles { .. } |
+				pallet_nomination_pools::Call::chill { .. },
+			) => true,
+			_ => false,
+		}
+	}
+}
+
 pub struct XcmConfig;
 impl xcm_executor::Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
@@ -166,6 +345,7 @@ impl xcm_executor::Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = SafeCallFilter;
 }
 
 parameter_types! {
diff --git a/runtime/parachains/src/ump.rs b/runtime/parachains/src/ump.rs
index 2bd4056bdd45..b8e642de654f 100644
--- a/runtime/parachains/src/ump.rs
+++ b/runtime/parachains/src/ump.rs
@@ -505,6 +505,8 @@ impl<T: Config> Pallet<T> {
 
 	/// Devote some time into dispatching pending upward messages.
 	pub(crate) fn process_pending_upward_messages() -> Weight {
+		const MAX_MESSAGES_PER_BLOCK: u8 = 10;
+		let mut messages_processed = 0;
 		let mut weight_used = Weight::zero();
 
 		let config = <configuration::Pallet<T>>::config();
@@ -512,7 +514,12 @@ impl<T: Config> Pallet<T> {
 		let mut queue_cache = QueueCache::new();
 
 		while let Some(dispatchee) = cursor.peek() {
-			if weight_used.any_gte(config.ump_service_total_weight) {
+			if weight_used.any_gte(config.ump_service_total_weight) ||
+				messages_processed >= MAX_MESSAGES_PER_BLOCK
+			{
+				// Temporarily allow for processing of a max of 10 messages per block, until we
+				// properly account for proof size weights.
+				//
 				// Then check whether we've reached or overshoot the
 				// preferred weight for the dispatching stage.
 				//
@@ -534,6 +541,7 @@ impl<T: Config> Pallet<T> {
 			// our remaining weight limit, then consume it.
 			let maybe_next = queue_cache.peek_front::<T>(dispatchee);
 			if let Some(upward_message) = maybe_next {
+				messages_processed += 1;
 				match T::UmpSink::process_upward_message(dispatchee, upward_message, max_weight) {
 					Ok(used) => {
 						weight_used += used;
diff --git a/runtime/polkadot/src/xcm_config.rs b/runtime/polkadot/src/xcm_config.rs
index fcdc0c6427f0..76d4d754eb4a 100644
--- a/runtime/polkadot/src/xcm_config.rs
+++ b/runtime/polkadot/src/xcm_config.rs
@@ -22,7 +22,7 @@ use super::{
 };
 use frame_support::{
 	match_types, parameter_types,
-	traits::{Everything, Nothing},
+	traits::{Contains, Everything, Nothing},
 	weights::Weight,
 };
 use runtime_common::{xcm_sender, ToAuthor};
@@ -131,6 +131,152 @@ pub type Barrier = (
 	AllowSubscriptionsFrom<OnlyParachains>,
 );
 
+/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly
+/// account for proof size weights.
+///
+/// Calls that are allowed through this filter must:
+/// 1. Have a fixed weight;
+/// 2. Cannot lead to another call being made;
+/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters.
+pub struct SafeCallFilter;
+impl Contains<RuntimeCall> for SafeCallFilter {
+	fn contains(t: &RuntimeCall) -> bool {
+		match t {
+			RuntimeCall::System(
+				frame_system::Call::fill_block { .. } |
+				frame_system::Call::kill_prefix { .. } |
+				frame_system::Call::set_heap_pages { .. },
+			) |
+			RuntimeCall::Babe(..) |
+			RuntimeCall::Timestamp(..) |
+			RuntimeCall::Indices(..) |
+			RuntimeCall::Balances(..) |
+			RuntimeCall::Staking(
+				pallet_staking::Call::bond { .. } |
+				pallet_staking::Call::bond_extra { .. } |
+				pallet_staking::Call::unbond { .. } |
+				pallet_staking::Call::withdraw_unbonded { .. } |
+				pallet_staking::Call::validate { .. } |
+				pallet_staking::Call::chill { .. } |
+				pallet_staking::Call::set_payee { .. } |
+				pallet_staking::Call::set_controller { .. } |
+				pallet_staking::Call::set_validator_count { .. } |
+				pallet_staking::Call::increase_validator_count { .. } |
+				pallet_staking::Call::scale_validator_count { .. } |
+				pallet_staking::Call::force_no_eras { .. } |
+				pallet_staking::Call::force_new_era { .. } |
+				pallet_staking::Call::set_invulnerables { .. } |
+				pallet_staking::Call::force_unstake { .. } |
+				pallet_staking::Call::force_new_era_always { .. } |
+				pallet_staking::Call::payout_stakers { .. } |
+				pallet_staking::Call::unbond { .. } |
+				pallet_staking::Call::reap_stash { .. } |
+				pallet_staking::Call::set_staking_configs { .. } |
+				pallet_staking::Call::chill_other { .. } |
+				pallet_staking::Call::force_apply_min_commission { .. },
+			) |
+			RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) |
+			RuntimeCall::Grandpa(..) |
+			RuntimeCall::ImOnline(..) |
+			RuntimeCall::Democracy(
+				pallet_democracy::Call::second { .. } |
+				pallet_democracy::Call::vote { .. } |
+				pallet_democracy::Call::emergency_cancel { .. } |
+				pallet_democracy::Call::fast_track { .. } |
+				pallet_democracy::Call::veto_external { .. } |
+				pallet_democracy::Call::cancel_referendum { .. } |
+				pallet_democracy::Call::delegate { .. } |
+				pallet_democracy::Call::undelegate { .. } |
+				pallet_democracy::Call::clear_public_proposals { .. } |
+				pallet_democracy::Call::unlock { .. } |
+				pallet_democracy::Call::remove_vote { .. } |
+				pallet_democracy::Call::remove_other_vote { .. } |
+				pallet_democracy::Call::blacklist { .. } |
+				pallet_democracy::Call::cancel_proposal { .. },
+			) |
+			RuntimeCall::Council(
+				pallet_collective::Call::vote { .. } |
+				pallet_collective::Call::close_old_weight { .. } |
+				pallet_collective::Call::disapprove_proposal { .. } |
+				pallet_collective::Call::close { .. },
+			) |
+			RuntimeCall::TechnicalCommittee(
+				pallet_collective::Call::vote { .. } |
+				pallet_collective::Call::close_old_weight { .. } |
+				pallet_collective::Call::disapprove_proposal { .. } |
+				pallet_collective::Call::close { .. },
+			) |
+			RuntimeCall::PhragmenElection(
+				pallet_elections_phragmen::Call::remove_voter { .. } |
+				pallet_elections_phragmen::Call::submit_candidacy { .. } |
+				pallet_elections_phragmen::Call::renounce_candidacy { .. } |
+				pallet_elections_phragmen::Call::remove_member { .. } |
+				pallet_elections_phragmen::Call::clean_defunct_voters { .. },
+			) |
+			RuntimeCall::TechnicalMembership(
+				pallet_membership::Call::add_member { .. } |
+				pallet_membership::Call::remove_member { .. } |
+				pallet_membership::Call::swap_member { .. } |
+				pallet_membership::Call::change_key { .. } |
+				pallet_membership::Call::set_prime { .. } |
+				pallet_membership::Call::clear_prime { .. },
+			) |
+			RuntimeCall::Treasury(..) |
+			RuntimeCall::Claims(
+				super::claims::Call::claim { .. } |
+				super::claims::Call::mint_claim { .. } |
+				super::claims::Call::move_claim { .. },
+			) |
+			RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) |
+			RuntimeCall::Identity(
+				pallet_identity::Call::add_registrar { .. } |
+				pallet_identity::Call::set_identity { .. } |
+				pallet_identity::Call::clear_identity { .. } |
+				pallet_identity::Call::request_judgement { .. } |
+				pallet_identity::Call::cancel_request { .. } |
+				pallet_identity::Call::set_fee { .. } |
+				pallet_identity::Call::set_account_id { .. } |
+				pallet_identity::Call::set_fields { .. } |
+				pallet_identity::Call::provide_judgement { .. } |
+				pallet_identity::Call::kill_identity { .. } |
+				pallet_identity::Call::add_sub { .. } |
+				pallet_identity::Call::rename_sub { .. } |
+				pallet_identity::Call::remove_sub { .. } |
+				pallet_identity::Call::quit_sub { .. },
+			) |
+			RuntimeCall::Vesting(..) |
+			RuntimeCall::Bounties(
+				pallet_bounties::Call::propose_bounty { .. } |
+				pallet_bounties::Call::approve_bounty { .. } |
+				pallet_bounties::Call::propose_curator { .. } |
+				pallet_bounties::Call::unassign_curator { .. } |
+				pallet_bounties::Call::accept_curator { .. } |
+				pallet_bounties::Call::award_bounty { .. } |
+				pallet_bounties::Call::claim_bounty { .. } |
+				pallet_bounties::Call::close_bounty { .. },
+			) |
+			RuntimeCall::ChildBounties(..) |
+			RuntimeCall::ElectionProviderMultiPhase(..) |
+			RuntimeCall::VoterList(..) |
+			RuntimeCall::NominationPools(
+				pallet_nomination_pools::Call::join { .. } |
+				pallet_nomination_pools::Call::bond_extra { .. } |
+				pallet_nomination_pools::Call::claim_payout { .. } |
+				pallet_nomination_pools::Call::unbond { .. } |
+				pallet_nomination_pools::Call::pool_withdraw_unbonded { .. } |
+				pallet_nomination_pools::Call::withdraw_unbonded { .. } |
+				pallet_nomination_pools::Call::create { .. } |
+				pallet_nomination_pools::Call::create_with_pool_id { .. } |
+				pallet_nomination_pools::Call::set_state { .. } |
+				pallet_nomination_pools::Call::set_configs { .. } |
+				pallet_nomination_pools::Call::update_roles { .. } |
+				pallet_nomination_pools::Call::chill { .. },
+			) => true,
+			_ => false,
+		}
+	}
+}
+
 pub struct XcmConfig;
 impl xcm_executor::Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
@@ -159,6 +305,7 @@ impl xcm_executor::Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = SafeCallFilter;
 }
 
 parameter_types! {
diff --git a/runtime/rococo/src/xcm_config.rs b/runtime/rococo/src/xcm_config.rs
index 2b35d4e9a11f..bd550c97bff4 100644
--- a/runtime/rococo/src/xcm_config.rs
+++ b/runtime/rococo/src/xcm_config.rs
@@ -22,7 +22,7 @@ use super::{
 };
 use frame_support::{
 	match_types, parameter_types,
-	traits::{Everything, Nothing},
+	traits::{Contains, Everything, Nothing},
 	weights::Weight,
 };
 use runtime_common::{xcm_sender, ToAuthor};
@@ -133,6 +133,127 @@ pub type Barrier = (
 	AllowSubscriptionsFrom<OnlyParachains>,
 );
 
+/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly
+/// account for proof size weights.
+///
+/// Calls that are allowed through this filter must:
+/// 1. Have a fixed weight;
+/// 2. Cannot lead to another call being made;
+/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters.
+pub struct SafeCallFilter;
+impl Contains<RuntimeCall> for SafeCallFilter {
+	fn contains(t: &RuntimeCall) -> bool {
+		match t {
+			RuntimeCall::System(
+				frame_system::Call::fill_block { .. } |
+				frame_system::Call::kill_prefix { .. } |
+				frame_system::Call::set_heap_pages { .. },
+			) |
+			RuntimeCall::Babe(..) |
+			RuntimeCall::Timestamp(..) |
+			RuntimeCall::Indices(..) |
+			RuntimeCall::Balances(..) |
+			RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) |
+			RuntimeCall::Grandpa(..) |
+			RuntimeCall::ImOnline(..) |
+			RuntimeCall::Democracy(
+				pallet_democracy::Call::second { .. } |
+				pallet_democracy::Call::vote { .. } |
+				pallet_democracy::Call::emergency_cancel { .. } |
+				pallet_democracy::Call::fast_track { .. } |
+				pallet_democracy::Call::veto_external { .. } |
+				pallet_democracy::Call::cancel_referendum { .. } |
+				pallet_democracy::Call::delegate { .. } |
+				pallet_democracy::Call::undelegate { .. } |
+				pallet_democracy::Call::clear_public_proposals { .. } |
+				pallet_democracy::Call::unlock { .. } |
+				pallet_democracy::Call::remove_vote { .. } |
+				pallet_democracy::Call::remove_other_vote { .. } |
+				pallet_democracy::Call::blacklist { .. } |
+				pallet_democracy::Call::cancel_proposal { .. },
+			) |
+			RuntimeCall::Council(
+				pallet_collective::Call::vote { .. } |
+				pallet_collective::Call::close_old_weight { .. } |
+				pallet_collective::Call::disapprove_proposal { .. } |
+				pallet_collective::Call::close { .. },
+			) |
+			RuntimeCall::TechnicalCommittee(
+				pallet_collective::Call::vote { .. } |
+				pallet_collective::Call::close_old_weight { .. } |
+				pallet_collective::Call::disapprove_proposal { .. } |
+				pallet_collective::Call::close { .. },
+			) |
+			RuntimeCall::PhragmenElection(
+				pallet_elections_phragmen::Call::remove_voter { .. } |
+				pallet_elections_phragmen::Call::submit_candidacy { .. } |
+				pallet_elections_phragmen::Call::renounce_candidacy { .. } |
+				pallet_elections_phragmen::Call::remove_member { .. } |
+				pallet_elections_phragmen::Call::clean_defunct_voters { .. },
+			) |
+			RuntimeCall::TechnicalMembership(
+				pallet_membership::Call::add_member { .. } |
+				pallet_membership::Call::remove_member { .. } |
+				pallet_membership::Call::swap_member { .. } |
+				pallet_membership::Call::change_key { .. } |
+				pallet_membership::Call::set_prime { .. } |
+				pallet_membership::Call::clear_prime { .. },
+			) |
+			RuntimeCall::Treasury(..) |
+			RuntimeCall::Claims(
+				super::claims::Call::claim { .. } |
+				super::claims::Call::mint_claim { .. } |
+				super::claims::Call::move_claim { .. },
+			) |
+			RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) |
+			RuntimeCall::Identity(
+				pallet_identity::Call::add_registrar { .. } |
+				pallet_identity::Call::set_identity { .. } |
+				pallet_identity::Call::clear_identity { .. } |
+				pallet_identity::Call::request_judgement { .. } |
+				pallet_identity::Call::cancel_request { .. } |
+				pallet_identity::Call::set_fee { .. } |
+				pallet_identity::Call::set_account_id { .. } |
+				pallet_identity::Call::set_fields { .. } |
+				pallet_identity::Call::provide_judgement { .. } |
+				pallet_identity::Call::kill_identity { .. } |
+				pallet_identity::Call::add_sub { .. } |
+				pallet_identity::Call::rename_sub { .. } |
+				pallet_identity::Call::remove_sub { .. } |
+				pallet_identity::Call::quit_sub { .. },
+			) |
+			RuntimeCall::Society(
+				pallet_society::Call::bid { .. } |
+				pallet_society::Call::unbid { .. } |
+				pallet_society::Call::vouch { .. } |
+				pallet_society::Call::unvouch { .. } |
+				pallet_society::Call::vote { .. } |
+				pallet_society::Call::defender_vote { .. } |
+				pallet_society::Call::payout { .. } |
+				pallet_society::Call::unfound { .. } |
+				pallet_society::Call::judge_suspended_member { .. } |
+				pallet_society::Call::judge_suspended_candidate { .. } |
+				pallet_society::Call::set_max_members { .. },
+			) |
+			RuntimeCall::Recovery(..) |
+			RuntimeCall::Vesting(..) |
+			RuntimeCall::Bounties(
+				pallet_bounties::Call::propose_bounty { .. } |
+				pallet_bounties::Call::approve_bounty { .. } |
+				pallet_bounties::Call::propose_curator { .. } |
+				pallet_bounties::Call::unassign_curator { .. } |
+				pallet_bounties::Call::accept_curator { .. } |
+				pallet_bounties::Call::award_bounty { .. } |
+				pallet_bounties::Call::claim_bounty { .. } |
+				pallet_bounties::Call::close_bounty { .. },
+			) |
+			RuntimeCall::ChildBounties(..) |
+			RuntimeCall::Gilt(..) => true,
+			_ => false,
+		}
+	}
+}
+
 pub struct XcmConfig;
 impl xcm_executor::Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
@@ -162,6 +283,7 @@ impl xcm_executor::Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = SafeCallFilter;
 }
 
 parameter_types! {
diff --git a/runtime/test-runtime/src/xcm_config.rs b/runtime/test-runtime/src/xcm_config.rs
index 992b116e98ea..7308772e0e6e 100644
--- a/runtime/test-runtime/src/xcm_config.rs
+++ b/runtime/test-runtime/src/xcm_config.rs
@@ -104,4 +104,5 @@ impl xcm_executor::Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = super::RuntimeCall;
+	type SafeCallFilter = Everything;
 }
diff --git a/runtime/westend/src/xcm_config.rs b/runtime/westend/src/xcm_config.rs
index 2ad4ede3e247..123dfbc9aa97 100644
--- a/runtime/westend/src/xcm_config.rs
+++ b/runtime/westend/src/xcm_config.rs
@@ -22,7 +22,7 @@ use super::{
 };
 use frame_support::{
 	parameter_types,
-	traits::{Everything, Nothing},
+	traits::{Contains, Everything, Nothing},
 };
 use runtime_common::{xcm_sender, ToAuthor};
 use xcm::latest::prelude::*;
@@ -99,6 +99,93 @@ pub type Barrier = (
 	AllowSubscriptionsFrom<Everything>,
 );
 
+/// A call filter for the XCM Transact instruction. This is a temporary measure until we properly
+/// account for proof size weights.
+///
+/// Calls that are allowed through this filter must:
+/// 1. Have a fixed weight;
+/// 2. Cannot lead to another call being made;
+/// 3. Have a defined proof size weight, e.g. no unbounded vecs in call parameters.
+pub struct SafeCallFilter;
+impl Contains<RuntimeCall> for SafeCallFilter {
+	fn contains(t: &RuntimeCall) -> bool {
+		match t {
+			RuntimeCall::System(
+				frame_system::Call::fill_block { .. } |
+				frame_system::Call::kill_prefix { .. } |
+				frame_system::Call::set_heap_pages { .. },
+			) |
+			RuntimeCall::Babe(..) |
+			RuntimeCall::Timestamp(..) |
+			RuntimeCall::Indices(..) |
+			RuntimeCall::Balances(..) |
+			RuntimeCall::Staking(
+				pallet_staking::Call::bond { .. } |
+				pallet_staking::Call::bond_extra { .. } |
+				pallet_staking::Call::unbond { .. } |
+				pallet_staking::Call::withdraw_unbonded { .. } |
+				pallet_staking::Call::validate { .. } |
+				pallet_staking::Call::chill { .. } |
+				pallet_staking::Call::set_payee { .. } |
+				pallet_staking::Call::set_controller { .. } |
+				pallet_staking::Call::set_validator_count { .. } |
+				pallet_staking::Call::increase_validator_count { .. } |
+				pallet_staking::Call::scale_validator_count { .. } |
+				pallet_staking::Call::force_no_eras { .. } |
+				pallet_staking::Call::force_new_era { .. } |
+				pallet_staking::Call::set_invulnerables { .. } |
+				pallet_staking::Call::force_unstake { .. } |
+				pallet_staking::Call::force_new_era_always { .. } |
+				pallet_staking::Call::payout_stakers { .. } |
+				pallet_staking::Call::unbond { .. } |
+				pallet_staking::Call::reap_stash { .. } |
+				pallet_staking::Call::set_staking_configs { .. } |
+				pallet_staking::Call::chill_other { .. } |
+				pallet_staking::Call::force_apply_min_commission { .. },
+			) |
+			RuntimeCall::Session(pallet_session::Call::purge_keys { .. }) |
+			RuntimeCall::Grandpa(..) |
+			RuntimeCall::ImOnline(..) |
+			RuntimeCall::Utility(pallet_utility::Call::as_derivative { .. }) |
+			RuntimeCall::Identity(
+				pallet_identity::Call::add_registrar { .. } |
+				pallet_identity::Call::set_identity { .. } |
+				pallet_identity::Call::clear_identity { .. } |
+				pallet_identity::Call::request_judgement { .. } |
+				pallet_identity::Call::cancel_request { .. } |
+				pallet_identity::Call::set_fee { .. } |
+				pallet_identity::Call::set_account_id { .. } |
+				pallet_identity::Call::set_fields { .. } |
+				pallet_identity::Call::provide_judgement { .. } |
+				pallet_identity::Call::kill_identity { .. } |
+				pallet_identity::Call::add_sub { .. } |
+				pallet_identity::Call::rename_sub { .. } |
+				pallet_identity::Call::remove_sub { .. } |
+				pallet_identity::Call::quit_sub { .. },
+			) |
+			RuntimeCall::Recovery(..) |
+			RuntimeCall::Vesting(..) |
+			RuntimeCall::ElectionProviderMultiPhase(..) |
+			RuntimeCall::VoterList(..) |
+			RuntimeCall::NominationPools(
+				pallet_nomination_pools::Call::join { .. } |
+				pallet_nomination_pools::Call::bond_extra { .. } |
+				pallet_nomination_pools::Call::claim_payout { .. } |
+				pallet_nomination_pools::Call::unbond { .. } |
+				pallet_nomination_pools::Call::pool_withdraw_unbonded { .. } |
+				pallet_nomination_pools::Call::withdraw_unbonded { .. } |
+				pallet_nomination_pools::Call::create { .. } |
+				pallet_nomination_pools::Call::create_with_pool_id { .. } |
+				pallet_nomination_pools::Call::set_state { .. } |
+				pallet_nomination_pools::Call::set_configs { .. } |
+				pallet_nomination_pools::Call::update_roles { .. } |
+				pallet_nomination_pools::Call::chill { .. },
+			) => true,
+			_ => false,
+		}
+	}
+}
+
 pub struct XcmConfig;
 impl xcm_executor::Config for XcmConfig {
 	type RuntimeCall = RuntimeCall;
@@ -125,6 +212,7 @@ impl xcm_executor::Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = SafeCallFilter;
 }
 
 /// Type to convert an `Origin` type value into a `MultiLocation` value which represents an interior location
diff --git a/xcm/pallet-xcm/src/mock.rs b/xcm/pallet-xcm/src/mock.rs
index 9c0d34194ab0..11f343fd3cb3 100644
--- a/xcm/pallet-xcm/src/mock.rs
+++ b/xcm/pallet-xcm/src/mock.rs
@@ -301,6 +301,7 @@ impl xcm_executor::Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = Everything;
 }
 
 pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, AnyNetwork>;
diff --git a/xcm/xcm-builder/src/tests/mock.rs b/xcm/xcm-builder/src/tests/mock.rs
index eb7fd9491c29..3662a4acf7b5 100644
--- a/xcm/xcm-builder/src/tests/mock.rs
+++ b/xcm/xcm-builder/src/tests/mock.rs
@@ -19,7 +19,7 @@ pub use crate::{
 	AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, AllowTopLevelPaidExecutionFrom,
 	AllowUnpaidExecutionFrom, FixedRateOfFungible, FixedWeightBounds, TakeWeightCredit,
 };
-use frame_support::traits::ContainsPair;
+use frame_support::traits::{ContainsPair, Everything};
 pub use frame_support::{
 	dispatch::{
 		DispatchError, DispatchInfo, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo,
@@ -642,6 +642,7 @@ impl Config for TestConfig {
 	type UniversalAliases = TestUniversalAliases;
 	type MessageExporter = TestMessageExporter;
 	type CallDispatcher = TestCall;
+	type SafeCallFilter = Everything;
 }
 
 pub fn fungible_multi_asset(location: MultiLocation, amount: u128) -> MultiAsset {
diff --git a/xcm/xcm-builder/tests/mock/mod.rs b/xcm/xcm-builder/tests/mock/mod.rs
index 99e7843f8dc1..a4b3439b3392 100644
--- a/xcm/xcm-builder/tests/mock/mod.rs
+++ b/xcm/xcm-builder/tests/mock/mod.rs
@@ -196,6 +196,7 @@ impl xcm_executor::Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = Everything;
 }
 
 pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, KusamaNetwork>;
diff --git a/xcm/xcm-executor/src/config.rs b/xcm/xcm-executor/src/config.rs
index bd8143adcb7b..9bb98055fb20 100644
--- a/xcm/xcm-executor/src/config.rs
+++ b/xcm/xcm-executor/src/config.rs
@@ -100,4 +100,10 @@ pub trait Config {
 	/// XCM will use this to dispatch any calls. When no special call dispatcher is required,
 	/// this can be set to the same type as `Self::Call`.
 	type CallDispatcher: CallDispatcher<Self::RuntimeCall>;
+
+	/// The safe call filter for `Transact`.
+	///
+	/// Use this type to explicitly whitelist calls that cannot undergo recursion. This is a
+	/// temporary measure until we properly account for proof size weights for XCM instructions.
+	type SafeCallFilter: Contains<Self::RuntimeCall>;
 }
diff --git a/xcm/xcm-executor/src/lib.rs b/xcm/xcm-executor/src/lib.rs
index 07e6c063c486..06a1afe3f481 100644
--- a/xcm/xcm-executor/src/lib.rs
+++ b/xcm/xcm-executor/src/lib.rs
@@ -515,6 +515,7 @@ impl<Config: config::Config> XcmExecutor<Config> {
 
 				// TODO: #2841 #TRANSACTFILTER allow the trait to issue filters for the relay-chain
 				let message_call = call.take_decoded().map_err(|_| XcmError::FailedToDecode)?;
+				ensure!(Config::SafeCallFilter::contains(&message_call), XcmError::NoPermission);
 				let dispatch_origin = Config::OriginConverter::convert_origin(origin, origin_kind)
 					.map_err(|_| XcmError::BadOrigin)?;
 				let weight = message_call.get_dispatch_info().weight;
diff --git a/xcm/xcm-simulator/example/src/parachain.rs b/xcm/xcm-simulator/example/src/parachain.rs
index 023e1a41ac15..26dfd12de08c 100644
--- a/xcm/xcm-simulator/example/src/parachain.rs
+++ b/xcm/xcm-simulator/example/src/parachain.rs
@@ -242,6 +242,7 @@ impl Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = Everything;
 }
 
 #[frame_support::pallet]
diff --git a/xcm/xcm-simulator/example/src/relay_chain.rs b/xcm/xcm-simulator/example/src/relay_chain.rs
index c4161c0770a1..c70307cf042f 100644
--- a/xcm/xcm-simulator/example/src/relay_chain.rs
+++ b/xcm/xcm-simulator/example/src/relay_chain.rs
@@ -182,6 +182,7 @@ impl Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = Everything;
 }
 
 pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, RelayNetwork>;
diff --git a/xcm/xcm-simulator/fuzzer/src/parachain.rs b/xcm/xcm-simulator/fuzzer/src/parachain.rs
index 6598589f2b3e..2b6ab01e20ae 100644
--- a/xcm/xcm-simulator/fuzzer/src/parachain.rs
+++ b/xcm/xcm-simulator/fuzzer/src/parachain.rs
@@ -156,6 +156,7 @@ impl Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = Everything;
 }
 
 #[frame_support::pallet]
diff --git a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs
index 4a464b64ec01..4fdcf51789ee 100644
--- a/xcm/xcm-simulator/fuzzer/src/relay_chain.rs
+++ b/xcm/xcm-simulator/fuzzer/src/relay_chain.rs
@@ -147,6 +147,7 @@ impl Config for XcmConfig {
 	type MessageExporter = ();
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
+	type SafeCallFilter = Everything;
 }
 
 pub type LocalOriginToLocation = SignedToAccountId32<RuntimeOrigin, AccountId, ThisNetwork>;