Skip to main content
Version: Next

0038: The web surface sets tracker and Slack credentials write-only, into Orion's own 0600 file

  • Status: Accepted (Navjyot, 2026-10-06)
  • Date: 2026-10-06
  • Related: 0026 (which said credentials are never rendered, so never edited, by the page, and that a credential boundary needs its own ADR), 0027 (the web process holds no tracker credential), 0024 (the token that guards every write)

Context​

A person who has installed Orion and opened the web surface has to leave it to connect a tracker or Slack: orion config in a terminal, an interactive wizard. They asked for a Settings page for it.

0026 refused this by default: "Credentials are never rendered by the page, so they are never edited by it", and 0027 records that the web package holds no tracker credential, with a test (trackercredential_test.go) that fails if a credential-shaped field appears in anything the page draws or Orion writes down. Both rules exist because one leaked Jira token carries issue-read and usually issue-write scope across every project the account can see.

What is being asked is narrower than what those rules guard against. The danger they name is a credential kept in, or drawn by, the page. Setting one the way orion config does keeps it nowhere new: it goes into the same file the CLI already writes (ORION_HOME/config.env, 0600, internal/creds).

Decision​

  1. Write-only, and nothing to copy. A secret (ORION_JIRA_TOKEN, ORION_SLACK_TOKEN, ORION_NOTIFY_WEBHOOK) can be set or cleared from the page and is never sent back to it: the page learns only that it is set and where it came from (environment, or the config file). A saved secret shows as dots that cannot be selected, and its input is a password field that refuses copy, cut and drag. The Jira URL and the account email are shown, not selectable, and editable. There is no "reveal".
  2. Same file, same function. Writes go through creds.Save, which creates the file 0600 and replaces it atomically. The web process stores no value of its own: it receives one in a request body, validates it, passes it to creds.Save and forgets it. It is never logged, never in an error message, and never in a response.
  3. A closed list of keys. Only the Jira URL, Jira email, Jira token, Slack bot token and Slack webhook can be written, each with its own check before anything is saved: a Jira URL is https:// with a host and no path; a Slack bot token starts xoxb- (a webhook URL is refused there, since it cannot create channels); a webhook is an https://hooks.slack.com/ URL; an email has an @. A value with a control character, or over 512 characters, is refused, because the file format is one line per key.
  4. An exported variable still wins. The environment overrides the file, as it does for the CLI. The page says when a value shown as "set" comes from the environment, because clearing the file's copy will not change what runs.
  5. Authenticated like every write. Registered with Handle (0024): the token, Origin and Host checks apply, and an unauthenticated request changes nothing.
  6. Linear is not offered as a connection. The engine has one tracker implementation, Jira; the interface exists so another can be added (internal/tracker). A Linear field would be a setting nothing reads. The page says Linear is not supported yet rather than showing one.
  7. Per-project tracker and Slack switches (tracker.*, slack.enabled, channel prefix) are orion.json settings, not credentials, and stay a separate decision under 0026's allowlist.

Consequences​

  • A person can connect Jira and Slack without a terminal. The wizard remains and writes the same file.

  • Anyone who can load the page can read the write token from its source (0024 accepts this) and so can replace a credential, though not read one. That is a new capability for a reader already running as the operator, and no more than the operator could do with the file.

  • A request body briefly holds a secret in the web process's memory. A debug log of request bodies would capture it; the handler logs none, and a test asserts a saved secret appears in no response.

  • trackercredential_test.go keeps its meaning: nothing the page draws or Orion records gains a credential field. This ADR adds a write path, not a stored value.

  • Not encrypted at rest. The file holds the secrets as plain text with mode 0600, exactly as orion config has always written it. "Never shown in the page" is not "encrypted on disk". Moving the secrets into the operating system's keychain would encrypt them and is a separate decision (it changes the CLI, cron and launchd paths too).

Rejected​

  • Show the stored token, masked. creds.Mask keeps the first six and last four characters, which for a short token is most of it. "Set" and its source say what a person needs.
  • A "test connection" button in this slice. It would make the server call out with the credential. Useful, and its own decision.
  • A Linear field now. Dead configuration.

Status​

Accepted. It permits the write-only credentials form; nothing else in the web surface changes.