skills/hack23/homepage/access-control

access-control

SKILL.md

Access Control Skill

Purpose

This skill enforces access control requirements as defined in the Hack23 ISMS Access Control Policy. It ensures that all systems implement proper authentication, authorization, and session management based on the principle of least privilege.

Rules

Principle of Least Privilege

MUST:

  • Grant minimum permissions necessary to perform job functions
  • Default to deny access (allowlist approach)
  • Separate duties for critical functions (no single person has complete control)
  • Regularly review and revoke unnecessary permissions
  • Document permission requirements for each role
  • Implement time-limited access for temporary needs

MUST NOT:

  • Grant administrative privileges by default
  • Share user accounts between multiple people
  • Use shared service accounts without audit trails
  • Grant permanent access when temporary access is sufficient
  • Allow users to escalate their own privileges

Role-Based Access Control (RBAC)

MUST:

  • Define roles based on job functions
  • Assign permissions to roles, not individual users
  • Document role definitions and their permissions
  • Implement role hierarchy where appropriate
  • Enforce separation of duties through role conflicts
  • Review role assignments quarterly

Roles for Hack23 Projects:

  • Public: Unauthenticated users (read-only access to public content)
  • User: Authenticated regular users (basic functionality)
  • Contributor: Code contributors (read/write to repositories)
  • Maintainer: Project maintainers (merge, release, moderate)
  • Administrator: System administrators (full control, user management)
  • Security Officer: Security team (audit logs, security settings)

MUST NOT:

  • Create user-specific permissions (use roles instead)
  • Assign multiple conflicting roles to same user
  • Allow role self-assignment without approval
  • Create overly broad roles that violate least privilege

Authentication Requirements

MUST:

  • Require authentication for all non-public resources
  • Implement multi-factor authentication (MFA) for:
    • Administrative accounts
    • Access to Confidential or Restricted data
    • Remote access to internal systems
    • Privileged operations (deployments, configuration changes)
  • Enforce strong password requirements:
    • Minimum 12 characters
    • At least one uppercase, lowercase, number, symbol
    • No common passwords (check against known breached passwords)
    • No reuse of last 5 passwords
  • Implement account lockout after 5 failed attempts
  • Require password change every 90 days for privileged accounts
  • Terminate sessions after 30 minutes of inactivity
  • Support passwordless authentication (FIDO2/WebAuthn) where possible

MUST NOT:

  • Use basic authentication over unencrypted connections
  • Store passwords in plaintext or reversible encryption
  • Allow anonymous access to sensitive resources
  • Use weak hashing algorithms (MD5, SHA-1) for passwords
  • Implement custom authentication without security review
  • Bypass authentication for "trusted" networks

Authorization Requirements

MUST:

  • Verify authorization at every access point
  • Check permissions server-side (never rely on client-side checks)
  • Validate user permissions for each resource accessed
  • Implement attribute-based access control (ABAC) for complex policies
  • Deny access by default (fail closed)
  • Log all authorization failures
  • Enforce authorization consistently across all interfaces (web, API, CLI)

MUST NOT:

  • Assume authentication implies authorization
  • Use client-side authorization checks as primary control
  • Grant access based solely on URL/endpoint knowledge
  • Skip authorization checks for internal APIs
  • Use security through obscurity (hidden URLs don't require authentication)

Session Management

MUST:

  • Generate cryptographically random session IDs (minimum 128 bits entropy)
  • Set HTTPOnly flag on session cookies (prevent JavaScript access)
  • Set Secure flag on session cookies (HTTPS only)
  • Set SameSite=Strict or Lax on session cookies (CSRF protection)
  • Implement session timeout (30 minutes inactivity, 8 hours absolute)
  • Regenerate session ID after authentication
  • Invalidate sessions on logout
  • Store session data securely (encrypted, integrity-protected)
  • Implement concurrent session limits
  • Provide users visibility into active sessions

MUST NOT:

  • Include session IDs in URLs
  • Use predictable session IDs
  • Transmit session cookies over unencrypted connections
  • Store sensitive data in client-side storage (localStorage, sessionStorage)
  • Allow session fixation attacks
  • Share sessions across different security contexts

API Authentication

MUST:

  • Require authentication for all non-public API endpoints
  • Use OAuth 2.0 or OpenID Connect for third-party integrations
  • Implement API key rotation (at least annually)
  • Rate limit API requests to prevent abuse
  • Validate API tokens on every request
  • Use short-lived access tokens (< 1 hour) with refresh tokens
  • Scope API tokens to minimum necessary permissions

MUST NOT:

  • Include API keys in URLs or logs
  • Use long-lived API keys without rotation
  • Share API keys across multiple applications
  • Transmit API keys over unencrypted connections
  • Store API keys in client-side code

Access Reviews

MUST:

  • Review user access rights quarterly
  • Remove access for terminated employees immediately
  • Review privileged access monthly
  • Document access review results
  • Revoke unused accounts after 90 days of inactivity
  • Audit access control changes

MUST NOT:

  • Skip access reviews for "trusted" users
  • Allow indefinite access without review
  • Ignore orphaned accounts
  • Delay access revocation for terminated users

Examples

Example 1: RBAC Implementation (Node.js/Express)

// Define roles and permissions
const ROLES = {
  PUBLIC: 'public',
  USER: 'user',
  CONTRIBUTOR: 'contributor',
  MAINTAINER: 'maintainer',
  ADMIN: 'administrator'
};

const PERMISSIONS = {
  READ_PUBLIC: 'read:public',
  READ_PRIVATE: 'read:private',
  WRITE_CODE: 'write:code',
  MERGE_PR: 'merge:pr',
  MANAGE_USERS: 'manage:users',
  MANAGE_SECURITY: 'manage:security'
};

// Role to permissions mapping
const rolePermissions = {
  [ROLES.PUBLIC]: [PERMISSIONS.READ_PUBLIC],
  [ROLES.USER]: [PERMISSIONS.READ_PUBLIC, PERMISSIONS.READ_PRIVATE],
  [ROLES.CONTRIBUTOR]: [PERMISSIONS.READ_PUBLIC, PERMISSIONS.READ_PRIVATE, PERMISSIONS.WRITE_CODE],
  [ROLES.MAINTAINER]: [PERMISSIONS.READ_PUBLIC, PERMISSIONS.READ_PRIVATE, PERMISSIONS.WRITE_CODE, PERMISSIONS.MERGE_PR],
  [ROLES.ADMIN]: Object.values(PERMISSIONS) // All permissions
};

// Middleware to check permission
function requirePermission(permission) {
  return (req, res, next) => {
    // Must be authenticated
    if (!req.user) {
      return res.status(401).json({ error: 'Authentication required' });
    }
    
    // Get user's permissions from their role
    const userPermissions = rolePermissions[req.user.role] || [];
    
    // Check if user has required permission
    if (!userPermissions.includes(permission)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    
    next();
  };
}

// Usage in routes
app.get('/api/public/data', requirePermission(PERMISSIONS.READ_PUBLIC), (req, res) => {
  // Public data endpoint
});

app.post('/api/code/commit', requirePermission(PERMISSIONS.WRITE_CODE), (req, res) => {
  // Commit code endpoint
});

app.post('/api/pr/merge', requirePermission(PERMISSIONS.MERGE_PR), (req, res) => {
  // Merge PR endpoint
});

Example 2: Secure Session Management (Express)

const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');

const redisClient = redis.createClient({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT,
  password: process.env.REDIS_PASSWORD
});

// GOOD: Secure session configuration
app.use(session({
  store: new RedisStore({ 
    client: redisClient,
    prefix: 'sess:' 
  }),
  secret: process.env.SESSION_SECRET, // Strong random secret from env
  name: 'sid', // Don't use default name
  resave: false,
  saveUninitialized: false,
  rolling: true, // Reset expiry on activity
  cookie: {
    secure: true,        // HTTPS only
    httpOnly: true,      // No JavaScript access
    maxAge: 1800000,     // 30 minutes
    sameSite: 'strict',  // CSRF protection
    domain: 'hack23.com' // Explicit domain
  }
}));

// Session timeout middleware
app.use((req, res, next) => {
  if (req.session) {
    const now = Date.now();
    const lastActivity = req.session.lastActivity || now;
    const inactivityTimeout = 30 * 60 * 1000; // 30 minutes
    
    if (now - lastActivity > inactivityTimeout) {
      req.session.destroy();
      return res.status(401).json({ error: 'Session expired due to inactivity' });
    }
    
    req.session.lastActivity = now;
  }
  next();
});

// Regenerate session ID after authentication
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  
  const user = await authenticateUser(username, password);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  
  // Regenerate session to prevent fixation
  req.session.regenerate((err) => {
    if (err) {
      return res.status(500).json({ error: 'Session error' });
    }
    
    req.session.userId = user.id;
    req.session.role = user.role;
    req.session.lastActivity = Date.now();
    
    res.json({ success: true, user: { id: user.id, role: user.role } });
  });
});

// Logout
app.post('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).json({ error: 'Logout failed' });
    }
    res.clearCookie('sid');
    res.json({ success: true });
  });
});

Example 3: MFA Implementation (TOTP)

const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

// Enable MFA for user
app.post('/api/user/mfa/enable', requireAuth, async (req, res) => {
  const user = req.user;
  
  // Generate secret
  const secret = speakeasy.generateSecret({
    name: `Hack23 (${user.email})`,
    issuer: 'Hack23'
  });
  
  // Generate QR code
  const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
  
  // Store secret (encrypted) in database
  await db.users.update(user.id, {
    mfa_secret: encrypt(secret.base32),
    mfa_enabled: false // Not enabled until verified
  });
  
  res.json({
    secret: secret.base32,
    qrCode: qrCodeUrl
  });
});

