본문으로 건너뛰기
cccskills
무료GitHub에 공개

authoring-box-policy

Author or edit a Strands Box policy (`policy.dw`) — turn an operator's natural-language allow/deny intent into a validated Dogwood policy over the box's fixed action vocabulary, including `when temporal { … }` history rules. Use when writing, editing, reviewing, or converting an intent into a box policy. This skill CREATES or EDITS a policy against the box's built-in schema — you never author a Dogwood schema, because the box ships a fixed one.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

strands-agents/box스타 475(GitHub 475 + cccskills 0)

실행 점수 미검증

설치 방법 보기

포함된 파일(10)

  • SKILL.md21.4 KB
  • examples/agent-anthropic.dw2.1 KB
  • examples/agent-bedrock.dw1.9 KB
  • examples/agent-openai.dw1.7 KB
  • examples/forbid-delete.dw533 B
  • examples/README.md1.5 KB
  • examples/shell-guard.dw1.3 KB
  • examples/temporal-write-budget.dw1.6 KB
  • references/dogwood-policy-language.md42.0 KB
  • references/dogwood-temporal-expressions.md49.4 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Authoring a Strands Box policy (Dogwood)

Your job: turn a natural-language authorization intent into a Dogwood policy.dw for a Strands Box that loads clean and means what the operator actually intended. This is a formalization task — the hard part is not the syntax (that is documented; see Ground truth) but pinning down ambiguous intent and mapping it onto the right construct over the box's fixed vocabulary.

This is an interactive session, not a one-shot generator. You work with the operator: draw out the intent, ask a sharp question rather than guess when a requirement is underspecified, propose rules, and confirm what stays refused. Do not return a policy you have not loaded.

You author rules, not a schema. A generic Dogwood deployment makes the operator author an action schema and a service schema. Strands Box does not — the box ships a fixed, built-in schema, so the vocabulary of actions, the one principal, and the context shape are already set. You write the permit and forbid rules against that schema — the fixed vocabulary is below.

Follow the loop in order:

  1. Disambiguate the intent (resolve every gap that changes the output).
  2. Formalize it into policy.dw.
  3. Validate by loading it into the box — fail-closed, mandatory (see Step 3). An unloaded policy is not a finished answer.
  4. Round-trip the intent and present the result.

The ground truth (read before authoring)

Treat these as authoritative; do not invent syntax, an action, or a field from memory. If a construct is not in these sources, it does not exist.

Box-specific — the vocabulary and the floors:

  • The fixed vocabulary below — the one principal, the fixed resource, every action and its context, and the temporal event shape. The source of truth for the box.
  • The built-in schema you author against, read but never edit. Generate it in the workspace with box policy generate-schema, which writes .strands-box/actions.cedarschema (actions and their context) and .strands-box/events.dwschema (what a when temporal { … } rule observes). The generated files also carry each declared MCP server's per-tool actions, which a static copy cannot.
  • The bundled examples/ — whole agent policies and single-shape refinements to copy from. See examples/README.md.
  • The decisions behind the vocabulary, in docs/design/decisions.md: the closed action vocabulary, the filesystem actions, temporal rules, and a credential binding is not an authorization.

Language grammar — bundled in this skill's references/:

  • references/dogwood-policy-language.md — core policy syntax: permit/forbid, the (principal, action, resource) scope, when/unless, and the full Cedar expression language (operators, literals, methods, has/like/is, sets/records). See its "Condition expression language" and "What parses but is rejected" sections.
  • references/dogwood-temporal-expressions.md — the temporal { … } sublanguage: formerly/previous/since, windows, exists/tp, count/sum, and the acceptance rules. Read "Writing temporal expressions that are accepted" in full before writing any history rule.

The fixed vocabulary

Every request is (principal, action, resource, context). The principal is always Box::Agent::"self" (leave it a bare principal) and the resource is always Box::Resource::"unused", so the action scopes a rule and every readable field rides context.input. There is no context.system, no context.principal, and no now; the only field outside input is output, on a ::response, read only by a temporal rule: output.result on a filesystem response, output.status on an http:request response, and the exit output.status on a shell:exec or shell:spawn response.

