Skip to content

Restrict file location for SSH and certificates credentials#533

Closed
Kevin-CB wants to merge 1 commit into
jenkinsci:masterfrom
Kevin-CB:restrict-file-creation
Closed

Restrict file location for SSH and certificates credentials#533
Kevin-CB wants to merge 1 commit into
jenkinsci:masterfrom
Kevin-CB:restrict-file-creation

Conversation

@Kevin-CB

@Kevin-CB Kevin-CB commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

#532

Testing done

Confirmed locally the file is not created outside the expected directory with the following pipeline

node {
  withCredentials([sshUserPrivateKey(credentialsId:'demo-ssh-key',
    keyFileVariable:'../../../../myFile')]) { }
}

did the same for certificate creds.

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests that demonstrate the feature works or the issue is fixed

@Kevin-CB
Kevin-CB requested a review from a team as a code owner July 8, 2026 14:45
@Kevin-CB
Kevin-CB requested a review from daniel-beck July 8, 2026 14:45

@daniel-beck daniel-beck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix looks correct, but the tests could be improved.

FilePath secretsDir = UnbindableDir.create(workspace).getDirPath();
FilePath resolvedPath = secretsDir.child("keystore-" + payload);

assertFalse(resolvedPath.exists(), "Keystore file should not exist outside secure directory");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this lone assertion unconvincing. Didn't we do better in 3f6dece already with the #assertLogContains positive assertion? Same in e52b232 followup. Why not add something similar here?

if (workspace != null) {
final UnbindableDir secrets = UnbindableDir.create(workspace);
final FilePath secret = secrets.getDirPath().child("keystore-" + keystoreVariable);
String safeKeystoreVariable = new FilePath(new File(keystoreVariable)).getName();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks familiar 👍 Also looks correctly adapted for the prefix unique to this case.

@jtnord jtnord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the variable should not even be used for file names, create a unique file in the directory.

Comment on lines +88 to +89
String safeKeystoreVariable = new FilePath(new File(keystoreVariable)).getName();
final FilePath secret = secrets.getDirPath().child("keystore-" + safeKeystoreVariable);

@jtnord jtnord Jul 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keystore variable is supposed to be the name of an environment variable not the temporary file!?

this should be a temporary file and not take in user specified paths.

e.g.

 withCredentials([certificate(credentialsId: 'id1', keystoreVariable: 'hello')]) {
 sh 'echo path is $hello and should exist'
   withCredentials([certificate(credentialsId: 'id2', keystoreVariable: 'hello')]) {
     sh 'echo path is $hello and should exist but be different to before'
   }
  sh 'echo path is $hello and should still exist (but the path for the previous id2 should be deleted'
}

So rather than attempting to sanitize somthing, this should surely just create a unique file without any user supplied being used.
(additionally - this does not sanitize against max filename lengths!)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tend to agree. But:

String baseName = new FilePath(new File(credentials.getFileName())).getName();

Also

this.secretValues = new LinkedHashMap<>(secretValues);
this.publicValues = new LinkedHashMap<>(publicValues);
should probably block anything that does not look like a valid variable name. (Too late for this attack.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the original intent of using specific file names was to simplify diagnosis from build logs. But the bound file path is treated as a secret and masked anyway. So we might as well use arbitrary filenames (like random UUIDs).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#534

Comment on lines +88 to +89
String safeKeystoreVariable = new FilePath(new File(keystoreVariable)).getName();
final FilePath secret = secrets.getDirPath().child("keystore-" + safeKeystoreVariable);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
String safeKeystoreVariable = new FilePath(new File(keystoreVariable)).getName();
final FilePath secret = secrets.getDirPath().child("keystore-" + safeKeystoreVariable);
final FilePath secret = secrets.getDirPath().createTempFile("keystore-", ".ks");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createTempFile perhaps overkill given that each binding calls

final String dirName = UUID.randomUUID().toString();
final FilePath dir = secrets.child(dirName);
so even a fixed filename would work, but sure

@Kevin-CB

Kevin-CB commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Closed in favor of #534

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants