web-artifacts-builder — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-artifacts-builder (Agent Skill) and scored it 91/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 1 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} is the classic direct prompt-injection phrasing. Placed in a skill body that the agent reads as trusted instructions, it tries to make the agent abandon its prior rules and follow whatever comes next — a full system-prompt override.
ignore/disregard/forget … previous instructions sentence.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.
| Approach | When to Use | Complexity |
|---|---|---|
| Pure HTML/CSS/JS | Simple interactions, no framework needed | Low |
| React + Tailwind (CDN) | Rich UI, components, state management | Medium |
| React + Vite + Bundle | Production app, many dependencies | High |
Best for: calculators, visualizations, simple tools, games.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Budget Calculator</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', system-ui, sans-serif;
background: #0f172a;
color: #e2e8f0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.card {
background: #1e293b;
border: 1px solid #334155;
border-radius: 1rem;
padding: 2rem;
width: 100%;
max-width: 480px;
}
h1 {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 1.5rem;
color: #f8fafc;
}
.field {
margin-bottom: 1rem;
}
label {
display: block;
font-size: 0.875rem;
color: #94a3b8;
margin-bottom: 0.4rem;
}
input {
width: 100%;
padding: 0.6rem 0.8rem;
background: #0f172a;
border: 1px solid #475569;
border-radius: 0.5rem;
color: #f8fafc;
font-size: 1rem;
outline: none;
transition: border-color 0.2s;
}
input:focus { border-color: #6366f1; }
button {
width: 100%;
padding: 0.75rem;
background: #6366f1;
color: white;
border: none;
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
margin-top: 1rem;
transition: background 0.2s;
}
button:hover { background: #4f46e5; }
.result {
margin-top: 1.5rem;
padding: 1rem;
background: #0f172a;
border-radius: 0.5rem;
border-left: 4px solid #6366f1;
display: none;
}
.result.show { display: block; }
.result-label { color: #94a3b8; font-size: 0.875rem; }
.result-value { color: #f8fafc; font-size: 1.5rem; font-weight: 700; }
</style>
</head>
<body>
<div class="card">
<h1>💰 Budget Calculator</h1>
<div class="field">
<label>Monthly Income ($)</label>
<input type="number" id="income" placeholder="5000">
</div>
<div class="field">
<label>Monthly Expenses ($)</label>
<input type="number" id="expenses" placeholder="3500">
</div>
<button onclick="calculate()">Calculate Savings</button>
<div class="result" id="result">
<div class="result-label">Monthly Savings</div>
<div class="result-value" id="savings-value"></div>
<div class="result-label" style="margin-top: 0.5rem" id="savings-pct"></div>
</div>
</div>
<script>
function calculate() {
const income = parseFloat(document.getElementById('income').value) || 0
const expenses = parseFloat(document.getElementById('expenses').value) || 0
const savings = income - expenses
const pct = income > 0 ? ((savings / income) * 100).toFixed(1) : 0
const result = document.getElementById('result')
document.getElementById('savings-value').textContent = `$${savings.toLocaleString()}`
document.getElementById('savings-pct').textContent = `${pct}% of income`
result.classList.add('show')
}
</script>
</body>
</html>Best for: interactive dashboards, component demos, data visualization with state.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard</title>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/[email protected]/umd/Recharts.js"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: { 500: '#6366f1', 600: '#4f46e5' }
}
}
}
}
</script>
</head>
<body class="bg-slate-950 text-slate-100 min-h-screen">
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useCallback } = React
const { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } = Recharts
const data = [
{ month: 'Jan', revenue: 4200, users: 1200 },
{ month: 'Feb', revenue: 5800, users: 1450 },
{ month: 'Mar', revenue: 5100, users: 1380 },
{ month: 'Apr', revenue: 7200, users: 1820 },
{ month: 'May', revenue: 6800, users: 1750 },
{ month: 'Jun', revenue: 9100, users: 2100 },
]
function MetricCard({ label, value, change }) {
const isPositive = change >= 0
return (
<div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
<div className="text-sm text-slate-400 mb-1">{label}</div>
<div className="text-2xl font-bold text-white">{value}</div>
<div className={`text-sm mt-1 ${isPositive ? 'text-green-400' : 'text-red-400'}`}>
{isPositive ? '↑' : '↓'} {Math.abs(change)}% vs last month
</div>
</div>
)
}
function Dashboard() {
const [activeMetric, setActiveMetric] = useState('revenue')
return (
<div className="max-w-5xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-6">Analytics Dashboard</h1>
<div className="grid grid-cols-3 gap-4 mb-8">
<MetricCard label="Revenue" value="$9,100" change={33.8} />
<MetricCard label="Active Users" value="2,100" change={20.0} />
<MetricCard label="Conversion" value="3.4%" change={-0.2} />
</div>
<div className="bg-slate-800 rounded-xl p-4 border border-slate-700">
<div className="flex gap-4 mb-4">
{['revenue', 'users'].map(metric => (
<button
key={metric}
onClick={() => setActiveMetric(metric)}
className={`px-3 py-1 rounded-full text-sm capitalize ${
activeMetric === metric
? 'bg-brand-500 text-white'
: 'text-slate-400 hover:text-white'
}`}
>
{metric}
</button>
))}
</div>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
<XAxis dataKey="month" stroke="#94a3b8" />
<YAxis stroke="#94a3b8" />
<Tooltip
contentStyle={{ background: '#1e293b', border: '1px solid #334155' }}
/>
<Line
type="monotone"
dataKey={activeMetric}
stroke="#6366f1"
strokeWidth={2}
dot={{ fill: '#6366f1' }}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
)
}
ReactDOM.createRoot(document.getElementById('root')).render(<Dashboard />)
</script>
</body>
</html>#0f172a background, #1e293b card, #6366f1 accent#f8fafc background, #ffffff card, #6366f1 accentfont-size: clamp(1.5rem, 4vw, 2.5rem) - scales on mobileInter or Geist via Google Fontsmax-width on content containers (800-1200px)transition: all 0.2s, hover states, focus ringsmeta viewport and test at 375px widthtype="button", form inputs need labels~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.