Releases: shatheitguy/it-vault
Release list
v2.5.3 — One footer, on everything that leaves the building
One footer, on everything that leaves the building.
The signed PDF arrives signed off properly
The email that carries the signed acknowledgement had your organisation's
name and the credit line as dead text — no links. So did the sign-in code,
the password reset, every ticket mail and the SMTP check. Only the
acknowledgement request ever had an HTML part, and that is the only part
where links can live: the plain-text footer deliberately carries no URLs,
because mail security rewrites any link it can see and in plain text the
reader sees the rewrite instead of the name.
Six senders each built their own message body. There is one now, so every
email gets both parts — the text one without links, the HTML one with them.
An attachment no longer costs you the footer either: the body parts go on
first, so the PDF arrives beside a body that still has its links.
And it is not an email-only idea
- Telegram alerts were two bare lines with nothing saying which system
sent them. They now carry the same footer with working links, sent in HTML
mode — with the event's own text escaped first, so an asset called
<Laptop>cannot break the message. - Slack gets the same, in its own link markup.
- Webhooks get the provenance as fields (
app,version,
project_url,author), not as a string to parse back out.
On the phone
- The start-up screen shows your organisation's name under your logo,
sized to fit, instead of "IT-VAULT" — the name of the software, on the one
screen everybody sees every time they open the app, under a logo that was
already yours. - The app-icon colour picker is gone. None of those three tiles was
anybody's logo, so choosing between red, white and grey was not an answer
to "make the icon match our branding". The home-screen shortcut — which is
a bitmap, and really is your mark — is what that section offers now.
Anyone the old picker left on a variant is put back on the default, and
the aliases stay in the app so nobody's icon disappears.
Backups
The scope called "All (config + assets + history)" now says what it is:
Everything, with the create box spelling out what that means — every
table in the database, your logo, your letterhead and every invoice file,
with the table list read from the database itself so a future version's
tables are included without being listed.
Upgrading
curl -fsSL https://raw.githubusercontent.com/shatheitguy/it-vault/main/install.sh | sh
Android: IT-Vault.apk below, version 2.5.3 (versionCode 32), signed
with the same key as every previous release, so it installs over your
existing copy.
v2.5.2 — Nothing but you overwrites what you typed
Nothing but you overwrites what you typed.
If you have ever typed a job title into IT-Vault and found it back to the old
one later, or empty, this is the release that fixes it. Two separate faults
produced that one symptom, and both surfaced after an update because that is
when people look.
A directory with no answer no longer erases
Active Directory returns an empty value for an attribute a person does not
have. The sync wrote name, designation, department and email regardless, so
every run emptied whatever IT-Vault held for anyone with no title in AD —
and the automatic sync runs every thirty minutes, so work done in the
afternoon was gone by the evening. The sync now writes only values AD
actually gave it.
A field you edit becomes yours
When AD did have an answer it was usually the stale one you had typed over,
and the sync put it back; retyping did not help, because the next sync undid
that too. Editing an employee now records that field as yours and the sync
leaves it alone from then on. AD still fills in everyone nobody has touched,
which is almost everyone. The sync tells you how many rows it left as
entered.
A partial update only touches what it carries
PUT /api/employees/<id> and PUT /api/assets/<id> wrote every column from
the request, treating an absent field as "clear this". The web forms post
whole records so it never showed there — but the phone app, an import and
anything holding an API key send only what they changed, and a status update
should not take the serial number, the warranty and the invoice reference
with it. Both now write only the fields present in the body. Clearing a
field on purpose still works: send it as an empty string.
And a record of it, kept
- Every employee change is written to the audit log field by field, with the
value it had before. - Clearing the audit log takes a full backup first, and refuses to clear if
that backup fails. install.shnow takes a full backup before it replaces the container —
every table, the branding and every invoice attachment, into the backups
volume. An update never touches your volumes, so this is not insurance
against the update: it is the copy of "before" that cannot be
reconstructed afterwards.
Also
The acknowledgement email is one link and one button again — the code
printed under the button read as a hoop to jump through. The short /s/
address is what survives mail security. Opening the sign page without a code
still asks for one.
Upgrading
curl -fsSL https://raw.githubusercontent.com/shatheitguy/it-vault/main/install.sh | sh
The Employees.manual_fields column is added on start; nothing is claimed
until somebody edits a field, so the sync behaves exactly as before for
records nobody has touched.
One thing this cannot do: designations already emptied by the old sync are
not recoverable from inside the app, because they were overwritten without a
history row. If you have a backup archive from before that happened, restore
the employee data from it.
Android: IT-Vault.apk below, version 2.5.2 (versionCode 31), signed
with the same key as every previous release, so it installs over your
existing copy.
IT-Vault 2.5.1 — a name field you can see, and a code for blocked links
The name field looked like the background
It was an unstyled input on a dark card, sitting next to a white dashed signature pad the size of a postcard. One of those reads as something to fill in; the other reads as nothing. So people signed, submitted, and got told the name was required.
It's now the same white dashed box as the pad, the same size, and the two are numbered 1 and 2 — because they're two blanks in an order, not a form. The dashes turn solid green once there's a name in it. Submitting without one now highlights the field, focuses it and scrolls to it, instead of only printing a line under a button that's off screen on a phone.
A blocked link is no longer a dead end
Shortening the link in 2.4.0 stopped gateways mangling it, but a gateway that refuses a link refuses it whatever shape it is — that's their mail security's decision about an uncategorised host, and nothing in this software can overrule it.
So the code no longer exists only inside the link:
- the HTML part prints it under the button — "Button blocked by your mail system? Go to …/sign and enter this code: RUYGBPMS"
- the text part spells out the same thing
/signopened with no code now asks for it, instead of saying "No token" — which told the reader nothing they could act on
A gateway rewrites hrefs. It leaves eight characters of text alone. Anyone whose link is dead can read the code off the same email and type it.
Two test fixes, neither a product change
The footer test looked for the charset declaration inside a 600-character slice of source, and adding the code block pushed it past that — it now asks the rendered email. And the TrueNAS catalogue test caught the app still pinning 2.5.0 after the version bump, which is exactly what it was written to catch.
IT-Vault 2.5.0 — choose the app icon
Settings → App icon, and a home-screen shortcut that carries your own logo.
What Android allows, and what it doesn't
The launcher tile is a resource compiled into the APK. Nothing at runtime — and no setting on the server — can replace it with a logo fetched from somewhere. So an uploaded branding logo cannot become the app's icon, and the Branding panel now says so instead of leaving you to conclude the upload is broken.
Two things are possible, and both are in:
Pick from the tiles the app ships — Settings → App icon:
- Shield — the red tile (default)
- Light — the crest on white
- Mono — desaturated crest on near-black
Add a home-screen shortcut with your logo. A shortcut's icon is a bitmap rather than a compiled resource, so that one really is your mark. It sits beside the app icon rather than replacing it — the honest version of the request. It refuses with a reason if no logo has been cached yet, rather than pinning the shield and calling it branding.
The connect screen shows your logo too, since that screen is the app's own to draw.
Details that matter
- The new icon is enabled before the old one is disabled. The other order leaves a moment with no enabled LAUNCHER component, and some launchers respond by dropping the app off the home screen until a reboot.
- The aliases ship disabled, or a launcher lists the app three times.
- The alternates are adaptive icons like the default, with flat colour tiles instead of a PNG set per density, plus legacy PNGs for pre-adaptive launchers.
- Verified in the built APK: three icon resources present, both aliases in the manifest and disabled, one launchable activity.
Your launcher may take a moment to redraw, or need the home screen reopened — the app says so when you switch.
IT-Vault 2.4.0 — short signing links, and NAS templates
Acknowledgement links stopped being blocked by mail security.
A signing link was a couple of hundred characters of base64 in a query string — the shape of a phishing link, and gateways treat it accordingly. One install's Trend Micro rewrote it into a click-time proxy and then refused it: the mail arrived, the link didn't.
Links are now a short code in the path:
before: https://host/sign?token=eyJhIjp7ImFzc2V0X2lkIjoi… 234 characters
now: https://host/s/gnkdst2j 33 characters
The code is stored rather than carried, which fixes something the old form gave away for free: that token was plain base64, so anyone who decoded it — and every gateway that rewrote it, and every log that recorded it — could read the asset name straight out of the URL.
Everything about the flow holds: single use, one live link per asset, seven days. Links already in inboxes keep working — the old ?token= form is still accepted until it expires. Superseded codes are kept rather than deleted, because only a row that still exists can say why it's dead: "a newer one was issued, use the most recent email" is the difference between finding the right mail and ringing IT.
The email footer had the same problem, smaller
A gateway rewrites every link it can see. In an HTML part that's invisible — the reader sees the anchor text. In the plain-text part the reader sees the rewrite itself: 200 characters of clicktime proxy, twice, under every message. The text footer now names the project and the author; the links live in the HTML part where they belong.
Install it on a NAS without reading a compose file
unraid/it-vault.xml— a Community Applications template: ports, the three data paths, the database fields with passwords masked, and an overview that says up front that IT-Vault brings no database of its own.truenas/docker-compose.yaml— for TrueNAS Apps → Custom App → Install via YAML. This one does bring MariaDB, because on a NAS "install the database first" is where people stop.
tests/test_nas_templates.py keeps both honest against the code rather than against each other: every variable they set is one app.py reads, every path they mount is one it writes, and both name the same published image.
Upgrading
Pull the matching image; the SignLinks table is created on start.
IT-Vault 2.3.0 — security hardening, block one
The first block of the security audit. Five findings, worst first.
Passwords are no longer cheap to crack
They were one round of salted SHA-256 — the hash every cracking rig is built for, at billions of guesses a second. A stolen Users table, or one backup file, was effectively a list of plaintext passwords.
Now PBKDF2-HMAC-SHA256 at 600,000 rounds: a single guess costs ~0.5s instead of a nanosecond. Existing hashes still verify — refusing them would lock every user out of their own system — and each is replaced the next time its owner signs in, the only moment the password is in hand. The round count is stored inside the value, so raising it later upgrades everyone the same quiet way.
Nothing to do by hand. Your admin account still holds a legacy hash; it becomes PBKDF2 on your next login.
Logging in had no limit
No delay, no lockout, nothing in the audit log. Ten wrong passwords from one address for one account within fifteen minutes and it waits. A correct password clears the count, so mistyping four times costs nothing. Failures and throttling are both recorded.
Signatures needed only a login, not the right one
/api/assets/<id>/signature sat behind a bare auth check — any account at all: a viewer, a helpdesk-only login, an API key handed to an agent for reading tickets. A signature is a person's handwriting. It asks for Assets read now, like every other asset route.
Session cookies are Secure over TLS
They had HttpOnly and SameSite but not Secure, so on a plain HTTP hop the session travelled in clear. It can't simply be switched on — most installs are HTTP on a LAN, where a Secure cookie is never sent and nobody can log in — so it's decided per request from the connection (including X-Forwarded-Proto, so it works behind a proxy), or forced with ITVAULT_COOKIE_SECURE=1.
Acknowledgement links carry a full signature
They were signed with a truncated 64-bit tag. Full digest now. Links already in inboxes keep working until they expire.
Two bugs the tests caught, not the reading
Users.passwordwasVARCHAR(100)— fits the old hash exactly, truncates a PBKDF2 one. MySQL refused every upgrade and would have refused new users outright. Widened by migration; fresh installs get 255.- The first attempt at the Secure flag rewrote
Set-Cookieinafter_request, where the session cookie doesn't exist yet — Flask writes it insave_session, after every handler. It comes from the session interface now.
Still to come
Global clickjacking/sniffing headers, invoice upload content-sniffing and download disposition, hashed API keys, then encrypted backups and a signature retention policy.
Upgrading
Pull the matching image — this is all server-side, and the schema migration runs on start. The APK carries no app changes.
IT-Vault 2.2.3 — every email signs off properly
Every email now ends the same way, in small type:
—
Northside Sports Club
IT-Vault v2.2.3 · GitHub · Sha The IT Guy
Your organisation's name first, because to whoever opens the mail it is from them. Then, smaller and grey, what sent it: IT-Vault, its version, the project, and Sha The IT Guy linking to shatheitguy.in.
Mail is two documents, so it's two footers. The plain-text part can't size type or hyperlink, so it carries the order and spells both URLs out. The HTML part sets 12px for the organisation over 10px grey for the credit, with a hairline rule above — every style inline and every colour a literal hex, because clients strip <style> blocks and Outlook has no var().
The HTML footer is injected before </body> rather than appended after it: some clients drop what follows the document, and Gmail clips it.
Two things found by looking at it rendered
- The separator came out as
·— the button template declared no charset, and nothing guarantees a client reads the MIME header. The footer now uses·and the template declares utf-8. - A brand name containing markup would have injected it, so the footer escapes what it's given.
The version is read from APP_VERSION, never typed — a hand-written version in a footer nobody reads is one that's wrong within a month.
tests/test_email_footer.py holds all of it, including that every one of the nine set_content() sites in app.py appends the footer. Adding a tenth without it is the easy mistake here.
Upgrading
Pull the matching image — this one is server-side. The APK carries no app changes; versions move together by design.
IT-Vault 2.2.2 — no price on the signed copy
The signed acknowledgement sheet no longer prints the price.
That sheet is the one printout that leaves the IT department: somebody signs it to say they have the laptop, it's handed to them, and a copy is mailed to their inbox. What the organisation paid for the thing isn't theirs to know.
Everywhere else keeps it — the asset record, the internal "print asset" sheet, and every export.
The currency lookup went with it: it existed only to format that one row, so signing an asset no longer queries Settings for a currency it has no use for.
Checked, not assumed
A real PDF built from an asset that has a price, with the text pulled back out of it: no "Price", no amount, no currency, and every other row still present — including the employee block, and including the sheet an unassigned asset produces. tests/test_signed_pdf_fields.py keeps it that way, because a price could come back through a field added later or through the notes, and reading the code wouldn't show it.
Upgrading
Pull the matching image — this one is server-side. The APK carries no app changes; versions move together by design.
IT-Vault 2.2.1 — Claude and ChatGPT connectors
The MCP server now covers seven ways in: Hermes, OpenClaw, ZeroClaw, Claude Desktop, Claude Code (all stdio), plus Claude custom connectors and ChatGPT over HTTP.
The two network clients changed the security picture
ChatGPT can't spawn a local process, and Claude's custom connectors are dialled from Anthropic's cloud rather than from your machine — so both need the HTTP transport on a public HTTPS URL ending in /mcp. A public URL in front of a process holding your API key is an open door, so the server now:
- refuses to bind anywhere but loopback without
ITVAULT_MCP_BEARER, printing what to set and why, instead of serving; - returns 401 to any request without a matching
Authorization: Bearerheader, before it reaches a tool.
The gate is raw ASGI rather than Starlette's BaseHTTPMiddleware (it sits in front of a streaming endpoint, and that one buffers) and compares with hmac.compare_digest. Verified against a running server: no token → 401, wrong token → 401, right token → a real MCP handshake.
Two limits written down rather than glossed
- Claude's Request headers field — where a bearer token goes — is a beta not every organisation has. Without it, the answer is to authenticate at the edge (a Cloudflare Access service token, or a proxy that injects the header), not to run it open. OAuth is the properly supported route and this server doesn't implement it.
- Nothing in a repository puts an entry in Claude's Discover directory or ChatGPT's app list. Those are submissions and reviews. "Add custom connector" works today, and that's what the instructions cover.
Also
A settings table covering every environment variable — added after the contract test caught ITVAULT_MCP_TIMEOUT being implemented but undocumented. That test now also checks the loopback refusal, the bearer gate, the /mcp path, and that every runtime the README claims actually has a config for it.
The APK carries no app changes; versions move together by design.
IT-Vault 2.2.0 — an agent can run the register
IT-Vault now ships an MCP server, so an AI agent can work the register the way a person does.
Twenty tools over IT-Vault's own HTTP API: find a device, see who has it, assign it, take it back, raise and answer tickets, chase expiring contracts, and deal with the reports that arrive when a stranger scans a lost asset's tag.
cd mcp && pip install -e .
ITVAULT_URL=http://itvault.lan:5000 ITVAULT_API_KEY=your-key itvault-mcp --selftestWiring for Hermes, OpenClaw and ZeroClaw is in mcp/README.md.
The permission story is one sentence
Every call is authenticated with an IT-Vault API key, so the agent has exactly the rights of the user that key belongs to, and nothing in the server can exceed them. Give a watching agent a key from a read-only user and no amount of prompting changes anything.
Two switches on top, because a role is coarser than intent:
ITVAULT_MCP_READONLY=1refuses every write regardless of the key.ITVAULT_MCP_ALLOW_DELETE=1is required fortrash_asset, which is also marked destructive so a host that confirms destructive tools will confirm it. Kit should not leave the register because a sentence sounded like it should.
Three things the design turns on
PUT /api/assets/<id>rewrites all seventeen columns. A partial update from an agent would blank the serial, the location and the holder. Every write reads the record first and lays the change on top — and a test fails if any write ever bypasses that.- An agent pays for every token it reads. Lists are trimmed to the fields that answer "which one is this and what is happening with it", and the signature blob — a multi-kilobyte data URI — is never handed over.
- Exact matching.
get_assettakes an asset tag, the code printed on a QR label, or an id, andIT-10never resolves toIT-1004.
Tested on both sides of the seam
mcp/test_server.py runs the tools against a stub IT-Vault that records every request: the merge keeps every field, both gates hold, and the key only ever travels as a header. tests/test_mcp_contract.py checks that all eighteen routes the tools call exist in app.py with that method — rename one and it fails there rather than under an agent mid-task.
Found while verifying against a real server: whoami read the identity from the wrong field and reported an empty user. That route also returns the account's own API key, so whoami picks fields rather than forwarding the response — otherwise the key would land in the agent's transcript. Both are covered now.
Upgrading
Pull the matching image; the MCP server runs on the agent's side and needs nothing on the server beyond an API key. The APK carries no app changes in this release — versions move together by design.