5 Commits

Author SHA1 Message Date
7483e131ef Merge pull request 'fix(perms): never write a never-seen player into Carbon's shared blank record' (#47) from fix/carbon-blank-user into main
All checks were successful
Release plugin / release (push) Successful in 43s
Reviewed-on: #47
2026-10-30 09:31:54 +00:00
59db88d006 fix(perms): never write a never-seen player into Carbon's shared blank record
Some checks failed
PR Checks / plugin-checks (pull_request) Failing after -14s
Carbon 2.0.262 answers GetUserData for a Steam id it has no record for with
one shared, static blank record, and GrantUserPermission and AddUserGroup
write into it. A grant or membership for one never-seen player was then held
by every Steam id Carbon had no record for, until the server restarted, and
UserHasGroup read the membership back as a success, so the sync reported it
applied and later syncs saw nothing to fix.

On Carbon the sync now checks permission.UserExists before either write. A
never-seen player's grant is reported notLanded and their membership pending,
without touching the store; both land at the first audit sync after they
join, as PLAN.md section 29.8 and section 12.2 rule 4 already describe. Oxide
creates a real record and is unchanged.

Proven on the Carbon rig (Rust 26139947, Carbon 2.0.262): before, three
random never-seen ids each held autodoors.use after one admin grant; after,
a known id got its grant and membership, the never-seen id was reported
notLanded + pending, and random ids held nothing. Found in the account
deletion walk (docs/website/ACCOUNT_DELETION.md step 6, W3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-30 04:23:31 -05:00
19ad9279fa Merge pull request 'docs(bridge): bring EventsEnabled's comment to main' (#46) from edge into main
All checks were successful
Release plugin / release (push) Successful in 15s
Reviewed-on: #46
2026-10-23 07:07:09 +00:00
22da0026ca Merge pull request 'docs(bridge): EventsEnabled's comment says what it gates today' (#45) from fix/eventsenabled-comment into edge
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 22s
Reviewed-on: #45
2026-10-22 20:32:36 +00:00
ef9596ce67 docs(bridge): EventsEnabled's comment says what it gates today
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 9s
The doc comment on PluginConfig.EventsEnabled still said it "gates
taking a lease only", which stopped being true at protocol 9 when it
began gating every world write (D94). It now lists what the switch
gates as of protocol 14 - a lease, every world write (zones, crates,
NPCs, world calls, Dangerous Treasures, Guarded Crate, raid bases,
console commands), a tally and a run's chat line, and paying coins -
what always works with it off, and that the website can set it without
a reload. Comment only; no behaviour change.

Found while writing the runicgateway.com Rust docs (its PLAN.md §16,
phase 15).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-22 15:29:34 -05:00

View File

@@ -445,12 +445,20 @@ namespace Oxide.Plugins
public string ServerId = "main"; public string ServerId = "main";
/// <summary> /// <summary>
/// Whether the website may borrow values on this server for events (protocol 8, /// Whether the website may run events on this server at all (protocol 8, D76).
/// D76). <b>Off until an operator turns it on.</b> Letting staff moderate from a /// <b>Off until an operator turns it on.</b> Letting staff moderate from a screen
/// screen they are looking at, and letting a schedule change the world at four in /// they are looking at, and letting a schedule change the world at four in the
/// the morning, are different consents, and one switch cannot honestly express /// morning, are different consents, and one switch cannot honestly express both.
/// both. It gates taking a lease only: listing and giving back always work, so /// <para>
/// turning it off never strands a value somebody already borrowed. /// It began as the lease switch and grew with the protocol. It now gates taking a
/// lease (protocol 8), every world write (protocol 9, D94: zones, placed crates and
/// NPCs, and from protocol 14 world calls, Dangerous Treasures, Guarded Crate,
/// raid bases and console commands — <see cref="WorldWritesAllowed"/>), opening a
/// tally and a run's chat line (protocol 10, D107), and paying coins (protocol 14).
/// Listing, giving back, reverting and every read always work, so turning it off
/// never strands anything an event already holds. An admin can set it from the
/// website's config editor, and it takes effect without a reload.
/// </para>
/// </summary> /// </summary>
public bool EventsEnabled = false; public bool EventsEnabled = false;
@@ -4770,6 +4778,15 @@ namespace Oxide.Plugins
break; break;
case PermOpKind.EnsureMember: case PermOpKind.EnsureMember:
// Before the read, because on Carbon the read is the thing that lies: a
// never-seen player's UserHasGroup reads the shared blank record (see
// HasNoCarbonRecord), so it can say "already a member" of nobody.
if (HasNoCarbonRecord(op.Subject))
{
job.Pending.Add(op.Subject + ":" + op.Object);
break;
}
if (permission.UserHasGroup(op.Subject, op.Object)) if (permission.UserHasGroup(op.Subject, op.Object))
{ {
job.AlreadyCorrect++; job.AlreadyCorrect++;
@@ -4792,6 +4809,15 @@ namespace Oxide.Plugins
if (!Registered(job, op.Object)) if (!Registered(job, op.Object))
break; break;
// Never-seen on Carbon: the grant would land in the shared blank record and be
// held by every Steam id Carbon has no record for. It lands at the first sync
// after they join, as PLAN.md §29.8 already says of Carbon.
if (HasNoCarbonRecord(op.Subject))
{
job.NotLanded.Add(op.Subject + ":" + op.Object);
break;
}
if (HoldsDirectly(op.Subject, op.Object)) if (HoldsDirectly(op.Subject, op.Object))
{ {
job.AlreadyCorrect++; job.AlreadyCorrect++;
@@ -4934,6 +4960,24 @@ namespace Oxide.Plugins
return false; return false;
} }
/// <summary>
/// Whether this is Carbon and Carbon has no user record for the player.
///
/// <para>
/// Carbon (2.0.262) answers <c>GetUserData</c> for a Steam id it has never seen with ONE
/// shared, static blank record, and <c>AddUserGroup</c> and <c>GrantUserPermission</c>
/// write into whatever it returns. So a membership or grant for one never-seen player
/// goes to that shared record and is then held by every Steam id Carbon has no record
/// for, until the server restarts — and <c>UserHasGroup</c> reads it back as a success.
/// Oxide creates a real record instead, so this is never true there and Oxide's
/// behaviour is unchanged. Found in the account deletion walk (W3).
/// </para>
/// </summary>
private bool HasNoCarbonRecord(string steamId)
{
return IsCarbon() && !permission.UserExists(steamId);
}
/// <summary> /// <summary>
/// Whether a player holds a permission through a grant of their own, as opposed to /// Whether a player holds a permission through a grant of their own, as opposed to
/// through a group. /// through a group.