agentsclimarketplace

Owasp asvs

Skill siva01c/claude-plugins/security-tools/skills/owasp-asvs

Claude Code plugin marketplace: Drupal development, DDEV, Docker, CI/CD, git workflows, and OWASP ASVS security

Install
npx -y skills add siva01c/claude-plugins --skill owasp-asvs

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 17 stars17 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

OWASP ASVS v5.0 security verification skill with Drupal 11 mappings. Use when performing security code reviews, implementing security requirements, or verifying security compliance in Drupal projects.

SKILL.md

15.9 KB, as published. Nobody here has run it

ASVS Security Verification Skill

Reference: OWASP ASVS v5.0.0 — cross-reference IDs against https://github.com/OWASP/ASVS for the authoritative source.

When This Activates

  • Performing security code reviews
  • Implementing security features
  • Responding to security review findings
  • Security compliance verification

ASVS Requirements Checklist

V1 — Encoding and Sanitization (Critical)

IDRequirementDrupal Validation
V1.1.1Output encoding for HTTP/HTML contextTwig auto-escape or #plain_text
V1.1.2Dynamic URL building with encodingUrl::fromRoute(), never string concat
V1.1.3JavaScript/JSON encodingJson::encode() before output
V1.1.4SQL injection preventionParameterized queries only
V1.1.5OS command injection preventionAvoid shell_exec, exec, system
V1.1.6LDAP injection preventionUse parameterized LDAP queries
V1.1.7Regex metacharacter escapingpreg_quote() on user input
V1.2.1HTML sanitizationXss::filter() or Xss::filterAdmin()
V1.2.2No eval() with user inputAvoid eval(), use Json::decode()
V1.2.3SSRF preventionValidate URLs against explicit allowlist

V2 — Validation

IDRequirementDrupal Validation
V2.1.1Input validation at serverValidate all user input server-side
V2.1.2Server-side validation onlyNever trust client-only validation
V2.2.1Positive validation (allowlist)Use \Drupal\Component\Utility\Html::escape() for plain text
V2.3.1Anti-automation / rate limitingFlood control or custom rate limit service

V3 — Web Frontend Security

IDRequirementDrupal Validation
V3.1.1Cookie Secure attributeini_set('session.cookie_secure', TRUE)
V3.1.2Cookie SameSite attributesession.cookie_samesite = Lax in settings.php
V3.1.3Cookie HttpOnlysession.cookie_httponly = TRUE
V3.2.1HSTS headerStrict-Transport-Security response header
V3.2.2CORS allowlistValidate Origin; never use wildcard
V3.2.3CSP headerContent-Security-Policy response header
V3.2.4X-Content-Type-Optionsnosniff
V3.2.5Referrer-PolicySet appropriate policy
V3.2.6frame-ancestorsPrevent clickjacking via CSP or X-Frame-Options
V3.3.1CSRF protectionForm API tokens (automatic) or CsrfTokenGenerator for custom endpoints

V4 — API and Web Service

IDRequirementDrupal Validation
V4.1.1Content-Type headerSet correct Content-Type on all responses
V4.1.2HTTP method restrictionsrouting.yml methods: key
V4.1.3HTTP request smugglingProper header parsing; avoid custom HTTP parsing
V4.2.1Authentication on every API endpoint_permission or _custom_access in routing

V5 — File Handling

IDRequirementDrupal Validation
V5.1.1File size limitsupload_max_filesize, managed file field limits
V5.1.2File type validationfile_validate_extensions() + MIME check
V5.2.1No file executionNever serve uploads from a web-accessible path as PHP
V5.2.2Path traversal preventionUse FileSystem::realpath(), validate against allowed dirs

V6 — Authentication

IDRequirementDrupal Validation
V6.1.1Password minimum lengthMinimum 12 chars (Drupal default: 8 — raise it)
V6.1.2Breached password checkIntegrate HaveIBeenPwned API or password_policy module
V6.1.3No restrictive composition rulesAllow all printable characters
V6.1.4Long password supportAllow 64+ characters
V6.2.1Multi-factor authenticationtfa module or SAML/OIDC with MFA
V6.3.1Secrets from secure sourcegetenv() or Key module — never config.get()
V6.3.2No production secret fallbackThrow exception if secret missing in prod

V7 — Session Management

IDRequirementDrupal Validation
V7.1.1Session identifier lengthMinimum 128 bits of entropy
V7.1.2Session identifier randomnessCryptographically random (Drupal core handles this)
V7.1.3Session inactivity timeoutConfigure in settings.php
V7.1.4Session termination on logoutsession_destroy() via AccountInterface::logout()
V7.2.1New session on privilege change\Drupal::service('session')->migrate() on role change
V7.2.2Session token not in URLsEnsure session.use_only_cookies = TRUE

V8 — Authorization

