Repository navigation
Refuse uploads without a real token (F1), Expo plugin endpoint option (F6), S-4 on an R8 build (F7) - #70
Conversation
…ption F1 (campaign STAGING lane): the iOS Archive dSYM post-action, in the bare example's scheme and in the script the Expo plugin writes, ran `bugsee-cli xcode post-action` with the placeholder token. That registers a build and uploads dSYMs to the configured endpoint, or to production api.bugsee.com when none is set. - Both post-actions now stop, exit 0, with one warning line when the token is missing, blank or the placeholder (isPlaceholderToken in POSIX sh, whitespace and dashes dropped first, as the CLI trims). The guard text is shared (TOKEN_GUARD) and a test keeps the bare scheme on it. - The source-map hook checks the trimmed token, so a padded placeholder skips too. - Android: the placeholder is no longer written as app_token in bugsee.properties (Expo plugin and the bare example's write-credentials), because the Gradle plugin uploads the R8 mapping and registers the APK with any app_token it finds. F6: the config plugin gains `endpoint` (https base, validated, no /v2), written for every upload and for the runtime: plugin.endpoint in bugsee.properties; BUGSEE_ENDPOINT in the iOS bundle phase's settings block and in the Archive post-action; com.bugsee.reactnative.endpoint meta-data and BugseeReactNativeEndpoint in Info.plist, which the native module applies when launch()/relaunch() get no endpoint of their own. With autoLaunch it is also the SDK's com.bugsee.option.$$ENDPOINT, marked as the plugin's; one the app wrote itself is never touched. Each item is the plugin's own, replaced on the next prebuild and removed without the option, byte for byte. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…cised F7 (campaign STAGING lane): AGP turns R8 obfuscation off for a debuggable build even with minify on, so the staging suite's Java crash case, on the debuggable Release it needs for run-as, shipped readable frames and never tested retrace (ART-111). S-4 now has its own file, staging-retrace.test.ts, for `assembleRelease -PbugseeE2eMinify=true` without bugseeE2eDebuggable: it refuses a debuggable build (run-as must be refused), requires the crash's own logcat frame to be obfuscated, clears data with pm clear and judges delivery from the app's event marks. The debuggable case keeps FLOW-26 only. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…set and warning paths Mutation gate (stryker.plugin.json on the changed files): 98.61, from 96.95. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
There was a problem hiding this comment.
Reviewed 3941270 (fix/ios-dsym-guard-expo-endpoint) against the surrounding upload hooks, native launch path, and campaign harness — not the diff alone.
Findings
No remaining P0–P3. I could not construct a credible failure that the implementation does not already refuse, skip, or pin with a test.
Adversarial checks that did not land:
- F1 placeholder leak.
TOKEN_GUARDruns after token resolution and before anybugsee-clispawn; dashes and whitespace are stripped the same way the CLI trims. A baked Expo placeholder still wins overBUGSEE_APP_TOKEN(the existing bake-first rule) and then skips — pinned intransforms.test.ts. Android omitsapp_tokenfrombugsee.propertiesinstead of writing the placeholder. Hermes now trims before the placeholder regex, so a padded CI token no longer reaches the network. - F6 endpoint injection / versioning. The shape rejects credentials, query, fragment, quotes, and
/v2; errors never echo the value. iOS Info.plist getsiosRuntimeEndpoint(…/v2), matchingendpointFor('ios', …); Android meta-data andplugin.endpointstay version-exclusive, matchingapiUrl(). JSserialize()already puts the platform key in the launch map, socontainsKey/given[@"endpoint"]correctly leaves a caller-supplied endpoint alone. An empty JS endpoint still lets the plugin fill in (same asendpointForreturning{}). Customer-ownedcom.bugsee.option.$$ENDPOINTis left in place and warned once. - F7 retrace false pass. S-4 is fail-closed:
run-asmust be refused and the crash frame must not beBugseeModule.java. Delivery is judged from lifecycle marks afterpm clear, becauserun-ascannot list bundles. Scenario steering still uses the release URI (bugsee-e2e://…), so the missingrun-asdoes not strand the case. The debuggable staging suite keeps FLOW-26 only.
Residual, not a finding here: a failed source-map upload still prints error: on stderr and can fail Archive even though the hook exits 0 — called out in the PR body as follow-up.
Verdict
- Overall risk: Low
- Merge recommendation: Approve
- Most important issues to fix: None
- What is solid: Shared
TOKEN_GUARDlocked to the bare scheme; endpoint writes are plugin-owned and byte-for-byte reversible; native apply helpers are pure and tested on both sides; S-4 cannot pass on a readable, debuggable build.
CI at review time (gh pr checks): lint, typecheck, unit, android, ios unit (BugseeRNSupport), ios (cocoapods), ios (spm), ios e2e (simulator), expo prebuild, pack-install and rn-compat matrices were green. Some mutation-plugin shards and Windows release jobs were still in flight; I did not run the suite locally.
Sent by Cursor Automation: Bugsee code review


Fixes three findings from the beta campaign's STAGING lane (
campaign-lane-staging.md).F1: no upload with a missing or placeholder token
The iOS Archive dSYM post-action (in the bare example's scheme and in the script the Expo plugin writes) ran
bugsee-cli xcode post-actionwith the placeholder token. That registers a build and uploads dSYMs to the configured endpoint, or to production when none is set.warning: bugsee: dSYM upload skipped: the app token is the placeholder(or… no app token is configured). The check isisPlaceholderTokenwritten in POSIX sh; it drops whitespace and dashes first, because the CLI trims the token. The guard text is shared (TOKEN_GUARD), and a test keeps the bare scheme in step with it.app_tokeninbugsee.properties, by either the Expo plugin or the bare example'swrite-credentials. The Gradle plugin uploads the R8 mapping and registers the APK with anyapp_tokenit finds. Those tasks now logCould not resolve appToken. Skipping ….F6: Expo plugin
endpointoptionendpointtakes an https API base with an optional port and path. Anything with credentials, a query, a fragment or/v2is refused at prebuild, and the error never echoes the value. When unset, everything stays on production. When set, it is written to:plugin.endpointinbugsee.properties;BUGSEE_ENDPOINTin the iOS bundle phase's marked settings block and in the Archive post-action;com.bugsee.reactnative.endpointmeta-data (Android) andBugseeReactNativeEndpointin Info.plist (iOS, with/v2). The native module applies it whenlaunch()/relaunch()get noendpoint, and an endpoint passed from JS wins.autoLaunch, the SDK's owncom.bugsee.option.$$ENDPOINT. That item is marked as the plugin's, and an app's own copy is never touched.Every item belongs to the plugin. The next prebuild replaces it, and a prebuild without the option removes it again, byte for byte (Part 17).
F7: S-4 exercises retrace
AGP disables R8 obfuscation for a debuggable build, so the staging suite's Java crash case never tested retrace (ART-111). S-4 now runs from
e2e/staging-retrace.test.tsagainstassembleRelease -PbugseeE2eMinify=truewithoutbugseeE2eDebuggable. The case first checks two things:run-asis refused, so the build is not debuggable;The debuggable case keeps FLOW-26.
Proof
xcodebuild archive(Release, generic iOS, placeholder token, endpoint and proxy set to loopback listeners): 3 Archives succeeded with 0 connections. A run with a temporary, uncommitted output redirect on the scheme script capturedwarning: bugsee: dSYM upload skipped: the app token is the placeholder; nobugsee-cli.logwas written. Control run with a fake non-placeholder token: the listeners recorded 12 connections frombugsee-cli/0.7.12.uploadBugseeReleaseMappinganduploadBugseeReleaseApkboth loggedCould not resolve appToken, and nothing connected.gen-expo-app.sh 57 --plugin-options '{"endpoint":"https://127.0.0.1:9"}', placeholder token, App.js passing no endpoint),getLaunchOptions()after launch:endpoint=https://127.0.0.1:9;endpoint=https://127.0.0.1:9/v2.run-as: package not debuggable; raw frameBugseeModule.testCrash(r8-map-id-8d19…:1); the upload was attempted against the dead endpoint. Secret scan: 0 findings.yarn test3497 passed; typecheck and lint clean; AndroidPluginEndpointTestpassed. The newBGSRNPluginEndpointTestsrun in CI.stryker.plugin.jsonon the changed files scored 98.61 (break threshold 97).Found along the way (not fixed here)
When a source-map upload fails,
bugsee-cliprintserror: upload failed: …to stderr.hermes-sourcemaps.jspasses that line through, Xcode reads it as an error, and the Archive fails, even though the hook exits 0. This contradicts "a failed upload never fails the build". Seen in the control Archive above.🤖 Generated with Claude Code