// Verify and activate MFA
app.post('/api/user/mfa/verify', requireAuth, async (req, res) => {
  const { token } = req.body;
  const user = req.user;
  
  const userData = await db.users.findById(user.id);
  const secret = decrypt(userData.mfa_secret);
  
  // Verify token
  const verified = speakeasy.totp.verify({
    secret: secret,
    encoding: 'base32',
    token: token,
    window: 1 // Allow 1 step before/after for clock skew
  });
  
  if (!verified) {
    return res.status(400).json({ error: 'Invalid verification code' });
  }
  
  // Enable MFA
  await db.users.update(user.id, {
    mfa_enabled: true
  });
  
  res.json({ success: true });
});

// Login with MFA
app.post('/api/login', async (req, res) => {
  const { username, password, mfa_token } = req.body;
  
  // Verify username/password
  const user = await authenticateUser(username, password);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  
  // Check if MFA is enabled
  if (user.mfa_enabled) {
    if (!mfa_token) {
      return res.status(401).json({ 
        error: 'MFA token required',
        mfa_required: true 
      });
    }
    
    // Verify MFA token
    const secret = decrypt(user.mfa_secret);
    const verified = speakeasy.totp.verify({
      secret: secret,
      encoding: 'base32',
      token: mfa_token,
      window: 1
    });
    
    if (!verified) {
      return res.status(401).json({ error: 'Invalid MFA token' });
    }
  }
  
  // Create session
  req.session.regenerate((err) => {
    if (err) {
      return res.status(500).json({ error: 'Session error' });
    }
    
    req.session.userId = user.id;
    req.session.role = user.role;
    
    res.json({ success: true, user: { id: user.id, role: user.role } });
  });
});

Example 4: API Key Management

const crypto = require('crypto');

// Generate API key
function generateApiKey() {
  // Generate 32 bytes (256 bits) of random data
  const key = crypto.randomBytes(32).toString('base64');
  return key;
}

// Hash API key for storage
function hashApiKey(apiKey) {
  return crypto.createHash('sha256').update(apiKey).digest('hex');
}

// Create API key with scopes
app.post('/api/keys', requireAuth, async (req, res) => {
  const { name, scopes, expiresInDays } = req.body;
  
  // Generate key
  const apiKey = generateApiKey();
  const hashedKey = hashApiKey(apiKey);
  
  // Calculate expiry
  const expiresAt = new Date();
  expiresAt.setDate(expiresAt.getDate() + (expiresInDays || 365));
  
  // Store hashed key
  await db.apiKeys.create({
    user_id: req.user.id,
    name: name,
    key_hash: hashedKey,
    scopes: scopes,
    expires_at: expiresAt,
    created_at: new Date()
  });
  
  // Return plaintext key ONCE (user must save it)
  res.json({
    apiKey: apiKey,
    name: name,
    scopes: scopes,
    expiresAt: expiresAt,
    warning: 'Save this key now. It will not be shown again.'
  });
});

// Validate API key middleware
async function validateApiKey(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'API key required' });
  }
  
  const apiKey = authHeader.substring(7);
  const hashedKey = hashApiKey(apiKey);
  
  // Lookup key
  const keyRecord = await db.apiKeys.findByHash(hashedKey);
  
  if (!keyRecord) {
    return res.status(401).json({ error: 'Invalid API key' });
  }
  
  // Check expiry
  if (new Date() > keyRecord.expires_at) {
    return res.status(401).json({ error: 'API key expired' });
  }
  
  // Check if key is active
  if (!keyRecord.active) {
    return res.status(401).json({ error: 'API key revoked' });
  }
  
  // Update last used
  await db.apiKeys.updateLastUsed(keyRecord.id);
  
  // Attach user and scopes to request
  req.apiKey = keyRecord;
  req.user = await db.users.findById(keyRecord.user_id);
  
  next();
}

// Usage
app.get('/api/data', validateApiKey, requireScope('read:data'), (req, res) => {
  // API endpoint
});

Related ISMS Policies

Related Documentation

Compliance Mapping

ISO 27001:2022

  • A.5.15 Access control
  • A.5.16 Identity management
  • A.5.17 Authentication information
  • A.5.18 Access rights

NIST Cybersecurity Framework

  • PR.AC-1: Identities and credentials are issued, managed, verified, revoked
  • PR.AC-4: Access permissions and authorizations are managed
  • PR.AC-6: Identities are proofed and bound to credentials
  • PR.AC-7: Users, devices, and assets are authenticated

CIS Controls

  • Control 5: Account Management
  • Control 6: Access Control Management
Weekly Installs
14
Repository
hack23/homepage
GitHub Stars
5
First Seen
14 days ago
Installed on
opencode14
gemini-cli14
github-copilot14
amp14
cline14
codex14