IDRequirementDrupal Validation
V8.1.1Least privilegeGrant minimal roles/permissions
V8.1.2Role-based access controluser.permissions + AccessResult
V8.1.3Access control on every request_permission, _entity_access, or _custom_access
V8.2.1No user/role enumerationReturn identical responses for missing vs forbidden
V8.2.2Sensitive data access loggingWatchdog or event subscriber
V8.3.1Object-level authorizationCheck entity ownership, not just route access

V9 — Communication (TLS/Transport)

IDRequirementDrupal Validation
V9.1.1TLS required for all trafficEnforce HTTPS; redirect HTTP → HTTPS
V9.1.2TLS certificate validityValid cert, no self-signed in production
V9.1.3TLS 1.2+ onlyDisable TLS 1.0/1.1 at server level
V9.2.1No mixed contentServe all assets over HTTPS
V9.3.1Outbound TLS verificationVerify peer cert in \GuzzleHttp\Client calls

V10 — Malicious Code / Supply Chain

IDRequirementDrupal Validation
V10.1.1Dependency integritycomposer.lock committed; use composer audit
V10.1.2No unused dependenciesRegularly prune composer.json
V10.2.1Code review for third-party modulesReview contrib module code before enabling
V10.3.1Integrity verificationUse drupal/core-recommended and check module checksums

V11 — Business Logic

IDRequirementDrupal Validation
V11.1.1Sequential step enforcementEnforce workflow states server-side
V11.1.2Business logic limitsRate limits per user/IP per operation
V11.1.3Transaction handlingUse \Drupal\Core\Database\Connection::startTransaction()

V12 — Configuration

IDRequirementDrupal Validation
V12.1.1Debug mode disabled$settings['twig_debug'] = FALSE in prod
V12.1.2Verbose errors suppressederror_reporting(0) in prod; use logging
V12.1.3Security headers presentHSTS, CSP, X-Content-Type-Options
V12.2.1Remove unused modulesUninstall and remove unused contrib
V12.3.1Protect sensitive config files.htaccess on sites/default/, settings.php read-only

V13 — Data Protection

IDRequirementDrupal Validation
V13.1.1Sensitive data not loggedStrip PII from watchdog/log entries
V13.1.2Sensitive data not in URLsNever pass tokens or PII as query parameters
V13.2.1Data retention policyDefine and enforce data retention in code
V13.3.1Alert on anomaliesEvent subscriber for suspicious patterns

V14 — Cryptography

IDRequirementDrupal Validation
V14.1.1Approved algorithms onlyAES-256, SHA-256+; no MD5/SHA-1 for security
V14.2.1Secure random number generation\Drupal\Component\Utility\Crypt::randomBytes()
V14.3.1Passwords hashed with strong algorithmDrupal's PhpassHashedPassword (or stronger via password_compat)
V14.4.1Secrets not in source codeKey module or environment variables
V14.5.1No hardcoded crypto keysUse Key module; rotate keys on breach

Critical Security Patterns for Drupal

SQL Injection (V1.1.4)

// VULNERABLE
$query = "SELECT * FROM users WHERE name = '" . $name . "'";

// SAFE — Drupal Query Builder
$result = $this->connection->select('users', 'u')
  ->fields('u')
  ->condition('name', $name)
  ->execute();

// SAFE — Placeholder
$result = $this->connection->query(
  'SELECT * FROM {users} WHERE name = :name',
  [':name' => $name]
);

XSS Prevention (V1.2.1)

// VULNERABLE
['#markup' => $user_input]

// SAFE — plain text
['#plain_text' => $user_input]

// SAFE — Twig auto-escape (always prefer)
{{ variable }}

// SAFE — Admin HTML (trusted editors only)
use Drupal\Component\Utility\Xss;
$safe = Xss::filterAdmin($html);

// SAFE — plain text in arbitrary context
use Drupal\Component\Utility\Html;
$safe = Html::escape($user_input);

CORS Configuration (V3.2.2)

// VULNERABLE — wildcard
$response->headers->set('Access-Control-Allow-Origin', '*');

// SAFE — explicit allowlist; only add Credentials header when the
// endpoint actually requires cookie/auth-header sharing
$allowed = $config->get('allowed_origins') ?: [];
$origin = $request->headers->get('Origin', '');
if ($origin && in_array($origin, $allowed, TRUE)) {
  $response->headers->set('Access-Control-Allow-Origin', $origin);
  $response->headers->set('Vary', 'Origin');
  // Only add if this endpoint specifically requires credentialed requests:
  // $response->headers->set('Access-Control-Allow-Credentials', 'true');
}

Authorization — Entity Access (V8.1.3, V8.3.1)

// VULNERABLE — route access only, no object-level check
$node = Node::load($nid);
return $node->get('field_secret')->value;

// SAFE — always check entity access
$node = Node::load($nid);
if (!$node || !$node->access('view')) {
  throw new AccessDeniedHttpException();
}
return $node->get('field_secret')->value;

