Backing up attachments: "Attachment `vault` was not found." (CLI)

I am having trouble automating backup of vault attachments.

My script is below. It is split into two as I backup for multiple family members. The parent script passes credentials for each user to the common backup script.

For each attachment it displays: “Attachment vault was not found.”
The same general logic works when exporting each organization.
It also works when executed manually.
I would like to automate this. What am I missing here?
Until I resolve this I am running the script with the attachment section disabled.

Thanks and best regards.

export.sh

#!/bin/bash

# Bitwarden CLI vault export

BW_CLIENTID='<retracted>'
BW_CLIENTSECRET='<retracted>'
BW_PASSWORD='<retracted>'
BW_ENC_PW='<retracted>'
USER='<retracted>'

./_export.sh "$BW_CLIENTID" "$BW_CLIENTSECRET" "$BW_PASSWORD" "$BW_ENC_PW" "$USER"

<other users retracted>
````Preformatted text`

_export.sh
==========

#!/bin/bash

# Bitwarden CLI vault export

export BW_CLIENTID="$1"
export BW_CLIENTSECRET="$2"
export BW_PASSWORD="$3"
export BW_ENC_PW="$4"
USER="$5"

TIMESTAMP=$(date +%Y%m%d%S)
export EXPORT_PATH=backups/$USER/$TIMESTAMP
EXPORT_ATTACHMENT=backups/$USER/attachments
EXPORT_FILE=vault_enc.json
save_folder_attachments=backups/$USER/attachments
LOG=$EXPORT_PATH/export.log

mkdir -p $EXPORT_PATH

# Check whether jq is installed.
if [ ! -x "$(command -v jq)" ]; then
  echo 'Error: jq is not installed.' >> $LOG
  exit 1
fi

# Check whether bw CLI is available.
if [ ! -f ./bw ]; then
  echo 'bw does not exist. Downloading...' >> $LOG
  curl -JLs "https://vault.bitwarden.com/download/?app=cli&platform=linux" -o bw.zip
  unzip -oq bw.zip
  rm bw.zip
  chmod +x ./bw
fi

# Check whether already logged in.
if ./bw login --check > /dev/null 2>&1; then
  echo "Already logged in. Logging out..." >> $LOG
  ./bw logout > /dev/null >> $LOG
fi

# Login
echo "Login..." >> $LOG
./bw login --apikey >> $LOG
echo >> $LOG

# Unlock
echo "Unlock vault..." >> $LOG
export BW_SESSION=$(./bw unlock --passwordenv BW_PASSWORD)

# Export vault
echo "Export vault..." >> $LOG
./bw export --session $BW_SESSION --format encrypted_json --password $BW_ENC_PW --output $EXPORT_PATH/$EXPORT_FILE >> $LOG
echo >> $LOG

# Check for attachments
if [[ $(./bw list items --session $BW_SESSION | jq -r '.[] | select(.attachments != null)') != "" ]] then
  echo "Export attachments"

  # FAILING HERE
  bash <(./bw list items --session $BW_SESSION | jq -r '.[]
    | select(.attachments != null)
    | "./bw get attachment --session $BW_SESSION \"\(.attachments[].fileName)\" --itemid \"\(.id)\" --output \"'$EXPORT_PATH'/attachments/\(.name)\""')
fi

# Check for organizations
if [[ $(./bw list organizations --session $BW_SESSION | jq -r '.[]') != "" ]] then
  echo "Export organizations..." >> $LOG
  bash <(./bw list organizations --session $BW_SESSION | jq -r '.[]
     | "./bw export --session $BW_SESSION --organizationid \"\(.id)\" --format encrypted_json --password $BW_ENC_PW --output \"$EXPORT_PATH/org/\(.name)/org_enc.json\""') >> $LOG
  echo >> $LOG
else
  echo "There are no organizations." >> $LOG
fi

./bw logout >> $LOG
echo >> $LOG

# Remove exported variables
unset BW_CLIENTID
unset BW_CLIENTSECRET
unset BW_PASSWORD
unset BW_SESSION

The line that is supposed to export the attachments from your script works for me:

kiko@penguin:~ $ cat test.sh 
#!/bin/bash

export EXPORT_PATH=/tmp/test2delete/
  # FAILING HERE
  bash <(bw list items --session $BW_SESSION | jq -r '.[]
    | select(.attachments != null)
    | "bw get attachment --session $BW_SESSION \"\(.attachments[].fileName)\" --itemid \"\(.id)\" --output \"'$EXPORT_PATH'/attachments/\(.name)\""')
kiko@penguin:~ $ ./test.sh 
Saved /tmp/test2delete/attachments/test item 01 login
Saved /tmp/test2delete/attachments/test item 02 secure note
Saved /tmp/test2delete/attachments/test item 02 secure note

Are you already logged in? When I try your simplified script it works but prompts me for the master password for each attachment.

Expanding it to the following results in the same vault not found though.

#!/bin/bash

export BW_PASSWORD='<retracted>'
export EXPORT_PATH=/tmp/test2delete/

./bw login --apikey
export BW_SESSION=$(./bw unlock --passwordenv BW_PASSWORD)

# FAILING HERE
bash <(./bw list items --session $BW_SESSION | jq -r '.[]
  | select(.attachments != null)
  | "./bw get attachment --session $BW_SESSION \"\(.attachments[].fileName)\" --itemid \"\(.id)\" --output \"'$EXPORT_PATH'/attachments/\(.name)\""')
$ ./test.sh
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.
Attachment `vault` was not found.

What version of BW CLI are you using? My version shows 2025.1.3.

Yes, of course, BW_SESSION is previously set and exported (outside of the test.sh script).

I don’t understand you: doest it work or does it not???

What is the output of the bw list items command that you feed to bash??

When I run it I get this:

kiko@penguin:~ $ export EXPORT_PATH=/tmp/test2delete/

kiko@penguin:~ $ bw list items --session $BW_SESSION | jq -r '.[] | select(.attachments != null) | "bw get attachment --session $BW_SESSION \"\(.attachments[].fileName)\" --itemid \"\(.id)\"
 --output \"'$EXPORT_PATH'/attachments/\(.name)\""'
bw get attachment --session $BW_SESSION "attachment-01-01.txt" --itemid "ebac0024-d656-4d67-9035-b28c009abdbb" --output "/tmp/test2delete//attachments/test item 01 login"
bw get attachment --session $BW_SESSION "attachment-02-01.txt" --itemid "c2853711-46c7-429d-b9a3-b28c009abdbb" --output "/tmp/test2delete//attachments/test item 02 secure note"
bw get attachment --session $BW_SESSION "attachment-02-02.txt" --itemid "c2853711-46c7-429d-b9a3-b28c009abdbb" --output "/tmp/test2delete//attachments/test item 02 secure note"

And if I run it in bash, the 3 attachments in my test vault get downloaded (with the item name as the downloaded filename; which in my case, it results, in the third attachment overwritting the second one; as I have an item that has two attachments).

I’m using the same version of bw cli than you (the latest available).

I don’t understand you: doest it work or does it not???

As I do not manually login or have BW_SESSION previously set it prompts from my master password for each and every attachment. In that sense it does work, though it is not the desired behavior.
The objective is to automate and schedule backups (for multiple accounts), so manually logging in and configuring BW_SESSION is not practical.
Are you able to export attachments without previously logging in or having BW_SESSION set?
Meaning 1) login, 2) set / export BW_SESSION, and 3) export attached from within the same script.

What is the output of the bw list items command that you feed to bash??

$ export EXPORT_PATH=/tmp/test2delete/
$ ./bw list items --session $BW_SESSION | jq -r '.[] | select(.attachments != null) | "/.bw get attachment --session $BW_SESSION \"\(.attachments[].fileName)\" --itemid \"\(.id)\"
 --output \"'$EXPORT_PATH'/attachments/\(.name)\""'
You are not logged in.

After manually logging in, setting BW_SESSION, and retrying…

$ ./bw list items --session $BW_SESSION | jq -r '.[] | select(.attachments != null) | "/.bw get attachment --session $BW_SESSION \"\(.attachments[].fileName)\" --itemid \"\(.id)\" --output \"'$EXPORT_PATH'/attachments/\(.name)\""'
/.bw get attachment --session $BW_SESSION "<redacted>" --itemid "5d591e63-1219-4437-9ead-b1e0008f0187" --output "/tmp/test2delete//attachments/<redacted>"
/.bw get attachment --session $BW_SESSION "<redacted>" --itemid "5d591e63-1219-4437-9ead-b1e0008f0187" --output "/tmp/test2delete//attachments/<redacted>"
/.bw get attachment --session $BW_SESSION "<redacted>" --itemid "3c657763-bb3b-4717-8945-af7e00693c48" --output "/tmp/test2delete//attachments/<redacted>"
/.bw get attachment --session $BW_SESSION "<redacted>" --itemid "3c657763-bb3b-4717-8945-af7e00693c48" --output "/tmp/test2delete//attachments/<redacted>"
/.bw get attachment --session $BW_SESSION "<redacted>" --itemid "55b91dda-4fa8-41ca-89bf-af7e00693c48" --output "/tmp/test2delete//attachments/<redacted>"
/.bw get attachment --session $BW_SESSION "<redacted>" --itemid "9b9f40ab-12c0-492b-8408-b1e0008e26e9" --output "/tmp/test2delete//attachments/<redacted>"
/.bw get attachment --session $BW_SESSION "<redacted>" --itemid "9b9f40ab-12c0-492b-8408-b1e0008e26e9" --output "/tmp/test2delete//attachments/<redacted>"

This is essentially the expected output, if only the login and session could be set from within the batch file.

It does not matter at all, as long as the vault is unlocked and BW_SESSION is set correctly. And yours, as mine, is correctly set, or else the list items command would fail.

I don’t know what else to tell you, I have no idea why it does work for me but not for you. I’m guessing the problem must be in the parts that you redacted.

The backup script that I use is, in essence, very similar to yours. The attachments exports part, I mean.

As far as I can see, the only significant difference is whether BW_SESSION is externally being set in advance or set within the script. I would expect that export is making the variable visible in the jq-bw dynamic scope, but that may be failing. Strangely though this same logic works fine for exporting organizations though. So it may be something specific to attachments.

I found the problem. The unlock command was missing the --raw argument resulting in BW_SESSION being set to junk. It should be:
export BW_SESSION=$(./bw unlock --passwordenv BW_PASSWORD --raw)