Skip to content

fix(policies): restrict policy script references to https and local paths - #3587

Merged
migmartri merged 4 commits into
chainloop-dev:mainfrom
migmartri:pfm-7685-restrict-loader-ref
Oct 9, 2026
Merged

migmartri merged 4 commits into
chainloop-dev:mainfrom
migmartri:pfm-7685-restrict-loader-ref

Conversation

@migmartri

@migmartri migmartri commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Hardens how the policy engine loads the script that a policy spec references through its ref or path fields.

  • All script references (V2 ref, deprecated V2 path, and legacy path) now go through one resolver.
  • Absolute references must be https:// URLs with a host.
  • A relative reference is resolved against the URL of the policy spec when the spec is served over https://.
  • A relative reference in a local policy spec (file://) is confined to the directory of that spec, so local policy development keeps working.
  • Rego parse errors now report only the error code and location, not the policy source.

Behavior changes:

  • env://, file://, http:// and chainloop:// script references are rejected. The script loader cannot fetch chainloop:// references.
  • Relative references from an http:// spec are rejected.
  • Relative references from embedded or chainloop:// specs are rejected. They used to resolve against the working directory of the process.
  • Absolute paths, and relative paths that leave the directory of a local spec, are rejected.

Testing: table-driven unit tests for the resolver, for the derivation of the spec location, and for loading scripts from embedded, local and remote specs. Parse error tests check that the policy source is not in the error. The pkg/policies and app/cli test suites pass.

AI assistance: this change was written with Claude Code.

🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri using the eng-autopilot skill

View guided diff

@migmartri
migmartri requested a review from a team October 9, 2026 11:05
@chainloop-platform

chainloop-platform Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

PR validation — ✅ 3 passing

Status Policy Material Messages
✅ Passed pr-min-approvals pr-info -
✅ Passed pr-description-required pr-info -
✅ Passed pr-user-story-linked pr-info -

View attestation ↗

AI Session Checks — 🟢 91% · ⚠️ 1 failing

Avg score Sessions Failing policies Attribution Files Lines Total Duration
🟢 91% 1 ⚠️ 1 100% AI / 0% Human 7 +545 / -227 1h35m24s

🟢 91% — 100% AI — ⚠️ 1 policies failing

Oct 9, 2026 10:51 UTC · 1h35m24s · $15.82 · 666 in / 180.1k out · claude-code 2.1.295 (claude-opus-5-5)

View session details ↗

Change Summary

  • Restricts policy script refs to HTTPS or policy-local paths, including symlink rejection.
  • Sanitizes rego parse errors so policy source is not echoed back.
  • Adds and reshapes loader tests, including follow-up coverage for policy-location semantics.

AI Session Overall Score

🟢 91% — Autonomous run stayed aligned and verified; only planning discipline was thinner.

AI Session Analysis Breakdown

🟢 95% · solution-quality

🟢 Follow-up work moved parse-error sanitization into the engine layer. · High Impact

🟢 94% · alignment

No notes.

🟢 92% · user-trust-signal

No notes.

🟢 91% · verification

🟢 Targeted tests failed first, then passed after fixes. · High Impact

🟡 Autonomous follow-ups left no explicit human confirmation of the final behavior. · Low Severity

🟢 90% · scope-discipline

🟢 Observed edits stayed on pkg/policies, its tests, and matching testdata. · High Impact

🟡 72% · context-and-planning

🟠 Complex multi-phase work ran without a visible plan, TODO list, or planning event. · Medium Severity

💡 Before editing across implementation, review, and rebase phases, write a short plan the session can steer against.


File Attribution

████████████████████ 100% AI / 0% Human

Status Attribution File Lines
modified ai pkg/policies/policies_test.go +285 / -104
modified ai pkg/policies/policies.go +138 / -104
modified ai pkg/policies/engine/rego/boilerplate.go +32 / -15
modified ai pkg/policies/engine/rego/rego_test.go +47 / -0
modified ai pkg/policies/engine/rego/boilerplate_test.go +32 / -1
created ai pkg/policies/testdata/policy_script_outside_dir.yaml +8 / -0
modified ai pkg/policies/engine/rego/rego.go +3 / -3

Policies (4, 1 failing)

Status Policy Material Messages
✅ Passed ai-config-ai-agents-allowed ai-coding-session-5c767c -
✅ Passed ai-config-no-dangerous-commands ai-coding-session-5c767c -
⚠️ Failed ai-config-no-secrets ai-coding-session-5c767c Secret (generic-password) detected in session content [turn=708, source=tool_result, line=8]: INF redacted secrets from the AI coding session before upload count=10 rules=["generic-[CHAINLOOP_TRACE_REDACTED:generic-password]"]
✅ Passed ai-config-mcp-servers-allowed ai-coding-session-5c767c -

Security Checks — ✅ 6 passing

✅ secret-scan

Status Policy Messages
✅ Passed secrets-detection -

✅ sast-scan

Status Policy Messages
✅ Passed owasp-top10-2025 -
✅ Passed sast -
✅ Passed cwe-top25 -
✅ Passed cwe-top26-40-cusp -

✅ iac-scan

Status Policy Messages
✅ Passed iac-misconfiguration -
Scans not applied (2)
Scan Reason
vulnerability-scan no manifest/lockfile changed
github-actions-scan no workflow files changed

View attestation ↗

Security context

This change touches code with 9 recorded security-fix advisories. These are pointers to what past fixes established, not findings in this diff, and they never fail the check.

View in Chainloop ↗ · How this works ↗


Powered by Chainloop and Chainloop Trace

@migmartri
migmartri marked this pull request as ready for review October 9, 2026 11:15

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread pkg/policies/policies.go
…aths

Policy script references (ref and path) are now resolved through a single
resolver that only accepts absolute https:// URLs. Relative references are
resolved against the policy spec URL when the spec is served over https,
and confined to the spec directory when the spec is a local file. Other
schemes, absolute paths and paths that leave the policy directory are
rejected.

Rego parse errors returned while loading a policy now report only the error
code and location instead of quoting the policy source.

Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 5c767ce4-bc27-47f4-8a11-def62c79205b
Apply the same parse error sanitization to the rego engine parse sites and
cover how the policy spec location used to resolve relative script
references is derived.

Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 5c767ce4-bc27-47f4-8a11-def62c79205b
A relative script path in a local policy spec is now checked after
resolving symlinks, so a link inside the policy directory cannot point to
a file outside it.

Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 5c767ce4-bc27-47f4-8a11-def62c79205b
@migmartri
migmartri force-pushed the pfm-7685-restrict-loader-ref branch from cdbe53b to 0c45f8a Compare October 9, 2026 11:26
javirln
javirln previously approved these changes Oct 9, 2026
Comment thread pkg/policies/policies_test.go Outdated
@migmartri

Copy link
Copy Markdown
Member Author

Let's double check that we have two loading areas her

1 - loading the policy from a contract or policy group, in which case we support chainloop://, etc
2 - Inside the policy.yaml itself, we reference scripts

which ones are we securing? And let's make sure the tests are clear about scenario and examples

…s cover

Name the tests and cases after the two loading steps: an attachment loads a
policy spec, and the policy spec references its scripts. Only the second
step is restricted. Add a test that runs both steps through the verifier.

Assisted-by: Claude Code
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>

Chainloop-Trace-Sessions: 5c767ce4-bc27-47f4-8a11-def62c79205b
@migmartri

Copy link
Copy Markdown
Member Author

This PR only restricts step 2: the script references inside a policy.yaml.

  1. Loading the policy spec from a contract or policy group (chainloop://, file://, https://, or embedded). This is not changed. getLoader and the loaders are as before. The only new use of the attachment ref is to read the location of the policy spec, so that step 2 can resolve relative script refs against it.
  2. Loading the scripts that the policy spec references (spec.policies[].ref, spec.policies[].path, and the legacy spec.path). This is what the PR restricts. A script ref must be an https:// URL, or a relative path. A relative path resolves against the URL of an https:// policy spec, or stays in the directory of a file:// policy spec. It is rejected for embedded and chainloop:// policy specs.

In eabec48 the tests follow this split. A comment block above them describes the two steps. TestPolicyLocation covers how step 1 gives the location. TestResolveScriptRef, TestResolveScriptRefSymlinks and TestLoadPolicyScriptsFromSpecRejectsInvalidRefs cover step 2, with cases named as "<script ref> in a <remote|local|embedded|chainloop provider> policy". The new TestVerifyStatementScriptRefs runs both steps through the verifier: a file:// attachment loads a policy spec, and its script inside the directory loads, while a script outside the directory is rejected.

🤖 Posted by Maximus bot (Claude Code) on behalf of @migmartri using the eng-autopilot skill

@migmartri
migmartri merged commit 5f7a2a4 into chainloop-dev:main Oct 9, 2026
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants