Foreman›Docs›Salesforce pack›Production stays yours

Production stays yours

Salesforce pack · page 7 of 9

What may and may not reach a production org, and the two accepted limits.

Foreman 0.11.05 sections

Preview. The Salesforce pack ships in Foreman 0.11.0 as a preview: its release paths pass their own tests, but no story has yet shipped through them on a live org. Treat the Gearset path as experimental.

With the Salesforce pack, deployments to production are blocked. This page is for the person who decides whether a production org may be connected at all, and for anyone who wants to know exactly what an agent can and cannot run against it. It lists the allowed commands, the refused ones, every way Foreman recognises a production org, and the two routes that stay open. The Salesforce pack is a preview: its release paths pass their own tests, but no story has yet shipped through them on a live org, and the Gearset path has not run against a live Gearset pipeline.

Connecting production without deploying

You do not have to connect production. A project whose salesforce.orgs holds only sandboxes never has a production login in Foreman’s isolated sf home, so no command can reach it, whatever it says.

A production org can still be useful: for reading records and describing objects while a story is designed, and as the Dev Hub that makes scratch orgs. To connect one, list it in salesforce.orgs with its orgId and "production": true:

"salesforce": {
  "orgs": {
    "acme-dev1": { "orgId": "00DAA0000000001" },
    "acme-qa": { "orgId": "00DAA0000000002" },
    "acme-main": { "orgId": "00DAA0000000009", "production": true }
  },
  "build": { "scratch": { "devHub": "acme-main", "definition": "config/project-scratch-def.json" } },
  "stages": [{ "name": "qa", "org": "acme-qa" }]
}

A marked org may be the scratch Dev Hub and the target of the commands in the next section. It is never a build org, never a test org, never a stage, and no pull request or stage branch may be salesforce.productionBranch. The config check refuses each of these, and foreman doctor shows a marked org in its salesforce layout check as “connected, deployments blocked, data changes not blocked”. An alias named prod or production is refused outright, marked or not.

The mark must be true on the server. The set-up connect step logs an org out at once when it is not a sandbox, unless salesforce.orgs marks it with that org ID. sf-org-guard, the first step of every release, queries each org and refuses a marked org whose server says it is a sandbox, and the doctor’s salesforce sf isolation check refuses an unmarked production org found in the home. The doctor also has a salesforce CONFIRM_PROD check, which confirms nothing confirms a production deploy for Foreman.

Four layers enforce the block. The isolated sf home holds only the orgs in salesforce.orgs, so an agent has no other login (see What the pack adds). The release steps check IsSandbox on the server before every deploy. Foreman’s sf wrapper, which every sf and sfdx call in a story session runs through, refuses a command that would reach a marked production org however sf was started: from a launcher, a script, an npm script, or through a default org. And the Bash guard reads each command before it runs and denies the common mistakes early, with a clear reason. The wrapper and the guard share one rule set, so both count commands the same way. When no production org is marked, the wrapper makes no check and the guard has nothing to refuse on this count.

Commands that are allowed

Against a marked production org, sf may run only these. Every other command is refused.

Group Commands
Reads sf data query, data search, data get record, data export tree, data export bulk, data export resume, data bulk results
Record changes sf data create record, data update record, data delete record (without the Tooling API, see below), data upsert bulk, data upsert resume, data import tree, data import bulk, data import resume, data delete bulk, data delete resume, data update bulk, data update resume
Describe and schema sf sobject describe, sobject list, schema generate field, schema generate sobject, schema generate platformevent, schema generate tab
Lists sf org list, org list limits, org list metadata, org list metadata-types
Retrieve sf project retrieve start, project retrieve preview
Apex logs sf apex list log, apex get log, apex tail log
Users and permission sets sf org create user, org assign permset, org assign permsetlicense, org list users
Dev Hub use sf org create scratch and org delete scratch, with production named only as the Dev Hub

The command words match as a set, so sf list org counts as sf org list, as the CLI itself runs it. Three things take an allowed command off the list:

  • a record change given -t, --use-tooling-api (any spelling, the old --usetoolingapi included) or --flags-dir: through the Tooling API a record change writes fields, flows, validation rules and other metadata, which is a metadata change to production
  • any argument that names a deploy endpoint (deployRequest, /services/Soap/m/, MetadataContainer, ContainerAsyncRequest): the command is then checked as a deploy
  • the two Dev Hub commands naming production anywhere but as the Dev Hub

The production block allows record changes because they are not deploys. Story sessions still cannot make them: the session’s Bash guard denies sf data commands that create, update, delete, upsert or import records against every org, production included, because runs never change records in an org and tests build their own data. The allowed record changes matter for your own shell and for Foreman’s own calls.

Commands that are refused

Against a marked production org, every command not in the table above is refused, with a reason that says which kind it is:

