CVE-2026-67439Medium· 4.3▾ SunlitOliveTin: StartActionAndWait Endpoints Bypass `logs` Permission and Return Action Output
▾ Sunlit zone — Low / medium · no exploitation signal
impact 23.7 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 30.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.3%
0.3% → 0.3%
The synchronous execution RPCs StartActionAndWait and StartActionByGetAndWait return the full LogEntry for the just-executed action without checking whether the caller is allowed to read that action's logs.
OliveTin's ACL model separates exec from logs. A deployment can intentionally allow a user to run an action while denying access to its historical or live output. That separation is enforced in GetLogs, GetActionLogs, ExecutionStatus, and EventStream, but it is not enforced in the synchronous ...AndWait endpoints.
As a result, any user who can execute an action through these endpoints can read the action output immediately even when the action's ACL explicitly sets logs:false.
OliveTin defines separate per-action permissions:
// service/internal/config/config.go
type PermissionsList struct {
View bool `koanf:"view"`
Exec bool `koanf:"exec"`
Logs bool `koanf:"logs"`
Kill bool `koanf:"kill"`
}
The normal log and streaming paths correctly enforce logs permission:
// service/internal/api/api.go
func (api *oliveTinAPI) isLogEntryAllowed(e *executor.InternalLogEntry, user *authpublic.AuthenticatedUser) bool {
if user == nil || !isValidLogEntry(e) {
return false
}
return acl.IsAllowedLogs(api.cfg, user, e.Binding.Action)
}
That check is used by:
GetLogsGetActionLogsExecutionStatusEventStreamHowever, the synchronous execution endpoints directly return the created LogEntry without any logs ACL check:
// service/internal/api/api.go
func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionAndWaitRequest]) (*connect.Response[apiv1.StartActionAndWaitResponse], error) {
...
internalLogEntry, ok := api.startActionAndWaitRun(binding, args, user)
if !ok {
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found"))
}
return connect.NewResponse(&apiv1.StartActionAndWaitResponse{
LogEntry: api.internalLogEntryToPb(internalLogEntry, user),
}), nil
}
func (api *oliveTinAPI) StartActionByGetAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionByGetAndWaitRequest]) (*connect.Response[apiv1.StartActionByGetAndWaitResponse], error) {
...
internalLogEntry, ok := api.executor.GetLog(execReq.TrackingID)
if ok {
return connect.NewResponse(&apiv1.StartActionByGetAndWaitResponse{
LogEntry: api.internalLogEntryToPb(internalLogEntry, user),
}), nil
}
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("execution not found"))
}
And internalLogEntryToPb() includes the full output:
// service/internal/api/api.go
func (api *oliveTinAPI) internalLogEntryToPb(logEntry *executor.InternalLogEntry, authenticatedUser *authpublic.AuthenticatedUser) *apiv1.LogEntry {
pble := &apiv1.LogEntry{
ActionTitle: logEntry.ActionTitle,
Output: logEntry.Output,
ExitCode: logEntry.ExitCode,
ExecutionTrackingId: logEntry.ExecutionTrackingID,
...
}
...
return pble
}
The executor separately enforces exec permission, but there is no subsequent check that the response should omit or deny Output when logs:false.
I verified this locally with a one-off test against the real StartActionAndWait handler.
Configuration used:
secret_action with shell echo SECRET_FROM_ACTIONlow matched by ACL:
exec: truelogs: falseview: falsekill: falseThen I invoked StartActionAndWait as low through the real handler path using header-based auth.
Observed output from the test run:
=== RUN TestTempStartActionAndWaitLeaksOutputWithoutLogsPermission
time="2026-03-12T23:36:33+01:00" level=info msg="Action requested" actionTitle=secret-action tags="[]"
time="2026-03-12T23:36:33+01:00" level=info msg="Action parse args - Before" actionTitle=secret-action cmd="echo SECRET_FROM_ACTION"
time="2026-03-12T23:36:33+01:00" level=info msg="Action parse args - After" actionTitle=secret-action cmd="echo SECRET_FROM_ACTION"
time="2026-03-12T23:36:33+01:00" level=info msg="Action started" actionTitle=secret-action timeout=5
time="2026-03-12T23:36:33+01:00" level=info msg="Action finished" actionTitle=secret-action exit=0 outputLength=40 timedOut=false
temp_acl_bypass_test.go:56: output="S\x00E\x00C\x00R\x00E\x00T\x00_\x00F\x00R\x00O\x00M\x00_\x00A\x00C\x00T\x00I\x00O\x00N\x00\r\x00\n\x00" blocked=false action="secret-action"
--- PASS: TestTempStartActionAndWaitLeaksOutputWithoutLogsPermission (0.03s)
The UTF-16LE formatting is from Windows echo, but the key result is that the response returned the real command output even though the caller's ACL explicitly denied log access.
...AndWait endpoints break that separation.Enforce logs permission before returning LogEntry content from synchronous execution endpoints.
Two reasonable fixes:
logs:falseif !acl.IsAllowedLogs(api.cfg, user, binding.Action) {
return nil, connect.NewError(connect.CodePermissionDenied, fmt.Errorf("permission denied to view action output"))
}
logs:falsepb := api.internalLogEntryToPb(internalLogEntry, user)
if !acl.IsAllowedLogs(api.cfg, user, binding.Action) {
pb.Output = ""
pb.ExitCode = 0
}
return connect.NewResponse(&apiv1.StartActionAndWaitResponse{LogEntry: pb}), nil
The same fix should be applied to both:
StartActionAndWaitStartActionByGetAndWaitservice/internal/api/api.goservice/internal/config/config.goservice/internal/acl/acl.gogithub.com/OliveTin/OliveTin < 0.0.0-20260708085316-e421780c9885Upgrade to a patched release:
github.com/OliveTin/OliveTin 0.0.0-20260708085316-e421780c9885Connected by shared product, vendor, weakness, or advisory.
CVE-2026-67437High· 7.5OliveTin: Unauthenticated DoS via OAuth2 State Memory Exhaustion (Unbounded Map Growth)
CVE-2026-67438Medium· 6.6OliveTin OS Command Injection via Custom regex: Argument Type Bypassing Shell Safety Check
CVE-2026-48708High· 7.5OliveTin has a Concurrent Template Parsing Race Condition which Leads to Cross-Request Command Contamination
CVE-2026-48709Low· 3.7OliveTin: ValidateArgumentType API Endpoint's Missing Authentication Allows Action and Argument Enumeration
CVE-2026-53541Medium· 4.3OliveTin gives access to predefined shell commands from a web interface
CVE-2020-3578Medium· 5.3A vulnerability in the web services interface of Cisco Adaptive Security Appliance (ASA) Software and Cisco Firepower Threat Defense (FTD) Software could allow an unauthenticated, remote attacker to bypass a configured access rule and ac…