The actions are a closed set. A policy names one; it cannot add one. The last column is the exact context.input fields you scope on.

Actioncontext.input fields
fs:read / fs:write / fs:delete / fs:move / fs:otherpath: String, operation: Fs<Kind>Operation
net:connecthost: String, ip?: ipaddr, port: Long
http:requesthost, port, method, path (a URL path), body_bytes: Long, intercepted: Bool; the reply's status: Long is on output, matched as ::response{ output.status: 500 } in a temporal predicate
shell:execcommand, program, arg1?, arg2?, arg_count: Long, cwd; the exit status: Long is on output, matched as ::response{ output.status: 0 } in a temporal predicate (a signal reports 128 plus its number, a permitted binary that cannot start reports 126, a command with no reported status reports -1, and a background & command records its status when it ends)
shell:spawnthe shell:exec fields plus program_path: String, and the same output.status. The workload can set a shell:exec status (a shell function of the same name does it), so a precondition on a host binary's success reads shell:spawn and pins program_path
mcp:callserver, method, tool?, prompt?, uri?
<server>::Action::"<tool>"per-tool, emitted by box policy generate-schema

Filesystem operation enums (narrow within an fs: action): FsReadOperation = read_content, read_metadata, enumerate, read_link, change_dir, exec; FsWriteOperation = write_content, create_dir, set_permissions, symlink; FsDeleteOperation = remove_file, remove_dir; FsMoveOperation = rename; FsOtherOperation = other.

Rules that bite:

  • No action groups — fs:read does not cover fs:write; cover a family one clause per action.
  • One outbound host is two decisions — net:connect (port, before TLS) and http:request (host, after TLS).
  • Guard an optional field with has (arg1, arg2, tool, prompt, uri, ip); an unguarded read is a load error.
  • path is a filesystem path on fs:* and a URL path on http:request; only fs:* carries operation, so guard a filesystem rule with context.input has operation.
  • Write a shell rule on program (what the first word resolved to), not command. Do not read ip in a temporal predicate — it faults.
  • tool:invoke, model:invoke, agent:invoke, and cred:inject are removed — naming one is an UnknownAction load error.

Temporal event shape. For action A: a A::request (its input fields), a A::response (its input and output fields, only for an effect that happened), and a A::error. Key a precondition on ::response; a window is mandatory and capped at 24h; there is no ||.

For the rationale behind the vocabulary, read the ## Policy section of docs/design/decisions.md.

The floors you cannot change

A policy only ever adds reachability. Some things hold whatever a rule says:

  • Deny-by-default, deny-overrides. No permit means deny, and a forbid always beats a permit. An exception to a broad allow is a forbid that carves a hole, never a narrower permit.
  • The reach floor refuses every path inside a .strands-box directory and every write to the box.toml and policy this run loaded, beneath policy, in every box. No rule widens it.
  • The SSRF / cloud-metadata floor sits below the engine. A rule can deny more, never re-open it.
  • A credential binding is not an authorization (decisions). An [egress.*] entry in box.toml says only what is attached to a request the policy already permitted. It never makes a destination reachable — reachability is this policy's decision alone. The two must name the same host.
  • Information providers are not available. A Provider::Name(args) call or a guardrails { … } clause aborts box startup. Do not reach for a computed fact — there is no provider path in the box.

Step 1 — Disambiguate intent (do this first)

A prose intent almost always leaves gaps that change the formal policy. Resolve every gap that affects the output. If you can infer the answer from an obvious convention, state your assumption and proceed; otherwise ask. Prefer a few sharp, batched questions over silently guessing.

