Diawi Jenkins plugin advisory: a safer way to upload builds
· 6 min read
Search for "diawi jenkins" and the first result is the community Diawi Upload plugin on the Jenkins plugin site. As read on November 2, 2026, the page shows version 1.5, released about six years ago, and a security warning from 2019 titled "Credentials stored in plain text", with no release that fixes it. Plenty of older Jenkins setups still use it, because it worked and nobody looked again. This guide explains what to check, how to clean up, and two ways to keep uploading from Jenkins with the token held in Jenkins credentials. One of them keeps Diawi; the other moves to AppForShare, the tool we make.
What the warning means in practice
A credential stored in plain text is a secret saved without encryption, typically as part of a job's configuration. Anyone who can read that configuration, the controller's disk or its backups can read the token. For a Diawi token, that means they can upload builds to your account and read upload status. Read the advisory linked from the plugin page for the exact scope. The fix on your side is the same whatever the details: assume the token has been exposed, replace it, and keep the new one somewhere Jenkins protects.
This is not a criticism of Diawi itself. The plugin is community made, not an official Diawi product, and Diawi has no official CLI or CI plugins. The plugin simply has not had a release since the warning.
Step 1: find out whether you use it
- In Jenkins, open Manage Jenkins, Plugins, Installed plugins and search for Diawi. Jenkins also shows active security warnings for installed plugins on the Manage Jenkins page.
- List the jobs that use it. In freestyle jobs it appears as a post build step; in pipelines, search your Jenkinsfiles for the plugin's step.
- Check who can configure or read those jobs, and who can read the controller's files and backups.
Step 2: rotate and remove
- Create a new API token in the Diawi dashboard.
- Delete the old token in Diawi, so a copy in an old config or backup stops working.
- Store the new token as a Jenkins credential: Manage Jenkins, Credentials, add a Secret text credential, for example with the id
diawi-token. - Replace the plugin step with one of the steps below, run a build, then uninstall the plugin.
Option A: keep Diawi, drop the plugin
You do not need a plugin to call Diawi's API. The withCredentials step from the Credentials Binding plugin puts the token in the environment for one step only, and Jenkins masks it in the console log. Diawi lists API access from Starter (EUR 2.99 a month) up.
stage('Share with testers') {
steps {
withCredentials([string(credentialsId: 'diawi-token', variable: 'DIAWI_TOKEN')]) {
sh '''
JOB=$(curl -sf https://upload.diawi.com/ \
-F token="$DIAWI_TOKEN" -F file=@build/App.ipa | jq -r .job)
for i in $(seq 1 60); do
R=$(curl -sf "https://upload.diawi.com/status?token=$DIAWI_TOKEN&job=$JOB")
S=$(echo "$R" | jq -r .status)
[ "$S" = 2000 ] && { echo "$R" | jq -r .link; exit 0; }
[ "$S" = 2001 ] || { echo "$R" | jq -r .message; exit 1; }
sleep 5
done
exit 1
'''
}
}
}
Use single quotes around the sh script, as above, so Groovy does not put the secret into the command string; the shell reads it from the environment. The fields and status codes are explained in the Diawi API with curl and Node. In a freestyle job, tick "Use secret text(s) or file(s)" under Build Environment and call the same script in an Execute shell step.
Option B: the AppForShare CLI
If you are moving anyway, the AppForShare CLI does the upload, waits for processing and prints the link in one command. It needs Node 18 or newer on the agent and an API token (Pro and Team) in a Secret text credential:
stage('Share with testers') {
steps {
withCredentials([string(credentialsId: 'appforshare-token', variable: 'APPFORSHARE_TOKEN')]) {
sh 'npx --yes appforshare upload build/App.ipa --notes "$(git log -1 --pretty=%B)" --channel beta'
}
}
}
The CLI uploads large files in resumable parts, puts the build into a channel with a stable link, and with --fail-on blocked fails the stage when the store readiness check finds a blocker. The full setup, including agents without internet access to npm, is in the Jenkins guide.
A third route sits between the two: keep the Option A script and point it at AppForShare's Diawi compatible endpoint, https://appforshare.io/api/ci/diawi and /api/ci/diawi/status, with your AppForShare token. One request carries up to 100 MB; bigger builds go through the CLI.
Where the .ipa comes from on a macOS agent
Both options upload a file the build already made. For iOS that means an archive exported for testing, not for the App Store, or iPhones will refuse it from a link. A typical export stage on a macOS agent:
stage('Export ad hoc IPA') {
steps {
sh '''
xcodebuild -workspace App.xcworkspace -scheme App \
-configuration Release -archivePath build/App.xcarchive archive
xcodebuild -exportArchive -archivePath build/App.xcarchive \
-exportOptionsPlist ExportOptions.plist -exportPath build
'''
}
}
In ExportOptions.plist, set the method to release-testing (called ad-hoc in older Xcode versions), or enterprise for an in-house build. The profile must include every tester's iPhone. On Android agents, ./gradlew assembleRelease gives an APK; Diawi does not accept .aab files, while AppForShare turns them into one APK.
Show the link in the build
Testers and reviewers rarely read console logs. Put the link where people look: the build description. With the Option A script, end the shell step by writing the link to a file, then set it from the pipeline:
script {
def link = readFile('link.txt').trim()
currentBuild.description = "<a href='${link}'>Install link</a>"
}
Showing HTML in descriptions depends on your Jenkins markup formatter setting; plain text works everywhere. The AppForShare CLI prints the install link and the channel link, so the same idea applies to Option B.
What about an AppForShare Jenkins plugin?
It is coming. It is built to use the Jenkins Credentials API, so the token is never saved in a job's configuration, and it will offer a freestyle step and a pipeline step. It is not published on the Jenkins plugin site yet. Until it is, the CLI step above is the supported way, and it already keeps the token in Jenkins credentials.
A checklist for any CI plugin that holds a token
- When was the last release, and is the repository still answered?
- Does the plugin page show security warnings?
- Does it read the token from the credentials store, or a text field in the job?
- Is the token masked in the console log?
- Can you do the same thing with a short script and a credentials binding? If yes, that is often safer than an unmaintained plugin.
The same questions apply to fastlane plugins and GitHub Actions. For Diawi, the original fastlane plugin's versions were removed from RubyGems and the community fork was last released in May 2023. See moving off Diawi for the replacement for each integration, and uploading builds from CI for other pipelines.
Common questions
Is the Diawi Jenkins plugin made by Diawi?
No. It is a community plugin. Diawi's GitHub organisation holds only its knowledge base.
Do I have to rotate the token if no one else uses our Jenkins?
Backups, old controllers and people who once had access count too. Rotating takes two minutes, so do it.
Does Jenkins hide secrets from the log on its own?
Values bound with withCredentials are masked in the console log. A token typed into a plain job field is not.
Is there an AppForShare plugin I can install today?
Not yet. Use the CLI step from the Jenkins guide; the plugin will follow.


