本文へ移動
cccskills
無料GitHub で公開

permission-set-expiration

Use when an assignment must expire on a date, not stand forever. Trigger keywords: permission set expiration date, permission set group expiry, temporary access, time-boxed elevation, contractor access, ExpirationDate. NOT for PSG composition - use admin/permission-set-group-composition. NOT for muting mechanics - use security/permission-set-groups-and-muting. NOT for criteria-based auto-assignment - use admin/user-access-policies.

インストール方法を見る

含まれるファイル(7)

  • SKILL.md23.0 KB
  • references/examples.md12.0 KB
  • references/gotchas.md22.5 KB
  • references/llm-anti-patterns.md14.0 KB
  • references/well-architected.md13.9 KB
  • scripts/check_permission_set_expiration.py14.5 KB
  • templates/permission-set-expiration-template.md7.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Permission Set Expiration

This skill activates when access has an end date: contractor and vendor grants, on-call elevation, audit-season read access, a migration cutover role, or any permission a security review has agreed must not become standing privilege. It covers where the expiration is stored, what the platform does and does not take away when the date passes, which assignment types cannot carry an expiry at all, and how to audit the population before it silently drifts back to permanent.


Before Starting

Gather this context before working on anything in this domain:

  • Is the expiration UI turned on in this org? UserManagementSettings.psaExpirationUIEnabled is documented as "Indicates if admins can use an updated user interface that includes an assignment expiration for permission sets and permission set groups (true) or not (false). The default value is false." It is available in API version 52.0 and later. If nobody has turned it on, an admin clicking through Setup will not see an expiration control and will conclude the feature does not exist.
  • Is the permission granted anywhere else? The Salesforce Security Guide is blunt about this: "To revoke a permission, you must remove all instances of the permission from the user." An expiring assignment closes one grant path. If the profile or a second permission set also enables the permission, the user keeps it after the date passes and the elevation was never time-boxed at all.
  • Which object holds the grant? PermissionSetAssignment carries ExpirationDate; PermissionSetLicenseAssign does not, and does not even support update(). If the elevation depends on a permission set license, no expiry exists for it.
  • Are user access policies enabled? With them on, IsRevoked, LastCreatedByChangeId, and LastDeletedByChangeId appear on the assignment (API version 57.0 and later) and a policy-driven revocation leaves the assignment row in place rather than deleting it. Audit queries written for a plain org return the wrong population in a policy org.

Core Concepts

The expiry is a field on the assignment row

PermissionSetAssignment "represents a user's assignment to a permission set or permission set group" and is available in API version 22.0 and later. Its supported calls are create(), delete(), describeSObjects(), query(), retrieve(), and update(). Access to the object requires View Setup and Configuration, Assign Permission Sets, or Manage User.

FieldType — PropertiesWhat it means for time-boxing
ExpirationDatedateTime — Create, Filter, Nillable, Sort, Update"The date that the assignment of the permission set or permission set group expires for the specified user." API version 52.0 and later. Nillable, so an assignment with no end date is the same row with this field null.
IsActiveboolean — Defaulted on create, Filter, Group, Sort"Indicates whether the assignment is active (true) or not (false). Defaults to false." API version 52.0 and later. Not createable and not updateable — the platform sets it. Filter on it; never try to write it.
IsRevokedboolean — Defaulted on create, Filter, Group, Sort, Update"Indicates whether the assignment was revoked (true) or not (false)." Present only when user access policies are enabled. API version 57.0 and later.
AssigneeIdreference (User) — Create, Filter, Group, SortNo Update. Retargeting an assignment to a different user is a delete plus an insert.
PermissionSetIdreference — Create, Filter, Group, Nillable, SortNo Update. Swapping which permission set is granted is a delete plus an insert.
PermissionSetGroupIdreference — Create, Filter, Group, Nillable, SortNo Update. API version 45.0 and later. One row grants the whole group.
LastCreatedByChangeId / LastDeletedByChangeIdreference (UserAccessChange)Populated only when user access policies are enabled. API version 57.0 and later. UserAccessChange.Source carries, "for example, UserAccessPolicyId."

