Files
ai-job-search/.claude/skills/job-application-assistant/SKILL.md
T
Mads LorentzenandClaude Fable 5.1 01a3c0e5f6 security: narrow skill grants, ship ask/deny rules, guard skill frontmatter, GET-only 403 retry (#642)
* security(skills): drop bare Bash and the portal CLI glob from skill grants

A skill's allowed-tools pre-approves the listed tools for the turn that
invokes it, even in an untrusted folder. job-application-assistant granted
bare Bash, Edit and Write; job-scraper granted bun run on any
.agents/skills/*/cli/src/cli.ts. Every Bash command the skills document is
already allowlisted per command in .claude/settings.json, so the bare and
glob grants bought nothing but exposure. The per-portal grants stay in
settings.json, which is where a portal added by /add-portal registers too
(Step 5 now says so).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* security(settings): deny exfil-shaped shell and credential reads, always ask on permission-defining files

Deny rules block in every permission mode including bypassPermissions and
override any allow rule or skill grant; ask rules force a prompt even when
a skill's allowed-tools would pre-approve the call. settings.json now
ships both: deny for shell that uploads or opens a remote shell (curl
data/form/upload flags, wget post, scp, sftp, ssh, nc) and for reads of
.env and ~/.ssh, ~/.aws, ~/.gnupg; ask for the files whose content is the
permission surface (settings, skill frontmatter, agents, CI, this guard)
and for git push. security_guards.py requires each rule to stay present,
the same way it requires the personal-data gitignore rules. Prefix rules
are tripwires, not walls; SECURITY.md will say so.

Also allowlists tools/robots_check.py and the upcoming tools/fetch_page.py
so the 403 retry no longer needs a curl grant.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* security(guards): hold skill and command allowed-tools to a reviewed allowlist

The guard allowlisted settings.json permissions but never read skill
frontmatter, so the narrow settings allowlist sat next to a bare Bash
grant it could not see. Check 4 parses every .claude/skills/*/SKILL.md,
.agents/skills/*/SKILL.md and .claude/commands/*.md frontmatter with a
stdlib parser (scalar, folded, and list forms) and holds each
allowed-tools entry to ALLOWED_SKILL_TOOLS; a portal skill may
additionally grant exactly its own cli/src/cli.ts, matched by path so no
portal name is hardcoded. Bare Bash and mid-pattern wildcards get their
own message. On master's frontmatter this check reports two failures.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* feat(tools): add GET-only fetch_page.py for the browser-header retry

09-web-research.md documented the 403 retry as a curl command with browser
headers plus an inline python3 -c tag-stripper, which meant the workflow
wanted a Bash(curl:*) grant - and curl with a free tail is an upload
channel. fetch_page.py is the same retry as one command with a fixed GET
argv (URL after --, bounded to 45 s / 5 redirects / 10 MB), http(s) only,
robots_check.gate() enforced with no bypass flag, --out refused inside
the repo, and --text for the stripped page. urllib fallback when curl is
absent. The settings.json grant names the script.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(security): describe the permission surface as a table; route the 403 retry through fetch_page.py

SECURITY.md now lists every shipped grant with its location, lifetime
and guard, and says plainly what the permission layer does not do: no
sandbox on native Windows, prefix rules are tripwires, WebFetch is
unrestricted by domain, subagents run with the parent's tools (next
change), and Edit/Write stay granted because tailoring a CV is editing
files. 09-web-research.md's retry section is the one fetch_page.py
command (framework_version 1.1.3 -> 1.1.4); apply, rank and scrape point
at it instead of curl. SETUP.md and the README tools tree follow.
CHANGELOG gains a Security section under [Unreleased].

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* security(review): ask on command frontmatter, portal skills grant only their CLI, bound the urllib fallback

Findings from a fresh-context review of the branch:

- A command's frontmatter is a grant too (check 4 already parses it), so
  Edit(.claude/commands/*.md) joins the required ask rules; without it a
  steered session could write `allowed-tools: Bash` into apply.md with no
  prompt and the next /apply would be pre-approved for bare Bash.
- SECURITY.md promised a portal skill may grant "its own CLI and nothing
  else"; the guard now enforces exactly that instead of own-CLI plus the
  core allowlist. All six shipped portal skills already comply.
- fetch_page.py's urllib fallback now matches curl's 5-redirect cap and
  applies the 10 MB cap after decompression, so the docstring's bounds
  hold on both paths.
- Frontmatter parser: a BOM no longer reads as "missing frontmatter",
  YAML flow lists split correctly, and a non-UTF-8 file fails with a
  message instead of a traceback. Module docstring lists check 4.
- CHANGELOG no longer claims "no new prompt": compile and aux cleanup,
  which the removed bare Bash covered in a skill-invoking turn, now
  prompt, deliberately. "Once per session" wording corrected to per call.
- Three upload-shaped deny siblings added (curl -XPOST, curl --request
  POST, wget --method=POST); /add-portal recon points at fetch_page.py.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-19 17:15:25 +02:00

4.6 KiB

name, description, allowed-tools, framework_version
name description allowed-tools framework_version
job-application-assistant Assists with job applications: evaluating job postings, tailoring CVs, writing cover letters, and preparing for interviews. Triggers on keywords like: job posting, job application, CV, cover letter, resume, interview prep, job fit, career, application, apply, ansøgning, stilling Read, Glob, Grep, WebFetch, WebSearch, Edit, Write, AskUserQuestion 1.3.5

Job Application Assistant


Workflow

When the user provides a job posting (URL or text), follow this workflow:

Step 1: Research & Evaluate Fit

  • Fetch the job posting content (use WebFetch for URLs). A 403 is not a dead end - follow the escalation order in 09-web-research.md before concluding a page is unavailable, and prefer the employer's own careers posting over an aggregator listing
  • Keep the full posting text verbatim for Step 3b to archive - never a summary
  • Analyze the posting for required competencies, keywords, and priorities
  • Research the company (website, LinkedIn, mission, recent news), per 09-web-research.md
  • Score the posting against the candidate's profile using the framework in 04-job-evaluation.md
  • Present the evaluation table and verdict
  • Suggest whether the candidate should call the employer before applying (see 04-job-evaluation.md for guidance)
  • Ask the user if they want to proceed with an application

Step 2: Tailor CV

  • Before writing either document, derive <company>_<role> once by the Subfolder naming rule in documents/README.md; reuse that exact value for the CV, cover letter, and Step 3b archive path. If the rule says to stop because the derived name is empty, stop before creating any file.
  • Read the most relevant existing CV variant from cv/ as a starting point
  • Follow the guidelines in 05-cv-templates.md
  • Create cv/main_<company>_<role>.tex with tailored content
  • Adjust: profile statement, skills section, experience bullet emphasis, section order

Step 3: Write Cover Letter

  • Follow the writing style rules in 03-writing-style.md (critical: no em-dashes, no cliches)
  • Follow the template structure in 06-cover-letter-templates.md
  • Create cover_letters/cover_<company>_<role>.tex
  • Ensure the letter connects specific experience to the role requirements

Step 3b: Record the Application

  • Run this once both documents exist. A CV or cover letter drafted alone is not yet an application.
  • Follow /apply Step 6b (.claude/commands/apply.md) exactly: same header, same match-then-update rule, same drafted row, same posting archive, same prohibition on touching job_scraper/seen_jobs.json. It is stated there once so the two paths cannot drift. Four of its values are named in /apply's own terms: cv_file/cover_letter_file are the paths written in Steps 2 and 3 here, source is the posting URL from Step 1, deadline is the application deadline from the posting text Step 1 keeps verbatim (empty when the posting states none - never guess one), and the posting text item 7 archives is the one Step 1 read.
  • This step exists here because /scrape Step 5 routes straight into this skill. Without it, that path writes two documents and records nothing.

Step 4: Interview Preparation

  • Follow the framework in 07-interview-prep.md
  • Prepare STAR-format answers for likely questions
  • Identify role-specific talking points
  • Draft questions the candidate should ask the interviewer

Reference Files

File Purpose
01-candidate-profile.md Education, experience, skills, publications, awards
02-behavioral-profile.md Behavioral assessment, strengths, ideal environments
03-writing-style.md Tone, structure, do's and don'ts
04-job-evaluation.md Scoring framework for job fit
05-cv-templates.md LaTeX CV structure and tailoring rules
06-cover-letter-templates.md LaTeX cover letter structure and tailoring rules
07-interview-prep.md STAR examples, tough questions, roleplay guidelines
08-application-forms.md Portal free-text fields: self-introduction, project entries, character-limited pitches
09-web-research.md Fetching postings and company pages: trust boundary, the WebFetch 403 fallback, escalation order, claim verification

Quick Commands

The user may also ask for individual steps without the full workflow:

  • "Evaluate this job posting" - Step 1 only
  • "Write a CV for [company]" - Step 2 only
  • "Write a cover letter for [role] at [company]" - Step 3 only
  • "Help me prepare for an interview at [company]" - Step 4 only
  • "What jobs should I look for?" - Career strategy discussion using profile + evaluation framework