Skip to content

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:

{"url_prefix": "github.com/example/private-benchmark", "group": "code-access-benchmark"}

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/repository spelling. 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.com prefixes stop at the repository. The prefix is literal: a usual clone suffix .git is absent from the normalized identity, but a repository actually named repo.git retains it (for example, when normalized from repo.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/hawk does not match github.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:

  1. Public: no rules exist, or the matching rule explicitly has a null group.
  2. Group: the matching rule requires the named code group.
  3. 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.