The Object Reference's blanket instruction — "To update an assignment, delete an existing assignment and insert a new one" — is about the Create-only fields above. ExpirationDate is the exception: it is explicitly updateable, which is why extending an expiry is a one-field update, not a re-assignment. See references/gotchas.md Gotcha 2 for what the delete-and-insert route costs you.

Expiry closes one door, not the room

Expiry ends this assignment. It does not audit the rest of the user's access. A permission that is also on the profile, on a second permission set, or inside a permission set group the user is still assigned to survives the date unchanged. Time-boxing therefore only works when the elevated permission lives in exactly one place — which is the whole argument for a narrow, single-purpose elevation permission set rather than hanging an expiry on a broad job-function group.

What can and cannot carry an expiration date

Grant mechanismExpiry supported?Evidence
Permission set assignmentYesPermissionSetAssignment.ExpirationDate, API version 52.0 and later
Permission set group assignmentYesSame field, same row — the description names "the permission set or permission set group"
Permission set license assignmentNoPermissionSetLicenseAssign has no ExpirationDate, and its supported calls are create(), delete(), describeSObjects(), query(), retrieve() — no update()
ProfileNoA profile is a lookup on the User record; there is no assignment object to hang a date on
Muting permission setNoMutingPermissionSet "is used in conjunction with PermissionSetGroup" — it is a component of a group, never assigned to a user, so there is no assignment row
Public group or queue membershipNoGroupMember has exactly two fields, GroupId and UserOrGroupId, and does not support update()
Package licenseNoNo expiration field on the assignment; user access policies can Grant or Revoke a PackageLicense, but with no date attached

Turning on the Setup control

The toggle lives on the User Management Settings Setup page, and it is deployable. UserManagementSettings values are stored in UserManagement.settings; in the package manifest all org settings types are referenced by the name Settings.

<!-- force-app/main/default/settings/UserManagement.settings-meta.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<UserManagementSettings xmlns="http://soap.sforce.com/2006/04/metadata">
    <psaExpirationUIEnabled>true</psaExpirationUIEnabled>
    <userAccessPoliciesEnabled>true</userAccessPoliciesEnabled>
</UserManagementSettings>
<!-- manifest/package.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
        <members>UserManagement</members>
        <name>Settings</name>
    </types>
    <version>67.0</version>
</Package>

There is only one settings file per settings component, so retrieve the org's existing UserManagement.settings and add the element rather than deploying the two-line file above over the top of whatever else is configured there. userAccessPoliciesEnabled is available in API version 58.0 and later and also defaults to false; include it only if the org has actually adopted policies.

User Access Policies assign; they do not hold the clock

A user access policy is the supported no-code way to make assignments from user criteria — UserAccessPolicy is available in API version 57.0 and later and requires the Manage User Access Policies permission. But the action shape is deliberately small. UserAccessPolicyAction has exactly three fields: action (Grant or Revoke), target, and type (Group, PackageLicense, PermissionSet, PermissionSetGroup, PermissionSetLicense, Queue). There is no expiration attribute anywhere in the policy metadata.

The consequence is architectural: a policy can express "everyone in this role gets this group" and "everyone who leaves this role loses it," but it cannot express "for ninety days." Criteria-driven revocation and date-driven expiry are two different controls. Use admin/user-access-policies for the first; use ExpirationDate for the second. A policy-driven revoke sets IsRevoked = true and leaves the row behind rather than deleting it, so ALL ROWS is required to see the revoked population — a date expiry and a policy revocation are two different terminal states and one audit query will not find both.


Common Patterns

Pattern: Single-Purpose Elevation Permission Set

When to use: A named individual needs one dangerous capability — Manage Users, an integration-user permission, an object's Delete — for a bounded period, and the org's job-function permission sets are too broad to expire safely.

How it works:

  1. Build a permission set that contains only the elevated capability. Name it for the elevation, not for the person.
  2. Assign it to the individual with ExpirationDate set to the agreed end instant.
  3. Verify the capability is not simultaneously granted by the assignee's profile or by any other assigned permission set — if it is, the expiry buys nothing.
  4. Record the approver and the business justification alongside the assignment; the platform stores the date, not the reason.
  5. Let the date pass. Do not build a job to remove the assignment.

