Skip to content
atlassian-labsPublic

About

A static analyzer for finding Forge app vulnerabilities

Topics

Resources

Code of conduct

Contributing

Stars

13 stars

Watchers

3 watching

Forks

Latest commit

 

History

608 Commits

Folders and files

FSRT - Forge Security Requirements Tester

Apache license MIT license PRs Welcome

A static analysis tool for finding common Forge vulnerabilities.

Usage

Usage: fsrt [OPTIONS] [DIRS]...
       fsrt [OPTIONS] <COMMAND>

Arguments:
  [DIRS]...  The directory to scan. Assumes there is a `manifest.yaml` file in the top level directory, and that the source code is located in `src/`

Commands:
  remote  Interact with deployed Forge apps

  Options:
    -d, --debug
        --dump-ir <DUMP_IR>                 Dump the IR for the specified function
    -dt, --dump-dt <DUMP_DOM_TREE>          Dump the Dominator Tree for the specified app
    -f, --function <FUNCTION>               A specific function to scan, must be an entrypoint specified in `manifest.yml`
    -h, --help                              Print help information
    -V, --version                           Print version information
    --verbose                               Print diagnostics to stderr
    --check-permissions                     Runs the permission checker
    --cached-permissions                    Uses cached swagger permissions to avoid redownloading them
    --cached-permissions-path <LOCATION>    Uses the designated cache location, otherwise selects ~/.cache dir
    --graphql-schema-path <LOCATION>        Uses the graphql schema in location; othwerwise selects ~/.config dir

Run fsrt --help, fsrt remote --help, or fsrt remote <COMMAND> --help for current options.

Secret logging

The secret-logging scanner reports values returned by getSecret on the kvs export (named or default) of @forge/kvs, or on the legacy storage export of @forge/api, when they reach console.log, console.info, console.warn, console.error or console.debug. It is disabled by default; select it explicitly, alone or with other scanners:

fsrt --scanners secret-logging ./my-forge-app
fsrt --scanners secret,secret-logging ./my-forge-app
import { kvs } from '@forge/kvs';

export async function run() {
  const token = await kvs.getSecret('token');
  console.log(`Token: ${token}`); // reported
}

It follows import aliases, assignments, expressions, arrays and objects, branches, loops, helper arguments and returns, and single-handler .then() fulfillment callbacks. It checks manifest entrypoints, resolver handlers, their reachable helpers, and module initializers. Ordinary kvs.get calls and other console methods (such as console.table) are outside this rule. Captured bindings use their values at the helper call, and operators returning only metadata (such as typeof, void, boolean negation, and comparisons) clear secret taint.

A property written through a known path keeps its own taint, so reading password from { host: secret.host, password: 'literal' } is clean, while reading host from { host: secret } is reported. Any other property read from a secret stays tracked, but is reported only when the last property name ends with a secret suffix: secret[account].password and const { apiToken } = secret.config are reported, while logging secret[account], secret.host or secret.length is not. Names compare case-insensitively after removing _ and -, and the longest matching suffix decides, so the excluded pagetoken wins over token in nextPageToken. --secret-logging-suffixes and --secret-logging-excluded-suffixes replace the defaults listed by fsrt --help:

fsrt --scanners secret-logging \
  --secret-logging-suffixes password,secret,apikey ./my-forge-app

Logging a whole secret, an object or array containing one, or a method result such as secret.trim() is reported. Splitting on or replacing the secret itself, such as text.split(secret).join('[REDACTED]') or url.replace(secret, '***'), redacts it. For now, so does partial masking: a slice, substring or substr of at most 12 characters, bounded by literals or the string's own length (such as key.slice(-4), key.substring(0, 12) or key.substring(key.length - 4)), is treated as clean and logged as a warning (visible with FORGE_LOG=warn or --verbose). Longer or unbounded substrings such as key.slice(7), other truncation, and encoding are still reported.

lodash's omit, pick and get select properties when their paths are string or array literals. They are recognized when imported from lodash or lodash-es (by name, default or namespace import) or from a per-method package such as lodash/omit or lodash.omit. omit({ url, token: secret }, ['token']) and get(config, 'url') are clean, while pick(config, ['token']) is reported. A secret read whole has no tracked properties, so omit(secret, ['password']) is still reported. lodash/fp, require('lodash') and other libraries' omit propagate like any other unresolved call.

The shared engine in crates/forge_analyzer/src/taint.rs propagates taint for a scanner's TaintPolicy: its sources, property reads (property_taint, and tracks_fields for written properties), sanitizing methods (method_taint) and sink predicate (is_violation). A runner selects TaintDataflow<MyPolicy>, passes its configured policy from Runner::dataflow, and can reuse visit_taint_call for interprocedural traversal. Both this scanner and the existing prototype-pollution checker use that engine; the defaults propagate taint conservatively. The predicate sees values before each instruction; only matching sink locations are retained for the diagnostic walk after analysis converges. Variable vectors are retained at block boundaries, rather than copied and stored at every instruction.

