Install
$ agentstack add skill-kurianoff-claude-skills-soc2-policies-soc2-policies ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
SOC 2 policy management dashboard
A persistent dashboard for managing a collection of SOC 2 policy documents. Displays policies as cards with status tracking, enables drill-down into individual policy review (via the policy-review skill), persists all progress across sessions using artifact storage, and exports completed policies as Word documents (via the policy-export skill).
All policy content is HTML throughout the entire pipeline. Templates are stored as HTML. Working copies are HTML. Review state stores HTML. Exports receive HTML. No markdown-to-HTML conversion happens at runtime.
Architecture overview
soc2-policies (this skill)
├── Dashboard UI (card grid, status, progress)
├── HTML templates (references/templates/*.html) — read-only, read on demand
├── Persistent storage (working copies, review state)
└── Hands off to:
├── policy-review skill (per-document review)
└── policy-export skill (Word document generation)
CRITICAL: Lazy initialization — do NOT pre-create all working copies
The dashboard does NOT need working copies of all 17 policies to render. It only needs the hardcoded policy manifest (titles, descriptions, categories) and review status from soc2:review:{id} in persistent storage.
Working copies are created lazily — only when a user first reviews a policy.
Why this matters
Previous implementations tried to read all 17 HTML template files, write them all to persistent storage, then render the dashboard. This caused:
- Multi-step initialization with helper widgets/scripts
- Race conditions between storage writes and dashboard reads
- Confusing "loading" states that broke the UX
The fix: the dashboard renders immediately from hardcoded metadata + storage reads for review status. No template files are read at dashboard launch time.
When working copies ARE created
A working copy is created at drill-down time (Step 3), not at dashboard launch:
- User clicks "Review" on a policy card
- Claude receives the sendPrompt with the policy ID
- Claude reads the ONE template file for that policy from
references/templates/ - Claude applies company name replacement
- Claude saves the HTML working copy to
soc2:working:{policy-id} - Claude hands the HTML to the policy-review skill
On subsequent reviews of the same policy, the working copy already exists in storage — Claude reads it from there instead of the template file.
Step 1: Determine company name and launch
When this skill triggers, Claude must determine the company name before rendering the dashboard (because the dashboard template needs it as an injection variable).
Decision flow
- Check if the company name is already known — from the current
conversation context (user said it, prior message included it, or a POLICY_REVIEW_COMPLETE message mentioned it).
- If known → go directly to Step 2 (render the dashboard).
- If NOT known → render the welcome widget (COPY-AND-INJECT from
references/welcome-widget.md). This widget:
- Shows "Welcome to SOC 2 policy manager"
- Has a text input for company name
- On "Get started", saves the name to
soc2:company-namein storage
and sends a sendPrompt() that triggers Step 2
Claude should NEVER ask for the company name in prose or via askuserinput. Always use the welcome widget. It provides a consistent first-run experience.
After the welcome widget
The welcome widget sends:
Company name set to: Acme Corp
Please render the SOC 2 policy dashboard for Acme Corp.
Claude receives this and renders the dashboard (Step 2) with var COMPANY = "Acme Corp";.
Bundled policy templates (HTML, read-only)
This skill ships with 17 pre-converted HTML templates in references/templates/. They are read-only and only read when a specific policy is drilled into for the first time.
| File | Policy ID | Policy | Category | |------|-----------|--------|----------| | acceptable_use_policy.html | acceptable-use | Acceptable use policy | Governance | | access_control_policy.html | access-control | Access control policy | Access | | asset_management_policy.html | asset-management | Asset management policy | Operations | | business_continuity_and_disaster_recovery_plan.html | business-continuity | Business continuity and disaster recovery plan | Operations | | code_of_conduct.html | code-of-conduct | Code of conduct | HR | | cryptography_policy.html | cryptography | Cryptography policy | Security | | data_management_policy.html | data-management | Data management policy | Data | | human_resources_security_policy.html | hr-security | Human resources security policy | HR | | incident_response_plan.html | incident-response | Incident response plan | Security | | information_security_policy.html | info-security | Information security policy | Security | | information_security_roles_and_responsibilities.html | roles-responsibilities | Information security roles and responsibilities | Governance | | operations_security_policy.html | operations-security | Operations security policy | Operations | | physical_security_policy.html | physical-security | Physical security policy | Security | | removable_media_policy.html | removable-media | Removable media policy | Security | | risk_management_policy.html | risk-management | Risk management policy | Risk | | secure_development_policy.html | secure-development | Secure development policy | Operations | | third_party_management_policy.html | third-party | Third-party management policy | Risk |
Step 2: Build the dashboard widget (COPY-AND-INJECT)
Read references/dashboard-widget.md — it contains the COMPLETE runnable widget code. Claude's job is:
- Read the template file
- Set
var COMPANY = "Actual Company Name";(injection point 1) - Set
var INJECTED = { ... };with any known policy statuses (injection point 2) - Copy the entire widget code into
visualize:show_widgetVERBATIM
Do NOT modify any CSS, HTML, event handlers, button labels, or storage logic. Do NOT add functions, rename classes, or change the layout.
The only two things Claude changes are the COMPANY and INJECTED variables. Everything else is fixed template code that renders identically every time.
Dashboard features
- Card grid with title, category badge, description, status, progress bar
- Top-level stats: readiness %, completed/in-progress/not-started counts
- Category filter buttons
- Per-card actions: Review, Export as Word (completed only), Reset
Persistent storage keys
soc2:company-name— the customer's company namesoc2:manifest— (optional) policy metadata, but the widget hardcodes thissoc2:working:{policy-id}— HTML working copy (created lazily on first review)soc2:review:{policy-id}— review decisions with HTML text per statementsoc2:versions:{policy-id}— version history entriessoc2:audit:{policy-id}— per-policy audit trail
Step 3: Handle drill-down to policy review (LAZY WORKING COPY CREATION)
When the user clicks "Review" on a card, the widget sends a sendPrompt(). Claude receives it and must:
- Check storage for an existing working copy at
soc2:working:{policy-id}. - If found: Use it directly — it may contain edits from a prior review.
- If not found: Read the HTML template file from
references/templates/,
apply company name replacement ([COMPANY NAME] → actual name), and this becomes the working copy. Do NOT save it to storage yet — the policy-review carousel will save it on export.
- Hand off to policy-review: Parse the HTML into sections and statements,
then render the review carousel.
The handoff prompt from the widget looks like:
I want to review the policy: "Incident Response Plan".
Please use the policy-review skill...
Policy ID: incident-response
Company name: Acme Corp
This is a WORKING COPY (HTML) — load from soc2:working:incident-response
in persistent storage. If no working copy exists, read the HTML template
from references/templates/ and apply company name replacement.
Claude reads ONE template file at this point — not all 17. This is the lazy initialization in action.
Step 4: Save review results back to dashboard (DUAL-SOURCE STATE)
When a policy review completes, you'll receive a POLICY_REVIEW_COMPLETE message containing explicit status data:
POLICY_REVIEW_COMPLETE
Policy: Incident Response Plan
Policy ID: incident-response
REVIEW STATUS: completed
STATEMENTS TOTAL: 23
STATEMENTS REVIEWED: 23
...
You MUST use dual-source state when re-rendering the dashboard.
What "dual-source" means
The carousel widget saves review data to storage before sendPrompt(), but writes are async and may not have propagated. The dashboard gets state from:
- Injected state (from the prompt) — Claude hardcodes the known policy
status directly into the widget JavaScript:
var INJECTED = {
"incident-response": { status: "completed", reviewed: 23, total: 23 }
};
- Storage state (for everything else) — Widget reads
soc2:review:{id}
for all 17 policies.
How the widget merges them
async function init() {
// Load from storage
for (var i = 0; i < P.length; i++) {
var r = await SL('soc2:review:' + P[i].id);
if (r) ST[P[i].id] = r;
}
// Injected state overrides storage
Object.keys(INJECTED).forEach(function(id) {
var inj = INJECTED[id];
if (!ST[id] || inj.status === 'completed') {
ST[id] = {
status: inj.status,
statements: Array.from({length: inj.total}, function(_, i) {
return { id: i+1, status: i < inj.reviewed ? 'approved' : 'pending' };
})
};
}
});
loaded = true; render();
}
When to inject
- After POLICYREVIEWCOMPLETE: Always inject the completed policy.
- Mid-session re-open: If conversation history shows reviewed policies,
inject those too.
- First launch / no known state:
var INJECTED = {};
Critical rule
Never render a dashboard that relies solely on storage reads after a review completes. Always inject the just-completed policy's status.
Step 5: Export completed policies as Word documents
When the user requests a Word export, hand off to the policy-export skill.
The export button sends a prompt including the policy ID, company name, and instructions to read the HTML working copy, version history, and audit trail from persistent storage.
The dashboard never generates Word documents itself.
Data format rule (enforced everywhere)
HTML is the only format for policy content in this system.
- Templates in
references/templates/are.htmlfiles. - Working copies in
soc2:working:{id}are HTML strings. - Statement text in
soc2:review:{id}is HTML. - AI rewrites produce HTML.
- Manual edits save as HTML.
- Export receives HTML.
- The widget renders HTML directly via
innerHTML— no parsing at runtime.
If any input arrives as markdown (e.g., user uploads a .md file), convert to HTML once at point of entry, then work exclusively with HTML.
Edge cases
- Returning with no prior state: Dashboard renders all 17 cards as "Not
started" from the hardcoded manifest. No initialization needed.
- Uploading custom policies: Convert to HTML, save as working copy,
add to manifest in storage.
- Re-reviewing a completed policy: Allow it — the working copy retains
prior edits so the user starts from their last approved version.
- Resetting a policy: Delete
soc2:review:{id},soc2:working:{id},
soc2:versions:{id}, and soc2:audit:{id}. Card reverts to "Not started".
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: kurianoff
- Source: kurianoff/claude-skills-soc2-policies
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.