Kind Examples Reason given
Deploys sf project deploy start, validate, quick, resume; deploy metadata; force:source:deploy, force:mdapi:deploy; project deploy pipeline *; delete source; source push; org delete; org refresh; package install and uninstall; package push-upgrade *; agent publish, create, activate, deactivate; community publish; DevOps Center’s devops promote and devops promotion complete or validate; and any command whose words name a deploy endpoint “this deploys to <alias>, a production org… Foreman never deploys to production”
Metadata changes an allowed record change with -t, --use-tooling-api or --flags-dir “this changes metadata in <alias>… through the Tooling API”
Credentials and raw API sf org display, user display, org open, source open, org generate password, org auth show-* (the auth commands that show), api request “this prints or hands out the credentials of <alias>… or calls its API directly”
Everything else anonymous Apex (sf apex run), sf package *, sf org create sandbox, every legacy force:* alias, project deploy report, and any command the list does not name “this command is not one Foreman allows against production”

Some commands are refused while a production org is marked whatever org they name. sf package push-upgrade schedule pushes package code into orgs named by org ID in a list or a file Foreman cannot see, so it never runs. --flags-dir with any command not plainly allowed is refused, because the flags in that folder could name a production org out of sight. A project deploy pipeline deploy that names no DevOps Center org (-c or --devops-center-username) is refused, since its org is not the target-org default. A deploy, Tooling write or credential command whose target Foreman cannot resolve at all is refused too: it fails closed rather than guess.

The same rule reaches beyond sf. While a production org is marked, the Bash guard denies any program whose words name a deploy endpoint, so a raw curl to deployRequest or the Metadata SOAP endpoint is refused, and bash -c scripts are read the same way. The wrapper also unsets SF_TEMP_SHOW_SECRETS, the one switch in this CLI that un-redacts tokens in org list, org list auth, org list users, org display and org resume scratch.

If the production check itself fails (a broken file in the home, say), the wrapper fails closed: only a plainly allowed command runs, and every other is refused with “the production check failed”.

How Foreman finds production

A marked org is recognised by its alias in salesforce.orgs or by its org ID, the 18-character or the 15-character form, compared on the first 15 characters. The wrapper goes further than the guard: it reads the home’s .sfdx/alias.json and the login files, so a username, or another alias that points at the same org, is recognised too.

Every argument word is read, not only flag values, so no spelling slips past:

  • -o, --target-org, -u, --u and --targetusername for the target org
  • -v, --target-dev-hub, --targetdevhubusername and --target-hub-org for the Dev Hub
  • -c and --devops-center-username, counted only on project deploy pipeline commands, since elsewhere -c means code coverage or ignore conflicts
  • each as flag value, flag=value, and for a one-letter flag glued (-oacme-main), including one-letter flags run together (-goacme-main is -g -o acme-main)
  • a bare word, and each part of a comma-separated list

With no target given, the wrapper reads the default sf would use, in sf’s order: SF_TARGET_ORG, SFDX_DEFAULTUSERNAME or SFDX_TARGET_ORG in the environment; else target-org (or the legacy defaultusername) in .sf/config.json or .sfdx/sfdx-config.json at the project root, the nearest folder with sfdx-project.json; else the same files in the home. A Dev Hub command with no dev-hub flag reads the dev-hub default the same way (SF_TARGET_DEV_HUB, SFDX_DEFAULTDEVHUBUSERNAME, target-dev-hub, defaultdevhubusername). Because the guard cannot see those files, it denies instead any attempt to change them from a story session: sf config set of a default org or dev hub, sf alias set, --flags-dir, and SF_TARGET_ORG, SF_TARGET_DEV_HUB, SFDX_DEFAULTUSERNAME or SFDX_DEFAULTDEVHUBUSERNAME set in front of sf, or, while a production org is marked, in front of any program or by export.

Beyond production, the guard keeps a story session on its own org in every stage: a target other than the slot’s org is denied (the exceptions are the test org for sf project deploy start --dry-run in the test stage, when salesforce.test names a stage, and a marked production org for an allow-listed command), a dev hub is allowed only in scratch mode and only the configured one, org login, org logout and the auth commands are denied, so are deploy quick, --ignore-conflicts, --ignore-errors, sf project retrieve (unless every target is a marked production org) and agent test run-eval, and any command that steps around the isolated home (sf written with a path, or HOME, FOREMAN_SF_HOME, SF_DATA_DIR or PATH set for it) or names the folder that holds Foreman’s sign-in key, ~/.config/foreman/sf. A command built at run time that names sf is denied as well, because Foreman cannot tell what would run.

Accepted limits

Two routes stay open beyond any hook or wrapper, and are accepted:

  1. A story session can read the credential files in the isolated sf home, $FOREMAN_SF_HOME/.sfdx (key.json and the encrypted auth files).
  2. A script that runs the real sf by its full path, with HOME set to the isolated home, skips the wrapper and its check.

Through those two routes alone a session could still reach a connected production org with any command, anonymous Apex included. If that is not acceptable for your org, do not mark it: leave production out of salesforce.orgs, and no login for it exists in the home.

The Bash guard has smaller gaps of its own, and the wrapper covers them: launcher flags that take values (nice -n 5 sf), xargs, heredocs, shell functions and aliases, scripts and npm scripts that call sf, dynamic flag names, and production named by a username or another alias. What those gaps let through is only what the wrapper then allows against production, the allow-listed commands, and the agents’ own rules forbid record changes and retrieves. A deploy from a script is still refused by the wrapper.