The Hidden Flaw in Forge Lint's Scope Checks

Developers building applications on Atlassian's Forge platform rely on tools like forge lint to catch configuration errors before deployment. However, a significant oversight means this linter fails to detect when crucial Jira API scopes are missing from an application's manifest. This can lead to applications deploying successfully only to fail at runtime when attempting to access Jira resources, returning cryptic 401 Unauthorized errors.

The core of the problem lies in how forge lint parses API calls. It only recognizes a requestJira call when the endpoint is hardcoded as a string literal, a template literal, or within a route`` tag. If the API endpoint path is passed as a variable, forge lint silently drops the call from its analysis. This means a single request helper function, designed to abstract API calls, can effectively hide every Atlassian REST API interaction within an entire codebase from the linter's scrutiny.

This issue is not theoretical. In testing, an application requiring write:jira-work and read:jira-user scopes was linted and reported "No issues found," despite neither scope being declared in the manifest. The application deployed without error, but any attempt to perform a write operation to Jira resulted in an HTTP 401 response, specifically with the header x-failure-category: FAILURE_CLIENT_SCOPE_CHECK and a JSON body indicating unauthorized access due to a scope mismatch.

Diagram showing Forge lint parsing logic for requestJira calls

Implications for Forge Developers

Since version 13.4.0 of the @forge/cli, the forge deploy command automatically runs the linter. A clean lint report is now presented as a passed gate, implying that the application is ready for deployment. This change inadvertently elevates the linter's output from a helpful hint to a critical prerequisite. When the linter fails to identify missing scopes, developers are lulled into a false sense of security, believing their application has the necessary permissions. This can lead to significant debugging efforts post-deployment, impacting user experience and development velocity.

The lack of explicit scope declarations means that applications might be granted broader permissions than necessary. While forge lint --fix can attempt to automatically add scopes, it has been observed to add scopes that are not actually required by the application. This practice of over-provisioning permissions is a security anti-pattern, increasing the attack surface should an attacker gain control of the application's execution environment. Developers must therefore manually verify all declared scopes against actual usage, a process that is tedious and error-prone, especially in large codebases.

The Runtime Experience

When an application makes a requestJira call without the necessary scope, the user experiences an immediate failure. The API returns an HTTP 401 status code, not a 403 Forbidden as one might expect for a permission issue, but a 401 Unauthorized. The accompanying response headers and body provide clues, such as x-failure-category: FAILURE_CLIENT_SCOPE_CHECK and a message like {"code":401,"message":"Unauthorized; scope does not exist"} (or similar, depending on the exact failure). This is a poor user experience, as the application may appear to be functioning correctly until a specific feature requiring the undeclared scope is triggered.

This silent failure mode of the linter is particularly insidious. Developers might spend hours debugging application logic, API integrations, or network configurations, only to discover the root cause is a simple, undeclared scope that the linter should have caught. The effort to manually audit every API call, especially those wrapped in helper functions, is substantial. Without a robust linting mechanism, ensuring correct API scope management becomes a significant manual burden.

What's Next?

Atlassian needs to address this critical flaw in the Forge linting tool. The parser must be updated to correctly identify requestJira calls regardless of how the endpoint path is provided, including when passed via variables. Until then, developers must implement their own pre-deployment checks or rely on thorough manual code reviews to ensure all necessary Jira scopes are declared. The current state of forge lint provides a misleading signal, turning a valuable development tool into a potential source of runtime failures and security misconfigurations.