Skip to content

Refuse uploads without a real token (F1), Expo plugin endpoint option (F6), S-4 on an R8 build (F7) - #70

Merged
krassx merged 3 commits into
mainfrom
fix/ios-dsym-guard-expo-endpoint
Oct 11, 2026
Merged

krassx merged 3 commits into
mainfrom
fix/ios-dsym-guard-expo-endpoint

Conversation

@krassx

@krassx krassx commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

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-action with the placeholder token. That registers a build and uploads dSYMs to the configured endpoint, or to production when none is set.

  • Both post-actions now stop before any CLI lookup. They exit 0 and print warning: bugsee: dSYM upload skipped: the app token is the placeholder (or … no app token is configured). The check is isPlaceholderToken written 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.
  • Source-map hook: the token is now checked after trimming, so a placeholder padded with whitespace also skips.
  • Android: the placeholder is no longer written as app_token in bugsee.properties, by either the Expo plugin or the bare example's write-credentials. The Gradle plugin uploads the R8 mapping and registers the APK with any app_token it finds. Those tasks now log Could not resolve appToken. Skipping ….

F6: Expo plugin endpoint option

endpoint takes an https API base with an optional port and path. Anything with credentials, a query, a fragment or /v2 is refused at prebuild, and the error never echoes the value. When unset, everything stays on production. When set, it is written to:

  • plugin.endpoint in bugsee.properties;
  • BUGSEE_ENDPOINT in the iOS bundle phase's marked settings block and in the Archive post-action;
  • the runtime: com.bugsee.reactnative.endpoint meta-data (Android) and BugseeReactNativeEndpoint in Info.plist (iOS, with /v2). The native module applies it when launch()/relaunch() get no endpoint, and an endpoint passed from JS wins.
  • With autoLaunch, the SDK's own com.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.ts against assembleRelease -PbugseeE2eMinify=true without bugseeE2eDebuggable. The case first checks two things:

  • run-as is refused, so the build is not debuggable;
  • the crash's own logcat frame is obfuscated.

The debuggable case keeps FLOW-26.

Proof

  • Bare example, real 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 captured warning: bugsee: dSYM upload skipped: the app token is the placeholder; no bugsee-cli.log was written. Control run with a fake non-placeholder token: the listeners recorded 12 connections from bugsee-cli/0.7.12.
  • Bare Android Release with placeholder credentials: uploadBugseeReleaseMapping and uploadBugseeReleaseApk both logged Could not resolve appToken, and nothing connected.
  • Expo 57 (gen-expo-app.sh 57 --plugin-options '{"endpoint":"https://127.0.0.1:9"}', placeholder token, App.js passing no endpoint), getLaunchOptions() after launch:
    • WOD_LX1: endpoint=https://127.0.0.1:9;
    • simulator: endpoint=https://127.0.0.1:9/v2.
  • S-4 dry run on WOD_LX1 (non-debuggable R8 build): passed. run-as: package not debuggable; raw frame BugseeModule.testCrash(r8-map-id-8d19…:1); the upload was attempted against the dead endpoint. Secret scan: 0 findings.
  • Tests: yarn test 3497 passed; typecheck and lint clean; Android PluginEndpointTest passed. The new BGSRNPluginEndpointTests run in CI.
  • Mutation gate: stryker.plugin.json on the changed files scored 98.61 (break threshold 97).

Found along the way (not fixed here)

When a source-map upload fails, bugsee-cli prints error: upload failed: … to stderr. hermes-sourcemaps.js passes 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

…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)

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_GUARD runs after token resolution and before any bugsee-cli spawn; dashes and whitespace are stripped the same way the CLI trims. A baked Expo placeholder still wins over BUGSEE_APP_TOKEN (the existing bake-first rule) and then skips — pinned in transforms.test.ts. Android omits app_token from bugsee.properties instead 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 gets iosRuntimeEndpoint (…/v2), matching endpointFor('ios', …); Android meta-data and plugin.endpoint stay version-exclusive, matching apiUrl(). JS serialize() already puts the platform key in the launch map, so containsKey / given[@"endpoint"] correctly leaves a caller-supplied endpoint alone. An empty JS endpoint still lets the plugin fill in (same as endpointFor returning {}). Customer-owned com.bugsee.option.$$ENDPOINT is left in place and warned once.
  • F7 retrace false pass. S-4 is fail-closed: run-as must be refused and the crash frame must not be BugseeModule.java. Delivery is judged from lifecycle marks after pm clear, because run-as cannot list bundles. Scenario steering still uses the release URI (bugsee-e2e://…), so the missing run-as does 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

  1. Overall risk: Low
  2. Merge recommendation: Approve
  3. Most important issues to fix: None
  4. What is solid: Shared TOKEN_GUARD locked 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.

Open in Web View Automation 

Sent by Cursor Automation: Bugsee code review

@krassx
krassx merged commit e81df78 into main Oct 11, 2026
46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant