Claude-code-plugins-plus-skills snowflake-data-handling

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/snowflake-pack/skills/snowflake-data-handling" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-snowflake-data-handling && rm -rf "$T"
manifest: plugins/saas-packs/snowflake-pack/skills/snowflake-data-handling/SKILL.md
source content

Snowflake Data Handling

Overview

Implement data governance in Snowflake using column-level masking policies, row access policies, object tagging, and data classification for GDPR/CCPA compliance.

Prerequisites

  • Enterprise Edition or higher (for masking and row access policies)
  • SECURITYADMIN or ACCOUNTADMIN role
  • Understanding of GDPR/CCPA data subject rights

Instructions

Step 1: Data Classification with Tags

-- Create tag taxonomy
CREATE TAG IF NOT EXISTS pii_type
  ALLOWED_VALUES 'email', 'phone', 'ssn', 'name', 'address';

CREATE TAG IF NOT EXISTS data_sensitivity
  ALLOWED_VALUES 'public', 'internal', 'confidential', 'restricted';

-- Apply tags to columns
ALTER TABLE users MODIFY COLUMN email SET TAG pii_type = 'email';
ALTER TABLE users MODIFY COLUMN phone SET TAG pii_type = 'phone';
ALTER TABLE users MODIFY COLUMN name SET TAG pii_type = 'name';
ALTER TABLE users MODIFY COLUMN email SET TAG data_sensitivity = 'confidential';

-- Find all tagged columns
SELECT * FROM TABLE(INFORMATION_SCHEMA.TAG_REFERENCES(
  'users', 'TABLE'
));

-- Discover PII with Snowflake's automatic classification (Enterprise+)
SELECT *
FROM TABLE(
  INFORMATION_SCHEMA.EXTRACT_SEMANTIC_CATEGORIES('users')
);

Step 2: Column-Level Masking Policies

-- Dynamic masking — shows real data to privileged roles, masked to others
CREATE OR REPLACE MASKING POLICY email_mask AS (val STRING)
  RETURNS STRING ->
  CASE
    WHEN CURRENT_ROLE() IN ('DATA_ENGINEER', 'SYSADMIN') THEN val
    WHEN CURRENT_ROLE() = 'DATA_ANALYST' THEN
      REGEXP_REPLACE(val, '.+@', '***@')  -- Show domain only
    ELSE '***MASKED***'
  END;

CREATE OR REPLACE MASKING POLICY phone_mask AS (val STRING)
  RETURNS STRING ->
  CASE
    WHEN CURRENT_ROLE() IN ('DATA_ENGINEER', 'SYSADMIN') THEN val
    ELSE CONCAT('***-***-', RIGHT(val, 4))  -- Show last 4 digits
  END;

CREATE OR REPLACE MASKING POLICY ssn_mask AS (val STRING)
  RETURNS STRING ->
  CASE
    WHEN CURRENT_ROLE() IN ('SYSADMIN') THEN val
    ELSE '***-**-' || RIGHT(val, 4)
  END;

-- Apply masking policies to columns
ALTER TABLE users MODIFY COLUMN email SET MASKING POLICY email_mask;
ALTER TABLE users MODIFY COLUMN phone SET MASKING POLICY phone_mask;

-- Tag-based masking (apply policy to all columns with a tag)
ALTER TAG pii_type SET MASKING POLICY email_mask;
-- Now ALL columns tagged pii_type='email' are automatically masked

Step 3: Row Access Policies

-- Row-level security — users only see their own department's data
CREATE OR REPLACE ROW ACCESS POLICY department_access AS (department_col VARCHAR)
  RETURNS BOOLEAN ->
  CURRENT_ROLE() = 'SYSADMIN'
  OR department_col = CURRENT_ROLE()  -- Role name matches department
  OR EXISTS (
    SELECT 1 FROM access_grants
    WHERE user_name = CURRENT_USER()
      AND department = department_col
  );

-- Apply to table
ALTER TABLE employees ADD ROW ACCESS POLICY department_access ON (department);

-- Verify: analyst role only sees their department
USE ROLE ANALYST_ROLE;
SELECT * FROM employees;  -- Only rows matching their department

Step 4: GDPR Data Subject Rights

-- Right to Access (DSAR): Export all user data
CREATE OR REPLACE PROCEDURE export_user_data(user_email VARCHAR)
  RETURNS TABLE (source VARCHAR, data VARIANT)
  LANGUAGE SQL
AS
$$
  SELECT 'users' AS source, OBJECT_CONSTRUCT(*) AS data
  FROM users WHERE email = user_email
  UNION ALL
  SELECT 'orders', OBJECT_CONSTRUCT(*)
  FROM orders WHERE customer_email = user_email
  UNION ALL
  SELECT 'events', OBJECT_CONSTRUCT(*)
  FROM events WHERE user_email = user_email
$$;

-- Right to Erasure: Delete all user data
CREATE OR REPLACE PROCEDURE delete_user_data(user_email VARCHAR)
  RETURNS VARCHAR
  LANGUAGE SQL
AS
$$
BEGIN
  -- Delete from all tables containing user data
  DELETE FROM events WHERE user_email = :user_email;
  DELETE FROM orders WHERE customer_email = :user_email;
  DELETE FROM users WHERE email = :user_email;

  -- Audit log (must retain for compliance)
  INSERT INTO gdpr_audit_log (action, subject_email, executed_at, executed_by)
  VALUES ('ERASURE', :user_email, CURRENT_TIMESTAMP(), CURRENT_USER());

  RETURN 'Deletion complete for ' || :user_email;
END;
$$;

-- Right to Rectification
UPDATE users SET name = 'New Name' WHERE email = 'user@example.com';
INSERT INTO gdpr_audit_log (action, subject_email, executed_at, executed_by)
VALUES ('RECTIFICATION', 'user@example.com', CURRENT_TIMESTAMP(), CURRENT_USER());

Step 5: Data Retention and Cleanup

-- Automated data retention with tasks
CREATE OR REPLACE TASK enforce_retention
  WAREHOUSE = ADMIN_WH
  SCHEDULE = 'USING CRON 0 2 * * * UTC'  -- 2 AM UTC daily
AS
BEGIN
  -- Delete audit logs older than 7 years
  DELETE FROM audit_logs
  WHERE created_at < DATEADD(years, -7, CURRENT_TIMESTAMP());

  -- Delete session logs older than 90 days
  DELETE FROM session_logs
  WHERE created_at < DATEADD(days, -90, CURRENT_TIMESTAMP());

  -- Anonymize old order data (keep for analytics, remove PII)
  UPDATE orders SET
    customer_email = SHA2(customer_email),
    customer_name = 'ANONYMIZED'
  WHERE order_date < DATEADD(years, -2, CURRENT_DATE())
    AND customer_name != 'ANONYMIZED';
END;

ALTER TASK enforce_retention RESUME;

Step 6: Audit Trail

-- Query access history — who accessed what
SELECT user_name, query_text, start_time,
       direct_objects_accessed
FROM SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY
WHERE start_time >= DATEADD(days, -7, CURRENT_TIMESTAMP())
  AND ARRAY_CONTAINS('USERS'::VARIANT,
    TRANSFORM(direct_objects_accessed, x -> x:objectName))
ORDER BY start_time DESC;

Error Handling

IssueCauseSolution
Masking policy error on queryPolicy function errorTest with
SELECT email_mask('test@test.com')
Row access blocks all rowsPolicy too restrictiveCheck CURRENT_ROLE() logic
Tag not foundWrong scopeEnsure tag is in same or parent schema
GDPR deletion incompleteForeign key dependenciesDelete child records first

Resources

Next Steps

For enterprise RBAC, see

snowflake-enterprise-rbac
.