Docs/CLI/cc-secrets
CLI

cc-secrets: passwords a session uses but never sees

8 min read

Sessions keep needing passwords: a website login for a browser check, the sudo password on a Linux server, an account the agent has to work in. Typed into the session, a password lands in the transcript. Written into a file in the repository, it gets committed. cc-secrets is the third way: you store the password once, by hand, on the machine that needs it, and a session asks the tool to do the thing that needs the password - run a command, or fill a login form. The tool acts with the password and hands back only the result.

There is no command that prints a password. An agent can see which entries exist, their user names and the sites they are for, and it can use them. It cannot read them out.

Note
What it protects against, in the tool's own words: accidental exposure - in transcripts, logs, command output and screenshots - not a hostile program running as the same user. The limits at the end of this page are part of the design, not small print.

How it works

  • You add entries from your own terminal: add and remove. Each entry is a name, a user name, the password, the site addresses it may be typed into, a note, which uses it allows, and whether sessions may use it at all.
  • A session uses them: list shows the entries it may use, run runs a command with the password supplied, and login fills and submits a login form in a browser profile the Director owns.
  • Every use is written down: log shows who used which entry, for what, and what happened - never the password.

Each machine has its own store, one per operating-system user. Nothing is synced and nothing is kept on the Gateway, so add an entry on every machine whose sessions need it.

Adding an entry

Only you add entries, and never through a session. add refuses to run inside a DevThrottle session, because a password must never be entered through one. Open an ordinary PowerShell or Command Prompt window and run:

PowerShell or cmd
cc-secrets add devlinux

It asks for the user name, the allowed site addresses (blank for none), a note, and whether sessions on this machine may use the entry (the answer defaults to no). Then it asks for the password twice, with the typing hidden. The password is never taken as a command-line argument, where it would land in your shell history and the process list.

You can also pipe the password in, for example from your password manager's own command. With nothing to prompt, the other fields must then be given as options:

Piping the password in
<password manager command> | cc-secrets add github-work --username alice --domains https://github.com --agents

Git Bash and other mintty windows cannot hide what you type, so cc-secrets will not read a typed password there. Use PowerShell or cmd, or pipe the password in as above - piping works from Git Bash.

  • --username - the user name that goes with the password.
  • --domains - comma-separated site addresses login may fill (see allowed addresses).
  • --notes - a note for yourself. Sessions see it in list, so keep it free of anything private.
  • --agents / --no-agents - whether sessions on this machine may use the entry.
  • --uses - comma-separated login, run; both by default.
  • --replace - replace an existing entry of the same name without asking.

An entry name is lowercase letters, digits, dots, dashes and underscores, starts with a letter or digit, and is at most 64 characters. A password must be at least four characters. A password that is part of the text cc-secrets shows in its place ([REDACTED]) is refused, because it could never be hidden.

Removing and reviewing entries

PowerShell or cmd
cc-secrets remove devlinux
cc-secrets list --all

remove asks for confirmation unless you pass --yes. list --all shows every entry, including the ones sessions may not use, with an Agents column. Both are owner commands and are refused inside a session.

Allowed addresses

An allowed address is a whole site origin - scheme, host and port - and login compares all three exactly:

  • example.com with no scheme means https://example.com on port 443, and nothing else. http://example.com and https://example.com:8443 are different sites.
  • https://*.example.com allows every subdomain of example.com on the same scheme and port, but not example.com itself. A wildcard as broad as *.com is refused.
  • A local address works when you give it in full, for example http://127.0.0.1:8080.
  • A path is dropped, and an address with a user name in it is refused.

An entry with no allowed addresses can still be used with run, but login refuses it: there is no page it may be typed into.

Agents on or off, and uses

Two settings on each entry decide what a session may do with it. Agents (--agents or --no-agents) applies to every session on the machine. An entry with agents off does not appear in a session's list at all, and using it gets the same answer as a name that does not exist, so a refusal does not reveal which entries you hold back. Uses limits an entry to login, to run, or allows both. To change either, add the entry again with --replace.

What a session can do

Inside a session
cc-secrets list
cc-secrets run devlinux -- sudo -S apt-get update
cc-secrets login github-work --browser center-consulting
cc-secrets log
  • list - the entries sessions may use: name, user name, allowed addresses, uses and your note. Never a password. --json for a script.
  • run - run a command with the password supplied, and get back its exit code and output with the password removed.
  • login - fill and submit the login form on the open tab of the entry's site, in a browser profile the Director owns.
  • log - read the audit log.
  • version - print the tool's version.

run

Put the command after --. The program must be on the PATH or given as a full path. --via chooses how the command receives the password:

  • stdin (the default) - the password and a newline are written to the command's standard input, which is what sudo -S reads.
  • env - the password is placed in one environment variable, named by --env-name (CC_SECRET by default), in the environment of the command it launches. Programs that command starts inherit it in the usual way.
  • askpass - SUDO_ASKPASS, SSH_ASKPASS and GIT_ASKPASS point at a small helper that fetches the password from a one-time listener on this machine, guarded by a random token and answering at most three requests.

The command is stopped after --timeout seconds (600 by default). Its output is captured in full and cleaned before anything is shown - it is not streamed, because a password split across two chunks of output would slip past a clean-up of each chunk on its own. The password is removed as typed and in the encoded forms output tends to carry it in: escaped for JSON, HTML or Python, percent-encoded, base64 (including of username:password, as in a Basic authorization header), hexadecimal, and as bytes in any text encoding. If output still cannot be shown safely it is withheld whole. run exits with the command's own exit code; --jsonreturns the exit code, whether it timed out, and both output streams.

