Session transcripts and searching them
Full-text search across every privileged session in your organisation: what a transcript contains, when it is written, who may read one, and the two things it does not do.
A transcript is the readable text of a privileged session, indexed so you can search across every session in your organisation at once. It answers the question a recording cannot answer quickly: which session was the one where somebody ran that command?
What a transcript contains
A single block of text with the two directions marked, so you can tell what the operator typed from what the machine answered.
>> sudo systemctl restart nginx
<< ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ====
>> cat /etc/nginx/nginx.conf
<< user www-data;
<< worker_processes auto;>> is input, << is output. Both are in the index, so a search matches on a command the operator ran and on something the machine printed — which is often the more useful of the two, because an error message is a better search term than a command you have to guess the exact form of.
The extractor understands both recording formats the platform produces — asciicast for terminal sessions and the parsed event stream for RDP — and dispatches on the recorded media type rather than assuming. That is worth stating because the previous extractor assumed one format, matched neither, and produced an empty transcript from a perfectly good recording without erroring.
When a transcript is written
Once, after the session ends, from the gateway's teardown — and only when three things are true: the recording row committed, the session was actually recorded, and a storage key exists.
The choice of moment is deliberate and it is worth understanding, because it explains a gap you might otherwise report as a bug. Indexing at session start would race the upload; indexing from the terminate endpoint would miss every session that ended on its own rather than being terminated by a person — which is most of them. The teardown is the one point that catches both.
So a session that is still open has no transcript, and a session that was not recorded never will have one. Both answer 404 transcript not available rather than an empty result.
Searching
GET /api/v1/organizations/{orgID}/pam/sessions/search?q=<terms>
GET /api/v1/organizations/{orgID}/pam/sessions/{sessionID}/transcriptSearch is Postgres full-text search over the English dictionary, so it stems: searching restarted finds restart. Results are ranked, capped at 25, and each hit carries the session id, the rank, when it was indexed, and a snippet with your terms marked — up to three fragments per hit, so you can tell a relevant session from a coincidental one without opening it.
The full transcript endpoint returns {"session_id": …, "full_text": …} for one session. Use search to find the session, then this to read it.
400 {"error":"q parameter required"}
403 {"error":"organization id required"}
404 {"error":"transcript not available"}Both endpoints fail closed on a missing organisation rather than searching across tenants. That is not a formality: an earlier release had these routes reachable without authentication, and the per-session fetch looked a transcript up by session id while ignoring which organisation was asking — so one tenant could read another's privileged session transcripts by guessing an identifier. Both defects are closed, the routes are inside the authenticated group, and the query is scoped in the database rather than only in the handler.
Who may read one, and why the subject of a session may not
Both endpoints require pam:session_content.read, the same permission as the recording itself, and no built-in role except admin holds it.
Every read is audited, including failures. Searches record the query but not the results, which is the right balance: the query is what tells you somebody went looking, and storing the results would duplicate the transcript into the audit log. Failed reads are recorded too, deliberately, because a run of them against session ids that do not belong to you is exactly what cross-tenant enumeration looks like.
Two things a transcript does not do
Using it in an investigation
Search for something the machine said, not only for what you think was typed
Error text, a service name, a file path in an output. You are more likely to remember
Failed to restart nginx.serviceaccurately than the exact form of the command that caused it, and both are indexed.Read the snippets before opening anything
Three marked fragments per hit is usually enough to tell a relevant session from a coincidence, and it saves you opening — and auditing a read of — a transcript that turns out not to matter.
Open the session, not just the transcript
The session row tells you who, against which credential and target, under which approval, and whether the injected identity matched the identity the target logged on. The transcript tells you what happened next. The two together are the answer; either alone is half of it.
Export what you need while the retention window is open
Retention has a thirty-day floor and a one-year default, and a purge destroys the transcript along with the recording. If a session is going to matter in a dispute, take a copy into your evidence store rather than relying on it still being here. The evidence bundle covers what else belongs alongside it.
One deployment note if you run Qorionix yourself: transcript search depends on there being a recording object store to read from. A deployment without one warns at start-up that transcript search will return nothing for every query, and that warning is the first thing to check if search is empty across the board rather than for one session.