// SAFE — AccessResult in access checker service
use Drupal\Core\Access\AccessResult;
public function access(AccountInterface $account): AccessResult {
  return AccessResult::allowedIfHasPermission($account, 'view secret content')
    ->cachePerPermissions();
}

CSRF for Custom Endpoints (V3.3.1)

// VULNERABLE — custom route with no CSRF check
public function myAction(Request $request): Response { ... }

// SAFE — Form API handles CSRF automatically; for non-form endpoints:
// In routing.yml add: requirements: { _csrf_token: 'TRUE' }
// Then validate token in controller:
$token = $request->query->get('token');
if (!$this->csrfTokenGenerator->validate($token, 'my-action')) {
  throw new AccessDeniedHttpException();
}

Rate Limiting — Flood Control (V2.3.1)

// Drupal's built-in flood control service
$flood = \Drupal::service('flood');
$identifier = $request->getClientIp();

if (!$flood->isAllowed('my_module.action', $threshold = 10, $window = 3600, $identifier)) {
  throw new TooManyRequestsHttpException(null, 'Rate limit exceeded.');
}

// Register the flood event after processing
$flood->register('my_module.action', $window = 3600, $identifier);

Secret Management (V6.3.1)

// VULNERABLE — stored in config
$secret = $config->get('jwt_secret');

// SAFE — environment variable
$secret = getenv('JWT_SECRET');

// SAFE — Key module (preferred for Drupal)
$key = \Drupal::service('key.repository')->getKey('jwt_secret');
$secret = $key->getKeyValue();

// Always fail hard in production if secret is missing
if ($is_production && empty($secret)) {
  throw new \RuntimeException('JWT secret is required in production.');
}

Security Headers (V3.2.1–V3.2.4)

// In an EventSubscriber on KernelEvents::RESPONSE:
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'self'");
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');

File Upload Validation (V5.1.2)

// SAFE — validate extension and MIME type
$validators = [
  'file_validate_extensions' => ['pdf jpg png'],
  'file_validate_size'       => [10 * 1024 * 1024], // 10 MB
  'file_validate_image_resolution' => ['2000x2000'],
];
$file = file_save_upload('my_field', $validators, 'private://', FILE_EXISTS_RENAME);
if (!$file) {
  // Upload failed validation
}

Cryptographic Operations (V14.2.1, V14.3.1)

// SAFE — cryptographically secure random bytes
use Drupal\Component\Utility\Crypt;
$token = Crypt::randomBytesBase64(32);

// SAFE — password hashing (Drupal core)
$hashed = \Drupal::service('password')->hash($plain);
$valid  = \Drupal::service('password')->check($plain, $hashed);

// UNSAFE — never use for security-sensitive hashing
$bad = md5($input);
$bad = sha1($input);

Quick Verification Checklist

Before any code is committed:

  • V1.1.4 SQL uses parameterized queries (Query Builder or placeholders)
  • V1.2.1 Output uses #plain_text, Html::escape(), or Twig auto-escape
  • V3.2.2 CORS uses explicit allowlist — no *
  • V3.2.3 CSP header defined and restrictive
  • V3.2.1 HSTS enforced for all HTTPS traffic
  • V3.3.1 CSRF tokens present (Form API or _csrf_token routing requirement)
  • V2.3.1 Rate limiting via flood control on sensitive endpoints
  • V6.3.1 Secrets from Key module or environment — not from config.get()
  • V7.1.4 Session destroyed on logout
  • V8.1.3 Entity access checked on every object load (not just route)
  • V9.1.1 All traffic over TLS; HTTP redirects to HTTPS
  • V10.1.1 composer audit passes; composer.lock committed
  • V12.1.1 Debug/verbose error modes disabled in production
  • V14.1.1 No MD5/SHA-1 for security-sensitive operations

Red Flags

PatternASVSRisk
String concat in SQLV1.1.4SQL injection
#markup with user dataV1.2.1XSS
Access-Control-Allow-Origin: *V3.2.2Cross-origin data leakage
eval() with inputV1.2.2Code injection
config.get() for secretsV6.3.1Credential exposure
No rate limiting on auth/formsV2.3.1Brute force / DoS
Missing CSRF tokenV3.3.1CSRF
No entity access checkV8.3.1Broken object-level auth
MD5/SHA-1 for passwords or tokensV14.1.1Weak cryptography
Uploaded files in public:// without type checkV5.1.2Remote code execution
HTTP-only routes in productionV9.1.1Credential interception
Debug mode / verbose errors in prodV12.1.1Information disclosure

Drupal-Specific Module References

ConcernRecommended Module / Core API
Secrets / key storagedrupal/key + drupal/key_asymmetric
Rate limitingCore Flood service or drupal/rate_limiter
MFAdrupal/tfa
Password policydrupal/password_policy
Security headersdrupal/seckit
SAML / OIDC SSOdrupal/samlauth or drupal/openid_connect
File scanningdrupal/clamav
Security auditdrupal/security_review
Dependency auditcomposer audit (built-in)

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.