RFC-138: Move fields out of invocation context to address header size limits for Forge Remote and Forge Containers

Hi everyone,

Project Summary

Publish: 19 June 2026

Discuss: 17 July 2026

Resolve: 30 July 2026

We’re proposing a reduction of the fields included in the signed invocation context sent to Forge Remotes and Forge Containers. This is to address the issue where large context causes requests to fail before reaching the remote or container.

Problem

Today, when a Forge frontend calls invokeRemote or requestRemote, Forge sends a Forge Invocation Token (FIT) to the remote backend in the Authorization header, which includes a signed context claim. For some modules, this context can become large enough to exceed individual or combined header limit sizes in common infrastructure, causing requests to fail before they reach the app’s remote backend.

Forge Containers use the same underlying invocation model, therefore a large header carrying the context will stop requests reaching the container. A Forge frontend calls a containerised service with invokeService and the context is exposed through the /invocation/context Containers API.

Proposed Solution

To address this, we’re proposing an opt-in manifest setting (context: trusted-only) that limits the fields in the signed invocation context sent to remotes and containers. Existing apps would not change by default.

When enabled, Forge Remote and Forge Containers would receive only trusted context in the signed invocation context.

Additional context would remain available to the app frontend through existing APIs, such as view.getContext() in Custom UI and useProductContext() in UI Kit. If your remote backend or container service needs any of that additional context, your frontend can send it explicitly in the request body.

Trusted and untrusted context

This proposal introduces a clearer distinction between two kinds of context:

  • Trusted context: product-specific context that Atlassian has permission-checked and signed into the invocation context.

  • Untrusted frontend context: additional context available to the app frontend, which can be sent to a remote backend or container service if needed, but must be treated as app-provided input and not relied on for security or authorisation decisions.

To clarify, only trusted context will now be available in the FIT context claim for Forge Remotes and through the /invocation/context API for Forge Containers. All remaining app context will be available in the frontend context.

What changes for app developers?

If your app does not opt in, there is no behaviour change.

If your app opts in with:

app: 
  id: ari:cloud:ecosystem::app/example-app-id
  endpoint:
    context: trusted-only // new opt-in property

some fields that were previously available in the context may no longer be present, but they will be available on the frontend context.

If your remote backend or container service needs those fields, your frontend should:

  1. Read the required values from frontend context.

  2. Send these values in the request body of the invokeRemote, requestRemote, or invokeService call.

  3. Treat them as untrusted on the backend.

  4. Validate them before use.

Example: sending frontent context

import { invokeRemote, view } from '@forge/bridge';
// import { invokeService, view } from '@forge/bridge';

export async function callRemote() {
  const context = await view.getContext();

  // same approach for invokeService()
  return await invokeRemote({
    path: '/my-remote-endpoint',
    method: 'POST',
    body: {
      frontendContext: {
        localId: context.localId,
        extension: {
          // Include only the fields your remote actually needs.
          macroParameters: context.extension?.macroParameters,
          gadgetConfiguration: context.extension?.gadgetConfiguration,
        },
      },
    }
  });
}

Example: reading trusted and untrusted context in a Forge Remote

app.post('/my-remote-endpoint', async (req, res) => {
  const fitPayload = await verifyForgeInvocationToken(
    req.header('authorization')
  );

  // Trusted: signed by Atlassian in the FIT.
  const trustedContext = fitPayload.context;

  // Untrusted: supplied by the app frontend.
  const frontendContext = req.body.frontendContext as {
    localId?: string;
    extension?: Record<string, unknown>;
  };

  if (
    frontendContext.localId !== undefined &&
    typeof frontendContext.localId !== 'string'
  ) {
    return res.status(400).json({ error: 'Invalid localId' });
  }

  // Do not use frontendContext as proof of authorization.
  // Validate or independently verify frontend-provided values before use.

  res.json({
    ok: true,
    cloudId: trustedContext?.cloudId,
    localId: frontendContext.localId,
  });
});

Example: reading trusted and untrusted context in a Forge Container

async function getInvocationContext() {
  // Use the local Containers runtime API base URL documented for your container environment.
  const response = await fetch(
    `${CONTAINERS_RUNTIME_API_BASE_URL}/invocation/context`
  );

  if (!response.ok) {
    throw new Error('Failed to read Forge Containers invocation context');
  }

  return response.json();
}

