auditing-foundry-smart-contract-security skill (Anthropic-Cybersecurity-Skills)
- Install
- SKILL.md (verbatim)
- Overview
- When to Use
- Prerequisites
- Steps
- Step 1: Build and sanity-check the project
- Step 2: Static analysis (fast, run every time)
- Step 3: Symbolic execution on critical contracts (optional, slow)
- Step 4: Property-based testing — fuzz + invariants
- Step 5: Manual review against the checklist
- Step 6: Key hygiene & secure deploy
- Step 7: Triage and report
- Expected Output
- Other files in this skill
- references/api-reference.md (verbatim)
- Slither (static analysis)
- Aderyn (Cyfrin, Rust static analyzer — complementary to Slither)
- Mythril (symbolic execution — slow, use on critical contracts only)
- Foundry — testing
- Fuzz test (property over random inputs)
- Revert test (replaces deprecated testFail)
- Key cheatcodes (vm.)
- Invariant testing — handler pattern (the important one)
- SWC Registry (key entries)
- References
- references/secure-deployment-and-keys.md (verbatim)
- Golden rules
- Foundry encrypted keystore (cast wallet)
- Anti-leak controls (wire into the project)
- MetaMask / wallet operational security
- RPC & dependency trust
- Post-deploy checklist
- References
- references/vulnerability-checklist.md (verbatim)
- 1. Reentrancy (SWC-107)
- 2. Access control (SWC-105/106/115)
- 3. Oracle / price manipulation (DeFi #1 exploit class)
- 4. Arithmetic & rounding (SWC-101)
- 5. Unchecked external calls / return values (SWC-104)
- 6. delegatecall / proxy storage (SWC-112)
- 7. Front-running / MEV / ordering (SWC-114)
- 8. Randomness (SWC-120)
- 9. Denial of service (SWC-113/128)
- 10. Token / standard pitfalls
- 11. General hygiene
- Severity triage (impact × likelihood)
What it does. Pre-deployment security audit of Solidity smart contracts in a Foundry project. Combines static analysis (Slither, Aderyn), symbolic execution (Mythril), and property-based testing (forge fuzz + invariant tests with handlers) to catch reentrancy, access-control, oracle/price manipulation, and arithmetic bugs BEFORE deploying to an EVM chain. Also enforces key hygiene (no plaintext private keys, encrypted cast keystore) and a secure deploy workflow. Use when writing, reviewing, testing, or deploying Solidity/Foundry contracts, building a dApp, or working with forge/cast/anvil, MetaMask, or Web3/DeFi code. Part of mukul975/Anthropic-Cybersecurity-Skills (817 security skills) (mukul975/Anthropic-Cybersecurity-Skills).
| Upstream | mukul975/Anthropic-Cybersecurity-Skills |
| Skill file | skills/auditing-foundry-smart-contract-security/SKILL.md |
| License | Apache-2.0 (skill folder LICENSE) |
| Author | mukul975 |
| Fetched | 2026-09-10 |
Install
npx skills add mukul975/Anthropic-Cybersecurity-Skills --skill auditing-foundry-smart-contract-security, or copy the skill folder into~/.claude/skills/auditing-foundry-smart-contract-security/.- Raw file:
curl -sL https://raw.githubusercontent.com/mukul975/Anthropic-Cybersecurity-Skills/HEAD/skills/auditing-foundry-smart-contract-security/SKILL.md
SKILL.md (verbatim)
name: auditing-foundry-smart-contract-security
description: >-
Pre-deployment security audit of Solidity smart contracts in a Foundry project.
Combines static analysis (Slither, Aderyn), symbolic execution (Mythril), and
property-based testing (forge fuzz + invariant tests with handlers) to catch
reentrancy, access-control, oracle/price manipulation, and arithmetic bugs
BEFORE deploying to an EVM chain. Also enforces key hygiene (no plaintext
private keys, encrypted cast keystore) and a secure deploy workflow. Use when
writing, reviewing, testing, or deploying Solidity/Foundry contracts, building
a dApp, or working with forge/cast/anvil, MetaMask, or Web3/DeFi code.
domain: cybersecurity
subdomain: blockchain-security
tags:
- solidity
- foundry
- forge
- smart-contract
- slither
- aderyn
- mythril
- reentrancy
- defi
- web3
- invariant-testing
- audit
version: "1.0"
author: devredious
license: Apache-2.0
based_on: mukul975/analyzing-ethereum-smart-contract-vulnerabilities
swc_registry: https://swcregistry.io/
mitre_attack:
- T1190
- T1059
Auditing Foundry Smart Contract Security
Overview
Deployed smart contracts are immutable and custody real funds, so a bug shipped to mainnet cannot be patched — it can only be exploited. Most catastrophic DeFi losses come from a small set of recurring classes: reentrancy, broken access control, oracle/price manipulation, and unchecked arithmetic or external calls.
This skill runs a defense-in-depth, pre-deployment audit of a Foundry project, layering four independent techniques that each catch what the others miss:
- Static analysis —
slither(90+ detectors) andaderyn(Cyfrin, Rust) scan the AST/IR in seconds for known anti-patterns. - Symbolic execution —
mythril(optional, slow) explores execution paths and SMT-solves for deep arithmetic/reentrancy bugs. - Property-based testing —
forge testwith fuzzing (testFuzz_*) and invariant tests (invariant_*+ handler contracts with ghost variables) proves protocol-level properties hold across millions of random sequences. - Manual review + key hygiene — a structured checklist (see
references/vulnerability-checklist.md) and a secrets/keystore audit so no private key ever lives in plaintext and deployment goes through an encryptedcastkeystore (seereferences/secure-deployment-and-keys.md).
The skill is dev-side and pre-deployment — it is run by the engineer building the contract, not by a SOC after an incident. Findings gate the deploy: any high/critical static finding, failing test, leaked key, or low coverage = FAIL.
When to Use
- Before deploying any Solidity contract to a testnet or mainnet EVM chain.
- When writing or reviewing a Foundry project (
foundry.toml,src/,test/,script/). - When a contract handles value: tokens (ERC-20/721/1155), vaults, staking, AMMs, bridges, governance.
- When adding fuzz or invariant tests, or when coverage of value-moving functions is unknown.
- When wiring deployment scripts — to verify keys are in an encrypted keystore, not
.envplaintext. - When integrating a price oracle, external call,
delegatecall, or upgradeable proxy. - When triaging a Slither/Aderyn report and needing to separate real bugs from false positives.
Prerequisites
- Foundry installed (
forge,cast,anvil):curl -L https://foundry.paradigm.xyz | bash && foundryup - Slither + solc:
pip install slither-analyzerandsolc-select install <ver> && solc-select use <ver> - Aderyn (recommended):
cargo install aderyn(ornpm i -g @cyfrin/aderyn) - Mythril (optional, slow symbolic exec):
pip install mythril - gitleaks (key-leak scan): see the companion
implementing-secret-scanning-with-gitleaksskill - A Foundry project that compiles (
forge buildsucceeds) — analyzers need build artifacts. - Solidity ^0.8.x is assumed (built-in overflow checks); pre-0.8 contracts need extra SafeMath review.
Install the Python tools in a virtualenv (recommended on externally-managed distros). Never run analysis against untrusted contract source on a machine with funded wallets unlocked.
Steps
Step 1: Build and sanity-check the project
forge build # analyzers require fresh artifacts
forge fmt --check # style gate (optional)
cat foundry.toml # note solc version, optimizer, remappings, evm_version
Step 2: Static analysis (fast, run every time)
# Slither — full project (uses foundry.toml + remappings automatically)
slither . --json slither-report.json
# Aderyn — Cyfrin Rust analyzer, complementary detectors
aderyn . -o aderyn-report.json
Or run the bundled orchestrator that runs both, deduplicates, and gates the result:
python3 scripts/agent.py --project . --output audit-report.json
Step 3: Symbolic execution on critical contracts (optional, slow)
# Only on the highest-value contract(s) — Mythril is path-explosive
myth analyze src/Vault.sol --solc-json mythril.config.json --execution-timeout 300 -o json
# or: python3 scripts/agent.py --project . --mythril src/Vault.sol
Step 4: Property-based testing — fuzz + invariants
forge test -vvv # unit + fuzz tests
forge coverage --report summary # coverage of value-moving code
forge test --match-test invariant_ -vvv # invariant suite (handler-based)
Every value-moving contract should have invariant tests with a handler (bounded
inputs, ghost variables, targetContract(handler)) — not just unit tests. See
references/api-reference.md for the handler pattern, and write a
test_RevertWhen_* (with vm.expectRevert) for each access-control guard.
Step 5: Manual review against the checklist
Walk references/vulnerability-checklist.md for every contract: reentrancy
(checks-effects-interactions / nonReentrant), access control, oracle manipulation,
delegatecall/proxy storage layout, unchecked return values, tx.origin, weak
randomness, DoS, front-running/MEV, and ERC-specific pitfalls (approve race,
fee-on-transfer, rebasing).
Step 6: Key hygiene & secure deploy
gitleaks detect --no-banner # no private keys / mnemonics / .env committed
git ls-files | grep -E '\.env$|keystore' && echo "WARN: secrets tracked by git"
# Import the deploy key ONCE into an encrypted keystore — never a plaintext PRIVATE_KEY env
cast wallet import deployer --interactive
# Deploy via the keystore account (testnet first), simulate before --broadcast
forge script script/Deploy.s.sol --account deployer --rpc-url <testnet> --broadcast --verify
See references/secure-deployment-and-keys.md for the full hardening rules
(MetaMask hygiene, hardware wallet for mainnet, RPC trust, post-deploy verification).
Step 7: Triage and report
Combine Slither + Aderyn + Mythril + test results, deduplicate by (file, line),
drop confirmed false positives, rank by exploitability × financial impact, and map
each to its SWC id. The orchestrator emits audit-report.json with a PASS/FAIL gate.
Expected Output
A JSON audit report listing findings with SWC identifiers, severity, tool source, affected contract/function/line, and remediation; plus the test/coverage summary and a single PASS / FAIL deploy gate. FAIL on any high/critical static finding, failing test, leaked secret, or coverage below the configured threshold on value-moving code.
Other files in this skill
- LICENSE
- references/api-reference.md
- references/secure-deployment-and-keys.md
- references/vulnerability-checklist.md
- scripts/agent.py
references/api-reference.md (verbatim)
API Reference — Tooling
Slither (static analysis)
slither . # whole project (reads foundry.toml + remappings)
slither . --json slither-report.json # machine-readable
slither . --json - # JSON to stdout (used by agent.py)
slither . --print human-summary # quick overview
slither . --print inheritance-graph # inheritance / proxy layout
slither . --detect reentrancy-eth,unprotected-upgrade # specific detectors
slither --list-detectors # all 90+ detectors
slither . --exclude-informational --exclude-low # focus high/medium
slither . --triage-mode # interactively suppress false positives -> slither.db.json
Severity matrix (impact × confidence):
| Impact | Confidence | Example detectors |
|---|---|---|
| High | High | reentrancy-eth, suicidal, arbitrary-send-eth |
| High | Medium | controlled-delegatecall, reentrancy-no-eth |
| Medium | High | locked-ether, incorrect-equality, tx-origin |
| Medium | Medium | uninitialized-state, shadowing-state, unchecked-transfer |
| Low | High | naming-convention, solc-version, low-level-calls |
| Informational | High | pragma, dead-code, assembly |
Aderyn (Cyfrin, Rust static analyzer — complementary to Slither)
aderyn . # markdown report.md by default
aderyn . -o aderyn-report.json # JSON (used by agent.py)
aderyn . --scope src/ # limit scope
Mythril (symbolic execution — slow, use on critical contracts only)
myth analyze src/Vault.sol -o json
myth analyze src/Vault.sol --execution-timeout 300 --max-depth 50 -o json
myth analyze --address 0x... --rpc <url> # deployed bytecode (read-only)
Foundry — testing
forge build # required before static analysis
forge test -vvv # unit + fuzz; -vvvv shows traces
forge test --match-contract VaultTest
forge test --match-test invariant_ # invariant suite only
forge coverage --report summary # line/branch coverage table
forge coverage --report lcov # for CI / tooling
forge snapshot # gas snapshots (DoS-by-gas review)
forge fmt --check # style gate
Fuzz test (property over random inputs)
function testFuzz_SetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
Revert test (replaces deprecated testFail)
function test_RevertWhen_Unauthorized() public {
vm.prank(attacker);
vm.expectRevert("Not authorized"); // or vm.expectRevert(MyError.selector)
target.adminOnly();
}
Key cheatcodes (vm.*)
| Cheatcode | Use |
|---|---|
vm.prank(addr) / vm.startPrank |
impersonate caller (test access control) |
vm.warp(ts) / vm.roll(n) |
manipulate block.timestamp / block.number |
vm.deal(addr, amt) |
set ETH balance |
vm.store(addr, slot, val) |
overwrite storage (test invariants under hostile state) |
vm.expectRevert(...) |
assert a call reverts (with msg / custom error selector) |
vm.expectEmit(...) |
assert events |
bound(x, lo, hi) |
constrain fuzz inputs in handlers |
makeAddr("name") |
deterministic labelled actor |
Invariant testing — handler pattern (the important one)
A handler wraps the target, bounds inputs, rotates actors, and tracks
ghost variables; targetContract(handler) makes the fuzzer drive only the
handler so sequences stay realistic.
// test/Invariant.t.sol
contract VaultInvariantTest is Test {
Vault vault;
VaultHandler handler;
function setUp() public {
vault = new Vault();
handler = new VaultHandler(vault);
targetContract(address(handler)); // fuzz the handler, not the vault directly
}
function invariant_ConservationOfDeposits() public view {
assertEq(address(vault).balance,
handler.ghost_depositSum() - handler.ghost_withdrawSum());
}
function invariant_Solvency() public view {
assertGe(address(vault).balance, vault.totalDeposits());
}
}
// test/handlers/VaultHandler.sol
contract VaultHandler is Test {
Vault public vault;
uint256 public ghost_depositSum;
uint256 public ghost_withdrawSum;
address[] public actors;
address internal currentActor;
modifier useActor(uint256 seed) {
currentActor = actors[bound(seed, 0, actors.length - 1)];
vm.startPrank(currentActor); _; vm.stopPrank();
}
constructor(Vault _v) {
vault = _v;
for (uint256 i; i < 10; i++) { actors.push(makeAddr(string(abi.encodePacked("actor", i)))); vm.deal(actors[i], 100 ether); }
}
function deposit(uint256 amt, uint256 seed) external useActor(seed) {
amt = bound(amt, 0.01 ether, 10 ether);
vault.deposit{value: amt}(); ghost_depositSum += amt;
}
function withdraw(uint256 amt, uint256 seed) external useActor(seed) {
uint256 bal = vault.balanceOf(currentActor);
if (bal == 0) return;
amt = bound(amt, 1, bal);
vault.withdraw(amt); ghost_withdrawSum += amt;
}
}
Tune in foundry.toml:
[invariant]
runs = 256
depth = 128
fail_on_revert = false # set true once the handler fully constrains inputs
[fuzz]
runs = 10000
SWC Registry (key entries)
| SWC | Title | Detected by |
|---|---|---|
| SWC-101 | Integer Overflow/Underflow | Mythril (pre-0.8 only) |
| SWC-104 | Unchecked Call Return | Slither + Mythril |
| SWC-105 | Unprotected Ether Withdrawal | Slither + Mythril |
| SWC-106 | Unprotected SELFDESTRUCT | Slither + Mythril |
| SWC-107 | Reentrancy | Slither + Mythril |
| SWC-112 | Delegatecall to Untrusted Callee | Slither |
| SWC-114 | Transaction Order Dependence (front-running) | manual |
| SWC-115 | tx.origin Authentication | Slither |
| SWC-116 | Block Timestamp Dependence | Mythril |
| SWC-120 | Weak Randomness | Slither + manual |
References
- Slither: https://github.com/crytic/slither
- Aderyn: https://github.com/Cyfrin/aderyn
- Mythril: https://github.com/Consensys/mythril
- Foundry Book: https://getfoundry.sh/
- SWC Registry: https://swcregistry.io/
- Solidity security: https://docs.soliditylang.org/en/latest/security-considerations.html
- Solodit (audit findings DB): https://solodit.xyz/
references/secure-deployment-and-keys.md (verbatim)
Secure Deployment & Key Hygiene
The contract code can be flawless and you still lose everything if a private key leaks or you sign a malicious transaction. This is the part most smart-contract guides skip. Treat keys as the highest-severity asset.
Golden rules
- A real private key or seed phrase NEVER touches a file, env var, shell history, or git.
- Plaintext
PRIVATE_KEY=0x...in.envis the #1 leak vector — use an encrypted keystore instead. - Separate wallets: a throwaway dev wallet (testnet only) ≠ the mainnet deployer ≠ your personal MetaMask with real funds.
- Hardware wallet (Ledger/Trezor) for any mainnet deploy or admin action that controls funds.
- Simulate before broadcasting; verify after.
Foundry encrypted keystore (cast wallet)
Import the key once into an encrypted, password-protected keystore — then reference it by name. The raw key never appears in commands or files again.
# Import interactively (key is typed, not in argv/history), set a strong password
cast wallet import deployer --interactive
# Or generate a fresh dev key directly into the keystore
cast wallet new
# List / inspect (addresses only)
cast wallet list
Deploy by account name, never by --private-key:
# Testnet first — simulate (no --broadcast) then broadcast + verify
forge script script/Deploy.s.sol --account deployer --rpc-url <testnet_rpc>
forge script script/Deploy.s.sol --account deployer --rpc-url <testnet_rpc> --broadcast --verify
# Mainnet (prefer a Ledger):
forge script script/Deploy.s.sol --ledger --hd-paths "m/44'/60'/0'/0/0" --rpc-url <mainnet_rpc> --broadcast --verify
In deploy scripts, use vm.startBroadcast() with no argument (it uses the
--account/--ledger signer). Avoid vm.envUint("PRIVATE_KEY").
Anti-leak controls (wire into the project)
# 1. .gitignore the usual suspects
printf '.env\n.env.*\n*.key\nkeystore/\nbroadcast/\n' >> .gitignore
# 2. Scan history + working tree for secrets (see the implementing-secret-scanning-with-gitleaks skill)
gitleaks detect --no-banner
gitleaks detect --no-banner --log-opts="--all" # full git history
# 3. Confirm nothing sensitive is tracked
git ls-files | grep -E '\.env$|\.key$|keystore' && echo "REMOVE THESE FROM GIT"
If a key was ever committed (even and then deleted): consider it compromised —
generate a new one, move funds, and purge history (BFG / git filter-repo).
MetaMask / wallet operational security
- Dedicated browser profile for Web3; review every signature — read what you sign.
- Beware blind signing and
eth_sign/personal_signphishing; reject opaque hex. - Token approval hygiene: avoid unlimited
approve; periodically revoke (revoke.cash); preferpermitwith deadlines. - Verify the contract address and chain id before interacting; bookmark dApps, don't follow links.
- Add networks/RPCs only from trusted sources — a malicious RPC can lie about state and simulate fake balances.
RPC & dependency trust
- Pin a reputable RPC (your own node, or a known provider); a hostile RPC can feed false data to scripts and frontends.
- Pin dependency versions (
forge installwith a tag/commit; lock OpenZeppelin version). Re-audit on bumps. - Verify deployed bytecode matches source on the explorer (
forge verify-contract/--verify).
Post-deploy checklist
- Source verified on the block explorer.
- Ownership/admin transferred to a multisig (Safe), not an EOA, for anything controlling funds.
- Timelock on privileged upgrades/parameter changes.
- Monitoring/alerting on critical events (large withdrawals, ownership changes, pause).
- Emergency runbook: pause + emergency-withdraw path tested on testnet.
- Deploy key rotated/retired if it ever touched a less-trusted machine.
References
- Foundry deploying guide: https://getfoundry.sh/guides/deploying
cast wallet: https://getfoundry.sh/cast/reference/wallet- OpenZeppelin Contracts: https://docs.openzeppelin.com/contracts
- Safe (multisig): https://safe.global/
- revoke.cash (approval management): https://revoke.cash/
references/vulnerability-checklist.md (verbatim)
Manual Review Checklist — Solidity / EVM
Walk this for every contract that moves value. Each item: what to look for → how to confirm (Foundry test / Slither detector) → fix. Tools catch the known patterns; this catches the logic bugs they can't.
1. Reentrancy (SWC-107)
- External call (
call,transfer, ERC-777 hooks, ERC-721onERC...Received, arbitrary token) before state updates? - Cross-function / read-only reentrancy: a view used by another protocol mid-call?
- Confirm: Slither
reentrancy-eth/reentrancy-no-eth; write an attacker contract test that re-enters in itsreceive(). - Fix: Checks-Effects-Interactions order;
nonReentrant(OpenZeppelinReentrancyGuard); pull-over-push payments. ("OZ" = OpenZeppelin throughout.)
2. Access control (SWC-105/106/115)
- Every state-changing/admin function gated (
onlyOwner, roles, custom modifier)? -
initialize()on upgradeable contracts protected against re-init and front-running? - No
tx.originfor auth (phishable) — usemsg.sender. -
selfdestruct/delegatecallreachable only by trusted roles? - Confirm: a
test_RevertWhen_*withvm.prank(attacker)+vm.expectRevertfor each guard. - Fix: OZ
Ownable2Step/AccessControl;initializermodifier; removetx.origin.
3. Oracle / price manipulation (DeFi #1 exploit class)
- Price from spot
getReserves()/ a single AMM pool? (flash-loan manipulable) - Using Chainlink: checked
updatedAtstaleness,answeredInRound, min/max bounds, L2 sequencer uptime? - Confirm: fork test that manipulates the pool within one tx and asserts your protocol stays solvent.
- Fix: TWAP / Chainlink with staleness+deviation checks; never trust spot for pricing.
4. Arithmetic & rounding (SWC-101)
- Solidity ^0.8 (built-in checked math) — and any
unchecked{}block justified? - Division before multiplication (precision loss)? Rounding always in protocol's favor?
- Share-inflation / first-depositor attack on ERC-4626-style vaults?
- Confirm:
testFuzz_*over amounts; invariantassertGe(assets, shares-implied). - Fix: mulDiv (OZ
Math.mulDiv); virtual shares/offset for vaults; explicit rounding direction.
5. Unchecked external calls / return values (SWC-104)
- Return value of low-level
call/sendand ERC-20transfer/transferFromchecked? - Non-standard ERC-20s (no return, fee-on-transfer, rebasing) handled?
- Confirm: Slither
unchecked-transfer,unchecked-lowlevel. - Fix: OZ
SafeERC20; measure balance delta for fee-on-transfer; require success.
6. delegatecall / proxy storage (SWC-112)
- Storage layout identical across implementation upgrades (no reordered/removed vars)?
- No
delegatecallto user-supplied address; implementation can't be re-initialized/self-destructed? - Confirm: Slither
controlled-delegatecall,unprotected-upgrade; storage-layout diff between versions. - Fix: OZ
UUPS/Transparentproxies + storage gaps;_disableInitializers()in constructor.
7. Front-running / MEV / ordering (SWC-114)
- Approve race (ERC-20
approvefrom non-zero to non-zero)? - Slippage / deadline params on swaps and mints? Commit-reveal where needed?
- Fix:
increaseAllowance/permit; enforceminOut+deadline; commit-reveal for sensitive ordering.
8. Randomness (SWC-120)
- Any
block.timestamp/blockhash/prevrandaoused as randomness for value? - Fix: Chainlink VRF; never on-chain pseudo-randomness for payouts.
9. Denial of service (SWC-113/128)
- Unbounded loops over user-growable arrays? Push payments that can revert and brick the contract?
- Single external dependency whose revert blocks all users?
- Fix: pull payments; bounded iteration / pagination; isolate failures.
10. Token / standard pitfalls
- ERC-20: decimals assumptions, fee-on-transfer, rebasing, missing return.
- ERC-721/1155: safe-transfer reentrancy hooks; approval scope.
- Permit (EIP-2612): replay, deadline, signature malleability.
11. General hygiene
-
pragmapinned (pragma solidity 0.8.26;not^)? Latest stable solc? - Events emitted for every state-changing action (off-chain monitoring)?
- No leftover
console.log/ test backdoors / hardcoded addresses? - Pausability + emergency withdraw for value-holding contracts?
- Invariants written for every conservation law (total supply, solvency, accounting)?
Severity triage (impact × likelihood)
- Critical/High → direct loss/lock of funds, unauthorized mint/withdraw, broken access control. Blocks deploy.
- Medium → loss under specific conditions, griefing, precision drift.
- Low/Info → best-practice, gas, style.
Cross-check real-world findings on Solodit (https://solodit.xyz/) for the contract type you're shipping (vault, AMM, staking, bridge, governance).
Back to mukul975/Anthropic-Cybersecurity-Skills (817 security skills) or Agent skills.