Last weekend I released @xurizaemon/eleventy-immich. I used Github Actions to trigger an NPM release from a pushed tag, and after making the initial release with an arbitrary version of v0.1.0, I reviewed the @xurizaemon/eleventy-immich@v0.1.0 release page on NPMJS.
The "unpacked size" value was 28.5MB, and I realised I'd made a mistake.
I'm more familiar with Composer releases, and when you release a Composer package the built archive is constrained to the sourcecode of the tagged release. If you wanted to release a Composer package with some generated files - say, translations from an external source, or built/compiled CSS or JS, my understanding is that you need to build those additional files first and commit them to your Composer codebase.
NPM doesn't have this limitation; the release process can include build steps, and the release archive can include build-time results.
My package's build steps included running tests, and the tests performed end-to-end integration tests against an Immich instance I host. That 28.5MB of files included about 5KB of sourcecode, and some additional files. This counts as "oversharing", and I now needed to review whether I'd inadvertently shared anything I shouldn't have.
The test images weren't my concern. I'll document how the tests work in a more appropriate location, but I had good confidence that the images themselves weren't sensitive; the tests retrieve specific images individually and from a specific album. My concern was other details, such as the API keys used for the integration, and any other details I might find in the built archive. Also, I find being interested in problems is a really effective way to approach them, so my inclination was to learn more rather than to panic.
By the time I'd identified my mistake, I could also see that there were 9 downloads of my newly released package. How exciting! Do I think there were nine people that stumbled onto an interesting NPM package and typed npm install @xurizaemon/eleventy-immich
straight away? No, I don't. Automated systems are watching new releases (and new Github pushes) for "interesting" details all the time, and in this world "interesting" means "profitable". I have to assume that any details shared will already have been seen by someone with potential for malicious use.
First action
My first action was to correct my mistake by adding some .gitignore
entries (which NPM respects) to the project, and releasing v0.1.1 at a much more reasonable ~5KB in place of the previous 28MB.
Assessing the damage
With a replacement release out the door, my next step was to review what I'd shared.
Let's grab a copy of the files I published:
chris@thip: ~$ mkdir /tmp/throwaway && cd /tmp/throwaway
chris@thip:/tmp/throwaway$ npm init -y
# output skipped
chris@thip:/tmp/throwaway$ npm install @xurizaemon/eleventy-immich@v0.1.0
added 1 package, and audited 2 packages in 2s
found 0 vulnerabilities
chris@thip:/tmp/throwaway$ cat package.json
{
"name": "throwaway",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@xurizaemon/eleventy-immich": "^0.1.0"
}
}
So, what made it into the release?
Firstly: does the downloaded files approximate what I think got released? Yes, it does.
chris@thip:/tmp/throwaway$ du -sh node_modules/@xurizaemon/eleventy-immich/
28M node_modules/@xurizaemon/eleventy-immich/
Initial inspection
Let's have a look at what I need to explore. tree -da
will show me the directories of a directory:
chris@thip:/tmp/throwaway/node_modules/@xurizaemon/eleventy-immich$ tree -ad
.
├── .cache
├── .github
│ └── workflows
├── .idea
│ ├── codeStyles
│ └── inspectionProfiles
├── public
│ └── media
│ └── img
└── test
10 directories
Looking at the file contents as well: tree -a
Click to see output
chris@thip:/tmp/throwaway/node_modules/@xurizaemon/eleventy-immich$ tree -a
.
├── .cache
│ ├── eleventy-fetch-13d629a2fb0ab7cea5b05589667f7e
│ ├── eleventy-fetch-13d629a2fb0ab7cea5b05589667f7e.buffer
│ ├── eleventy-fetch-317aeed09661ed23b93071dff930bd
│ ├── eleventy-fetch-317aeed09661ed23b93071dff930bd.json
│ ├── eleventy-fetch-67adde77a89e4672d3ac77c7313615
│ ├── eleventy-fetch-67adde77a89e4672d3ac77c7313615.json
│ ├── eleventy-fetch-77be3b5f8c2ec56bb3890fa6619ba6
│ ├── eleventy-fetch-77be3b5f8c2ec56bb3890fa6619ba6.json
│ ├── eleventy-fetch-7c85f08df0509d9d7825bf367d12d3
│ ├── eleventy-fetch-7c85f08df0509d9d7825bf367d12d3.json
│ ├── eleventy-fetch-a1fb53576fce4640adb4176510b7af
│ ├── eleventy-fetch-a1fb53576fce4640adb4176510b7af.buffer
│ ├── eleventy-fetch-a4f2ce9274ea106198f56d6355e3cc
│ ├── eleventy-fetch-a4f2ce9274ea106198f56d6355e3cc.json
│ ├── eleventy-fetch-ab5c0e42fecc55d7316494c515354d
│ ├── eleventy-fetch-ab5c0e42fecc55d7316494c515354d.buffer
│ ├── eleventy-fetch-b2e62be056ad837dbc96ebca8fb4ac
│ ├── eleventy-fetch-b2e62be056ad837dbc96ebca8fb4ac.buffer
│ ├── eleventy-fetch-b486d1283d68c1996d39e5c71be87f
│ ├── eleventy-fetch-b486d1283d68c1996d39e5c71be87f.json
│ ├── eleventy-fetch-c4f8f26c84ede2b465b604152680b1
│ ├── eleventy-fetch-c4f8f26c84ede2b465b604152680b1.buffer
│ ├── eleventy-fetch-c8a1319796be03a4a3a8fb0f82bd02
│ ├── eleventy-fetch-c8a1319796be03a4a3a8fb0f82bd02.json
│ ├── eleventy-fetch-f37c2de21a89f407822b63fbaa5f95
│ ├── eleventy-fetch-f37c2de21a89f407822b63fbaa5f95.json
│ ├── eleventy-fetch-f85c0fb9f6a0fd9713efd6f89f2eee
│ └── eleventy-fetch-f85c0fb9f6a0fd9713efd6f89f2eee.buffer
├── eslint.config.mjs
├── .github
│ └── workflows
│ ├── nodejs.yml
│ └── npm-publish.yml
├── .idea
│ ├── codeStyles
│ │ ├── codeStyleConfig.xml
│ │ └── Project.xml
│ ├── eleventy-immich.iml
│ ├── GitLink.xml
│ ├── inspectionProfiles
│ │ └── Project_Default.xml
│ ├── modules.xml
│ ├── php.xml
│ └── vcs.xml
├── immich.js
├── LICENSE
├── package.json
├── public
│ └── media
│ └── img
│ ├── 6F6xc8RT4F-300.jpeg
│ ├── 6F6xc8RT4F-600.jpeg
│ ├── 8wDxzJOqow-300.jpeg
│ ├── 8wDxzJOqow-600.jpeg
│ ├── AitV6V6-2X-300.jpeg
│ ├── AitV6V6-2X-600.jpeg
│ ├── heH3xr0I7G-300.jpeg
│ ├── heH3xr0I7G-600.jpeg
│ ├── oupuEcpEIs-300.jpeg
│ ├── oupuEcpEIs-600.jpeg
│ ├── rDX8xf_N04-300.jpeg
│ └── rDX8xf_N04-600.jpeg
├── README.md
└── test
└── test.js
10 directories, 56 files
Reviewing the files on the surface
What can I see from filenames alone?
.cache/
contains filesystem caches generated by eleventy-fetch. These requests were authenticated, so i need to review them for sensitive data - eg a cached request header..idea/
indicates use of JetBrains tooling: My IDE (family) is revealed.- An IDE plugin's configuration is present, revealing the use of GitLink. (Great for right-click and share link to colleague, btw.)
- Some other configuration is present in the
.idea/
directory, eg code formatting configuration.
Inspecting file contents
And inspecting the files, what else can I see?
.cache/
entries:- Each cache entry contains a pair of files: One with JSON metadata (eg
.cache/eleventy-fetch-7c85f08df0509d9d7825bf367d12d3
) with type and timestamp, and another with the cached response (eg.cache/eleventy-fetch-7c85f08df0509d9d7825bf367d12d3.json
) with the retrieved data. - For each of these pairs, the cached file is either a
.json
(Immich API responses) or a.buffer
(Immich asset downloads, ie image data - these can be identified withfile
command, then opened as that file type). .cache/*.buffer
file contents are already public; it's the same handful of images shared as an example from my page on the initial plugin concept..cache/*.json
entries are copies of the Immich API response and do reveal additional data. I can see:- My Immich account email (predictable), personal name, avatar colour (purple!)
- Imported file filenames (filenames may leak metadata)
- Exif data, including geolocation data. Fortunately I selected film scans as my test images, and only location tagged a single photo taken at Long Beach.
- Immich "people" identification results for three people (myself and two family members), revealing the personal names I've tagged those faces with.
- Immich "smartInfo" tags on a single image (
["loupe", jeweler's loupe"]
on the Dunedin Farmer's Market selfie - it's taken with a 170-degree lens, and Immich sees the bulbous shape of a magnifying glass). Interesting, and potentially revealing to see tags, but nothing to see here really.
- Each cache entry contains a pair of files: One with JSON metadata (eg
public/
entries contains the set of images again, scaled to 300w and 600w.
Is any of this a worry to me?
Is there any of that I'm concerned about? Not really. There's a little extra sharing, but very little of concern. It has got me wondering whether using different emails to sign into a service would add any additional security to my accounts, but not the best return for effort versus other account protection strategies.
What other options did I have?
I could have unpublished the release (npm unpublish @xurizaemon/eleventy-immich@v0.1.0
) from the registry. If I had been seriously concerned, I would have looked to that. However: at the time I knew of the error, I had already seen there were nine downloads. Unpublishing the release would not have addressed those, along with the fact that I'd supplied the release tarball to a couple of organisations along the way: Github and npm, Inc. I assumed seven of the nine downloads were malicious actors and moved on to threat assessment.
Shared, not redacted
Instead, I figure I should add a copy of the released files here as a reference: eleventy-immich-0.1.0.tgz
Or, you can get the original URL via npm view @xurizaemon/eleventy-immich@v0.1.0
, or browse the v0.1.0 source files on npmjs.com