This is a conservative analysis: helper summaries combine call sites within an entrypoint, and calls to unresolved functions (including HTTP clients other than Forge fetch and the product request APIs) propagate argument/receiver taint, so a response to an authenticated request can be reported. Dynamic dispatch, mutation through object aliases, and callback APIs other than the modeled fulfillment form can still miss flows; sanitization through unresolved functions can produce warnings. Callee summaries do not propagate writes to outer bindings back to callers. Findings identify the sink function, unique IR body, and instruction location, not a source line or a runtime secret value.

Installation

You will need to install Rust to compile FSRT. You can install Rust through Rustup or through your distro's package manager. You will also need Cargo, which comes by default with most Rust toolchains.1 latest stable release, and adding the toolchain

Installing from source:

git clone https://github.com/atlassian-labs/FSRT.git
cd FSRT
cargo install --path crates/fsrt --locked

or alternatively:

cargo install --git https://github.com/atlassian-labs/FSRT --locked

Commands

remote interacts with deployed Forge apps: it creates Forge Context Tokens (FCTs) and Forge Invocation Tokens (FITs), invokes extensions, and optionally saves a session cookie through browser login. It uses the configured Atlassian account and app installation.

fsrt remote mint-fct <MODULE_KEY> [OPTIONS]
fsrt remote mint-fit <REMOTE_KEY> (--module <MODULE_KEY> | --fct <FCT>) [OPTIONS]
fsrt remote invoke-extension [FUNCTION] <MODULE_KEY> [--entrypoint <ENTRYPOINT>] [--fct <FCT>] [OPTIONS]
fsrt remote mint-cookie [--config <PATH>] [--headed]

The FCT, FIT, and extension-invocation commands resolve the app ID with the following precedence: the --app-id <APP_ID> CLI argument, then app_id in the config file, then the app.id read from an app manifest (via --app-dir <DIR>, defaulting to the current directory). Supplying the ID by CLI or config skips the manifest entirely, so a deployed app can be accessed without a local checkout. The ID may be a bare {uuid} or a full ari:cloud:ecosystem::app/{uuid} ARI. These commands also use a TOML configuration file; see fsrt-remote.toml.example.

mint-fct mints an FCT for a deployed module. Pass --ctx '<JSON>' to populate the selected extension's context object.

mint-fit supports two mutually exclusive ways to provide its FCT:

Mode Required inputs Behavior
Use an existing FCT <REMOTE_KEY> --fct <FCT> Uses the supplied FCT when --module and --ctx are omitted.
Mint a new FCT <REMOTE_KEY> --module <MODULE_KEY> Mints an FCT with an empty context, then uses it to mint the FIT. Add --ctx '<JSON>' to set its context.

invoke-extension calls a deployed Forge module directly, which is useful for testing its behavior with custom payloads without going through the product UI. Pass a module key and, when needed, a resolver function or --entrypoint; omitting --entrypoint leaves selection to the backend.

The command mints an FCT automatically unless one is supplied with --fct. Use --ctx to provide invocation context.

By default, live mint commands print only the token, while invoke-extension prints the backend response. --dry-run queries metadata without signing tokens or invoking the extension and prints redacted request variables. --verbose prints live diagnostics to stderr. Run fsrt remote <COMMAND> --help for all options.

Save a session cookie

mint-cookie opens Chrome, signs in to the configured Atlassian site, and stores the resulting tenant.session.token in auth.raw_cookie_file. It is intentionally opt-in:

export ATL_PASSWORD='your-account-password'
cargo run -p fsrt --features mint_cookie -- remote mint-cookie --headed

Set [cookie].username in fsrt-remote.toml; --headed opens a browser window for MFA, bot checks, or other manual steps. Chrome must be installed, and the WebDriver may download a matching driver. The generated cookie file is a bearer credential: keep it untracked and do not paste it into logs, tickets, or chat.

Tests

To run the test suite:

cargo test

There are also two sample vulnerable Forge apps for testing. In the future these will be added to the test-suite, but until then you can test fsrt by manually invoking:

fsrt ./test-apps/jira-damn-vulnerable-forge-app

Testing with a GraphQl Schema:

cargo test --features graphql_schema

Contributions

Contributions to FSRT are welcome! Please see CONTRIBUTING.md for details.

License

Copyright (c) 2022 Atlassian and others.

FSRT is dual licensed under the MIT and Apache 2.0 licenses.

See LICENSE-APACHE and LICENSE-MIT for details.

With �� from Atlassian

Footnotes

  1. Cargo is technically not required if you want to download every dependency, invoke rustc, and link everything manually. However, I wouldn't recommend doing this unless you're extremely bored. ↩

About

A static analyzer for finding Forge app vulnerabilities

Topics

Resources

Code of conduct

Contributing

Stars

13 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages