The Log Reader Now Refuses Credential Paths
July 26, 2026 · InfraScout Team
Hardening across three surfaces, all of it already live and none of it requiring anything from you. The first item is the substantial one, and it is a control we should have had from the start rather than added later.
Two of the changes below share a shape worth naming, because it is the kind that a checklist pass does not catch: a comment in the code claimed a protection that the code did not actually deliver.
A credential-path denylist on the log reader
The cross-platform log reader reads a text file at a path you give it, narrows it with filters, and returns a bounded result. It cannot change anything, so it is classified read-only — and every role boundary in the product keys on that classification rather than on a list of tool names.
Which meant the tool was reachable by the callers you would least want holding an unrestricted file reader: the standard user tier, which is denied a shell outright, plus read-only delegated runs and read-only chat. Each could name any path the agent process itself can open, and the agent runs as LocalSystem on Windows and root on Linux.
For those callers a file reader with no path restriction is not a log reader. It is a route to credential material: password hashes from the Windows account database, SSH and TLS private keys, cloud provider token caches, and — the one that matters most — the agent's own certificate and private key, which on Unix is a plain file the agent must be able to read.
A restriction had been considered and documented before, but scoped only to delegated runs. Because the role gate keys on the read-only classification rather than on the caller, it applied to the standard user tier too. That is the gap this closes.
The fix
A hard denylist of credential locations, refused outright. It is enforced in two places, and both matter for different reasons.
Before dispatch, on the server. A refusal there costs no execution and no round trip, and — critically — the protection is live for your entire fleet the moment this ships, rather than waiting for every agent to update.
At the point of reading, on the agent. So that every other path to a file read inherits the rule without anyone having to remember to add it.
Matching is done on a normalized path, segment by segment, rather than as a string prefix. That closes the variations that would otherwise slip a denied name past a rule: a drive letter, a UNC share, a shadow-copy path, a .. traversal. Two Windows-specific canonicalizations are applied because Windows performs them itself before opening a file, and either would otherwise smuggle a name through — an alternate-data-stream suffix on a hive name, and trailing dots or spaces on a filename. The check is also re-run against the resolved target of a symlink, since a planted link from a log directory to a credential file would otherwise pass a purely textual check and then present as an ordinary file.
The rule applies uniformly, to every caller and every role. An administrator has other, better ways to read a credential file on a host they administer; carving out an exception here would mean the control had a bypass, and a bypass that exists for one role is a bypass.
Two ways past the read-only PowerShell floor
The read-only PowerShell tool validates a script against a command policy before dispatch and blocks anything that would change host state. Testing that floor by deliberately trying to write past it closed three gaps in what the policy recognized. All three mattered for the same reason: because the tool is classified read-only, anything the policy fails to recognize as a write is a write available to a read-only caller.
The first two were the same underlying miss. PowerShell lets you write through a provider path as an assignment — ${C:\Windows\Temp\x.txt} = 'value' writes a file, and the environment-variable form does the same thing to a process variable. Neither is a command invocation, so none of the rules that examine command names, verbs, or redirections ever saw them. An arbitrary file write is an indirect path to code execution. A new rule now matches an assignment whose left-hand side is a drive-qualified variable path — keyed on the drive qualifier specifically, so ordinary comparisons and reads are unaffected.
The third was a plain omission: piping into a cmdlet that writes its input to a file, whose verb was not in the list of modifying verbs and whose name was missing from the file-write list. Added.
Rate limiting across the whole surface
Rate limiting covered some endpoints and not others. It now covers the surface as a whole.
Every rejection carries a retry indication, so a client that hits a limit backs off correctly instead of hammering. The authentication configuration endpoint and the OAuth callback have their own per-address shields, since both are unauthenticated by necessity. The OAuth client-registration and authorization endpoints are shielded tightly, being the paths where an attacker gets to make InfraScout do work before proving anything. Authenticated API traffic has a per-user limit applied after sign-in, so one account cannot crowd out the rest of a tenant. And a generous catch-all sits around everything else as a backstop — with health probes exempt, because rate-limiting a platform's own liveness check is a way to take yourself offline.
Client-side retry handling was added alongside, so a limit that does fire is usually invisible rather than an error somebody sees.
And one more, from the same review
A group-membership rule that matched on a regular expression was passing that expression through to the database's own matching engine. Rules are written by tenant admins rather than by attackers, so this was defense in depth rather than an active exposure — but a pattern reaching a database engine is a pattern you have handed considerably more power than matching. Regular expressions in group rules are now evaluated in the application, against an engine with predictable performance characteristics, so a pattern is a pattern and nothing more.
What you need to do
Nothing. All of it is server-side, and the log reader denylist in particular took effect across every agent the moment it shipped, without waiting for agent updates.
If your assessments read log files, the one visible change is that a path in a credential location now returns a refusal explaining itself. That is the control working. For what the log reader can and cannot open, see Agent Tools; for the trust model as a whole, see Security Architecture.
Questions, or a finding of your own to report? info@infrascout.cloud.