Add a bounded configured-host development auth bypass
- commit
- 4f77397944a19a8f3853296095a173126b368a4d
- parent
- 97a6bdb05c1a6c0b879a1da6e217dc9bc6c6265a
- parent
- 26e145fcaa2af4911f0f56c62978a154a5d8907d
- author
- FelineStateMachine <dami.etoile@gmail.com>
@@ -113,8 +113,9 @@ jobs:
113113 test "$(jq -r .release_kind "$manifest")" = development114114 test "$(jq -r .release_tag "$manifest")" = development115115 test "$(jq -r .asset_name "$manifest")" = "$(basename -- "$archive")"116+ test "$(jq -r .demo_auth_bypass "$manifest")" = false116117 test "$(jq -r .demo_direct_node "$manifest")" = false117- test "$(jq -c .features "$manifest")" = '["default","in-process-gstreamer"]'118+ test "$(jq -c .features "$manifest")" = '["in-process-gstreamer"]'118119 printf '%s\n' \119120 "repository=$GITHUB_REPOSITORY" \120121 "source_commit=$UAT_COMMIT" \@@ -11,6 +11,9 @@ default-run = "sigil"
1111 1212 [features]1313 default = []14+# Explicitly insecure configured-host authorization mode for bounded tests.15+# Never infer this capability from the optimization or debug-assertion profile.16+demo-auth-bypass = []1417 in-process-gstreamer = ["dep:gstreamer", "dep:gstreamer-app", "dep:gstreamer-video"]1518 1619 [[bin]]@@ -32,6 +32,9 @@ pub enum AuthorizationPolicy {
3232 /// Explicit direct `sigil serve --identity ... --source test-pattern`3333 /// proof mode. Configured hosts never select this branch.3434 TestPatternProof,35+ /// Explicitly insecure configured-host mode for development hardware tests.36+ /// Ordinary release builds cannot select this branch.37+ DevelopmentBypass,3538 }3639 3740 @@ -600,10 +603,10 @@ impl AuthorizationPolicy {
600603 ) -> Result<InvitationGrants> {601604 match self {602605 Self::Required(store) => store.authorize_or_redeem(remote, invitation_token, now),603- Self::TestPatternProof => {606+ Self::TestPatternProof | Self::DevelopmentBypass => {604607 ensure!(605608 invitation_token.is_none(),606- "proof mode does not accept invitations"609+ "unrestricted development modes do not accept invitations"607610 );608611 Ok(InvitationGrants::ALL)609612 }@@ -868,6 +871,24 @@ mod tests {
868871 assert!(snapshot.peer.is_none());869872 }870873 874+ 875+ fn development_bypass_grants_all_without_touching_persistent_state() {876+ let directory = tempfile::tempdir().unwrap();877+ let remote = SecretKey::from_bytes(&[29; 32]).public();878+ let policy = AuthorizationPolicy::DevelopmentBypass;879+880+ assert_eq!(881+ policy.authorize_or_redeem(remote, None, 1_000).unwrap(),882+ InvitationGrants::ALL883+ );884+ assert!(885+ policy886+ .authorize_or_redeem(remote, Some("not-an-invitation"), 1_000)887+ .is_err()888+ );889+ assert!(fs::read_dir(directory.path()).unwrap().next().is_none());890+ }891+871892 872893 fn invitation_clock_boundaries_are_inclusive_and_fail_closed_outside_skew() {873894 let (_directory, host, peer, expiry_store) = store();@@ -2636,6 +2636,20 @@ fn load_invitation_file(
26362636 expected_host: EndpointId,26372637 expected_peer: EndpointId,26382638 required_grants: InvitationGrants,2639+) -> Result<String> {2640+ let now = SystemTime::now()2641+ .duration_since(UNIX_EPOCH)2642+ .context("system clock is before the Unix epoch")?2643+ .as_secs();2644+ load_invitation_file_at(path, expected_host, expected_peer, required_grants, now)2645+}2646+2647+fn load_invitation_file_at(2648+ path: &Path,2649+ expected_host: EndpointId,2650+ expected_peer: EndpointId,2651+ required_grants: InvitationGrants,2652+ now: u64,26392653 ) -> Result<String> {26402654 let mut options = OpenOptions::new();26412655 options.read(true);@@ -2713,10 +2727,6 @@ fn load_invitation_file(
27132727 || invitation.claims.grants.contains(InvitationGrants::GAMEPAD),27142728 "invitation must grant pointer/keyboard or gamepad for the probe input acknowledgment"27152729 );2716- let now = SystemTime::now()2717- .duration_since(UNIX_EPOCH)2718- .context("system clock is before the Unix epoch")?2719- .as_secs();27202730 ensure!(27212731 now >= invitation27222732 .claims@@ -3398,34 +3408,54 @@ mod tests {
33983408 33993409 write_private_invitation(&path, &token, b"\n");34003410 assert_eq!(3401- load_invitation_file(3411+ load_invitation_file_at(34023412 &path,34033413 host.public(),34043414 peer.public(),34053415 InvitationGrants::VIEW.union(InvitationGrants::GAMEPAD),3416+ now,34063417 )34073418 .unwrap(),34083419 token34093420 );34103421 write_private_invitation(&path, &token, b"\r\n");34113422 assert!(3412- load_invitation_file(&path, host.public(), peer.public(), InvitationGrants::VIEW,)3413- .is_ok()3423+ load_invitation_file_at(3424+ &path,3425+ host.public(),3426+ peer.public(),3427+ InvitationGrants::VIEW,3428+ now,3429+ )3430+ .is_ok()34143431 );34153432 assert!(3416- load_invitation_file(&path, other.public(), peer.public(), InvitationGrants::VIEW,)3417- .is_err()3433+ load_invitation_file_at(3434+ &path,3435+ other.public(),3436+ peer.public(),3437+ InvitationGrants::VIEW,3438+ now,3439+ )3440+ .is_err()34183441 );34193442 assert!(3420- load_invitation_file(&path, host.public(), other.public(), InvitationGrants::VIEW,)3421- .is_err()3443+ load_invitation_file_at(3444+ &path,3445+ host.public(),3446+ other.public(),3447+ InvitationGrants::VIEW,3448+ now,3449+ )3450+ .is_err()34223451 );34233452 assert!(3424- load_invitation_file(3453+ load_invitation_file_at(34253454 &path,34263455 host.public(),34273456 peer.public(),34283457 InvitationGrants::POINTER_KEYBOARD,3458+ now,34293459 )34303460 .is_err()34313461 );@@ -3439,10 +3469,16 @@ mod tests {
34393469 );34403470 write_private_invitation(&path, &view_only, b"\n");34413471 assert!(3442- load_invitation_file(&path, host.public(), peer.public(), InvitationGrants::VIEW,)3443- .unwrap_err()3444- .to_string()3445- .contains("input acknowledgment")3472+ load_invitation_file_at(3473+ &path,3474+ host.public(),3475+ peer.public(),3476+ InvitationGrants::VIEW,3477+ now,3478+ )3479+ .unwrap_err()3480+ .to_string()3481+ .contains("input acknowledgment")34463482 );34473483 34483484 let expired = test_invitation(@@ -3454,15 +3490,27 @@ mod tests {
34543490 );34553491 write_private_invitation(&path, &expired, b"\n");34563492 assert!(3457- load_invitation_file(&path, host.public(), peer.public(), InvitationGrants::VIEW,)3458- .unwrap_err()3459- .to_string()3460- .contains("expired")3493+ load_invitation_file_at(3494+ &path,3495+ host.public(),3496+ peer.public(),3497+ InvitationGrants::VIEW,3498+ now,3499+ )3500+ .unwrap_err()3501+ .to_string()3502+ .contains("expired")34613503 );34623504 write_private_invitation(&path, &token, b"\n\n");34633505 assert!(3464- load_invitation_file(&path, host.public(), peer.public(), InvitationGrants::VIEW,)3465- .is_err()3506+ load_invitation_file_at(3507+ &path,3508+ host.public(),3509+ peer.public(),3510+ InvitationGrants::VIEW,3511+ now,3512+ )3513+ .is_err()34663514 );34673515 let future = test_invitation(34683516 &host,@@ -3473,10 +3521,16 @@ mod tests {
34733521 );34743522 write_private_invitation(&path, &future, b"\n");34753523 assert!(3476- load_invitation_file(&path, host.public(), peer.public(), InvitationGrants::VIEW,)3477- .unwrap_err()3478- .to_string()3479- .contains("future")3524+ load_invitation_file_at(3525+ &path,3526+ host.public(),3527+ peer.public(),3528+ InvitationGrants::VIEW,3529+ now,3530+ )3531+ .unwrap_err()3532+ .to_string()3533+ .contains("future")34803534 );34813535 }34823536 @@ -286,6 +286,9 @@ struct ServeArgs {
286286 /// Strict TOML host configuration.287287 )]288288 config: Option<PathBuf>,289+ /// INSECURE: admit an un-enrolled Portal peer for configured-host testing.290+ 291+ dev_allow_unauthorized: bool,289292 /// Host identity for direct proof-mode configuration.290293 291294 identity: Option<PathBuf>,@@ -308,6 +311,8 @@ struct ServeArgs {
308311 max_runtime_seconds: Option<u64>,309312 }310313 314+const MAX_DEVELOPMENT_AUTH_BYPASS_SECONDS: u64 = 3_600;315+311316 enum CliFailure {312317 General(anyhow::Error),313318 Management(config_management::ManagementError),@@ -786,6 +791,7 @@ fn validate_capture_probe_args(args: &CaptureProbeArgs, configured_framerate: u3
786791 }787792 788793 async fn serve_command(args: ServeArgs) -> Result<()> {794+ validate_development_auth_bypass(args.dev_allow_unauthorized, args.max_runtime_seconds)?;789795 let configured_service = args.config.is_some();790796 let (config, loaded_config_revision, _lifecycle) = if let Some(config_path) = &args.config {791797 let (loaded, lifecycle) =@@ -806,12 +812,21 @@ async fn serve_command(args: ServeArgs) -> Result<()> {
806812 configured_service,807813 loaded_config_revision,808814 )?;809- let authorization_result = if configured_service {810- AuthorizationStore::open(config.state_path.clone(), secret.public())811- .map(AuthorizationPolicy::Required)812- } else {813- Ok(AuthorizationPolicy::TestPatternProof)814- };815+ let authorization_result = select_authorization_policy(816+ configured_service,817+ args.dev_allow_unauthorized,818+ &config.state_path,819+ secret.public(),820+ );821+ if args.dev_allow_unauthorized {822+ warn!(823+ max_runtime_seconds = args.max_runtime_seconds,824+ "DEVELOPMENT AUTHORIZATION BYPASS ACTIVE: any peer with this host node ID may request a session"825+ );826+ eprintln!(827+ "warning=development-authorization-bypass-active; do not expose or ship this host"828+ );829+ }815830 let authorization = match authorization_result {816831 Ok(authorization) => authorization,817832 Err(error) => {@@ -1046,6 +1061,45 @@ async fn serve_command(args: ServeArgs) -> Result<()> {
10461061 Ok(())10471062 }10481063 1064+const fn development_auth_bypass_available() -> bool {1065+ cfg!(feature = "demo-auth-bypass")1066+}1067+1068+fn validate_development_auth_bypass(1069+ requested: bool,1070+ max_runtime_seconds: Option<u64>,1071+) -> Result<()> {1072+ ensure!(1073+ !requested || development_auth_bypass_available(),1074+ "--dev-allow-unauthorized requires the explicit demo-auth-bypass feature; ordinary builds always require enrollment"1075+ );1076+ if requested {1077+ ensure!(1078+ matches!(1079+ max_runtime_seconds,1080+ Some(1..=MAX_DEVELOPMENT_AUTH_BYPASS_SECONDS)1081+ ),1082+ "--dev-allow-unauthorized requires --max-runtime-seconds between 1 and {MAX_DEVELOPMENT_AUTH_BYPASS_SECONDS}"1083+ );1084+ }1085+ Ok(())1086+}1087+1088+fn select_authorization_policy(1089+ configured_service: bool,1090+ development_bypass: bool,1091+ state_path: &Path,1092+ host: EndpointId,1093+) -> Result<AuthorizationPolicy> {1094+ if development_bypass {1095+ Ok(AuthorizationPolicy::DevelopmentBypass)1096+ } else if configured_service {1097+ AuthorizationStore::open(state_path.to_owned(), host).map(AuthorizationPolicy::Required)1098+ } else {1099+ Ok(AuthorizationPolicy::TestPatternProof)1100+ }1101+}1102+10491103 async fn wait_for_shutdown_signal() -> Result<&'static str> {10501104 10511105 {@@ -1127,6 +1181,7 @@ fn parse_size(value: &str) -> std::result::Result<(u32, u32), String> {
11271181 11281182 mod tests {11291183 use super::*;1184+ use std::fs;11301185 11311186 use std::os::unix::fs::PermissionsExt;11321187 @@ -1168,6 +1223,94 @@ mod tests {
11681223 );11691224 }11701225 1226+ 1227+ fn development_auth_bypass_requires_configured_host_syntax() {1228+ assert!(1229+ Cli::try_parse_from([1230+ "sigil",1231+ "serve",1232+ "--config",1233+ "/tmp/host.toml",1234+ "--dev-allow-unauthorized",1235+ "--max-runtime-seconds",1236+ "600",1237+ ])1238+ .is_ok()1239+ );1240+ assert!(1241+ Cli::try_parse_from([1242+ "sigil",1243+ "serve",1244+ "--identity",1245+ "/tmp/host.key",1246+ "--source",1247+ "test-pattern",1248+ "--dev-allow-unauthorized",1249+ "--max-runtime-seconds",1250+ "600",1251+ ])1252+ .is_err()1253+ );1254+ assert!(1255+ Cli::try_parse_from([1256+ "sigil",1257+ "serve",1258+ "--config",1259+ "/tmp/host.toml",1260+ "--dev-allow-unauthorized",1261+ ])1262+ .is_ok()1263+ );1264+ assert!(validate_development_auth_bypass(true, None).is_err());1265+ assert!(validate_development_auth_bypass(true, Some(0)).is_err());1266+ assert!(1267+ validate_development_auth_bypass(true, Some(MAX_DEVELOPMENT_AUTH_BYPASS_SECONDS + 1))1268+ .is_err()1269+ );1270+ }1271+1272+ 1273+ fn ordinary_release_excludes_configured_host_auth_bypass() {1274+ if cfg!(not(feature = "demo-auth-bypass")) {1275+ assert!(!development_auth_bypass_available());1276+ assert!(validate_development_auth_bypass(true, Some(60)).is_err());1277+ }1278+ }1279+1280+ 1281+ fn configured_host_auth_bypass_is_explicitly_build_contained() {1282+ assert_eq!(1283+ validate_development_auth_bypass(true, Some(60)).is_ok(),1284+ development_auth_bypass_available()1285+ );1286+ }1287+1288+ 1289+ fn development_auth_bypass_does_not_open_persistent_authorization_state() {1290+ let directory = tempfile::tempdir().unwrap();1291+ let state_path = directory.path();1292+ let state_file = state_path.join("authorization-v1.json");1293+ fs::write(&state_file, b"malformed-sentinel").unwrap();1294+ 1295+ fs::set_permissions(&state_file, fs::Permissions::from_mode(0o600)).unwrap();1296+ let before = fs::read(&state_file).unwrap();1297+ let host = iroh::SecretKey::from_bytes(&[31; 32]).public();1298+1299+ assert!(matches!(1300+ select_authorization_policy(true, true, state_path, host).unwrap(),1301+ AuthorizationPolicy::DevelopmentBypass1302+ ));1303+ assert_eq!(fs::read(&state_file).unwrap(), before);1304+ assert!(!state_path.join("authorization-v1.lock").exists());1305+1306+ let required_directory = tempfile::tempdir().unwrap();1307+ let required_state = required_directory.path().join("authorization-v1.json");1308+ fs::write(&required_state, b"malformed-sentinel").unwrap();1309+ 1310+ fs::set_permissions(&required_state, fs::Permissions::from_mode(0o600)).unwrap();1311+ assert!(select_authorization_policy(true, false, required_directory.path(), host).is_err());1312+ }1313+11711314 11721315 fn invitation_and_enrollment_commands_parse_strictly() {11731316 let peer = iroh::SecretKey::from_bytes(&[19; 32]).public().to_string();@@ -67,6 +67,22 @@ indicator while the bypass is active. Build the optimized temporary client
6767 with `cargo tauri build --features demo-direct-node`; do not ship that feature6868 in a production package.6969 70+`--dev-connect` alone does not authorize its ephemeral Portal identity. For an71+explicit configured-host hardware test without a security key, run Sigil with72+the explicit development feature:73+74+```bash75+cargo run -p sigil-host --features demo-auth-bypass -- serve \76+ --config <host.toml> --dev-allow-unauthorized --max-runtime-seconds 180077+```78+79+An optimized test binary requires the same feature. This host flag80+admits any peer that knows the node ID, grants all session capabilities, prints81+a startup warning, and deliberately avoids reading or mutating enrollment82+state. It requires `--max-runtime-seconds` between 1 and 3600 so the exposure83+expires automatically. Stop that process immediately after the bounded test.84+Ordinary release builds and shipped Sigil packages reject the flag.85+7086 ### Current security boundary7187 7288 Portal derives a stable client Iroh identity from the security key. Sigil signs@@ -269,7 +269,8 @@ expected = {
269269 "compatibility_executable": "sigil-host",270270 "target": "x86_64-unknown-linux-gnu.2.17",271271 "profile": "release",272- "features": ["default", "in-process-gstreamer"],272+ "features": ["in-process-gstreamer"],273+ "demo_auth_bypass": False,273274 "demo_direct_node": False,274275 }275276 for key, value in expected.items():@@ -203,7 +203,8 @@ else
203203 PKG_CONFIG_ALLOW_CROSS=1 PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 \204204 CARGO_TARGET_DIR="$build_target" \205205 cargo zigbuild --locked -p sigil-host --bins \206- --target "$linux_target" --release --features in-process-gstreamer206+ --target "$linux_target" --release --no-default-features \207+ --features in-process-gstreamer207208 )208209 host_binary="$build_target/$linux_output_target/release/sigil"209210 probe_binary="$build_target/$linux_output_target/release/sigil-probe"@@ -284,7 +285,8 @@ manifest = {
284285 "version": version,285286 "target": target,286287 "profile": "release",287- "features": ["default", "in-process-gstreamer"],288+ "features": ["in-process-gstreamer"],289+ "demo_auth_bypass": False,288290 "demo_direct_node": False,289291 "git_commit": commit,290292 "git_dirty": dirty == "true",@@ -363,7 +363,8 @@ jq -e --arg commit "$uat_commit" '
363363 and .release_kind == "development"364364 and .release_tag == "development"365365 and .target == "x86_64-unknown-linux-gnu.2.17"366- and .features == ["default", "in-process-gstreamer"]366+ and .features == ["in-process-gstreamer"]367+ and .demo_auth_bypass == false367368 and .demo_direct_node == false368369 ' "$manifest" >/dev/null369370 [[ -x "$sigil" && -x "$probe" ]]@@ -76,7 +76,8 @@ manifest = {
7676 "version": tag[1:],7777 "target": "x86_64-unknown-linux-gnu.2.17",7878 "profile": "release",79- "features": ["default", "in-process-gstreamer"],79+ "features": ["in-process-gstreamer"],80+ "demo_auth_bypass": False,8081 "demo_direct_node": False,8182 "git_commit": commit,8283 "git_dirty": False,@@ -46,7 +46,9 @@ printf '%s\n' \
4646 'test "${1:-}" = zigbuild' \4747 'test "${PKG_CONFIG_ALLOW_CROSS:-}" = 1' \4848 'test "${PKG_CONFIG_ALLOW_SYSTEM_LIBS:-}" = 1' \49+ 'case " $* " in *" --no-default-features "*) ;; *) exit 63 ;; esac' \4950 'case " $* " in *" --features in-process-gstreamer "*) ;; *) exit 64 ;; esac' \51+ 'case " $* " in *"demo-auth-bypass"*) exit 65 ;; esac' \5052 'output="$CARGO_TARGET_DIR/x86_64-unknown-linux-gnu/release"' \5153 'install -d -m 0700 "$output"' \5254 'printf "#!/usr/bin/env bash\\nexit 0\\n" >"$output/sigil"' \@@ -48,8 +48,7 @@ config_path="$config_directory/host.toml"
4848 unsafe_config_path="$unsafe_config_directory/host.toml"4949 gamescope_probe_config="$config_directory/gamescope-probe.toml"5050 fake_pw_dump="$temp_root/fake-pw-dump"51-fake_gst_inspect="$temp_root/fake-gst-inspect"52-gamescope_preflight_marker="$temp_root/gamescope-preflight-ran"51+fake_render_node="$temp_root/renderD999"5352 host_log="$temp_root/host.log"5453 second_log="$temp_root/second.log"5554 live_status="$temp_root/live-status.json"@@ -94,12 +93,6 @@ printf '%s\n' \
9493 "printf '[]\\n'" \9594 >"$fake_pw_dump"9695 chmod 0700 "$fake_pw_dump"97-printf '%s\n' \98- '#!/usr/bin/env bash' \99- "touch '$gamescope_preflight_marker'" \100- 'exit 1' \101- >"$fake_gst_inspect"102-chmod 0700 "$fake_gst_inspect"10396 false_path="/usr/bin/false"10497 [[ -x "$false_path" ]]10598 printf '%s\n' \@@ -119,9 +112,9 @@ printf '%s\n' \
119112 'xwayland_display = ":0"' \120113 "pw_dump_path = \"$fake_pw_dump\"" \121114 "gst_launch_path = \"$false_path\"" \122- "gst_inspect_path = \"$fake_gst_inspect\"" \115+ "gst_inspect_path = \"$false_path\"" \123116 'vaapi_encoder = "vah264enc"' \124- 'vaapi_render_node = "/dev/dri/renderD128"' \117+ "vaapi_render_node = \"$fake_render_node\"" \125118 'rate_control = "cqp"' \126119 'quantizer = 24' \127120 >"$gamescope_probe_config"@@ -154,7 +147,6 @@ PY
154147 XDG_RUNTIME_DIR="$runtime_directory" \155148 "$sigil" serve \156149 --config "$config_path" \157- --max-runtime-seconds 4 \158150 >"$host_log" 2>&1 &159151 host_pid=$!160152 @@ -239,7 +231,10 @@ if XDG_RUNTIME_DIR="$runtime_directory" \
239231 fi240232 grep -Fq 'another Sigil daemon or capture probe already owns this lifecycle scope' \241233 "$temp_root/contended-capture.error"242-[[ ! -e "$gamescope_preflight_marker" ]]234+if grep -Fq 'inspecting VAAPI render node' "$temp_root/contended-capture.error"; then235+ printf 'contended capture reached hardware preflight before acquiring the lifecycle lock\n' >&2236+ exit 1237+fi243238 244239 python3 - "$live_status" "$host_node_id" <<'PY'245240 import json@@ -308,7 +303,16 @@ fi
308303 grep -Fq 'another Sigil daemon or capture probe already owns this lifecycle scope' \309304 "$temp_root/live-reset.log"310305 311-wait "$host_pid"306+if ! kill -TERM "$host_pid" 2>/dev/null; then307+ printf 'Sigil exited before the appliance status proof requested shutdown\n' >&2308+ sed -n '1,160p' "$host_log" >&2309+ exit 1310+fi311+if ! wait "$host_pid"; then312+ printf 'Sigil did not shut down cleanly during the appliance status proof\n' >&2313+ sed -n '1,160p' "$host_log" >&2314+ exit 1315+fi312316 host_pid=''313317 if XDG_RUNTIME_DIR="$runtime_directory" \314318 "$sigil" capture probe \@@ -320,7 +324,8 @@ if XDG_RUNTIME_DIR="$runtime_directory" \
320324 printf 'fake Gamescope capture unexpectedly completed\n' >&2321325 exit 1322326 fi323-[[ -e "$gamescope_preflight_marker" ]]327+grep -Fq "inspecting VAAPI render node $fake_render_node" \328+ "$temp_root/released-capture.error"324329 XDG_RUNTIME_DIR="$runtime_directory" \325330 "$sigil" serve \326331 --identity "$identity_path" \@@ -49,7 +49,8 @@ manifest = {
4949 "version": tag[1:],5050 "target": "x86_64-unknown-linux-gnu.2.17",5151 "profile": "release",52- "features": ["default", "in-process-gstreamer"],52+ "features": ["in-process-gstreamer"],53+ "demo_auth_bypass": False,5354 "demo_direct_node": False,5455 "git_commit": commit,5556 "git_dirty": False,@@ -64,6 +65,8 @@ manifest = {
6465 }6566 if variant == "default-features":6667 manifest["features"] = ["default"]68+if variant == "demo-auth-bypass":69+ manifest["demo_auth_bypass"] = True6770 binary = b"fixture executable\n"6871 release = {6972 "sigil": binary,@@ -178,7 +181,7 @@ printf '%064d %s\n' 0 "$(basename -- "$bad_archive")" >"$bad_archive.sha256"
178181 assert_rejected bad-checksum 'checksum declaration does not exactly match' \179182 "$verifier" --tag "$release_tag" --archive "$bad_archive" --candidate180183 181-for variant in symlink duplicate unexpected default-features; do184+for variant in symlink duplicate unexpected default-features demo-auth-bypass; do182185 fixture_dir="$temp_root/$variant"183186 make_fixture "$fixture_dir" "$variant"184187 fixture_archive="$fixture_dir/sigil-$release_tag-bazzite-x86_64.tar.gz"@@ -187,6 +190,7 @@ for variant in symlink duplicate unexpected default-features; do
187190 duplicate) expected='duplicate members' ;;188191 unexpected) expected='do not match the package allowlist' ;;189192 default-features) expected='field features does not match the product contract' ;;193+ demo-auth-bypass) expected='field demo_auth_bypass does not match the product contract' ;;190194 esac191195 assert_rejected "$variant" "$expected" \192196 "$verifier" --tag "$release_tag" --archive "$fixture_archive" --candidate@@ -130,5 +130,13 @@ cargo test --locked -p portal --release --features demo-direct-node \
130130 commands::state::tests::app_state_accepts_direct_node_only_in_debug_builds \131131 -- --exact132132 133+cargo test --locked -p sigil-host --release \134+ tests::ordinary_release_excludes_configured_host_auth_bypass \135+ -- --exact136+137+cargo test --locked -p sigil-host --release --features demo-auth-bypass \138+ tests::configured_host_auth_bypass_is_explicitly_build_contained \139+ -- --exact140+133141 git diff --check134142 echo 'demo_build_preflight=ok'@@ -252,7 +252,8 @@ expected_values = {
252252 "version": release_tag[1:],253253 "target": "x86_64-unknown-linux-gnu.2.17",254254 "profile": "release",255- "features": ["default", "in-process-gstreamer"],255+ "features": ["in-process-gstreamer"],256+ "demo_auth_bypass": False,256257 "demo_direct_node": False,257258 "git_dirty": False,258259 "binary_provenance": "self-built-clean-head",