Every policy needs egress to an LLM provider. The agent cannot run without reaching a model, so a policy that grants no provider access does not work — confirm which one (Amazon Bedrock, OpenAI, or Anthropic) and write its two egress legs (net:connect and http:request) before anything else. Match the provider to the box's configured endpoint. See the three agent examples in examples/.

  1. Effect and default. Granting (permit) or restricting (forbid)? The box is default-deny with deny-overrides. "The agent may X" → a permit. "Never X" / "block X when Y" → a forbid that carves a hole out of the permits.
  2. Which action(s)? The principal and resource are fixed, so the action is what scopes a rule. Map the operator's nouns to concrete actions from the table above. Remember: no groups (one clause per fs: verb), and two legs for one outbound host (net:connect and http:request).
  3. Where does each fact live? Every fact rides context.input.<field> — confirm the field name and type against the table. There is no principal/resource attribute to read (both are fixed), no context.system, and no provider for a computed fact. A fact about the past goes to the temporal sublanguage (Step 2b).
  4. Path and host scope. For a filesystem rule, get the exact path and whether it is one file or a subtree — a subtree needs two clauses (the directory and its contents; see Common shapes). Prefer ~-relative paths so the file is portable. For a host, get the exact hostname and port.
  5. Temporal specifics (if history is involved) — the highest-value disambiguations:
    • "happened at least once recently" → formerly within W.
    • "the immediately preceding event" → previous within W.
    • "has held continuously since an anchor" → left since within W right; "has NOT happened since" → a negated since left (there is no dedicated operator).
    • The window W is mandatory (s/m/h/d; no week/month/year) and capped at 24h in the box. If the operator says "recently" without a number, ask — there is no default, and you cannot raise the cap (the event schema is fixed).
    • "the same X" → pin the past predicate's field to the current request: input.path: context.input.path. With one principal, principal-correlation is automatic; correlate anything else (same path, same host) explicitly, or "the same X" silently becomes "any X".
    • Counting/summing → count/sum (no min/max/avg). Confirm the threshold and operator.
    • Key on ::response, never ::request. A ::request event exists even for a denied attempt, so a step-up keyed on ::request is defeated by asking and being refused. A ::response exists only for an effect that happened.
  6. Edge cases the prose glosses over. An absent optional field (has guard)? Inclusive boundary (windows are closed)? Surface these and pick a defensible default, stating it.

Tie each question to how it changes the policy — "'recent' — within what window (e.g. 1h)? And the same host the agent reached, or any host?" — not a vague "can you clarify?".

Step 2 — Formalize

2a. Core (single-request) rules

Map the disambiguated intent onto the rule shape (doc comment, @id, @description on a forbid, effect, scope, when/unless, ;). Lead each rule with a // comment in the operator's words and an @id("…"). Keep the scope as the coarse filter (the action) and put fine logic in when/unless.

Give every forbid a @description("…") that the agent can act on. The box prints every @description on a determining forbid into the denial message that the agent sees, so the string is how the policy talks to the agent about why a request was refused. Say what is refused and what the agent should do instead — a sentence the agent can read and take a different action from, not an internal note. A @description on a permit is ignored, so put the author's reason there in a leading // comment instead.

// Refuse setting permissions anywhere, whatever a write permit allows.
@id("no_chmod")
@description("This workload cannot change file permissions. Make the file writable before the box starts, or ask the operator to add a chmod rule.")
forbid ( principal, action == Box::Action::"fs:write", resource )
when { context.input.operation == Box::FsWriteOperation::"set_permissions" };

Use the operator/method vocabulary from references/dogwood-policy-language.md. Watch the type rules it calls out: decimals are equality-only, / and % are unsupported, and there is no let … in.

2b. History-dependent rules (temporal { … })

Attach when temporal { … } (or unless temporal { … } for absence) and build the body from predicates and the formerly/previous/since operators. Every field a predicate reads is derived from the action's input record.

// Refuse a write once six writes have completed in the last 60 seconds.
@id("write_budget")
forbid ( principal, action == Box::Action::"fs:write", resource )
when temporal {
    exists (total: Long). (
        (count for (t: Timepoint). where (
            formerly within 60s (
                Box::Action::"fs:write"::response{ input.path: _, input.operation: Box::FsWriteOperation::"write_content" } && tp(t)
            )
        )) == total
        && total >= 6
    )
};