Why not hang the expiry on the job-function group: the group carries the user's normal, permanent access as well. When it expires, the user loses their day job, not just the elevation. Expiring a group assignment is correct only when the entire group is the temporary grant — a project role, a migration cutover role, an audit-window reader.

Pattern: Renewal by Update, Not Re-Assignment

When to use: A contractor's engagement is extended and the assignment is close to expiring, or already inactive.

How it works: ExpirationDate is updateable. Query the assignment by AssigneeId and PermissionSetId, then update the single field with the new instant.

// Extend one contractor's elevation by another quarter.
PermissionSetAssignment psa = [
    SELECT Id, ExpirationDate, IsActive
    FROM PermissionSetAssignment
    WHERE AssigneeId = :contractorId
      AND PermissionSet.Name = 'PS_Temp_ManageUsers'
    LIMIT 1
];
psa.ExpirationDate = newEndInstant;
update psa;

Why not delete and re-insert: a new row resets the assignment's creation audit and breaks any reporting keyed on the original grant. It also risks a window where the user has no assignment at all if the insert fails. Re-insert only when the target changes — a different user, a different permission set, a different group — because those fields are Create-only.

Pattern: Expiry-Aware Access Review

When to use: A quarterly or SOX-style access review must show which elevations are live, which are about to lapse, and which already have.

How it works: three queries, run together, because no single one covers the population.

-- 1. Live elevations with a scheduled end, next 30 days first.
--    IsActive = true is load-bearing: without it, NEXT_N_DAYS:30 also
--    returns every assignment that already lapsed.
SELECT Id, Assignee.Name, Assignee.Username, PermissionSet.Name,
       PermissionSetGroup.DeveloperName, ExpirationDate, IsActive
FROM PermissionSetAssignment
WHERE IsActive = true
  AND ExpirationDate != NULL
  AND ExpirationDate <= NEXT_N_DAYS:30
ORDER BY ExpirationDate ASC

-- 2. Standing privilege: elevation permission sets assigned with NO end date.
SELECT Id, Assignee.Name, PermissionSet.Name, ExpirationDate
FROM PermissionSetAssignment
WHERE ExpirationDate = NULL
  AND PermissionSet.Name LIKE 'PS_Temp_%'

-- 3. Policy-revoked rows, which are NOT deleted and NOT returned without ALL ROWS.
SELECT Id, ExpirationDate, Assignee.Name, PermissionSet.Name
FROM PermissionSetAssignment
WHERE IsRevoked = true ALL ROWS

Query 2 is the one that finds the failure. Every org that adopts time-boxing accumulates elevation permission sets assigned without a date, because the API path makes ExpirationDate optional and nillable and nothing rejects the insert.


Decision Guidance

SituationRecommended approachReason
One person needs one capability for a fixed windowSingle-purpose permission set + ExpirationDateNarrow grant means expiry actually revokes something
A whole project role is temporaryPermission set group assignment + ExpirationDateOne row expires the entire aggregate grant
Access should end when a user attribute changes, not on a dateUser access policy with a Revoke actionPolicy actions have no date; criteria are the trigger. See admin/user-access-policies
Access must end the moment the session doesSession-based permission setTighter than any wall-clock date. Note: session-based permission sets included in a permission set group do not require activation for users assigned to the group
The elevation depends on a permission set licenseNo expiry exists — build a manual removal stepPermissionSetLicenseAssign has no ExpirationDate and no update()
The permission is also on the profileFix the profile first, then time-boxExpiry cannot remove a grant it does not own
An engagement is extendedupdate the ExpirationDate fieldThe field is updateable; delete-and-insert is only needed for Create-only fields
Reporting must show elevations that already endedFilter IsActive = false and add ALL ROWSThe state, not the row's existence, is what changes. ALL ROWS is correct whether or not lapsed rows are hidden — see references/gotchas.md Gotcha 3
You are tempted to write a nightly job that unassignsUse ExpirationDateA scheduled job fails silently and leaves standing privilege behind — the exact outcome the control exists to prevent

Recommended Workflow

