Загрузка данных
--[[
CLIENT EXPOSURE AUDITOR v6.1
Passive security / client-exposure review.
Behavior:
1) On execution, ONLY a small "AUDIT" launcher appears.
2) Clicking it opens the debugger menu.
3) No heavy scan runs until you press a scan button.
4) Full Scan captures client-visible content once, then analyzes that snapshot.
5) Results are paginated, deduplicated and severity-sorted.
6) Close (X) hides the window; launcher remains available.
Scope / limitations:
- Scan logic uses normal Roblox client APIs only.
- If an executor exposes gethui(), it is used only as a safer GUI parent.
- Read-only: does NOT fire/invoke remotes, prompts, detectors or mutate game state.
- Does NOT use executor-specific introspection, hooks or decompilers.
- A client-only audit CANNOT prove whether server handlers validate requests correctly.
- With Workspace.StreamingEnabled, Workspace enumeration is only the currently streamed subset.
- Replicated LocalScripts / ModuleScripts must be treated as inspectable by an exploiter.
]]
local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ReplicatedFirst = game:GetService("ReplicatedFirst")
local Lighting = game:GetService("Lighting")
local SoundService = game:GetService("SoundService")
local TextChatService = game:GetService("TextChatService")
local Teams = game:GetService("Teams")
local StarterGui = game:GetService("StarterGui")
local StarterPlayer = game:GetService("StarterPlayer")
local StarterPack = game:GetService("StarterPack")
local player = Players.LocalPlayer
if not player then
warn("[AUDIT] LocalPlayer unavailable.")
return
end
local playerGui = player:WaitForChild("PlayerGui")
local auditorGui = nil
----------------------------------------------------------------
-- GUI PARENT / CLEAN PREVIOUS INSTANCE
----------------------------------------------------------------
local GUI_NAME = "client_exposure_auditor_v6_1"
local function resolveGuiParent()
-- Studio / normal client: PlayerGui.
-- Injected client: prefer gethui() when the executor exposes it.
local parent = playerGui
local ok, hiddenGui = pcall(function()
if type(gethui) == "function" then
return gethui()
end
return nil
end)
if ok and typeof(hiddenGui) == "Instance" then
parent = hiddenGui
end
return parent
end
local guiParent = resolveGuiParent()
local cleanupParents = {playerGui}
if guiParent ~= playerGui then
table.insert(cleanupParents, guiParent)
end
for _, parent in ipairs(cleanupParents) do
for _, name in ipairs({
"client_exposure_auditor_v5",
"client_exposure_auditor_v6",
"client_exposure_auditor_v6_1",
}) do
local old = parent:FindFirstChild(name)
if old then
old:Destroy()
end
end
end
----------------------------------------------------------------
-- COLORS / CONSTANTS
----------------------------------------------------------------
local C = {
bg = Color3.fromRGB(14, 15, 18),
panel = Color3.fromRGB(21, 23, 28),
panel2 = Color3.fromRGB(28, 30, 36),
panel3 = Color3.fromRGB(35, 38, 45),
text = Color3.fromRGB(236, 238, 242),
muted = Color3.fromRGB(157, 163, 175),
accent = Color3.fromRGB(77, 133, 255),
accent2 = Color3.fromRGB(99, 154, 255),
ok = Color3.fromRGB(83, 196, 126),
low = Color3.fromRGB(114, 164, 255),
warn = Color3.fromRGB(242, 187, 74),
danger = Color3.fromRGB(239, 97, 97),
border = Color3.fromRGB(58, 62, 73),
}
local PAGE_SIZE = 80
local MAX_RESULTS_TOTAL = 30000
local SNAPSHOT_YIELD_EVERY = 800
----------------------------------------------------------------
-- UTIL
----------------------------------------------------------------
local function corner(parent, radius)
local x = Instance.new("UICorner")
x.CornerRadius = UDim.new(0, radius or 8)
x.Parent = parent
return x
end
local function stroke(parent, color, thickness, transparency)
local x = Instance.new("UIStroke")
x.Color = color or C.border
x.Thickness = thickness or 1
x.Transparency = transparency or 0
x.Parent = parent
return x
end
local function safeFullName(obj)
local ok, value = pcall(function()
return obj:GetFullName()
end)
if ok then
return value
end
return obj.Name
end
local function safeRead(obj, property)
local ok, value = pcall(function()
return obj[property]
end)
if ok then
return value
end
return nil
end
local function valueToString(value)
local t = typeof(value)
if t == "Instance" then
return safeFullName(value)
elseif t == "string" then
if #value > 240 then
return value:sub(1, 240) .. "..."
end
return value
elseif t == "Vector3" or t == "Vector2" or t == "CFrame"
or t == "Color3" or t == "UDim" or t == "UDim2"
or t == "NumberRange" or t == "NumberSequence"
or t == "ColorSequence" or t == "BrickColor"
or t == "EnumItem" then
return tostring(value)
elseif t == "table" then
return "<table>"
end
return tostring(value)
end
local SEVERITY_RANK = {
INFO = 1,
LOW = 2,
MEDIUM = 3,
HIGH = 4,
}
local SECRET_NAME_WORDS = {
"password", "passwd", "secret", "token", "apikey", "api_key",
"privatekey", "private_key", "authorization", "auth_token",
"access_token", "refresh_token", "session_token", "webhook",
}
local REVIEW_WORDS = {
"admin", "moderator", "owner", "money", "cash", "coin", "currency",
"reward", "price", "damage", "cooldown", "inventory", "item", "weapon",
"level", "xp", "experience", "ban", "anticheat", "anti_cheat", "purchase",
"buy", "sell", "give", "grant", "teleport", "trade", "loot", "pickup",
"finish", "goal", "claim", "redeem", "save", "data", "profile",
}
local PHYSICS_REVIEW_WORDS = {
"damage", "hit", "projectile", "bullet", "rocket", "weapon", "sword",
"reward", "cash", "coin", "money", "pickup", "collect", "loot", "finish",
"goal", "sell", "buy", "vehicle", "car", "ball", "objective",
}
local REMOTE_REVIEW_WORDS = {
"admin", "moderator", "owner", "money", "cash", "coin", "currency",
"reward", "price", "damage", "cooldown", "inventory", "weapon", "ban",
"purchase", "buy", "sell", "give", "grant", "teleport", "trade", "loot",
"pickup", "finish", "goal", "claim", "redeem", "save", "delete", "kick",
}
local function containsAny(text, words)
local s = string.lower(tostring(text or ""))
for _, word in ipairs(words) do
if string.find(s, word, 1, true) then
return true, word
end
end
return false, nil
end
local function stringLooksSensitive(value)
if typeof(value) ~= "string" then
return false, nil
end
local s = string.lower(value)
local patterns = {
{"discord.com/api/webhooks/", "webhook URL"},
{"discordapp.com/api/webhooks/", "webhook URL"},
{"hooks.slack.com/services/", "webhook URL"},
{"-----begin private key-----", "private key"},
{"-----begin rsa private key-----", "private key"},
{"bearer ", "bearer credential"},
{"xoxb-", "Slack token-like value"},
{"sk_live_", "secret-key-like value"},
}
for _, pair in ipairs(patterns) do
if string.find(s, pair[1], 1, true) then
return true, pair[2]
end
end
return false, nil
end
local function classifyName(pathOrName)
local hasSecret = containsAny(pathOrName, SECRET_NAME_WORDS)
if hasSecret then
return "MEDIUM"
end
local needsReview = containsAny(pathOrName, REVIEW_WORDS)
if needsReview then
return "LOW"
end
return "INFO"
end
local function classifyState(name, value)
local hasSecretName = containsAny(name, SECRET_NAME_WORDS)
local valueLooksSecret = stringLooksSensitive(value)
if valueLooksSecret then
return "HIGH"
end
if hasSecretName and value ~= nil and tostring(value) ~= "" then
return "HIGH"
end
if hasSecretName then
return "MEDIUM"
end
return classifyName(name)
end
local function stateValueForDisplay(name, value)
local severity = classifyState(name, value)
if severity == "HIGH" then
local t = typeof(value)
if t == "string" then
return string.format("<REDACTED STRING len=%d>", #value)
end
return "<REDACTED " .. string.upper(t) .. ">"
end
return valueToString(value)
end
local function isAuditorObject(obj)
if not auditorGui then
return false
end
local ok, value = pcall(function()
return obj == auditorGui or obj:IsDescendantOf(auditorGui)
end)
return ok and value or false
end
local function isCharacterObject(obj)
local current = obj
while current and current ~= workspace do
if current:IsA("Model") and current:FindFirstChildOfClass("Humanoid") then
return true
end
current = current.Parent
end
return false
end
local function nearestBasePart(obj)
local current = obj
while current and current ~= game do
if current:IsA("BasePart") then
return current
end
current = current.Parent
end
return nil
end
local function assemblyRoot(part)
if not part or not part:IsA("BasePart") then
return nil
end
local root = safeRead(part, "AssemblyRootPart")
if root and root:IsA("BasePart") then
return root
end
return part
end
local function networkInfo(part)
local ownerText = "unavailable"
local autoText = "unavailable"
local localOwned = false
local okOwner, owner = pcall(function()
return part:GetNetworkOwner()
end)
if okOwner then
if owner then
ownerText = owner.Name
localOwned = owner == player
else
ownerText = "server / nil"
end
end
local okAuto, isAuto = pcall(function()
return part:GetNetworkOwnershipAuto()
end)
if okAuto then
autoText = tostring(isAuto)
end
return ownerText, autoText, localOwned
end
local function buildScanRoots()
local roots = {
ReplicatedFirst,
ReplicatedStorage,
workspace,
Players,
Lighting,
SoundService,
TextChatService,
Teams,
StarterGui,
StarterPlayer,
StarterPack,
}
return roots
end
local function captureSnapshot()
local output = {}
local seen = {}
for _, root in ipairs(buildScanRoots()) do
if root and not seen[root] then
seen[root] = true
if not isAuditorObject(root) then
table.insert(output, root)
end
local ok, descendants = pcall(function()
return root:GetDescendants()
end)
if ok then
for _, obj in ipairs(descendants) do
if not seen[obj] then
seen[obj] = true
if not isAuditorObject(obj) then
table.insert(output, obj)
end
end
end
end
end
end
return output
end
----------------------------------------------------------------
-- GUI ROOT
----------------------------------------------------------------
local gui = Instance.new("ScreenGui")
auditorGui = gui
gui.Name = GUI_NAME
gui.ResetOnSpawn = false
gui.IgnoreGuiInset = true
gui.DisplayOrder = 999999
gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
gui.Parent = guiParent
----------------------------------------------------------------
-- LAUNCH BUTTON
----------------------------------------------------------------
local launcher = Instance.new("TextButton")
launcher.Name = "launcher"
launcher.Size = UDim2.fromOffset(92, 40)
launcher.Position = UDim2.new(0, 18, 0.5, -20)
launcher.BackgroundColor3 = C.accent
launcher.BorderSizePixel = 0
launcher.AutoButtonColor = true
launcher.Text = "AUDIT"
launcher.TextColor3 = Color3.new(1, 1, 1)
launcher.TextSize = 15
launcher.Font = Enum.Font.GothamBold
launcher.ZIndex = 100
launcher.Parent = gui
corner(launcher, 10)
stroke(launcher, Color3.fromRGB(125, 166, 255), 1, 0.35)
----------------------------------------------------------------
-- MAIN WINDOW
----------------------------------------------------------------
local main = Instance.new("Frame")
main.Name = "main"
main.Size = UDim2.new(0.88, 0, 0.82, 0)
main.Position = UDim2.new(0.06, 0, 0.09, 0)
main.BackgroundColor3 = C.bg
main.BorderSizePixel = 0
main.Visible = false
main.ZIndex = 20
main.Parent = gui
corner(main, 12)
stroke(main, C.border, 1, 0.2)
local sizeConstraint = Instance.new("UISizeConstraint")
sizeConstraint.MaxSize = Vector2.new(1450, 900)
sizeConstraint.Parent = main
-- Bind opening IMMEDIATELY. If a later compatibility error occurs,
-- the launcher still opens this base window instead of becoming dead.
local function openMain()
gui.Enabled = true
main.Visible = true
launcher.Visible = false
end
local function hideMain()
main.Visible = false
launcher.Visible = true
end
local function bindButton(button, callback)
-- MouseButton1Down is widely reliable in injected GUI environments.
-- Activated remains as a touch/gamepad/normal-client fallback.
local lastFire = -math.huge
local function fire()
local now = os.clock()
if now - lastFire < 0.35 then
return
end
lastFire = now
callback()
end
button.MouseButton1Down:Connect(fire)
button.Activated:Connect(fire)
end
-- Multiple idempotent input paths are intentional for executor compatibility.
launcher.MouseButton1Down:Connect(openMain)
launcher.MouseButton1Click:Connect(openMain)
launcher.Activated:Connect(openMain)
local bootNotice = Instance.new("TextLabel")
bootNotice.Name = "boot_notice"
bootNotice.Size = UDim2.new(1, -32, 1, -32)
bootNotice.Position = UDim2.fromOffset(16, 16)
bootNotice.BackgroundColor3 = C.panel
bootNotice.BorderSizePixel = 0
bootNotice.Text = "AUDITOR INITIALIZING...\n\nIf this message stays on screen, execution stopped during setup.\nCheck the executor console / Roblox developer console for the first error."
bootNotice.TextColor3 = C.text
bootNotice.TextSize = 15
bootNotice.Font = Enum.Font.GothamMedium
bootNotice.TextWrapped = true
bootNotice.ZIndex = 1000
bootNotice.Parent = main
corner(bootNotice, 10)
stroke(bootNotice, C.border, 1, 0.15)
----------------------------------------------------------------
-- TOP BAR
----------------------------------------------------------------
local top = Instance.new("Frame")
top.Name = "top"
top.Size = UDim2.new(1, 0, 0, 54)
top.BackgroundColor3 = C.panel
top.BorderSizePixel = 0
top.ZIndex = 21
top.Parent = main
local title = Instance.new("TextLabel")
title.Size = UDim2.new(1, -180, 1, 0)
title.Position = UDim2.fromOffset(18, 0)
title.BackgroundTransparency = 1
title.Text = "CLIENT EXPOSURE AUDITOR"
title.TextColor3 = C.text
title.Font = Enum.Font.GothamBold
title.TextSize = 18
title.TextXAlignment = Enum.TextXAlignment.Left
title.ZIndex = 22
title.Parent = top
local subtitle = Instance.new("TextLabel")
subtitle.Size = UDim2.new(0, 380, 0, 18)
subtitle.Position = UDim2.new(0, 18, 1, -18)
subtitle.BackgroundTransparency = 1
subtitle.Text = "Passive client audit • server validation cannot be proven from the client"
subtitle.TextColor3 = C.muted
subtitle.Font = Enum.Font.Gotham
subtitle.TextSize = 11
subtitle.TextXAlignment = Enum.TextXAlignment.Left
subtitle.ZIndex = 22
subtitle.Parent = top
local minimize = Instance.new("TextButton")
minimize.Size = UDim2.fromOffset(38, 34)
minimize.Position = UDim2.new(1, -86, 0, 10)
minimize.BackgroundColor3 = C.panel3
minimize.BorderSizePixel = 0
minimize.Text = "—"
minimize.TextColor3 = C.text
minimize.TextSize = 20
minimize.Font = Enum.Font.GothamBold
minimize.ZIndex = 30
minimize.Parent = top
corner(minimize, 8)
local close = Instance.new("TextButton")
close.Name = "close"
close.Size = UDim2.fromOffset(38, 34)
close.Position = UDim2.new(1, -44, 0, 10)
close.BackgroundColor3 = Color3.fromRGB(65, 36, 41)
close.BorderSizePixel = 0
close.Text = "×"
close.TextColor3 = Color3.fromRGB(255, 208, 208)
close.TextSize = 22
close.Font = Enum.Font.GothamBold
close.ZIndex = 30
close.Parent = top
corner(close, 8)
----------------------------------------------------------------
-- LEFT SIDEBAR
----------------------------------------------------------------
local sidebar = Instance.new("Frame")
sidebar.Size = UDim2.new(0, 210, 1, -54)
sidebar.Position = UDim2.fromOffset(0, 54)
sidebar.BackgroundColor3 = C.panel
sidebar.BorderSizePixel = 0
sidebar.ZIndex = 21
sidebar.Parent = main
local sidebarPadding = Instance.new("UIPadding")
sidebarPadding.PaddingTop = UDim.new(0, 12)
sidebarPadding.PaddingLeft = UDim.new(0, 10)
sidebarPadding.PaddingRight = UDim.new(0, 10)
sidebarPadding.Parent = sidebar
local sidebarList = Instance.new("UIListLayout")
sidebarList.Padding = UDim.new(0, 7)
sidebarList.SortOrder = Enum.SortOrder.LayoutOrder
sidebarList.Parent = sidebar
local function makeNav(text, order)
local b = Instance.new("TextButton")
b.Size = UDim2.new(1, 0, 0, 38)
b.BackgroundColor3 = C.panel2
b.BorderSizePixel = 0
b.Text = text
b.TextColor3 = C.text
b.TextSize = 13
b.Font = Enum.Font.GothamMedium
b.TextXAlignment = Enum.TextXAlignment.Left
b.AutoButtonColor = true
b.LayoutOrder = order
b.ZIndex = 22
b.Parent = sidebar
corner(b, 8)
local p = Instance.new("UIPadding")
p.PaddingLeft = UDim.new(0, 12)
p.Parent = b
return b
end
local btnOverview = makeNav("Overview", 1)
local btnRemotes = makeNav("Scan Remotes", 2)
local btnCode = makeNav("Scan Client Code", 3)
local btnState = makeNav("Scan State / Values", 4)
local btnInteractions = makeNav("Scan Interactions", 5)
local btnPhysics = makeNav("Scan Physics", 6)
local btnAssets = makeNav("Scan Assets", 7)
local btnRisks = makeNav("Show Risks", 8)
local separator = Instance.new("Frame")
separator.Size = UDim2.new(1, 0, 0, 1)
separator.BackgroundColor3 = C.border
separator.BorderSizePixel = 0
separator.LayoutOrder = 9
separator.ZIndex = 22
separator.Parent = sidebar
local btnFull = makeNav("FULL SCAN", 10)
btnFull.BackgroundColor3 = Color3.fromRGB(44, 74, 132)
local btnClear = makeNav("Clear Results", 11)
----------------------------------------------------------------
-- CONTENT
----------------------------------------------------------------
local content = Instance.new("Frame")
content.Size = UDim2.new(1, -210, 1, -54)
content.Position = UDim2.fromOffset(210, 54)
content.BackgroundColor3 = C.bg
content.BorderSizePixel = 0
content.ZIndex = 21
content.Parent = main
local statusBar = Instance.new("Frame")
statusBar.Size = UDim2.new(1, -24, 0, 44)
statusBar.Position = UDim2.fromOffset(12, 12)
statusBar.BackgroundColor3 = C.panel
statusBar.BorderSizePixel = 0
statusBar.ZIndex = 22
statusBar.Parent = content
corner(statusBar, 9)
local status = Instance.new("TextLabel")
status.Size = UDim2.new(1, -220, 1, 0)
status.Position = UDim2.fromOffset(14, 0)
status.BackgroundTransparency = 1
status.Text = "READY — choose a scan"
status.TextColor3 = C.ok
status.Font = Enum.Font.GothamBold
status.TextSize = 12
status.TextXAlignment = Enum.TextXAlignment.Left
status.ZIndex = 23
status.Parent = statusBar
local countLabel = Instance.new("TextLabel")
countLabel.Size = UDim2.fromOffset(205, 44)
countLabel.Position = UDim2.new(1, -213, 0, 0)
countLabel.BackgroundTransparency = 1
countLabel.Text = "0 results"
countLabel.TextColor3 = C.muted
countLabel.Font = Enum.Font.Gotham
countLabel.TextSize = 12
countLabel.TextXAlignment = Enum.TextXAlignment.Right
countLabel.ZIndex = 23
countLabel.Parent = statusBar
local sectionTitle = Instance.new("TextLabel")
sectionTitle.Size = UDim2.new(1, -24, 0, 30)
sectionTitle.Position = UDim2.fromOffset(12, 66)
sectionTitle.BackgroundTransparency = 1
sectionTitle.Text = "Overview"
sectionTitle.TextColor3 = C.text
sectionTitle.Font = Enum.Font.GothamBold
sectionTitle.TextSize = 17
sectionTitle.TextXAlignment = Enum.TextXAlignment.Left
sectionTitle.ZIndex = 22
sectionTitle.Parent = content
local resultsFrame = Instance.new("Frame")
resultsFrame.Size = UDim2.new(1, -24, 1, -146)
resultsFrame.Position = UDim2.fromOffset(12, 98)
resultsFrame.BackgroundColor3 = C.panel
resultsFrame.BorderSizePixel = 0
resultsFrame.ClipsDescendants = true
resultsFrame.ZIndex = 22
resultsFrame.Parent = content
corner(resultsFrame, 9)
local scrolling = Instance.new("ScrollingFrame")
scrolling.Size = UDim2.new(1, -10, 1, -50)
scrolling.Position = UDim2.fromOffset(5, 5)
scrolling.BackgroundTransparency = 1
scrolling.BorderSizePixel = 0
scrolling.ScrollBarThickness = 6
scrolling.ScrollBarImageColor3 = C.border
scrolling.CanvasSize = UDim2.fromOffset(0, 0)
scrolling.AutomaticCanvasSize = Enum.AutomaticSize.Y
scrolling.ZIndex = 23
scrolling.Parent = resultsFrame
local resultList = Instance.new("UIListLayout")
resultList.Padding = UDim.new(0, 4)
resultList.SortOrder = Enum.SortOrder.LayoutOrder
resultList.Parent = scrolling
local resultPadding = Instance.new("UIPadding")
resultPadding.PaddingLeft = UDim.new(0, 5)
resultPadding.PaddingRight = UDim.new(0, 5)
resultPadding.PaddingTop = UDim.new(0, 3)
resultPadding.PaddingBottom = UDim.new(0, 3)
resultPadding.Parent = scrolling
local footer = Instance.new("Frame")
footer.Size = UDim2.new(1, 0, 0, 40)
footer.Position = UDim2.new(0, 0, 1, -40)
footer.BackgroundColor3 = C.panel2
footer.BorderSizePixel = 0
footer.ZIndex = 24
footer.Parent = resultsFrame
local prevPage = Instance.new("TextButton")
prevPage.Size = UDim2.fromOffset(72, 28)
prevPage.Position = UDim2.fromOffset(8, 6)
prevPage.BackgroundColor3 = C.panel3
prevPage.BorderSizePixel = 0
prevPage.Text = "< Prev"
prevPage.TextColor3 = C.text
prevPage.TextSize = 12
prevPage.Font = Enum.Font.GothamMedium
prevPage.ZIndex = 25
prevPage.Parent = footer
corner(prevPage, 7)
local nextPage = Instance.new("TextButton")
nextPage.Size = UDim2.fromOffset(72, 28)
nextPage.Position = UDim2.new(1, -80, 0, 6)
nextPage.BackgroundColor3 = C.panel3
nextPage.BorderSizePixel = 0
nextPage.Text = "Next >"
nextPage.TextColor3 = C.text
nextPage.TextSize = 12
nextPage.Font = Enum.Font.GothamMedium
nextPage.ZIndex = 25
nextPage.Parent = footer
corner(nextPage, 7)
local pageLabel = Instance.new("TextLabel")
pageLabel.Size = UDim2.new(1, -180, 1, 0)
pageLabel.Position = UDim2.fromOffset(90, 0)
pageLabel.BackgroundTransparency = 1
pageLabel.Text = "Page 1 / 1"
pageLabel.TextColor3 = C.muted
pageLabel.TextSize = 12
pageLabel.Font = Enum.Font.Gotham
pageLabel.ZIndex = 25
pageLabel.Parent = footer
----------------------------------------------------------------
-- RESULT STATE
----------------------------------------------------------------
local results = {}
local resultKeys = {}
local currentPage = 1
local currentSection = "Overview"
local scanning = false
local omittedResults = 0
local minimumStoredRank = 1
local function setStatus(text, color)
status.Text = text
status.TextColor3 = color or C.muted
end
local function yieldProgress(i, total, label)
if i % SNAPSHOT_YIELD_EVERY == 0 then
setStatus(string.format("%s — %d / %d", label, i, total), C.warn)
RunService.Heartbeat:Wait()
end
end
local function clearRenderedRows()
for _, child in ipairs(scrolling:GetChildren()) do
if child:IsA("TextButton") or child:IsA("TextLabel") then
child:Destroy()
end
end
end
local function addResult(category, severity, path, detail)
severity = severity or "INFO"
path = path or ""
detail = detail or ""
local rank = SEVERITY_RANK[severity] or 1
if rank < minimumStoredRank then
return
end
local key = table.concat({category, severity, path, detail}, "")
if resultKeys[key] then
return
end
resultKeys[key] = true
if #results >= MAX_RESULTS_TOTAL then
omittedResults = omittedResults + 1
return
end
table.insert(results, {
category = category,
severity = severity,
path = path,
detail = detail,
})
end
local function severityColor(level)
if level == "HIGH" then
return C.danger
elseif level == "MEDIUM" then
return C.warn
elseif level == "LOW" then
return C.low
end
return C.muted
end
local function sortResults()
table.sort(results, function(a, b)
local ar = SEVERITY_RANK[a.severity] or 1
local br = SEVERITY_RANK[b.severity] or 1
if ar ~= br then
return ar > br
end
if a.category ~= b.category then
return a.category < b.category
end
return a.path < b.path
end)
end
local function resultSummary()
local counts = {HIGH = 0, MEDIUM = 0, LOW = 0, INFO = 0}
for _, item in ipairs(results) do
if counts[item.severity] ~= nil then
counts[item.severity] = counts[item.severity] + 1
end
end
return string.format(
"%d • H%d M%d L%d I%d",
#results,
counts.HIGH,
counts.MEDIUM,
counts.LOW,
counts.INFO
)
end
local function renderPage()
clearRenderedRows()
local total = #results
local totalPages = math.max(1, math.ceil(total / PAGE_SIZE))
if currentPage > totalPages then
currentPage = totalPages
end
if currentPage < 1 then
currentPage = 1
end
local first = (currentPage - 1) * PAGE_SIZE + 1
local last = math.min(total, first + PAGE_SIZE - 1)
if total == 0 then
local empty = Instance.new("TextLabel")
empty.Size = UDim2.new(1, -10, 0, 92)
empty.BackgroundTransparency = 1
empty.Text = currentSection == "Overview"
and "Choose a scan from the left.\nNothing invasive runs automatically."
or "No results in this section."
empty.TextColor3 = C.muted
empty.TextSize = 14
empty.Font = Enum.Font.Gotham
empty.TextWrapped = true
empty.ZIndex = 24
empty.Parent = scrolling
else
for i = first, last do
local item = results[i]
local expanded = false
local row = Instance.new("TextButton")
row.Size = UDim2.new(1, -4, 0, 50)
row.BackgroundColor3 = C.panel2
row.BorderSizePixel = 0
row.AutoButtonColor = false
row.Text = ""
row.LayoutOrder = i
row.ZIndex = 24
row.Parent = scrolling
corner(row, 7)
local sev = Instance.new("TextLabel")
sev.Size = UDim2.fromOffset(72, 18)
sev.Position = UDim2.fromOffset(8, 6)
sev.BackgroundTransparency = 1
sev.Text = item.severity
sev.TextColor3 = severityColor(item.severity)
sev.TextSize = 10
sev.Font = Enum.Font.GothamBold
sev.TextXAlignment = Enum.TextXAlignment.Left
sev.ZIndex = 25
sev.Parent = row
local cat = Instance.new("TextLabel")
cat.Size = UDim2.fromOffset(150, 18)
cat.Position = UDim2.fromOffset(78, 6)
cat.BackgroundTransparency = 1
cat.Text = item.category
cat.TextColor3 = C.accent2
cat.TextSize = 10
cat.Font = Enum.Font.GothamBold
cat.TextXAlignment = Enum.TextXAlignment.Left
cat.ZIndex = 25
cat.Parent = row
local pathLabel = Instance.new("TextLabel")
pathLabel.Size = UDim2.new(1, -16, 0, 18)
pathLabel.Position = UDim2.fromOffset(8, 25)
pathLabel.BackgroundTransparency = 1
pathLabel.Text = item.path
pathLabel.TextColor3 = C.text
pathLabel.TextSize = 11
pathLabel.Font = Enum.Font.Code
pathLabel.TextXAlignment = Enum.TextXAlignment.Left
pathLabel.TextYAlignment = Enum.TextYAlignment.Top
pathLabel.TextTruncate = Enum.TextTruncate.AtEnd
pathLabel.ZIndex = 25
pathLabel.Parent = row
bindButton(row, function()
expanded = not expanded
if expanded then
pathLabel.Text = item.path .. (item.detail ~= "" and ("\n" .. item.detail) or "")
pathLabel.TextWrapped = true
pathLabel.TextTruncate = Enum.TextTruncate.None
row.Size = UDim2.new(1, -4, 0, 86)
pathLabel.Size = UDim2.new(1, -16, 0, 56)
else
pathLabel.Text = item.path
pathLabel.TextWrapped = false
pathLabel.TextTruncate = Enum.TextTruncate.AtEnd
row.Size = UDim2.new(1, -4, 0, 50)
pathLabel.Size = UDim2.new(1, -16, 0, 18)
end
end)
end
end
countLabel.Text = resultSummary()
pageLabel.Text = string.format("Page %d / %d", currentPage, totalPages)
end
local function resetResults(sectionName, minimumRank)
results = {}
resultKeys = {}
omittedResults = 0
currentPage = 1
currentSection = sectionName
minimumStoredRank = minimumRank or 1
sectionTitle.Text = sectionName
renderPage()
end
local function finishResults(label)
sortResults()
local streamingPartial = currentSection ~= "Overview" and safeRead(workspace, "StreamingEnabled") == true
if omittedResults > 0 then
setStatus(string.format("%s — %d RESULT(S) OMITTED BY LIMIT", label, omittedResults), C.warn)
elseif streamingPartial then
setStatus(label .. " — WORKSPACE PARTIAL (STREAMING)", C.warn)
else
setStatus(label, C.ok)
end
renderPage()
end
----------------------------------------------------------------
-- SCANNERS
----------------------------------------------------------------
local assetProperties = {
Sound = {"SoundId"},
Animation = {"AnimationId"},
Decal = {"Texture"},
Texture = {"Texture"},
MeshPart = {"MeshId", "TextureID"},
SpecialMesh = {"MeshId", "TextureId"},
ImageLabel = {"Image"},
ImageButton = {"Image"},
VideoFrame = {"Video"},
Shirt = {"ShirtTemplate"},
Pants = {"PantsTemplate"},
SurfaceAppearance = {"ColorMap", "NormalMap", "MetalnessMap", "RoughnessMap"},
}
local function remoteSeverity(path)
local hasSecret = containsAny(path, SECRET_NAME_WORDS)
if hasSecret then
return "MEDIUM"
end
local gameplayEffect = containsAny(path, REMOTE_REVIEW_WORDS)
if gameplayEffect then
return "MEDIUM"
end
return "LOW"
end
local function processRemote(obj)
if obj:IsA("RemoteEvent") or obj:IsA("RemoteFunction") or obj:IsA("UnreliableRemoteEvent") then
local path = safeFullName(obj)
addResult(
"REMOTE SURFACE",
remoteSeverity(path),
path,
obj.ClassName .. " | Client can address this remote; security depends on server validation/rate limits."
)
elseif obj:IsA("BindableEvent") or obj:IsA("BindableFunction") then
addResult(
"LOCAL BUS",
"INFO",
safeFullName(obj),
obj.ClassName .. " | Same-side communication only; not a client-server remote by itself."
)
end
end
local function processCode(obj)
local path = safeFullName(obj)
if obj:IsA("LocalScript") then
local sev = classifyName(path)
if SEVERITY_RANK[sev] < SEVERITY_RANK.LOW then
sev = "LOW"
end
addResult(
"CLIENT CODE",
sev,
path,
"LocalScript is replicated client-side. Assume its logic/constants can be inspected or altered by an exploiter."
)
elseif obj:IsA("ModuleScript") then
local sev = classifyName(path)
if SEVERITY_RANK[sev] < SEVERITY_RANK.LOW then
sev = "LOW"
end
addResult(
"CLIENT MODULE",
sev,
path,
"Replicated ModuleScript should be treated as inspectable even if the client never requires it."
)
elseif obj:IsA("Script") then
local rc = safeRead(obj, "RunContext")
if rc and tostring(rc):find("Client", 1, true) then
local sev = classifyName(path)
if SEVERITY_RANK[sev] < SEVERITY_RANK.LOW then
sev = "LOW"
end
addResult(
"CLIENT SCRIPT",
sev,
path,
"Script.RunContext is client-side: " .. tostring(rc)
)
end
end
end
local function processState(obj)
local path = safeFullName(obj)
local okAttrs, attrs = pcall(function()
return obj:GetAttributes()
end)
if okAttrs then
for name, value in pairs(attrs) do
local keyPath = path .. ".@" .. tostring(name)
local severity = classifyState(keyPath, value)
addResult("ATTRIBUTE", severity, keyPath, stateValueForDisplay(keyPath, value))
end
end
local okTags, tags = pcall(function()
return CollectionService:GetTags(obj)
end)
if okTags then
for _, tag in ipairs(tags) do
local severity = classifyName(path .. "." .. tag)
addResult("TAG", severity, path, tag)
end
end
if obj:IsA("ValueBase") then
local value = safeRead(obj, "Value")
local severity = classifyState(path, value)
addResult("VALUE", severity, path, stateValueForDisplay(path, value))
end
if obj:IsA("Tool") then
addResult("TOOL", "INFO", path, "Client-visible Tool instance")
end
end
local function processInteraction(obj)
local path = safeFullName(obj)
local severity = classifyName(path)
if SEVERITY_RANK[severity] < SEVERITY_RANK.LOW then
severity = "LOW"
end
local part = nearestBasePart(obj)
local root = assemblyRoot(part)
local unanchoredContext = false
if root then
local anchored = safeRead(root, "Anchored")
unanchoredContext = anchored == false
if unanchoredContext then
severity = "MEDIUM"
end
end
if obj:IsA("ProximityPrompt") then
local detail = string.format(
"Action=%s | Distance=%s | Hold=%s | Enabled=%s | Server must validate authorization/state/rate; client distance/hold is not a security boundary%s",
valueToString(safeRead(obj, "ActionText")),
valueToString(safeRead(obj, "MaxActivationDistance")),
valueToString(safeRead(obj, "HoldDuration")),
valueToString(safeRead(obj, "Enabled")),
unanchoredContext and " | Parent assembly is unanchored" or ""
)
addResult("PROMPT", severity, path, detail)
elseif obj:IsA("ClickDetector") then
local detail = "Distance=" .. valueToString(safeRead(obj, "MaxActivationDistance"))
.. " | Treat as client-triggerable interaction; validate server effects"
.. (unanchoredContext and " | Parent assembly is unanchored" or "")
addResult("CLICK", severity, path, detail)
elseif obj:IsA("DragDetector") then
addResult(
"DRAG",
severity,
path,
"Client interaction / manipulation surface"
.. (unanchoredContext and " | Parent assembly is unanchored" or "")
)
end
end
local function processPhysics(obj, seenRoots)
if obj:IsA("BasePart") then
local root = assemblyRoot(obj)
if not root or seenRoots[root] then
return
end
seenRoots[root] = true
if isCharacterObject(root) then
return
end
local anchored = safeRead(root, "Anchored")
if anchored ~= false then
return
end
local path = safeFullName(root)
local ownerText, autoText, localOwned = networkInfo(root)
local riskyName = containsAny(path, PHYSICS_REVIEW_WORDS)
local canTouch = safeRead(root, "CanTouch")
local severity = "LOW"
if riskyName and (localOwned or canTouch == true) then
severity = "MEDIUM"
end
local detail = string.format(
"AssemblyRoot | Owner=%s | AutoOwnership=%s | CanTouch=%s | CanCollide=%s | Massless=%s",
ownerText,
autoText,
valueToString(canTouch),
valueToString(safeRead(root, "CanCollide")),
valueToString(safeRead(root, "Massless"))
)
if localOwned then
detail = detail .. " | CURRENTLY OWNED BY LOCAL CLIENT"
end
if riskyName then
detail = detail .. " | Gameplay-sensitive name: review server-side physics/touch validation"
end
addResult("PHYSICS ASSEMBLY", severity, path, detail)
return
end
if obj:IsA("Constraint")
or obj:IsA("BodyMover")
or obj:IsA("LinearVelocity")
or obj:IsA("AngularVelocity")
or obj:IsA("VectorForce")
or obj:IsA("AlignPosition")
or obj:IsA("AlignOrientation") then
if not isCharacterObject(obj) then
local path = safeFullName(obj)
local severity = classifyName(path)
if SEVERITY_RANK[severity] < SEVERITY_RANK.LOW then
severity = "LOW"
end
addResult("PHYSICS CTRL", severity, path, obj.ClassName)
end
end
end
local function processAssets(obj)
for className, props in pairs(assetProperties) do
if obj:IsA(className) then
for _, prop in ipairs(props) do
local value = safeRead(obj, prop)
if value ~= nil and tostring(value) ~= "" then
addResult(
"ASSET",
"INFO",
safeFullName(obj) .. "." .. prop,
valueToString(value)
)
end
end
end
end
end
local function addStreamingNotice()
local streaming = safeRead(workspace, "StreamingEnabled")
if streaming == true then
addResult(
"LIMITATION",
"LOW",
"Workspace.StreamingEnabled",
"Workspace results are only the subset currently streamed to this client. A complete world inventory requires a server-side audit."
)
end
end
local function runScanner(sectionName, statusName, processor)
if scanning then
setStatus("SCAN ALREADY RUNNING", C.danger)
return
end
scanning = true
resetResults(sectionName, 1)
addStreamingNotice()
local ok, err = pcall(function()
setStatus("CAPTURING CLIENT SNAPSHOT...", C.warn)
local list = captureSnapshot()
setStatus(statusName .. "...", C.warn)
local context = {}
for i, obj in ipairs(list) do
processor(obj, context)
yieldProgress(i, #list, statusName)
end
end)
scanning = false
if ok then
finishResults(statusName .. " COMPLETE")
else
setStatus("ERROR: " .. tostring(err), C.danger)
warn("[AUDIT] " .. tostring(err))
renderPage()
end
end
local function scanRemotes()
runScanner("Remotes", "REMOTE SCAN", function(obj)
processRemote(obj)
end)
end
local function scanCode()
runScanner("Client Code", "CLIENT CODE SCAN", function(obj)
processCode(obj)
end)
end
local function scanState()
runScanner("State / Values", "STATE SCAN", function(obj)
processState(obj)
end)
end
local function scanInteractions()
runScanner("Interactions", "INTERACTION SCAN", function(obj)
processInteraction(obj)
end)
end
local function scanPhysics()
local seenRoots = {}
runScanner("Physics", "PHYSICS SCAN", function(obj)
processPhysics(obj, seenRoots)
end)
end
local function scanAssets()
runScanner("Assets", "ASSET SCAN", function(obj)
processAssets(obj)
end)
end
local function runCombined(sectionName, statusName, minimumRank)
if scanning then
setStatus("SCAN ALREADY RUNNING", C.danger)
return
end
scanning = true
resetResults(sectionName, minimumRank or 1)
addStreamingNotice()
local ok, err = pcall(function()
setStatus("CAPTURING CLIENT SNAPSHOT...", C.warn)
local list = captureSnapshot()
local physicsSeen = {}
setStatus(statusName .. "...", C.warn)
for i, obj in ipairs(list) do
processRemote(obj)
processCode(obj)
processState(obj)
processInteraction(obj)
processPhysics(obj, physicsSeen)
processAssets(obj)
yieldProgress(i, #list, statusName)
end
end)
scanning = false
if ok then
finishResults(statusName .. " COMPLETE")
else
setStatus("ERROR: " .. tostring(err), C.danger)
warn("[AUDIT] " .. tostring(err))
renderPage()
end
end
local function showRisks()
runCombined("Risk Review", "RISK REVIEW", SEVERITY_RANK.MEDIUM)
end
local function runFullScan()
runCombined("Full Scan", "FULL SCAN", SEVERITY_RANK.INFO)
end
----------------------------------------------------------------
-- OVERVIEW
----------------------------------------------------------------
local function showOverview()
resetResults("Overview", 1)
local streaming = safeRead(workspace, "StreamingEnabled")
addResult("SYSTEM", "INFO", "PlaceId", tostring(game.PlaceId))
addResult("SYSTEM", "INFO", "GameId", tostring(game.GameId))
addResult("SYSTEM", "INFO", "StreamingEnabled", tostring(streaming))
addResult(
"MODEL",
"INFO",
"Client visibility != vulnerability",
"RemoteEvents, replicated values and client code are expected surfaces. Security depends on server-side validation and authority."
)
addResult(
"LIMITATION",
"LOW",
"Server validation is not visible here",
"This passive client script cannot prove whether OnServerEvent/OnServerInvoke handlers validate types, permissions, distance, state or rate limits."
)
addResult(
"LIMITATION",
"LOW",
"Replicated code",
"Treat every LocalScript and replicated ModuleScript as inspectable/modifiable by an exploiter."
)
if streaming == true then
addResult(
"LIMITATION",
"LOW",
"Workspace streaming",
"Workspace scans can miss objects that are not currently streamed to this client."
)
end
finishResults("READY")
end
----------------------------------------------------------------
-- BUTTON BINDINGS
----------------------------------------------------------------
-- X and minimize both intentionally hide, never destroy.
bindButton(close, hideMain)
bindButton(minimize, hideMain)
bindButton(btnOverview, showOverview)
bindButton(btnRemotes, scanRemotes)
bindButton(btnCode, scanCode)
bindButton(btnState, scanState)
bindButton(btnInteractions, scanInteractions)
bindButton(btnPhysics, scanPhysics)
bindButton(btnAssets, scanAssets)
bindButton(btnRisks, showRisks)
bindButton(btnFull, runFullScan)
bindButton(btnClear, function()
resetResults("Overview")
setStatus("RESULTS CLEARED", C.ok)
end)
bindButton(prevPage, function()
currentPage = currentPage - 1
renderPage()
end)
bindButton(nextPage, function()
currentPage = currentPage + 1
renderPage()
end)
----------------------------------------------------------------
-- HOTKEY
----------------------------------------------------------------
UserInputService.InputBegan:Connect(function(input, processed)
if processed then
return
end
if input.KeyCode == Enum.KeyCode.RightShift then
if main.Visible then
hideMain()
else
openMain()
end
end
end)
----------------------------------------------------------------
-- LAUNCHER NOTE
----------------------------------------------------------------
-- Dragging is intentionally disabled in v6.1. Some injected input layers
-- report tiny mouse movement during a click, which could suppress opening.
----------------------------------------------------------------
-- START STATE
----------------------------------------------------------------
local overviewOk, overviewErr = pcall(showOverview)
if overviewOk then
bootNotice.Visible = false
else
bootNotice.Text = "AUDITOR SETUP ERROR\n\n" .. tostring(overviewErr) .. "\n\nThe launcher still works so this error remains visible."
bootNotice.TextColor3 = C.danger
warn("[AUDIT] setup error: " .. tostring(overviewErr))
end
main.Visible = false
launcher.Visible = true
print("[AUDIT] v6.1 loaded. Studio + injector compatible launcher ready.")