A cap is a forbid. A permit only adds permission, so a permit cannot narrow another permit: permit … when temporal { total < 6 } beside a broader fs:write permit caps nothing, and the box refuses that shape at load. Write the budget as a forbid that fires at or above the cap. The permit shape is correct only when it is the sole permit for that action; then write that reason in a comment above the rule.

The sublanguage has strict acceptance rules — closedness, range restriction, conjunct order, aggregates as operands, tp-dependence — and a plausible-looking rule is routinely rejected. Read "Writing temporal expressions that are accepted" in references/dogwood-temporal-expressions.md before you write one, and copy the budget shape above from examples/temporal-write-budget.dw. There is no || — "A or B" is two rules.

2c. Computed facts

Not available. The box aborts startup on an information provider or a guardrails clause. If the intent needs a computed fact (a regex denylist, a risk score), it cannot be expressed as box policy — say so rather than reach for a provider.

Step 3 — Validate by loading (MANDATORY — never skip)

Every policy MUST load into the box before you present it. There is no standalone validator, no dogwood CLI in this repository, and no validate verb on the box. The box validates the policy at load, fail-closed: an unknown action or a mistyped attribute aborts startup with a load error, so an unloaded policy is a guess.

strands-box run re-reads box.toml and policy.dw every time, so the whole loop is: edit a rule, run again, read the result.

  • A load error names the parse, schema, or unknown-action fault. Fix it and re-run.
  • A denied action at runtime returns a refusal that names the component, the reason, and the rule that decided (decisions).

How to test the policy you just wrote. Use the existing Strands getting-started guide for a complete configuration. From a project with .strands-box/box.toml and .strands-box/policy.dw, run its configured agent:

strands-box run --config .strands-box/box.toml

Ask the agent to perform one permitted action and one forbidden action. Inspect the decision log for the expected rules. Edit the policy and run the same configuration again.

A load proves the policy is legal, not that a temporal rule means what you intended — the box has no replay or trace tool. For a history rule, reason carefully against the acceptance rules and, where you can, exercise the box to see the effect deny as expected.

