-
Notifications
You must be signed in to change notification settings - Fork 348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CLI: replace keytar with custom solution #4750
Conversation
ee83417
to
bb81f51
Compare
TODO: report helpful error messages when missing dependencies Linux:
Windows with Admin Powershell
|
agent/src/cli/auth/secrets.ts
Outdated
class MacOSKeychain extends KeychainOperations { | ||
async readSecret(): Promise<string> { | ||
const { stdout } = await execAsync( | ||
`security find-generic-password -s "${this.service()}" -a "${this.account.username}" -w` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems prone for command injection, is there some safe shellquoting package on NPM like Gos shellquote?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you could use the spawn APIs where you pass in the args array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@olafurpg This might be worth fixing, since we have security-sensitive customers, and I don't think this is a PLG-only feature.
agent/src/cli/auth/secrets.ts
Outdated
|
||
async writeSecret(secret: string): Promise<void> { | ||
await execAsync( | ||
`echo "${secret}" | secret-tool store --label="${this.service()}" service '${this.service()}' account ${ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is secret-tool
expected to exist in most distros? I assume server-environments (VSCode remote SSH for example) won't have this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
secret-tool isn't usually installed, but the libsecret stuff via dbus is normally. I think as long as we are graceful here it should be fine. Personally for me I prefer not having tools like this, and rather manage it myself via envvars on my linux desktop. EG see this problem I had trying out cody-emacs where the agent wanted to store secrets, even though cody-emacs was responsible for the secret. sourcegraph/emacs-cody#30 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was going to follow up separately to report helpful error messages if the dependencies are missing #4750 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@keegancsmith auth
is an optional feature, you can still use environment variables or explicit --access-token to manage secrets. From personal experience, I find it incredibly convenient when tools like gh
just work consistently on macOS/Windows/Linux regardless of how or where you started the terminal. Storing secrets as environment variables has lots of problems. For example, any program you run has access to SRC_ACCESS_TOKEN without doing any additional work. With a secret manager, you need to at least know the server endpoint and username to read the correct secret (which you can do by reading the plaintext user-settings.json file, but it's still a bit more effort).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My primary motivation for all this effort is to increase onboarding and retention. I want the cli tool to be super easy to get started with, and my personal experience is that I stick most easily with cli tools that manage secrets for me (example: gh
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I 100% agree, these tools should have a slick way to manage this stuff. But for example in automation and people who like to make like difficult like myself its nice to be able to have escape hatches. EG for emacs-cody I'd rather have auth be handled in the normal emacs way (if configured).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quoting the shell args somehow does seem like a good thing to add. I won't stand in the way of merging, though, since keytar needs to die die die.
agent/src/cli/auth/secrets.ts
Outdated
class MacOSKeychain extends KeychainOperations { | ||
async readSecret(): Promise<string> { | ||
const { stdout } = await execAsync( | ||
`security find-generic-password -s "${this.service()}" -a "${this.account.username}" -w` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@olafurpg This might be worth fixing, since we have security-sensitive customers, and I don't think this is a PLG-only feature.
|
547e48d
to
a918dde
Compare
Thank you for the review! I've addressed the feedback on shell quoting, the solution is now using |
CI failures are unrelated, posted about it here https://sourcegraph.slack.com/archives/C05AGQYD528/p1720014583901319 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One remaining question about command injection in the powershell scripts, otherwise LGTM
class MacOSKeychain extends KeychainOperations { | ||
installationInstructions = '' | ||
async readSecret(): Promise<string> { | ||
return await this.spawnAsync('security', [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
much nicer than a shell script, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for raising the concern! This is much cleaner and easier to reason about and maintain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would probably have used spawn
in the first iteration, it was using exec
because that's what Cody generated on the first shot 😅
return `${this.service()}:${this.account.username}` | ||
} | ||
async readSecret(): Promise<string> { | ||
const powershellCommand = `(Get-StoredCredential -Target "${this.target()}").GetNetworkCredential().Password` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is prone to injection I believe, as it's using string concatenation again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a change to escape all "
in the target. This is a low-risk injection vector because the target is a combination of the server endpoint and username. Still, worth escaping out of principle
try { | ||
await keytar.deletePassword(codyServiceName, account) | ||
execSync(`which ${command}`, { stdio: 'ignore' }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking but might be nice to reduce the dependency on a shell that has which - https://github.com/otiai10/lookpath/blob/main/src/index.ts seems to be an implementation of Gos exec.LookPath for node.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it, but we only do this on Linux so it's not a problem. On macOS, security
is pre-installed and on Windows the user needs to install CredentialManager
with the pre-installed powershell
.
// modules for each supported OS. The current implementation shells out to the | ||
// `security` command on macOS, `CredentialManager` on Windows, and `secret-tool` on | ||
// Linux. This is an o)ptional feature. Users can always avoid using this functionality | ||
// by setting the `CODY_ACCESS_TOKEN` environment variable or passing the `--access-token` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cody clients should also be able to pass the token as part of the initialize
call.
It was semi-broken though, I was fixing it today by the way of looking tat the telemetry issue:
https://github.com/sourcegraph/cody/pull/4766/files#diff-85c89382076aeb49c8645dd41e6422552fba7bbf25575701c1a9253027164a76
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NICE! I've had a gut feeling we are doing some broken things like that. Glad we are fixing it <3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
afafd1e
to
21a5b26
Compare
Previously, we used Keytar to write/read/delete secrets. This was problematic for two reasons: - Keytar has been unmaintained since 2022 - Keytar required native Node modules that meant `cody-agent` was crashing on macOS when using a release that was built on Linux (in CI) This PR addresses both problems by replacing keytar with a custom solution that shells out to different secret managers (`security` on macOS, `secret-tool` on Linux, and `powershell` on Windows). See comment in `secrets.ts` for a more detailed reasoning why we went with this approach.
Only tested on macOS for now, will test on Linux/Windows once I push the commit.
21a5b26
to
aa27404
Compare
Rebase on top of main to fix CI #4768 |
Thank you everybody for the review! 🙇🏻 I will cut a release and get a second round of review from the security team CODY-2738 : Complete security review on Cody cli |
Previously, we used Keytar to write/read/delete secrets. This was problematic for two reasons:
cody-agent
was crashing on macOS when using a release that was built on Linux (in CI)This PR addresses both problems by replacing keytar with a custom solution that shells out to different secret managers (
security
on macOS,secret-tool
on Linux, andpowershell
onWindows). See comment in
secrets.ts
for a more detailed reasoning whywe went with this approach.
Test plan
Manually tested on macOS/Linux/Windows.