app.post('/my-container-endpoint', async (req, res) => {
  const invocationContext = await getInvocationContext();

  // Trusted: exposed by the Forge Containers runtime API after platform validation.
  const trustedContext = invocationContext.context;

  // Untrusted: supplied by the app frontend.
  const frontendContext = req.body.frontendContext as {
    localId?: string;
    extension?: Record<string, unknown>;
  };

  if (
    frontendContext.localId !== undefined &&
    typeof frontendContext.localId !== 'string'
  ) {
    return res.status(400).json({ error: 'Invalid localId' });
  }

  // Do not use frontendContext as proof of authorization.
  // Validate or independently verify frontend-provided values before use.

  res.json({
    ok: true,
    cloudId: trustedContext?.cloudId,
    localId: frontendContext.localId,
  });
});

Proposed rollout

Date Milestone
19 June 2026 Publish this RFC for community feedback.
17 July 2026 Close RFC feedback period.
31 July 2026 Resolve RFC with a final decision.
1 October 2026 Publish developer documentation about the opt-in process and which module fields will be included in the trusted context vs untrusted frontend context.
1 April 2027 Deprecate the original context payload.

Note that there are currently no plans to remove the original context payload but the expectation is that new apps should use the new, reduced, trusted context.

What we’d like feedback on

We’d especially like feedback from developers using Forge Remote or Forge Containers.

  1. Does the proposed opt-in manifest property work for your app?

  2. Which fields do your remote endpoints currently read from the FIT context claim, or your container services read from the Containers /invocation/context API and to which modules do they relate?

  3. Would sending non-sensitive frontend context explicitly in the request body of invokeRemote, requestRemote, or invokeService calls work for your app?

  4. Are the proposed rollout dates workable?

To reiterate, this proposal is intended to reduce failed remote and container invocations caused by oversized headers while making the trust boundary around invocation context clearer.

Please share feedback by 17 July 2026.

(First: Feel free to assign a “RFC-XXX” number, so as to align with all the other RFCs. Next sensible free number is “RFC-139”; last used number “RFC-137” is used twice, I guess one of those will be re-numbered to “RFC-138”)

On-topic:

| 1 April 2027 | Deprecate the original context payload. |

Note that there are currently no plans to remove the original context payload

So the deprecation will be indefinitely? Or how will this work?

  1. Which fields do your remote endpoints currently read from the FIT context claim?

We read:

  • context.cloudId: for analytics tracking
  • context.siteUrl: for redirecting from an external site back to the customer’s Cloud site

Will these fields continue to be in context in the reduced version? I’m unclear about which fields are included.

Would this also apply to the the Forge lifecycle events (app:installed et al)? Because the context there is kind of important to us.

Thanks @LauraHowarthKirke for taking the time to tackle this issue blocking many partners.

Taking the opt-in approach works great as we can check endpoint by endpoint to opt into the new context.

I do have a couple of question though:

  1. You mention deprecating the original context payload

What does this mean? Will the opt-in become enforced in the in 2027?

  1. What fields are considered trusted in the new context?

Currently it is not clear what product-specific context is included.

  1. How are other calls to a remote or service impacted by this RFC?

This RFC only mentions calls from the frontend to a remote/service. But there are also other invocations that will benefit from this change, like triggers and scheduledTriggers or any other module that uses endpoint to connect with a remote or service.

  1. As noted by Andreas, the RFC is inconsistent about the deprecation: it says both that no deprecation is planned for the original context payload, but also that it will be deprecated next April. Regardless, please be careful about planning any future deprecation. Customers who are using older versions of Forge app code will not necessarily upgrade due to permissions issues, so unilaterally deprecating a core platform feature can easily break old versions of apps that are unable to be updated easily.

  2. Atlassian has not defined exactly what it considers to be “trusted context”. Can the proposed fields please be spelled out for app vendors? The RFC says that this would be provided in October, but the ideal time to do this is while the RFC is open for discussion. Even if it is just a proposal of Atlassian’s current thinking.

  3. If Atlassian can make the field list clear, vendors would then need to assess how it impacts them. For example, proposals to remove values that vendors are currently relying on may result in additional round-trips required to the server (especially if this reduced context is used for triggers and so on) which increases app latency.

  4. I have heard in other places echoing the concern that data in the FIT has to be limited due to size concerns. Still, other data that would be otherwise be extremely helpful in the FIT (like data related to rolling releases) cannot be added due to the FIT size already being constrained.

Instead of adding additional limitations and band-aids to work around the problem, what about shifting the architecture and allow vendors to opt in an endpoint to sending the full FIT as part of the requestRemote() payload, rather than trying to stuff it into headers?

This permanently solves the problem of header size, it allows additional critical data to be added to the FIT if this is ever necessary, and it remains secure: the payload can be { "fit": { /* ... */ }, "unverifiedUserPayload": { /* ... */ } }.

The only downside is that it would require all requestRemote() calls to use POST (but it’s not like previous GET requests could be cached anyway, given that the FIT is always different).