Step-by-step instructions for an AI agent or practitioner working on this task:

  1. Confirm the org can express an expiry. Check UserManagementSettings for psaExpirationUIEnabled. If it is false and the admin will work through Setup, turn it on first — the metadata snippet above is deployable. Also record whether userAccessPoliciesEnabled is on, because it changes which fields exist on the assignment.
  2. Locate every grant of the target permission. Enumerate the assignee's profile, all assigned permission sets, and all assigned permission set groups. If the elevated permission appears more than once, either narrow the grant or accept that the expiry is decorative and say so out loud.
  3. Choose the unit of expiry. A single-purpose permission set for an individual capability; a permission set group only when the entire group is the temporary grant. Never attach an expiry to a permission set that also carries the user's day-to-day access.
  4. Set ExpirationDate as an explicit instant. The field is a dateTime. Through the API, write a full timestamp so the cutoff is unambiguous; through Setup, confirm what clock time and time zone the org's assignment screen applies before you promise an auditor a cutoff time. Do not assume the two paths resolve to the same moment.
  5. Record the decision outside the platform record. The assignment stores a date, not an approver or a justification. Capture both in the request ticket or in the workbook produced by templates/permission-set-expiration-template.md.
  6. Verify the expiry landed and schedule the review. Re-query the assignment and confirm ExpirationDate is populated. Run the three access-review queries above and confirm the new row appears in query 1 and not in query 2. Read references/gotchas.md before writing any reporting that assumes an expired assignment disappears.
  7. Run the checker script — python3 skills/admin/permission-set-expiration/scripts/check_permission_set_expiration.py --plan <plan.json> to validate the assignment plan's field shape before anyone loads it.

Review Checklist

Run through these before marking a time-boxed elevation complete:

  • psaExpirationUIEnabled state confirmed in the target org and recorded in the change ticket
  • The elevated permission is granted by exactly one assignment — profile and all other permission sets checked
  • The expiring assignment does not also carry the user's normal job access
  • ExpirationDate is populated, and the instant is written down in a stated time zone
  • The approver and business justification are recorded outside the assignment row
  • The elevation permission set is named for the elevation, not for the individual
  • The access-review queries return the new assignment in the "expiring soon" set and nothing unexpected in the "no end date" set
  • Reporting that counts assignments filters on IsActive, uses ALL ROWS when hunting lapsed rows, and covers IsRevoked separately if user access policies are enabled
  • No scheduled Flow or Apex job was created to duplicate what ExpirationDate already does
  • Setup Audit Trail is being exported on a cadence shorter than 180 days, since setup entity records are deleted after that

Salesforce-Specific Gotchas

Non-obvious platform behaviours that cause real production problems. references/gotchas.md carries ten of these in full, with the mechanism and the recovery; items 1-4 and 6 below summarise five of them, and item 5 is stated in full here.

  1. The Setup control is off by default — psaExpirationUIEnabled defaults to false, so an admin can work through the assignment screens and never see an expiration option while the API field has existed since API version 52.0.
  2. The two Setup paths are not equivalent — the Security Guide's expiration step appears in the permission-set-side Manage Assignments → Add Assignments flow, not in the user-record-side Edit Assignments procedure. Admins who always start from the user record will not find it.
  3. Liveness is a state, not the row's existence — IsActive is the platform-owned, read-only flag that carries it. Any report or integration that treats "an assignment row exists" as "the user has access" reads a lapsed elevation as live.
  4. Setup Audit Trail still labels the expiration-date entry beta — in the Summer '26 Security Guide the tracked change reads "Permission set (or group) changes to the assignment expiration date (beta)". Confirm the entry appears in your org before relying on it as audit evidence.
  5. The bulk Setup flow caps removal at 1,000 users at a time — relevant when an expiry experiment has to be unwound by hand across a large population.
  6. Guest, integration, and system users are not in the Manage Assignments UI — "Certain types of users, such as guest, Self-Service, integration, and system users, aren't available in the Manage Assignments page. To view or manage these users, use the PermissionSetAssignment API object." Time-boxing an integration user's elevation is an API-only operation.

Output Artifacts

ArtifactDescription
Time-boxed assignment planPermission set or group, assignee, ExpirationDate instant, approver, justification, review date
Access-review query setThe three SOQL queries covering expiring-soon, no-end-date, and policy-revoked populations
UserManagement.settings-meta.xmlDeployable settings file enabling psaExpirationUIEnabled
Renewal procedureSingle-field update on ExpirationDate, with the delete-and-insert cases called out
Checker script outputField-shape validation of the assignment plan before it is loaded

