Загрузка данных
--[[
CLIENT EXPOSURE AUDITOR v7 - CONSOLE EDITION
Purpose:
- One small AUDIT button only.
- Clicking it performs one passive client-side exposure scan.
- Findings are printed to the Roblox / Delta console.
- No remotes/prompts are fired and game state is not intentionally mutated.
- No executor introspection, hooks, decompilers or remote firing are used.
Important limitations:
- This cannot see server-only code or prove server-side validation quality.
- With Workspace.StreamingEnabled, only currently streamed Workspace content is visible.
- Replicated client code/data should be treated as inspectable by an exploiter.
]]
local PREFIX = "[AUDIT v7]"
local VERSION = "7.0-console"
local YIELD_EVERY = 500
local MAX_RESULTS = 20000
local MAX_LINE_LENGTH = 1200
local PRINT_ALL_INFO = false -- false = tablet-friendly; LOW/MEDIUM/HIGH + system/limitations are printed in detail
local function out(message)
pcall(function()
print(PREFIX .. " " .. tostring(message))
end)
end
local function outWarn(message)
pcall(function()
warn(PREFIX .. " " .. tostring(message))
end)
end
local function safeGetService(name)
local ok, service = pcall(function()
return game:GetService(name)
end)
if ok then
return service
end
return nil
end
local Players = safeGetService("Players")
local RunService = safeGetService("RunService")
local CollectionService = safeGetService("CollectionService")
local ReplicatedStorage = safeGetService("ReplicatedStorage")
local ReplicatedFirst = safeGetService("ReplicatedFirst")
local Lighting = safeGetService("Lighting")
local SoundService = safeGetService("SoundService")
local TextChatService = safeGetService("TextChatService")
local Teams = safeGetService("Teams")
local StarterGui = safeGetService("StarterGui")
local StarterPlayer = safeGetService("StarterPlayer")
local StarterPack = safeGetService("StarterPack")
out("BOOT 01 script entered")
if not Players then
outWarn("FATAL: Players service unavailable")
return
end
local player = Players.LocalPlayer
if not player then
out("BOOT 02 waiting for LocalPlayer")
local deadline = os.clock() + 15
repeat
if RunService then
RunService.Heartbeat:Wait()
else
task.wait(0.05)
end
player = Players.LocalPlayer
until player or os.clock() >= deadline
end
if not player then
outWarn("FATAL: LocalPlayer unavailable after 15 seconds")
return
end
out("BOOT 02 LocalPlayer=" .. tostring(player.Name))
local playerGui = player:FindFirstChildOfClass("PlayerGui")
if not playerGui then
local ok, value = pcall(function()
return player:WaitForChild("PlayerGui", 15)
end)
if ok then
playerGui = value
end
end
if not playerGui then
outWarn("FATAL: PlayerGui unavailable")
return
end
out("BOOT 03 PlayerGui ready")
-- Clean previous console-auditor launchers only.
for _, name in ipairs({
"client_exposure_auditor_v7_console",
"client_exposure_auditor_v6_2",
"client_exposure_auditor_v6_1",
"client_exposure_auditor_v6",
"client_exposure_auditor_v5",
}) do
local old = playerGui:FindFirstChild(name)
if old then
pcall(function()
old:Destroy()
end)
end
end
local gui = Instance.new("ScreenGui")
gui.Name = "client_exposure_auditor_v7_console"
gui.ResetOnSpawn = false
gui.IgnoreGuiInset = true
gui.DisplayOrder = 999999
gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
gui.Parent = playerGui
local button = Instance.new("TextButton")
button.Name = "audit"
button.Size = UDim2.fromOffset(116, 46)
button.Position = UDim2.fromOffset(16, 70)
button.BackgroundColor3 = Color3.fromRGB(57, 108, 230)
button.BorderSizePixel = 0
button.Text = "AUDIT"
button.TextColor3 = Color3.fromRGB(255, 255, 255)
button.TextSize = 16
button.Font = Enum.Font.GothamBold
button.AutoButtonColor = true
button.ZIndex = 100
button.Parent = gui
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 10)
corner.Parent = button
local stroke = Instance.new("UIStroke")
stroke.Color = Color3.fromRGB(130, 166, 255)
stroke.Thickness = 1
stroke.Transparency = 0.25
stroke.Parent = button
out("BOOT 04 AUDIT button created")
local runAudit = nil
local initError = nil
local scanning = false
local lastButtonFire = 0
local function invokeButton()
local now = os.clock()
if now - lastButtonFire < 0.18 then
return
end
lastButtonFire = now
if scanning then
outWarn("scan already running")
return
end
if runAudit then
task.spawn(runAudit)
else
outWarn("scanner is not ready" .. (initError and (" | init error: " .. tostring(initError)) or ""))
end
end
-- Bind immediately. If later initialization fails, the button still reports it.
pcall(function()
button.MouseButton1Down:Connect(invokeButton)
end)
pcall(function()
button.MouseButton1Click:Connect(invokeButton)
end)
pcall(function()
button.Activated:Connect(invokeButton)
end)
out("BOOT 05 button handlers connected")
local okInit, initErr = pcall(function()
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 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 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 ASSET_PROPERTIES = {
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 isA(obj, className)
local ok, value = pcall(function()
return obj:IsA(className)
end)
return ok and value == true
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 safeFullName(obj)
local ok, value = pcall(function()
return obj:GetFullName()
end)
if ok and value then
return value
end
return tostring(safeRead(obj, "Name") or obj)
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 == "table" then
return "<table>"
end
return tostring(value)
end
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
end
local s = string.lower(value)
local patterns = {
"discord.com/api/webhooks/",
"discordapp.com/api/webhooks/",
"hooks.slack.com/services/",
"-----begin private key-----",
"-----begin rsa private key-----",
"bearer ",
"xoxb-",
"sk_live_",
}
for _, pattern in ipairs(patterns) do
if string.find(s, pattern, 1, true) then
return true
end
end
return false
end
local function classifyName(path)
if containsAny(path, SECRET_NAME_WORDS) then
return "MEDIUM"
end
if containsAny(path, REVIEW_WORDS) then
return "LOW"
end
return "INFO"
end
local function classifyState(name, value)
local hasSecretName = containsAny(name, SECRET_NAME_WORDS)
if stringLooksSensitive(value) 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)
if classifyState(name, value) == "HIGH" then
if typeof(value) == "string" then
return "<REDACTED STRING len=" .. tostring(#value) .. ">"
end
return "<REDACTED " .. string.upper(typeof(value)) .. ">"
end
return valueToString(value)
end
local function remoteSeverity(path)
if containsAny(path, SECRET_NAME_WORDS) then
return "MEDIUM"
end
if containsAny(path, REMOTE_REVIEW_WORDS) then
return "MEDIUM"
end
return "LOW"
end
local function isAuditorObject(obj)
local ok, value = pcall(function()
return obj == gui or obj:IsDescendantOf(gui)
end)
return ok and value or false
end
local function isCharacterObject(obj)
local current = obj
local guard = 0
while current and current ~= workspace and guard < 64 do
guard = guard + 1
if isA(current, "Model") then
local ok, humanoid = pcall(function()
return current:FindFirstChildOfClass("Humanoid")
end)
if ok and humanoid then
return true
end
end
current = safeRead(current, "Parent")
end
return false
end
local function nearestBasePart(obj)
local current = obj
local guard = 0
while current and current ~= game and guard < 64 do
guard = guard + 1
if isA(current, "BasePart") then
return current
end
current = safeRead(current, "Parent")
end
return nil
end
local function assemblyRoot(part)
if not part or not isA(part, "BasePart") then
return nil
end
local root = safeRead(part, "AssemblyRootPart")
if root and isA(root, "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 = tostring(safeRead(owner, "Name") or owner)
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 = {}
local candidates = {
ReplicatedFirst,
ReplicatedStorage,
workspace,
Players,
Lighting,
SoundService,
TextChatService,
Teams,
StarterGui,
StarterPlayer,
StarterPack,
}
for _, root in ipairs(candidates) do
if root then
table.insert(roots, root)
end
end
return roots
end
local function captureSnapshot()
local output = {}
local seen = {}
for _, root in ipairs(buildScanRoots()) do
if 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 and descendants 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
local function oneLine(text)
text = tostring(text or "")
text = text:gsub("[\r\n\t]+", " ")
if #text > MAX_LINE_LENGTH then
return text:sub(1, MAX_LINE_LENGTH) .. "..."
end
return text
end
runAudit = function()
if scanning then
outWarn("scan already running")
return
end
scanning = true
button.Text = "SCANNING..."
button.AutoButtonColor = false
local scanOk, scanErr = pcall(function()
local started = os.clock()
local results = {}
local resultKeys = {}
local omitted = 0
local objectErrors = 0
local counts = {
HIGH = 0,
MEDIUM = 0,
LOW = 0,
INFO = 0,
}
local categoryCounts = {}
local function addResult(category, severity, path, detail)
severity = severity or "INFO"
path = tostring(path or "")
detail = tostring(detail or "")
local key = table.concat({category, severity, path, detail}, "\31")
if resultKeys[key] then
return
end
resultKeys[key] = true
if #results >= MAX_RESULTS then
omitted = omitted + 1
return
end
table.insert(results, {
category = category,
severity = severity,
path = path,
detail = detail,
})
if counts[severity] ~= nil then
counts[severity] = counts[severity] + 1
end
categoryCounts[category] = (categoryCounts[category] or 0) + 1
end
local streaming = safeRead(workspace, "StreamingEnabled")
addResult("SYSTEM", "INFO", "Version", VERSION)
addResult("SYSTEM", "INFO", "PlaceId", tostring(game.PlaceId))
addResult("SYSTEM", "INFO", "GameId", tostring(game.GameId))
addResult("SYSTEM", "INFO", "Player", tostring(player.Name))
addResult("SYSTEM", "INFO", "StreamingEnabled", tostring(streaming))
addResult(
"LIMITATION",
"LOW",
"Server validation is not visible from client",
"This scan cannot prove whether OnServerEvent/OnServerInvoke handlers validate types, permissions, distance, state or rate limits."
)
if streaming == true then
addResult(
"LIMITATION",
"LOW",
"Workspace is streamed",
"Workspace findings cover only objects currently streamed to this client."
)
end
out("============================================================")
out("FULL PASSIVE SCAN STARTED")
out("capturing client-visible snapshot...")
local snapshot = captureSnapshot()
out("snapshot captured: " .. tostring(#snapshot) .. " objects")
local seenPhysicsRoots = {}
for i, obj in ipairs(snapshot) do
local okObject, errObject = pcall(function()
local path = safeFullName(obj)
-- Network / local communication surfaces.
if isA(obj, "RemoteEvent") or isA(obj, "RemoteFunction") or isA(obj, "UnreliableRemoteEvent") then
addResult(
"REMOTE SURFACE",
remoteSeverity(path),
path,
tostring(safeRead(obj, "ClassName") or "Remote") .. " | Client can address this remote; review its server-side validation and rate limits."
)
elseif isA(obj, "BindableEvent") or isA(obj, "BindableFunction") then
addResult(
"LOCAL BUS",
"INFO",
path,
tostring(safeRead(obj, "ClassName") or "Bindable") .. " | Same-side communication only; not a client-server remote by itself."
)
end
-- Replicated client code.
if isA(obj, "LocalScript") then
local sev = classifyName(path)
if SEVERITY_RANK[sev] < SEVERITY_RANK.LOW then
sev = "LOW"
end
addResult("CLIENT CODE", sev, path, "LocalScript is client-visible; assume logic/constants can be inspected or altered.")
elseif isA(obj, "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 by the client.")
elseif isA(obj, "Script") then
local rc = safeRead(obj, "RunContext")
if rc and string.find(tostring(rc), "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=" .. tostring(rc))
end
end
-- Attributes / tags / ValueBase state.
local okAttrs, attrs = pcall(function()
return obj:GetAttributes()
end)
if okAttrs and attrs then
for name, value in pairs(attrs) do
local keyPath = path .. ".@" .. tostring(name)
addResult("ATTRIBUTE", classifyState(keyPath, value), keyPath, stateValueForDisplay(keyPath, value))
end
end
if CollectionService then
local okTags, tags = pcall(function()
return CollectionService:GetTags(obj)
end)
if okTags and tags then
for _, tag in ipairs(tags) do
addResult("TAG", classifyName(path .. "." .. tostring(tag)), path, tostring(tag))
end
end
end
if isA(obj, "ValueBase") then
local value = safeRead(obj, "Value")
addResult("VALUE", classifyState(path, value), path, stateValueForDisplay(path, value))
end
if isA(obj, "Tool") then
addResult("TOOL", "INFO", path, "Client-visible Tool")
end
-- Interaction surfaces.
if isA(obj, "ProximityPrompt") or isA(obj, "ClickDetector") or isA(obj, "DragDetector") then
local severity = classifyName(path)
if SEVERITY_RANK[severity] < SEVERITY_RANK.LOW then
severity = "LOW"
end
local part = nearestBasePart(obj)
local root = assemblyRoot(part)
local unanchored = root and safeRead(root, "Anchored") == false
if unanchored then
severity = "MEDIUM"
end
if isA(obj, "ProximityPrompt") then
local detail = "Action=" .. valueToString(safeRead(obj, "ActionText"))
.. " | Distance=" .. valueToString(safeRead(obj, "MaxActivationDistance"))
.. " | Hold=" .. valueToString(safeRead(obj, "HoldDuration"))
.. " | Enabled=" .. valueToString(safeRead(obj, "Enabled"))
.. " | Server must validate authorization/state/rate"
if unanchored then
detail = detail .. " | parent assembly unanchored"
end
addResult("PROMPT", severity, path, detail)
elseif isA(obj, "ClickDetector") then
local detail = "Distance=" .. valueToString(safeRead(obj, "MaxActivationDistance"))
.. " | Treat server effects as client-triggerable"
if unanchored then
detail = detail .. " | parent assembly unanchored"
end
addResult("CLICK", severity, path, detail)
else
addResult("DRAG", severity, path, "Client manipulation surface" .. (unanchored and " | parent assembly unanchored" or ""))
end
end
-- Physics assemblies and controllers.
if isA(obj, "BasePart") then
local root = assemblyRoot(obj)
if root and not seenPhysicsRoots[root] then
seenPhysicsRoots[root] = true
if not isCharacterObject(root) and safeRead(root, "Anchored") == false then
local rootPath = safeFullName(root)
local ownerText, autoText, localOwned = networkInfo(root)
local riskyName = containsAny(rootPath, PHYSICS_REVIEW_WORDS)
local canTouch = safeRead(root, "CanTouch")
local severity = "LOW"
if riskyName and (localOwned or canTouch == true) then
severity = "MEDIUM"
end
local detail = "Owner=" .. ownerText
.. " | AutoOwnership=" .. autoText
.. " | CanTouch=" .. valueToString(canTouch)
.. " | CanCollide=" .. valueToString(safeRead(root, "CanCollide"))
.. " | Massless=" .. 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, rootPath, detail)
end
end
elseif isA(obj, "Constraint")
or isA(obj, "BodyMover")
or isA(obj, "LinearVelocity")
or isA(obj, "AngularVelocity")
or isA(obj, "VectorForce")
or isA(obj, "AlignPosition")
or isA(obj, "AlignOrientation") then
if not isCharacterObject(obj) then
local severity = classifyName(path)
if SEVERITY_RANK[severity] < SEVERITY_RANK.LOW then
severity = "LOW"
end
addResult("PHYSICS CTRL", severity, path, tostring(safeRead(obj, "ClassName") or "Physics controller"))
end
end
-- Asset references.
for className, props in pairs(ASSET_PROPERTIES) do
if isA(obj, className) then
for _, prop in ipairs(props) do
local value = safeRead(obj, prop)
if value ~= nil and tostring(value) ~= "" then
addResult("ASSET", "INFO", path .. "." .. prop, valueToString(value))
end
end
end
end
end)
if not okObject then
objectErrors = objectErrors + 1
if objectErrors <= 25 then
outWarn("object scan error #" .. tostring(objectErrors) .. " at index " .. tostring(i) .. ": " .. oneLine(errObject))
end
end
if i % YIELD_EVERY == 0 then
out("progress " .. tostring(i) .. "/" .. tostring(#snapshot))
if RunService then
RunService.Heartbeat:Wait()
else
task.wait()
end
end
end
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)
out("============================================================")
out("SCAN COMPLETE")
out("objects=" .. tostring(#snapshot)
.. " | results=" .. tostring(#results)
.. " | HIGH=" .. tostring(counts.HIGH)
.. " | MEDIUM=" .. tostring(counts.MEDIUM)
.. " | LOW=" .. tostring(counts.LOW)
.. " | INFO=" .. tostring(counts.INFO)
.. " | object_errors=" .. tostring(objectErrors)
.. " | omitted=" .. tostring(omitted))
out("elapsed=" .. string.format("%.2f", os.clock() - started) .. "s")
local categoryNames = {}
for category in pairs(categoryCounts) do
table.insert(categoryNames, category)
end
table.sort(categoryNames)
for _, category in ipairs(categoryNames) do
out("COUNT " .. category .. "=" .. tostring(categoryCounts[category]))
end
out("-------------------- FINDINGS -------------------------------")
local printed = 0
local hiddenInfo = 0
for index, item in ipairs(results) do
local show = PRINT_ALL_INFO
or item.severity ~= "INFO"
or item.category == "SYSTEM"
or item.category == "LIMITATION"
if show then
printed = printed + 1
local line = string.format(
"[%04d] [%s] [%s] %s%s",
index,
item.severity,
item.category,
oneLine(item.path),
item.detail ~= "" and (" | " .. oneLine(item.detail)) or ""
)
if item.severity == "HIGH" or item.severity == "MEDIUM" then
outWarn(line)
else
out(line)
end
else
hiddenInfo = hiddenInfo + 1
end
end
out("printed_findings=" .. tostring(printed) .. " | hidden_INFO=" .. tostring(hiddenInfo)
.. " | set PRINT_ALL_INFO=true at top if you need every informational asset/value/tag line")
if omitted > 0 then
outWarn(tostring(omitted) .. " additional result(s) omitted because MAX_RESULTS=" .. tostring(MAX_RESULTS))
end
if objectErrors > 25 then
outWarn(tostring(objectErrors - 25) .. " additional per-object scan errors were suppressed")
end
if streaming == true then
outWarn("Workspace.StreamingEnabled=true: Workspace inventory is partial on this client")
end
out("============================================================")
out("END OF REPORT")
end)
scanning = false
button.AutoButtonColor = true
if scanOk then
button.Text = "AUDIT AGAIN"
else
button.Text = "AUDIT ERROR"
outWarn("FATAL SCAN ERROR: " .. tostring(scanErr))
end
end
end)
if not okInit then
initError = tostring(initErr)
button.Text = "AUDIT ERROR"
outWarn("INIT ERROR: " .. tostring(initErr))
else
button.Text = "AUDIT"
out("BOOT 06 scanner initialized")
out("READY - press AUDIT; full report will be printed to console")
end