Code-access rules¶
Code-access rules map repositories and package-index packages to required groups. Administrators manage rules through the Hawk API; Hawk stores them in the database. Rules restrict submissions and results through their recorded code inventory. Changes update linked run policies in the database; readers check both code and model permissions. Older results retain their explicit baseline until backfilled.
Hawk admins can read runs with missing or incomplete code policies, to repair them; a run with no policy is decided from its rows' copies, and refused if Hawk has no rows for it. Like everyone else, they need each named code group and the model permissions, and submission validation still applies. Reading a run does not repair its inventory or change other users' access.
Native-run owners can also read through their own incomplete inventory via the Hawk API. Ownership must be recorded by Hawk; uploaded log metadata does not count. Named groups, invalid-source restrictions, model permissions and scan-source restrictions still apply; missing policies and pending inventory writes remain blocked. Runs without recorded ownership need admin help. This owner exception does not apply to direct SQL reads, or to submissions and other mutations.
Set a fallback rule¶
A valid source with no matching rule adds no code-group restriction. To require
a group for all otherwise unmatched repositories and packages, create a * rule:
code-access-public is an ordinary group: only its members satisfy this rule.
Assign it to intended readers, including direct SQL readers (see
Grant access). To deny otherwise unmatched sources, use a group
such as code-access-unclassified with no members, then add specific rules for
approved repositories and packages. Specific rules override * for each source.
A run requires all groups from its sources, plus its independent model permissions.
The catch-all applies to recorded source identities. Historical runs without
source links need the optional inventory backfill when rules must cover that
history. Missing or incomplete inventories remain restricted; a catch-all does
not repair them. Finish deploying updated API writers before creating *.
Manage rules¶
All routes require administrator user credentials.
A runner using user credentials inherits its submitter's administrative authority;
restricted tokens cannot authorize code-policy changes.
Read-only credentials cannot change rules, including admin credentials.
If the API's database role is not inspect, the database owner must grant it
EXECUTE ON FUNCTION public.sync_code_group_roles() during provisioning.
| Method | Path | Result |
|---|---|---|
GET |
/code_rules/ |
List all rules, ordered by prefix |
POST |
/code_rules/ |
Create a rule (201) |
PUT |
/code_rules/{pk} |
Replace a rule's prefix and group |
DELETE |
/code_rules/{pk} |
Delete a rule (204) |
POST |
/code_rules/preview |
Show what a change would re-tag, without saving it |
GET |
/code_rules/repos |
List inventoried sources by the rule that covers them, with the runs using them |
GET |
/code_rules/repos/{pk}/code |
List the tasks, solvers and other code items found in a source |
Create and update accept this JSON request body:
Use the full code-access-* permission name. Omitted or null groups and unknown
fields are rejected. The name after code-access- starts with a lowercase letter,
contains only lowercase letters, digits, _ and -, and has at most 52 characters.
Responses contain pk (the rule's UUID), url_prefix, and group. A conflicting
prefix returns 409; a missing rule returns 404; invalid input returns 422.
Rule edits automatically update existing runs linked to affected sources.
Deleting a rule falls back to the next matching broader rule, then * if present.
With no match, the source adds no restriction. Deleting a rule does not delete the
group or its memberships. A broad change can update many runs; the rule and their
requirements commit together.
Preview a change¶
Before saving, POST /code_rules/preview with {"rule": {...}} to create,
{"pk": ..., "rule": {...}} to replace, or {"pk": ...} to delete. The response
counts the sources whose required group would change (source_count) and how
many move between each pair of groups (changes, null meaning no restriction),
lists the first 50 of them (repos, with before and after), counts the runs
linked to them (run_count), and sets new_group when saving would create a
group Hawk has no record of. That doesn't mean nobody holds it: anyone the
identity provider already grants it gets access on save. Scans that read those
runs are updated too. A preview writes
nothing and never waits for the rule lock, so it can disagree with a save only
when another rule changes in between. It returns the same 404, 409 and 422
as the save for the rule itself; a clash with an existing database role for a new
group only shows on save. Read-only admin credentials may preview.
The registry (GET /code_rules/repos) groups sources by the rule that covers
them (rule_pk), resolved from the current rules on every request, so sources
inventoried after the last rule edit still show it. Sources no rule matches have
a null rule_pk; valid is false for legacy spellings that rules cannot
classify, and runs using them stay unresolved. Each group counts the runs using
any of its sources once (runs); each source also counts its own.
Prefixes and matching¶
Use a source's canonical identity:
- Git: lowercase
host/owner/repository, without a scheme, credentials, port, trailing slash, or clone suffix.git. Hosts and namespaces can cover multiple repositories, including nested namespaces on GitLab. - Package index: the exact normalized package name, such as
inspect-ai, without versions or extras. Names use hyphens in place of dots and underscores. - Catch-all: exactly
*, covering all other valid source identities. Other wildcard patterns such asgithub.com/*are not supported.
The longest specific matching prefix wins for each source; every specific match
takes priority over *. Matching respects / boundaries:
github.com/example/hawk does not match github.com/example/hawk-secret.
| Prefix | Required group |
|---|---|
* |
code-access-public |
github.com/example |
code-access-research |
github.com/example/private-benchmark |
code-access-benchmark |
inspect-ai |
code-access-research |
In this example, private-benchmark requires code-access-benchmark; other
repositories under github.com/example require code-access-research. Other
sources require code-access-public. A run using the private benchmark and an
otherwise unmatched package requires both code-access-benchmark and
code-access-public.
Update rules when repositories are renamed or transferred. A restriction on the old identity does not follow the new name automatically.
Grant access¶
Assign the exact permission named in the rule to intended readers through
the deployment's login provider, such as Okta or Amazon Cognito. For the example
above, assign code-access-benchmark to readers of the private benchmark.
The optional S3 Object Lambda path takes the caller's model and code-access-*
groups from IAM Identity Center and asks the database's code policy only for the
decision. Until that path is retired it has no admin or owner exception, and it
answers 503 when it cannot read the policy. API reads answer 503 too.
For direct database access, code-access-benchmark corresponds to the
code_group_benchmark role.
Creating a rule grants no reader memberships; those must be assigned separately.
To give a group to every warehouse SQL reader, such as the group of a * rule
that should stay readable, list it in the deployment's hawk:warehouseReaderCodeGroups
setting, for example ["code-access-public"]. Each deploy grants the listed
groups to rls_reader, which every read-only user and model-group login holds, once
a rule has created the group, and revokes groups removed from the list. SQL readers
lose runs under * until its group is granted, so create the group first with a
rule that matches no source, deploy, create *, then delete the first rule; its
group and grants stay. Never list a group meant to deny access, such as
code-access-unclassified.
Submitting and resuming runs¶
Eval, human-eval and scan launches check directly declared packages, including
model roles, online scanners, packages entries and eval-set code_sources.
Package-index requirements are identified by normalized package names. Git sources use hosts with supported
repository naming (GitHub and GitLab by default) and one of these forms:
git+https://host/owner/repo, optionally with@refand#subdirectory=path.git+ssh://git@host/owner/repo, optionally with@refand#subdirectory=path.
Use literal declarations without explicit ports, embedded credentials or
${...} substitutions. Local paths and archive URLs are unsupported. Embedded
URL credentials return 422. Incomplete source inventories, missing code
permissions and inaccessible source evals return 403.
The check parses at most 256 source declarations offline in an isolated API environment, without task secrets or ambient installer configuration. Source validation and inventory recording must succeed before the run starts.
Runner images, package indexes, and runtime installer variables remain configurable
as before. Submitted installer environment or secret names that could alter source
selection leave inventory incomplete and prevent launch, including request-only
secrets. Access checks cover declared sources; installer redirects, transitive
dependencies, and code downloaded at runtime are outside the inventory. With Hawk's
GitHub App, runners can't fetch a restricted repository the run didn't declare, so list
a task's restricted dependencies under packages, and repositories it only clones at
runtime under code_sources, which records and checks them without installing them.
Operators configure supported Git hosts through
the deployment settings; this setting does
not restrict network access.
Git's environment config (GIT_CONFIG_COUNT, GIT_CONFIG_KEY_<n>,
GIT_CONFIG_VALUE_<n>) is exempt from that refusal, so users can forward their own Git
credentials with --secret. An insteadOf entry in it is an installer redirect: it can
install a different repository than the one the run recorded.
Resumes keep dependencies from earlier attempts because their outputs share a
folder. Removing a dependency from a new config does not remove its earlier
restriction; current repository rules still apply to the accumulated inventory.
Reusing an ID requires its owner or an admin, and overlapping native launches
for the same ID return 409.
After interrupted preparation, the run's inventory stays pending: non-admin reads and retries are refused until it is repaired, so submit again under a new ID. A scan resume repairs its own scan: holding the launch lock, which proves the preparation was abandoned, it returns the inventory to its state before that attempt and then checks access as usual. Scan resumes never replace the saved config, so the earlier inventory still covers the folder.
Backfill historical inventories¶
The optional inventory backfill
brings historical runs under current rules; its README is the runbook. An old run
whose retained config is gone, or names sources that cannot be identified, links
imported-code.invalid/unknown/source, the placeholder imports use for unknown
provenance. A rule on that placeholder (or *) decides whether such history is
restricted; with none, it stays readable as before. Incomplete inventories stay
incomplete; the script never certifies one.
Importing results¶
Imports combine uploader declarations with log Git origins recognized by the
deployment's codeAccessGitHosts. The API commits sources to PostgreSQL before
publishing files; later uploads retain earlier sources, and scans retain their
source eval-set links. These are uploader claims, not verified provenance or a
complete dependency inventory. Finalization checks the accumulated policy before
publishing files; the uploader needs every required code group and model group.
Both import APIs accept declarations on POST /{id}/finalize. Eval-set imports
use /eval_sets/import/{id}/finalize and scan imports use /scan_imports/{id}/finalize.
The body is optional. When it is sent, it is
{"code_sources": ["github.com/example/tasks", "inspect-ai"]}. code_sources
cannot be null, and the list accepts up to 500 entries including duplicates. The server
adds log origins separately. [] declares no additional sources beyond those
origins; interactive confirmation without a file sends this when sources were
detected. Omitting the body finalizes without a declaration. The CLI posts that body
to the same route. See the CLI workflow.
Omitted declarations, uploads identifying no sources, and older incomplete
inventories retain imported-code.invalid/unknown/source. It is a policy identity,
never fetched, classified by the matching rules above, including *. It is separate
from an incomplete inventory. Recorded sources, including unknown, cannot currently
be removed through the CLI or API.
The CLI discovers Git hosts and supports_import_code_sources through /auth/config.
Upload helpers check support before allocating or uploading. Declarations require
support; imports without declarations can continue with a warning.
A failed copy can be retried without losing the recorded policy. Unmatched sources, including unknown, remain public subject to model permissions.
Read checks and rollout¶
Rule changes recompute linked run requirements in the same database transaction.
API readers check current database policies, including scans'
source-eval restrictions. Model-folder checks use the viewer's shared .models.json
reader. See the
reader trust boundary.
Existing results retain their explicit legacy access baseline until backfilled. Outside the read exceptions above, missing or incomplete inventories deny access. Before enabling enforcement, finish the recording rollout on every API replica and audit incomplete inventories. Historical backfill is optional when rules must cover older runs; it must establish complete source history before replacing a baseline. See the rollout procedure. Normal deployments do not run a policy refresh.