Code-access rule authoring¶
Hawk administrators can author the policy that maps code repositories and package-index packages to access groups. This API is the policy-authoring part of code ACLs. Saving a rule does not yet restrict submissions or access to results. Applying policy to existing runs, database row permissions, and S3 authorization files requires the remaining enforcement work.
All routes require a Hawk administrator, configured through the admin claim or admin permissions. Read-only credentials cannot create, update, or delete rules, including admin credentials.
Runner pods receive the launching user's access token. A runner launched by a Hawk admin can therefore author code policy with that delegated token. Use a separate day-to-day identity or short-lived admin group membership when running untrusted code; this API does not reduce the authority of delegated admin tokens.
| 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 access group |
DELETE |
/code_rules/{pk} |
Delete a rule (204) |
Create and update accept the same JSON body:
group is required. Set it explicitly to null for public access. An omitted
group or unknown field is rejected, so a typo cannot silently create a public
rule. Responses contain pk (a UUID), url_prefix, and group. A conflicting
prefix returns 409; a missing rule returns 404; invalid input returns 422.
Legacy URL credentials are redacted when listing rules; their UUIDs still identify
the rows for replacement or deletion.
See hawk/examples/code-rule.json
for a complete request body.
Prefixes and matching¶
Use the canonical identity produced by source normalization, rather than an installation URL:
- Git prefixes use lowercase
host/owner/repositoryspelling. Omit scheme, credentials, port, and trailing slash. Host and owner prefixes can cover multiple repositories. GitLab and other hosts can have nested namespaces; GitHub and*.ghe.comprefixes stop at the repository. The prefix is literal: a usual clone suffix.gitis absent from the normalized identity, but a repository actually namedrepo.gitretains it (for example, when normalized fromrepo.git.git). Numeric hosts must use canonical dotted-decimal IPv4 rather than hexadecimal or octal aliases. - Package-index rules use an exact PEP 503 name, such as
inspect-ai. These identities have no dots, underscores, version constraints, or extras. - Matching respects
/segment boundaries:github.com/example/hawkdoes not matchgithub.com/example/hawk-secret. - The longest matching prefix wins. A public rule can override a broader restricted namespace, and a restricted repository can override a public namespace.
For example:
| Prefix | Group | Meaning |
|---|---|---|
github.com/example |
null |
Public namespace |
github.com/example/private-benchmark |
code-access-benchmark |
Restricted repository |
inspect-ai |
null |
Public package-index package |
The shared matcher distinguishes three outcomes:
- Public: no rules exist, or the matching rule explicitly has a null group.
- Group: the matching rule requires the named code group.
- Unmatched: rules exist, but none matches this identity. Enforcement must refuse this outcome; it is never a public fallback.
An invalid identity raises an error even when no rules exist. The no-rules behavior applies to the complete rule table, not a subset filtered for one repository. Creating the first rule changes the outcome for every otherwise unmatched identity; deleting the last rule restores the bootstrap public policy.
Update rules before submitting a renamed or transferred repository under its new identity. A narrow restriction on the old URL does not follow the rename: the new URL may match a broader public namespace rule.
Transactions and current limits¶
New group names create a code_group row and a code_group_<name> PostgreSQL
role in the same transaction as the rule. Names must start with a lowercase
letter, use only lowercase letters, digits, _ and -, and contain at most
52 characters. Roles have no login; the API caller and end users gain no membership.
On PostgreSQL 16+, a non-superuser function owner receives administrative membership
in roles it creates, with inheritance and role switching disabled by default. This does not
grant the code role any of the owner's privileges.
Deleting a rule retains its group and role for reuse. If a group name was mistyped, replace the rule with the correct group name. Unused groups and roles currently require database-owner cleanup; group discovery and typo prevention belong with the admin tooling.
A new group cannot adopt a preexisting PostgreSQL role, even if that role has
no login: existing grants or members could belong to another purpose. An
existing group's role must remain NOLOGIN, without elevated role attributes
or membership in another role. Conflicts return 409 for database-owner
repair. Principals may be members of an existing code-group role; this API
does not grant or revoke those memberships.
The migration grants the standard API database role (inspect) permission to
execute public.sync_code_group_roles(). It does not grant CREATEROLE, role
membership, or execution to PUBLIC. Deployments with a different
API database role should have the database owner explicitly grant that role
EXECUTE ON FUNCTION public.sync_code_group_roles() during provisioning.
The managed AWS infrastructure still assumes inspect for IAM authentication;
custom API logins require separate IAM and warehouse RLS provisioning changes.
Missing privileges return a server provisioning error with that grant guidance.
The helper synchronizes all stored groups: creating a new group also recreates
any missing role for an older group. It does not restore that role's former grants
or memberships. Dropping a role is therefore not a way to remove its group policy.
Rule writes serialize so concurrent edits, first-rule creation, and shared
group creation commit consistently. Successful mutations emit an audit event
with the actor, action, rule ID, previous/new prefix and group, and affected_repos.
That count compares the effective policy before and after the change, including
public/unmatched transitions and changes of group. It covers existing canonical
repository rows; unnormalized legacy rows are excluded. API response bodies are
unchanged. Every write recomputes the matched group
projection on the existing repo rows. That nullable projection is not an
authorization cache: null also covers unmatched identities, old inventory
that has not been normalized, and newly inventoried repositories. Consumers
must resolve canonical identities against the current complete rule set.
Rule changes currently do not update run code_groups or .code-groups.json
files. Do not rely on this API alone to protect code or historical results.
The propagation follow-up tracks
idempotent DB/S3 updates and safe handling of incomplete historical inventory,
alongside enforcement. Unknown or
unnormalized history must not be converted to an empty, unrestricted group set.
CLI/client commands and an effective-policy lookup for a repository remain admin-tooling follow-ups.