Related Skills

  • admin/permission-set-group-composition — decides what goes inside a permission set group; read it before choosing a group as the unit of expiry
  • security/permission-set-groups-and-muting — muting mechanics; a muting permission set is a group component and can never carry an expiry
  • admin/user-access-policies — criteria-driven grant and revoke; the automated assignment path that has no date attribute
  • admin/permission-set-architecture — how to shape a single-purpose elevation permission set narrow enough that expiring it means something
  • security/privileged-access-management — the surrounding control: approval, break-glass, session-based alternatives, and the audit trail
  • admin/user-management — deactivation and offboarding, which is the other half of not leaving standing access behind

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use this skill when writing test-first, behavior-driven acceptance criteria in Given/When/Then format for a Salesforce user story. Covers happy path, edge cases, negative paths, permission boundaries, and data-state preconditions so the AC block can drive UAT scripts and Apex test design downstream. Trigger keywords: given when then, gherkin, behavior driven AC, test first acceptance criteria, scenario outline, BDD acceptance criteria. NOT for the user-story format itself (use admin/user-story-writing-for-salesforce). NOT for UAT script writing (use admin/uat-test-case-design). NOT for Apex test method generation (use agents/test-class-generator). NOT for high-level UAT planning (use admin/uat-and-acceptance-criteria). More triggers: acceptance criteria record, ac_id, req_id on a criterion, artefact under test, negative case for a rule-type requirement, no-match fall-through criterion, oracle for a Then clause, test type apex flow manual, criteria linter, check_ac_format.py, --manifest-dir.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Task and Event objects: polymorphic WhatId/WhoId, Activity object model, ActivityHistory vs OpenActivity, activity timeline customization, bulk task creation, Einstein Activity Capture boundaries. NOT for turning on EAC or calendar sync — use admin/einstein-activity-capture-setup. NOT for Email-to-Case — use admin/case-management-setup. Covers ActivitiesSettings metadata, enableActivities, shared Task/Event custom fields and FLS, TaskStatus value sets, Shared Activities and TaskRelation/EventRelation, recurring and child activities, and partial-success bulk DML on Task.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Design Invocable Apex actions that return deterministic, agent-friendly errors instead of surfacing raw exceptions to the LLM. NOT for authoring the action itself — @InvocableMethod schema, labels, security context — use agentforce/custom-agent-actions-apex. NOT for the Apex tests that force each error branch — use agentforce/agent-action-unit-tests.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Use when designing how an Agentforce agent extracts structured input parameters (slots) from a user's natural-language utterance to invoke an action. Triggers: 'agent extract date from user', 'agent action argument extraction', 'agentforce slot filling', 'agent invocable input mapping', 'agent fails to fill required parameter'. NOT for action authoring (use agentforce/agent-actions) or for prompt-template variable binding (use agentforce/prompt-builder-templates).

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Apex test patterns for @InvocableMethod agent actions: per-reason-code branch coverage, bulk safety, callout mocking, deterministic assertions. NOT for testing whether the agent routes to the right topic or answers well — use agentforce/agent-testing-and-evaluation. NOT for designing the error envelope the tests assert on — use agentforce/agent-action-error-handling.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

Designing or reviewing Agentforce actions: Flow actions, Apex invocable actions, prompt-template actions, action naming, input and output contracts, confirmation requirements, and safe error behavior. Triggers: 'agent actions', 'build an agent that can look up an order', 'make the agent able to check order status', 'give the agent a capability', 'agent that can retrieve a record', 'flow action for agent', 'agent invocable action', 'action schema design'. NOT for writing the Apex class itself — use agentforce/custom-agent-actions-apex. NOT for the reason_code error envelope that stops an agent retry loop — use agentforce/agent-action-error-handling.

日本語の概要は準備中です。原文の説明を表示しています。

PranavNagrecha/AwesomeSalesforceSkills192026年10月4日 更新

PranavNagrecha のスキルをすべて見る

このスキルの問題を報告する