login

login types into a browser profile the Director owns - the profiles cc-devthrottle browser list shows - and nothing else: it cannot be pointed at a port or address a session started for itself. It therefore only works inside a DevThrottle session. It uses the first open tab in that profile whose address the entry allows - not necessarily the tab in front - so keep one such tab open. Before calling it, start the profile with cc-devthrottle browser start <name> and open the site's login page in it. --browser takes the profile's id or name; --timeout is how long to wait for the login to finish (30 seconds by default).

The password goes from the tool straight to the page and never passes through the agent. Before anything is typed, three things are checked against the entry's allowed addresses:

  1. the tab's address, read from the browser itself, where a page script cannot alter it;
  2. where the form will send what is typed into it;
  3. how the form will send it - only POST. A GET form would put the password in the page address and the browser history.

From just before the password is typed until the tab has been cleaned, every request the tab makes is paused and judged. The password may travel only in a POST to an allowed address; a request that would carry it anywhere else, or in any request's address, is stopped before it leaves the browser and the login is refused. A redirect is judged again at each hop. That rule is what holds even when the page changes its own form: a page submit handler that switches the form to another address or method is also caught by a listener that cancels the submission, but a handler can stop that listener from running, and a change to another address the entry allows still goes through.

Whatever the outcome, once a password has been typed the tab is made safe and that is confirmed, not assumed: every password field is emptied, the tab's back and forward history is reset, and the tab is read back to check. If that cannot be confirmed the tab is closed, and if it cannot be closed either, the login fails with a message telling you to close it.

The answer is one of four outcomes:

  • logged in - the form was submitted and went away.
  • refused - a check failed, for example the tab is on an address the entry does not allow (then nothing was typed), or the page tried to send the password somewhere it may not go (then that request was blocked; an earlier POST to an allowed address may already have been sent).
  • failed - no login form, the site rejected the credentials, the site did not answer, or the profile could not be reached.
  • verification - the site asked for a two-step code or a captcha. Finish the login by hand in that browser profile.

Exit codes

  • 0 - done.
  • 1 - failed, including a login that stopped at a verification step. For run, the command's own non-zero exit code is passed through instead.
  • 2 - refused: an owner command inside a session, an entry that is not available for that use, or a login check that did not pass.

The audit log

Anywhere
cc-secrets log
cc-secrets log -n 200 --json

Every use writes one line: the time, the entry, the session that asked (empty outside a session), the machine, the command, the outcome (ok, refused or failed) and a detail such as the exit code. Adding and removing entries is recorded too. A line that would contain the password is refused before it reaches the disk. log shows the most recent 50 lines unless you pass --count (-n).

Where the store lives

  • Windows: %LOCALAPPDATA%\cc-director\secrets\secrets.json
  • Linux: ~/.cc-director/secrets/secrets.json
  • macOS: the store is not supported yet. On a Mac, cc-secrets refuses to create, read or write the store, so no entry can be added or used: a file's access control list there can let another account read it even when its permissions look private, and cc-secrets does not check those lists yet.

The audit log (secrets-audit.log) and the tool's own diagnostic log (the logs folder) sit beside the store. The store is a plain JSON file, protected by file permissions, not encrypted. On Windows the folder's access list grants your user alone and inherits nothing from its parent folder; on Linux the folder is 0700 and the store file 0600. When a command reads or saves the store, the folder is checked (once per command) and so is the store file. Loosened permissions on a folder cc-secrets created itself are tightened; an existing folder it did not create that is not already private is refused, never changed; and if anything is still open to someone else the command refuses to go on. Reading the audit log does not repeat that check. The store file is replaced in one step through a temporary file that is private from the moment it exists.

Limits

These are the limits the code states and the owner accepted. Read them before you store anything that matters.

  • A program running as you can read the store. An administrator can read any file on the machine, and any process running as your user - a session's own shell included - can open secrets.json. The rule that the model never sees a password is kept by the tool never printing one, not by the file being unreadable to the user the agent runs as.
  • The clean-up catches accidents, not intent. A command that deliberately transforms the password - reverses it, hashes it, splits it across lines - produces output with no recognisable form of it. What limits that is the entry's uses setting and the agents switch; what records it is the audit log.
  • The page receives the password. JavaScript already running in the login page gets it, because the site needs it. A listener attached to the page before login was called can copy it, and a page can transform it before sending, so it no longer looks like the password.
  • The request guard sees one tab. It covers the requests of the login tab's own frames. A service worker, a cross-origin frame running in another process, a WebSocket message and other tabs are outside it. A header a page script sets is judged as the browser reports it, and the browser reports a value holding a character outside plain ASCII with characters missing, so a page script that puts a non-ASCII password, as it is, into a header of a request to another site is not caught.
  • The guard lasts only while the tool is connected. If cc-secrets is stopped or crashes while a request is paused, the browser sends that request on without a judgement. The accidental case this leaves open needs a site that answers the login with a redirect to another address at the same moment the tool stops.
  • Not handled: login forms inside cross-origin frames, two-step codes and captchas (reported as verification for you to finish by hand), and a hostile program running as your user that takes over the browser profile's debug port.
  • One machine at a time. There is no sync between machines, and the agents switch applies to every session on the machine rather than to a named session or mission.
Warning
cc-secrets keeps a password out of the transcript, the logs and the output. It is not a vault against software you do not trust on your own account. Do not store a password there that a program running as you must never be able to read.

The other shipped tools are in the tool reference; browser profiles are managed with the cc-devthrottle browser commands listed there.