audit-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited audit-security (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A static marketing site has a significantly smaller attack surface than a web application with a backend. There is no database to inject, no authentication to break, no sessions to hijack. However, specific vulnerabilities exist in this context — a public site on shared hosting with external webhooks — that must be verified before launch.
Scope: static marketing site + interactive demo with external webhook integration.
Out of scope: backend platform vulnerabilities (user authentication, data isolation, role permissions). That requires a separate audit of the production system.
| Level | Description | Action |
|---|---|---|
| Critical | Sensitive data exposure or exploitable attack vector | Fix before launch |
| High | Significant security weakness without obvious immediate exploit | Fix before launch |
| Medium | Suboptimal configuration that reduces security posture | Fix in first week |
| Low | Best practice improvement with minor impact | Fix when convenient |
HTTP security headers are the first line of defense for a static site. Configured in .htaccess at site root on cPanel. No backend required — these are Apache server instructions.
<IfModule mod_headers.c>
# HSTS — forces HTTPS, prevents downgrade attacks
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Prevents clickjacking — blocks site from loading in iframe
Header always set X-Frame-Options "DENY"
# Prevents MIME sniffing — browser respects declared Content-Type
Header always set X-Content-Type-Options "nosniff"
# Controls information sent in Referer header
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Disables browser APIs not needed by the site
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
# Content Security Policy — defines which resources the browser can load
# Adjust domains to match actual resources used (fonts, analytics, webhooks)
Header always set Content-Security-Policy "\
default-src 'self'; \
script-src 'self' 'unsafe-inline'; \
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; \
font-src 'self' https://fonts.gstatic.com; \
img-src 'self' data: https:; \
connect-src 'self' https://[webhook-domain]; \
frame-ancestors 'none'; \
base-uri 'self'; \
form-action 'self' https://[webhook-domain]"
</IfModule>CSP notes:
'unsafe-inline' in script-src and style-src is needed if JS/CSS is inline. For better security, move all JS to external files and remove 'unsafe-inline' from script-srcscript-srcconnect-src must include the exact webhook URL — not a wildcardWhat to check:
.htaccess exists at site rootStrict-Transport-Security set with minimum 1 yearX-Frame-Options set to DENY or SAMEORIGINX-Content-Type-Options set to nosniffReferrer-Policy configuredPermissions-Policy disables unused APIsContent-Security-Policy configured — does not use default-src *connect-src includes exact webhook domain, not wildcardRewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]What to check:
A cPanel shared server makes all files inside public_html publicly accessible by default. Sensitive files must be explicitly protected.
# Block access to configuration and sensitive files
<FilesMatch "\.(env|json|md|log|sh|py|sql|bak|backup|zip|tar|gz)$">
Order allow,deny
Deny from all
</FilesMatch>
# Block hidden files (starting with dot)
<FilesMatch "^\.">
Order allow,deny
Deny from all
</FilesMatch>What to check:
.env files inside public_htmlconfig.json with sensitive data in public_html.zip, .tar.gz, backup_, _old) in public_html.htaccess blocks access to sensitive extensionshttps://[domain]/.env must return 403Options -IndexesWithout this, Apache shows a file listing for directories without index.html, exposing site structure.
What to check:
Options -Indexes in .htaccesshttps://[domain]/assets/ returns 403 or redirect, not a file listing| Type | Permission | Octal |
|---|---|---|
| HTML, CSS, JS, images | Owner read/write, others read-only | 644 |
.htaccess | Owner read/write, others read-only | 644 |
| Directories | Owner rwx, others rx | 755 |
| Sensitive config files | Owner read/write only | 600 |
What to check:
.htaccess has 644, not 777All JavaScript in a static site is publicly visible. Any visitor can view source or open DevTools and read the complete code.
// ❌ CRITICAL — visible to any visitor
const OPENAI_API_KEY = 'sk-proj-abc123...'
const WEBHOOK_SECRET = 'my-secret-token-xyz'
// ✅ CORRECT — client only calls the public webhook
// The webhook acts as proxy and holds server-side secrets
const WEBHOOK_URL = 'https://[webhook-domain]/webhook/demo'Correct architecture: Client JS calls webhook with user query → webhook server calls APIs with server-side keys → webhook returns response to client. The client never sees API keys.
What to check:
.js and HTML files: no strings starting with sk-, Bearer , api_key, apikey, secret, token followed by a literal valueWhat to check:
What to check:
TODO, FIXME, HACK, password, credential, key, token followed by valuesIf the project does not include an interactive demo with webhook integration, skip this section.
The demo receives user input, calls an external webhook, and inserts the response into the DOM. These three operations have specific attack vectors.
// ❌ CRITICAL — XSS if response contains or HTML tags
container.innerHTML = data.response
// ✅ For plain text responses
container.textContent = data.response
// ✅ For formatted responses — sanitize first
container.innerHTML = DOMPurify.sanitize(data.response, {
ALLOWED_TAGS: ['b', 'strong', 'em', 'p', 'br', 'ul', 'li'],
ALLOWED_ATTR: []
})What to check:
innerHTML directly assigns a webhook/fetch responseinnerHTML is used, response passes through sanitization firsttextContent, not innerHTML// ✅ Validate before sending to webhook
async function sendQuery() {
const query = document.getElementById('demo-input').value.trim()
if (!query) return showError('Please enter a question')
if (query.length > 500) return showError('Question too long (max 500 characters)')
if (!/\w/.test(query)) return showError('Please enter a valid question')
// Proceed with fetch...
}What to check:
maxlength + JS validation)Without rate limiting, a user or bot can make hundreds of webhook calls, generating API costs.
let lastRequestTime = 0
const MIN_REQUEST_INTERVAL = 3000
async function sendQuery() {
const now = Date.now()
if (now - lastRequestTime < MIN_REQUEST_INTERVAL) {
return showError('Please wait a moment before searching again')
}
lastRequestTime = now
// Continue with fetch...
}Note: Client-side rate limiting is a courtesy measure, not real security — an attacker can bypass it. Real rate limiting must be configured on the webhook server.
CORS controls which domains can call the webhook. Without correct configuration, any website could call the project's webhook. Correct — specific origin Access-Control-Allow-Origin: https://[project-domain] Access-Control-Allow-Methods: POST, OPTIONS Access-Control-Allow-Headers: Content-Type NEVER use wildcard Access-Control-Allow-Origin: *
What to check:
Access-Control-Allow-Origin — not *Hotlinking occurs when other sites link directly to the project's images or resources, using its bandwidth.
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https://(www\.)?[project-domain] [NC]
RewriteRule \.(jpg|jpeg|png|gif|webp|svg|mp4|pdf)$ - [F,NC]What to check:
// ❌ Exposes internal information
catch (error) {
showError(`Error: ${error.message}`)
}
// ✅ Generic message for user, technical log in console
catch (error) {
console.error('[Demo] Query processing error:', error)
showError('Unable to process your query right now. Please try again.')
}What to check:
console.error (useful for debugging) but not shown in UISecurity Audit — [Project Name]
Date: [Date]
Summary
Critical issues: X
High priority: X
Medium priority: X
Low priority: X
Security headers grade: [A+ / A / B / C / D / F] (via securityheaders.com)
Overall security posture: [Solid / Needs work / Critical vulnerabilities]
Critical Issues
[Issue title]
Category: [Headers / File protection / Information exposure / XSS / CORS / Permissions]
File: [filename and line]
Issue: [What is exposed or vulnerable]
Fix: [Specific correction]
High Priority
[Same format]
Medium Priority
[Same format]
Low Priority
[Same format]
Recommended Fix Order
API keys and credentials in client code — immediate data exposure
XSS in DOM insertion — active attack vector
CORS wildcard on webhook — unauthorized access
Security headers — defense in depth
File permissions and access — server hardening
Rate limiting and input validation — abuse preventionFull pre-launch checklist and tools reference: see references/checklist.md~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.