A runtime denial is often a box.toml problem, not a policy bug — read box.toml before you touch the policy. The policy decides only reachability; box.toml decides which endpoint the agent calls, what environment it runs in, and which credential is attached. When a run is refused, check box.toml for these before editing a rule:

  • The agent is pointed at the wrong host. A denial for a host your policy never granted usually means the harness is calling a different endpoint. Check the agent's model configuration and AWS_REGION against the destination in box.toml and the host in the policy.
  • The environment is composed, never inherited. A variable exported in your shell does not reach the workload. Set AWS_REGION and other workload variables in box.toml's [agent.env] table. Box reserves its proxy routing, certificate, and identity variables.
  • The credential is not attached. A permitted host still needs a credential wired through an [egress.<name>] entry naming a reference (env://VAR, aws://<profile>), never a literal. A binding is not an authorization — it and the policy must name the same host.

So a 403 or a refused host is frequently the policy working correctly over a box.toml that points the agent, its environment, or its credential somewhere the policy does not grant.

Step 4 — Intent round-trip and output

Only after the policy loads, do a final intent check and present the result. Re-read the policy back into English and confirm the effect (permit vs forbid), the correlation pins ("same" vs "any"), the window, and inclusive boundaries.

Return, in this order:

  1. A one-line restatement of the intent as you understood it.
  2. The assumptions you made (paths, windows, correlations, defaults), called out explicitly.
  3. Load status — state plainly that the box loaded the policy, or the load error and your fix.
  4. The policy.dw rules, each with a // comment, an @id, and (on every forbid) a @description the agent can act on.
  5. A short plain-English gloss of each rule, and what stays refused.

Keep the policy minimal and idiomatic — match the bundled examples/. Do not add rules the operator did not ask for.

Common shapes

Point the operator at the bundled examples/; these are the patterns that recur.

  • Reach exactly one host. Two rules — the L4 connect scoped to the port, the L7 request scoped to the host. See examples/agent-anthropic.dw.
  • Read or write a project subtree. Two clauses per verb, because a prefix is not a directory: context.input.path == "~/project" or context.input.path like "~/project/*". A bare prefix also matches a sibling whose name merely starts the same way.
  • Refuse an action a broad permit would allow. A forbid on the exact action. See examples/forbid-delete.dw.
  • Guard a shell command. A forbid on shell:exec with a has-guarded argument read. See examples/shell-guard.dw.
  • Bound a rate or a budget. A forbid with a when temporal { … } count over one action, keyed on ::response, that fires at or above the cap. A permit cannot narrow another permit, so a cap is a forbid. See examples/temporal-write-budget.dw.

Do not

  • Do not author a schema, an event schema, or an information provider. The box ships the schema and forbids providers.
  • Do not invent an action, an operator, or a context field. Use only what the fixed vocabulary, the bundled reference, and the examples show.
  • Do not rely on an [egress.*] binding to make a host reachable — write the net:connect and http:request rules.
  • Do not try to re-open a floor, or expect a permit to beat a forbid.
  • Do not key a temporal step-up on ::request, and do not read ip in a temporal predicate.
  • Do not write a budget as a permit beside another permit for the same action; the box refuses it at load, because the permit cannot narrow the other one.
  • Do not present a policy you have not loaded into the box.
  • Do not ship a forbid without a @description. The agent reads that string on a denial, so an absent one leaves it to retry the same request.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Debug a CI failure on an OS you are not on (you are on Linux, it fails on macos-latest, or the reverse) without opening a pull request per attempt. Use when a test is green on your machine and red on a GitHub runner, or when you need to run one test or one shell probe on a runner and read the result fast. The core move is a dispatchable, scoped workflow on your own fork: pick one OS, skip lint, filter to one test, and for environment questions run a no-build probe that returns in seconds instead of a ten-minute build. Iterate by pushing to the fork branch and re-dispatching, never by editing a PR.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

strands-agents/box4752026년 10월 26일 업데이트

Assess an existing Box documentation page (docs/user/, docs/design/, or a crate README.md) for accuracy against the code and its tests, placement, and voice, and recommend what to fix. Use before a rewrite, when a page may have drifted from the code, or when asked to "audit this page", "is this page still true", or "what is wrong with this doc".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

strands-agents/box4752026년 10월 26일 업데이트

Find the gaps in the Box documentation and produce a prioritized backlog for docs/user/ and docs/design/. Use when planning documentation work, after a feature or a binary ships without a page, for a periodic health check of the docs, or when asked "what docs need writing", "plan the docs work", "prioritize the docs backlog", or "what should we document next".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

strands-agents/box4752026년 10월 26일 업데이트

Review a Box documentation draft in docs/user/ or docs/design/ for placement, structure, altitude, readability, terminology, and claim scope, and give a verdict (Ship it, Tighten, or Rethink). Use after docs-writer produces a draft, before a docs pull request, or when asked to "review this draft", "check my docs", or "is this page ready to ship". Read-only.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

strands-agents/box4752026년 10월 26일 업데이트

Draft or rewrite a Box documentation page in docs/user/ (tutorial, how-to, or reference for an operator) or docs/design/ (explanation for a security evaluator or a contributor), or a crate README.md that serves a consumer. Use when asked to write a doc, draft a page, rewrite a page that failed audit, document a feature or a component, or write an architecture or security page. Not for docs/design/decisions.md (use kd), local specs (use spec), or research notes (use explore).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

strands-agents/box4752026년 10월 26일 업데이트

explore

무료

Launch a deep, multi-faceted investigation of a topic using parallel sub-agents. Each agent tackles a different angle simultaneously, and findings are synthesized into structured exploration documents.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

strands-agents/box4752026년 10월 26일 업데이트

strands-agents의 스킬 모두 보기

이 스킬의 문제 신고