wp-admin-browser — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited wp-admin-browser (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.
Model note: Primarily MCP tool calls — navigate, fill, click.haikuworks for simple CRUD flows. Complex UI sequences (multi-step forms, dynamic AJAX state) usesonnetto handle unexpected DOM states.
Not for: Headless automated testing without a real browser — use wp-plugin-testing. PHP code changes and plugin logic that don't require browser interaction.
/wp-admin/users.php?action=....// POST to wp-login.php via fetch (fastest, no UI needed for login itself)
async () => {
const res = await fetch('/wp-login.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
log: 'YOUR_USER',
pwd: 'YOUR_PASS',
'wp-submit': 'Log In',
redirect_to: '/wp-admin/',
testcookie: '1',
}),
credentials: 'include',
redirect: 'follow',
});
return { ok: res.ok, url: res.url };
}Login via fetch is acceptable because it's a pure auth step — no data mutation.
Always create a temp user before any testing that requires admin actions.
Navigate to Users → Add New via menu clicks, not direct URL.
Admin menu → Users → Add NewFill the form using fill_form:
| Field | Value |
|---|---|
| Username | tmp_admin_<timestamp> |
tmp+<timestamp>@example.com | |
| First Name | Temp |
| Last Name | Admin |
| Role | Administrator |
| Password | strong generated password |
| Send notification | unchecked |
After testing: delete the temp user via Users list → hover → Delete.
async () => {
return fetch('/wp-admin/admin-ajax.php?action=rest-nonce', {
credentials: 'include',
}).then(r => r.text());
}Use nonce only for GET requests to read data. All mutations go through WP forms.
✅ Click: Admin menu → Submenu item
❌ Never: navigate_page to hardcoded /wp-admin/edit.php?post_type=...✅ fill_form on visible fields → click Submit button
❌ Never: fetch POST directly to admin-post.php / admin-ajax.php for data changes
❌ Never: wp eval or wp post create via CLI for browser-visible operationsAlways wait for the page to load after each menu click before interacting with the next element.
| Operation | Method |
|---|---|
| Create post/page | Posts → Add New → fill form → Publish |
| Update user | Users → find user → Edit → fill form → Update |
| Delete item | List view → hover row → Delete (confirm dialog) |
| Change setting | Settings menu → fill field → Save Changes |
| Install plugin | Plugins → Add New → Search → Install → Activate |
| Mistake | Fix |
|---|---|
| Using main admin for destructive tests | Create temp admin first |
Navigating directly to ?action=delete&id=X | Use list UI → Delete link |
Using wp user create CLI to seed browser session | Use Add New User form |
Skipping fill_form and posting via fetch | Always fill the visible form |
| Leaving temp admin after testing | Delete via Users list when done |
Use evaluate_script to inspect JavaScript state without touching the UI.
() => ({
// Verify a localized WP global is available
myPlugin: typeof window.MY_PLUGIN,
keys: Object.keys(window.MY_PLUGIN || {}),
// Verify a library bundle loaded
driverLoaded: !!window.driver?.js?.driver,
// Check current URL
url: location.href,
hash: location.hash,
})If typeof window.MY_PLUGIN === 'undefined' the script may not be enqueued for this screen, or the page is in maintenance mode (document.title === 'Maintenance').
// Wrong: query DOM immediately after navigate
() => document.querySelector('.spa-element') // returns null
// Right: wrap in setTimeout
() => new Promise(r => setTimeout(() => r(
!!document.querySelector('.spa-element')
), 2000))() => {
const selectors = [
'#my-id',
'.class-one.class-two',
'.border-\\[\\#F0EDFB\\]', // Tailwind escaped
];
return selectors.map(s => ({
selector: s,
found: !!document.querySelector(s),
count: document.querySelectorAll(s).length,
}));
}Always test on the page where the selector is expected to exist. A selector for the orders page returns false on the dashboard — that's not a bug.
// Clear feature flags / completion state before testing
() => {
Object.keys(localStorage)
.filter(k => k.startsWith('myplugin_'))
.forEach(k => localStorage.removeItem(k));
return 'cleared';
}
// Read a specific key after an action
() => localStorage.getItem('myplugin_feature_completed') // "true" or nullIf navigate_page lands on a blank page or unexpected content:
() => ({ title: document.title, url: location.href })
// "Maintenance" title = WP in maintenance mode (.maintenance file or plugin)
// Redirected to /wp-login.php = session expired — re-login via fetchasync () => {
const res = await fetch('/wp-login.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
log: 'admin', pwd: 'admin',
'wp-submit': 'Log In',
redirect_to: '/wp-admin/',
testcookie: '1',
}),
credentials: 'include',
redirect: 'follow',
});
return { ok: res.ok, url: res.url };
}After every test session:
tmp_admin_* users → bulk delete.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.